' ADPCM decoder. this code decompresses the contents of a speech chip sound ROM set that
' is 4 bit ADPCM encoded. A table of addresses at the start of the ROM is used to find
' the start of the individual samples, the end and the sample length, is calculated from
' the sample block sizes. this version saves the decompressed samples as sixteen bit
' 16ksps mono .wav files with the hex start address of the sample as the file name
' 05/03/26
SCREEN 12 ' 640 x 480 x 16
CLS ' clear it
DEFINT A-Z ' all integers except where defined
DIM stepadj(7) ' step index adjust, 0 to 7
DIM steptable(48) ' step size table, 0 to 48
DIM wavaddr&(127) ' sample start addresses
adrcount% = 0 ' number of sample addresses found
RESTORE stepadj ' read the step index adjust table
FOR i = 0 TO 7
READ stepadj(i)
NEXT
RESTORE steptable ' read the step size table
FOR i = 0 TO 48
READ steptable(i)
NEXT
OPEN "snd_2.rom" FOR BINARY AS #1 ' open input file
i& = 3 ' skip unknown start bytes
DO
GOSUB GetLong ' get address longword
wavaddr&(adrcount%) = llong& ' save address
adrcount% = adrcount% + 1 ' increment address count
PRINT RIGHT$("00000" + HEX$(llong&), 6); " "; ' display address
LOOP WHILE llong&
PRINT
PRINT adrcount% - 1; "addresses found";
adrcount% = adrcount% - 2 ' correct for zero term and zero
' count start
FOR wavfiles% = 0 to adrcount% ' for each sample
filename$ = RIGHT$("00000" + HEX$(wavaddr&(wavfiles%)), 6) + ".wav"
OPEN filename$ FOR OUTPUT AS #2 ' open output file
PRINT
PRINT "File "; filename$; ' show file name
GOSUB GetLength ' get the sample length
GOSUB WriteRIFF ' write the header chunk
GOSUB WriteFORMAT ' write the format chunk
GOSUB WriteDATA ' write the data chunk
CLOSE #2 ' close output file
NEXT
CLOSE #1 ' close input file
PRINT
PRINT "Done. Any key to exit." ' show file name
DO
LOOP WHILE INKEY$="" ' wait for a key
END
' get the sample size. the sample is arranged into blocks, the first byte of the block
' is the byte count for the remainder of the block + $80. the sample is terminated by
' a block count of zero
GetLength:
sslength& = 0 ' reset length
i& = wavaddr&(wavfiles%) ' sample start address
DO
GOSUB GetByte ' read block length
byte% = byte% AND 127 ' mask block length
sslength& = sslength& + byte% ' add this chunk length
i& = i& + byte% ' index to next chunk length
LOOP WHILE byte% ' zero length is end marker
RETURN
' write RIFF header chunk
WriteRIFF:
PRINT #2, "RIFF"; ' always there
samplength& = sslength& * 4 ' length of output in bytes
llong& = samplength& + 36 ' length of rest of file
GOSUB WriteLongword ' write file length
PRINT #2, "WAVE"; ' always there
RETURN
' write FORMAT chunk
WriteFORMAT:
PRINT #2, "fmt "; ' always there
llong& = 16 : GOSUB WriteLongword ' length of chunk, always 16
llong& = 1 : GOSUB WriteWord ' always 1
llong& = 1 : GOSUB WriteWord ' mono
llong& = 16000 : GOSUB WriteLongword ' sample rate
llong& = 32000 : GOSUB WriteLongword ' bytes/second
llong& = 2 : GOSUB WriteWord ' 2 bytes/sample
llong& = 16 : GOSUB WriteWord ' 16 bits/sample
RETURN
' write DATA chunk
WriteDATA:
PRINT #2, "data"; ' always there
llong& = samplength& : GOSUB WriteLongword ' data chunk length
sstart& = wavaddr&(wavfiles%) ' sample start address
send& = wavaddr&(wavfiles% + 1) ' sample end address
GOSUB Decompress ' decompress the sample
RETURN
' write llong& as word to file, little endian format
WriteWord:
PRINT #2, CHR$(llong& AND 255&); ' write low byte
llong& = llong& \ 256&
PRINT #2, CHR$(llong& AND 255&); ' write high byte
RETURN
' write llong& as longword to file, little endian format
WriteLongword:
GOSUB WriteWord ' write low word
llong& = llong& \ 256&
GOSUB WriteWord ' write high word
RETURN
' decompress ADPCM sample data to .wav data. first get the blocksize, if it's zero then
' all done so exit else for each byte in the block unpack the high then the low nibbles
' into 12 bit samples
' This routine could be greatly simplified but is given in this form as it more closely
' represents how it would be implemented in assembly code or directly in hardware
Decompress:
stepindex = 0 ' reset decompression variables
stepsize = 16 ' initialise index
sample& = 0 ' set 50% for unsigned value
i& = sstart& ' set start of sample
eflag% = 0 ' clear end flag
DO
GOSUB GetByte ' get this sample blocklength
blklength% = byte% AND 127 ' mask and copy block length
IF blklength% THEN ' if there are samples
DO
GOSUB GetByte ' get two compressed nibbles
FOR nib% = 0 TO 1 ' for each nibble
IF nib% THEN
nibble% = byte% AND 15 ' second sample is low nibble
ELSE
nibble% = byte% \ 16 ' first sample is high nibble
END IF
newsample = nibble% AND 7 ' compressed sample magnitude
difference = stepsize \ 8 ' effectively add 0.5 to difference
IF newsample AND 4 THEN
difference = difference + stepsize ' + stepsize / 4 * 4
END IF
IF newsample AND 2 THEN
difference = difference + stepsize \ 2 ' + stepsize / 4 * 2
END IF
IF newsample AND 1 THEN
difference = difference + stepsize \ 4 ' + stepsize / 4 * 1
END IF
IF (nibble% AND 8) THEN ' if -ve compressed sample
sample& = sample& - difference ' subtract the difference
ELSE ' else compressed sample was +ve
sample& = sample& + difference ' so add the difference
END IF
IF sample& > 2047 THEN ' if > than signed 12 bit max
PRINT " +clip "; ' print a warning
sample& = 2047 ' clamp to 2047
ELSEIF sample& < -2048 THEN ' if < than signed 12 bit min
PRINT " -clip "; ' print a warning
sample& = -2048 ' clamp to -2048
END IF
llong& = (16& * sample&) AND 65535& ' convert to 16 bit word
GOSUB WriteWord ' save decompressed sample
stepindex = stepindex + stepadj(newsample) ' new decompress step index
IF stepindex > 48 THEN ' if > than step table max
stepindex = 48 ' clamp to step table max
ELSEIF stepindex < 0 THEN ' if < than step table min
stepindex = 0 ' clamp to step table min
END IF
stepsize = steptable(stepindex) ' get step size for next sample
NEXT nib% ' do other nibble
blklength% = blklength% - 1% ' decrement count
LOOP WHILE blklength% ' loop if more to do
ELSE
eflag% = -1% ' else set end flag
END IF
LOOP UNTIL eflag% ' loop until end
RETURN
' get an indexed byte from the input stream. return the value as a string and a number
GetByte:
byte$ = " " ' one byte long string
i& = i& + 1& ' increment the index
GET #1, (i&), byte$ ' get LEN(byte$) bytes
byte% = ASC(byte$) ' get numeric byte value
RETURN
' get a big endian longword from the input stream. return the value in llong&
GetLong:
GOSUB GetByte ' get MS byte
llong& = byte% ' copy it
GOSUB GetByte ' get next byte
llong& = llong& * 256& + byte% ' convert and add it
GOSUB GetByte ' get next byte
llong& = llong& * 256& + byte% ' convert and add it
GOSUB GetByte ' get next byte
llong& = llong& * 256& + byte% ' convert and add it
RETURN
' dialogic modified IMA tables
steptable:
DATA 16, 17, 19, 21, 23, 25, 28, 31, 34, 37
DATA 41, 45, 50, 55, 60, 66, 73, 80, 88, 97
DATA 107, 118, 130, 143, 157, 173, 190, 209, 230, 253
DATA 279, 307, 337, 371, 408, 449, 494, 544, 598, 658
DATA 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
stepadj:
DATA -1,-1,-1,-1,2,4,6,8
|