ADPCM to 16 bit .wav convert. By Lee Davison.


' 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



Last page update: 6th April, 2005. e-mail me