Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ADPCM compression
#1
Some WAV formats use (but there are really very few of them) ADPCM compression. And since its implementation is not too complicated, I tried it. So what's the point here:
If you want to slightly reduce the sound in WAV format, you have these options:
1) Reduce the bit depth (so instead of a 32-bit WAV file you use an 8-bit one) - the sound is slightly worse, but on cheap speakers you won't notice the difference. But it's not Hi-fi anymore. The space saving is fourfold.
2) Use mono instead of stereo. You'll lose the sound depth. This will reduce the sound in WAV format by half.
3) Reduce the sound refresh rate - so instead of 44100 records per minute you'll use only 22050, or 11025. This is a drastic loss of sound samples and it's very audible. The ratio of the original and new sizes is given by the ratio of discarded samples (if you go from 44100 to 22050, you get half the size, etc.)
4) You use a better form of data recording with minimal signal difference.
This is an example. ADPCM simply calculates the difference between two consecutive samples and records only this difference. The step it uses to do this is divided into 8 levels (because an 8-bit signal has 256 levels and ADPCM uses 4-bit notation to save space). The maximum value with a sign of 8 can fit into a 4-bit notation (one bit indicates whether it is a positive or negative value). In this way, sound can be stored with minimal loss of quality in half the file size. This compression cause small noise in signal.

The first example shows a function on an array of numbers and does not require any music file:

Code: (Select All)

'        ADPCM compression and decompression in QB64
' ----------------------------------------------------------
' This example uses 4-bit quantization for ADPCM compression

Dim originalSamples(0 To 9) As Single
Dim compressedData(0 To 9) As Integer
Dim decompressedSamples(0 To 9) As Single

'Original signal values
originalSamples(0) = 0
originalSamples(1) = 10
originalSamples(2) = 20
originalSamples(3) = 35
originalSamples(4) = 25
originalSamples(5) = 10
originalSamples(6) = 25
originalSamples(7) = 15
originalSamples(8) = 5
originalSamples(9) = 0

Print "Original Values:"
For i = 0 To 9
    Print Using "###.##"; originalSamples(i);
Next
Print

' Compression (ADPCM)
Dim predicted As Single
Dim difference As Single

predicted = 0 ' first prediction
For i = 0 To 9
    difference = originalSamples(i) - predicted
    compressedData(i) = Quantize(difference) ' Kvantování rozdílu
    predicted = predicted + Dequantize(compressedData(i)) ' Aktualizace predikce
Next

Print "Compressed Data (4 bite): "
For i = 0 To 9
    Print compressedData(i);
Next
Print

' Decompressing process
predicted = 0 ' First prediciton
For i = 0 To 9
    decompressedSamples(i) = predicted + Dequantize(compressedData(i))
    predicted = decompressedSamples(i)
Next

Print "Decompressed Samples:"
For i = 0 To 9
    Print Using "###.##"; decompressedSamples(i);
Next

End

' Difference quantization function
Function Quantize (difference As Single)
    Q = Int(difference / 5) ' Quentization step 5
    If Q > 7 Then Q = 7 ' limit to 4 bite
    If Q < -8 Then Q = -8
    Quantize = Q
End Function

' Difference dequantization function
Function Dequantize (quantizedDifference As Integer)
    Dequantize = quantizedDifference * 5 ' The reverse process of quantization
End Function

The second example shows the use on real audio (change the MP3 to another MP3 in stereo on line 20)

The result is the sound so as it would sound if written in 4 bits. Note the small background noise, which is an quantization bug of the original, because this program does not have floating compression.

This program actually saves an 8 bit stereo WAV file with the original sample rate, but in 4 bits. In this case only to memory, it does not save anything to the hard disk.

Code: (Select All)


' ADPCM compression and decompression in QB64 for an 8-bit audio signal
' -----------------------------------
' This example uses 4-bit quantization for ADPCM compression of an audio signal in the range 0 to 255.

_Title "ADPCM compression in QB4PE"
Screen _NewImage(100, 24, 0)

Dim originalSamplesL(0 To 9) As Integer
Dim compressedDataL(0 To 9) As Integer
Dim decompressedSamplesL(0 To 9) As Integer

Dim originalSamplesR(0 To 9) As Integer
Dim compressedDataR(0 To 9) As Integer
Dim decompressedSamplesR(0 To 9) As Integer


Dim m As _MEM, Snd As Long
Snd = _SndOpen("A.mp3")
m = _MemSound(Snd, 0)

Locate 1
Do Until a& >= m.SIZE
    j = 0
    'load music samples
    For i = 0 To 9
        originalSamplesL(i) = 128 + (_MemGet(m, m.OFFSET + a&, Single) * 127)
        originalSamplesR(i) = 128 + (_MemGet(m, m.OFFSET + a& + 4, Single) * 127)
        a& = a& + 8
        If a& >= m.SIZE Then Exit Do
    Next i


    ' Compression (ADPCM)
    ReDim predicted As Single
    ReDim difference As Single

    predictedL = 128
    predictedR = 128 ' Initial assumption (mean value for 8-bit range)
    For i = 0 To 9

        differenceL = originalSamplesL(i) - predictedL
        differenceR = originalSamplesR(i) - predictedR

        compressedDataL(i) = Quantize(differenceL) ' Difference quantization
        compressedDataR(i) = Quantize(differenceR)

        predictedL = predictedL + Dequantize(compressedDataL(i)) ' Update prediction
        predictedR = predictedR + Dequantize(compressedDataR(i)) ' Update prediction

        'Range verification for prediction
        If predictedL < 0 Then predictedL = 0
        If predictedL > 255 Then predictedL = 255

        If predictedR < 0 Then predictedR = 0
        If predictedR > 255 Then predictedR = 255
    Next

    Print
    Print "Original sound samples (0-255) (Left):"
    For i = 0 To 9
        Print Using "####"; originalSamplesL(i);
    Next
    Print
    Print "Original sound samples (0-255) (Right):"
    For i = 0 To 9
        Print Using "####"; originalSamplesR(i);
    Next
    Print



    Print "Compressed data (4bite):"
    For i = 0 To 9
        Print compressedDataL(i);
        Print compressedDataR(i);
    Next
    Print

    ' Decompresing process
    predictedL = 128 'Initial assumption (mean value for 8-bit range)
    predictedR = 128

    For i = 0 To 9
        decompressedSamplesL(i) = predictedL + Dequantize(compressedDataL(i))
        decompressedSamplesR(i) = predictedR + Dequantize(compressedDataR(i))

        ' Range verification for reconstructed samples
        If decompressedSamplesL(i) < 0 Then decompressedSamplesL(i) = 0
        If decompressedSamplesL(i) > 255 Then decompressedSamplesL(i) = 255

        If decompressedSamplesR(i) < 0 Then decompressedSamplesR(i) = 0
        If decompressedSamplesR(i) > 255 Then decompressedSamplesR(i) = 255

        predictedL = decompressedSamplesL(i)
        predictedR = decompressedSamplesR(i)
    Next

    Print
    Print "Decompressed samples (0-255) Left:"
    For i = 0 To 9
        Print Using "####"; decompressedSamplesL(i);
    Next
    Print
    Print "Decompressed samples (0-255) Right:"
    For i = 0 To 9
        Print Using "####"; decompressedSamplesR(i);
    Next

    For i = 0 To 9
        L = (decompressedSamplesL(i) - 128) / 128
        R = (decompressedSamplesR(i) - 128) / 128
        _SndRaw L, R
    Next i

    Do Until _SndRawLen < .1
        _Display
        _Limit 20
    Loop
Loop

End

' Difference quantization function
Function Quantize# (difference As Single)
    Dim Q As Integer
    Q = Int(difference / 8) ' Quantization step 8 for 8-bit signal
    If Q > 7 Then Q = 7 ' limit to 4 bites
    If Q < -8 Then Q = -8
    Quantize# = Q
End Function

' Function for dequantizing the difference
Function Dequantize (quantizedDifference As Integer)
    Dequantize = quantizedDifference * 8 ' The reverse process of quantization
End Function


Reply
#2
16 bit version - trying ADPCM Microsoft standard...

You would certainly be interested in how 16-bit sound would sound if it were compressed by ADPCM and then decompressed. This program will show you exactly this. How it works. Indeed, the differences between signal samples are also written using 4 bits in this case. But how is that possible, you ask yourself. After all, with normal linear stepping it would be jumping in the signal by a huge 32767 /15 (2184) levels in the signal! That would ruin the sound terribly!
Well, for this purpose Microsoft has developed a table (StepTable) that has 88 steps. The program simply jumps in these 88 positions to the nearest similar step value, which is listed in the table. The maximum jump size in the table is currently limited to 15 bits, in this case one bit is no longer used to determine the sign. Values 0–7 indicate the negative direction (delta 0–7). Values 8–15 indicate the positive direction (delta 0–7). This approach causes the sound to be not completely clean, however the resulting file should be 4x smaller in size than a normal 16bite wav file with similar quality. It is possible (almost certain) that the algorithm can be improved somehow...

Rename MP3 - row 44
Code: (Select All)

'ADPCM - downsize 16bit file 4x

' Original signal array (16bite value, -32768 to 32767)
Dim originalSignalL(0 To 255) As Integer
Dim originalSignalR(0 To 255) As Integer

' ADPCM Microsoft Step Table
Dim stepTable(0 To 87) As Integer
Data 7,8,9,10,11,12,13,14,16,17,19,21,23,25,28,31
Data 34,37,41,45,50,55,60,66,73,80,88,97,107,118,130,143
Data 157,173,190,209,230,253,279,307,337,371,408,449,494,544
Data 598,658,724,796,876,963,1059,1164,1278,1403,1539,1687,1849
Data 2025,2217,2426,2653,2899,3166,3456,3769,4107,4471,4863,5285
Data 5737,6222,6741,7296,7889,8521,9195,9912,10674,11483,12341
Data 13250,14213,15231,16307,17444,18644,19909,21243,22648,24127
For i = 0 To 87
    Read stepTable(i)
Next i

' Starting values for left and right channel
Dim stepIndexL As Integer, stepIndexR As Integer
Dim currentStepL As Integer, currentStepR As Integer
Dim predictedValueL As Integer, predictedValueR As Integer

stepIndexL = 0
stepIndexR = 0
currentStepL = stepTable(stepIndexL)
currentStepR = stepTable(stepIndexR)
predictedValueL = 0
predictedValueR = 0

' Output compressed data (4 bite to sample)
Dim compressedDataL(0 To 255) As Integer
Dim compressedDataR(0 To 255) As Integer
ReDim FullCompressedL(0) As Integer
ReDim FullCompressedR(0) As Integer

' Arry for decompressed values
Dim decompressedSignalL(0 To 255) As Integer
Dim decompressedSignalR(0 To 255) As Integer

' Load sound from file
Dim m As _MEM, Snd As Long
Snd = _SndOpen("n.mp3")
m = _MemSound(Snd, 0)

Dim a As Long
Do Until a& >= m.SIZE
    ' Load 256 samples into buffer
    predictedValueL = 0
    predictedValueR = 0

    For i = 0 To 255
        If a& >= m.SIZE Then Exit Do
        originalSignalL(i) = _MemGet(m, m.OFFSET + a&, Single) * 32768
        originalSignalR(i) = _MemGet(m, m.OFFSET + a& + 4, Single) * 32768
        a& = a& + 8
    Next i

    ' ADPCM compression for lft and right channel
    For i = 1 To UBound(originalSignalL)
        ' left channel
        diffL = originalSignalL(i) - predictedValueL
        directionL = 0
        If diffL >= 0 Then directionL = 1 Else diffL = -diffL
        deltaL = diffL \ currentStepL
        If deltaL > 7 Then deltaL = 7
        compressedValueL = directionL * 8 + deltaL
        compressedDataL(i - 1) = compressedValueL
        stepChangeL = (deltaL + 0.5) * currentStepL
        If directionL = 0 Then stepChangeL = -stepChangeL
        predictedValueL = predictedValueL + stepChangeL
        If predictedValueL > 32767 Then predictedValueL = 32767
        If predictedValueL < -32768 Then predictedValueL = -32768
        decompressedSignalL(i) = predictedValueL
        stepIndexL = stepIndexL + deltaL - 4
        If stepIndexL < 0 Then stepIndexL = 0
        If stepIndexL > 87 Then stepIndexL = 87

        currentStepL = stepTable(stepIndexL)

        ' right channel
        diffR = originalSignalR(i) - predictedValueR
        directionR = 0
        If diffR >= 0 Then directionR = 1 Else diffR = -diffR
        deltaR = diffR \ currentStepR
        If deltaR > 7 Then deltaR = 7
        compressedValueR = directionR * 8 + deltaR
        compressedDataR(i - 1) = compressedValueR
        stepChangeR = (deltaR + 0.5) * currentStepR
        If directionR = 0 Then stepChangeR = -stepChangeR
        predictedValueR = predictedValueR + stepChangeR
        If predictedValueR > 32767 Then predictedValueR = 32767
        If predictedValueR < -32768 Then predictedValueR = -32768
        decompressedSignalR(i) = predictedValueR
        stepIndexR = stepIndexR + deltaR - 4
        If stepIndexR < 0 Then stepIndexR = 0
        If stepIndexR > 87 Then stepIndexR = 87
        currentStepR = stepTable(stepIndexR)

        Print "Current compressed sample value: "; compressedDataR(i); compressedDataL(i); "    "
        If compresseddataR > 15 Or compressedDataL > 15 Then Print "Compression failure!" 'never printed!
    Next i


    ReDim signalL(0 To 512) As Integer
    ReDim signalR(0 To 512) As Integer

    ' LowPassFilter decompressedSignalL(), signalL(), 10000, _SndRate
    ' LowPassFilter decompressedSignalR(), signalR(), 10000, _SndRate

    'Play decompressed signal
    For i = 0 To UBound(decompressedSignalL)
        _SndRaw decompressedSignalL(i) / 32768, decompressedSignalR(i) / 32768
        '_SndRaw signalL(i) / 32768, signalR(i) / 32768
    Next i
    Do Until _SndRawLen < .1
    Loop
Loop
_MemFree m
_SndClose Snd
End


Sub LowPassFilter (inputSignal() As Integer, outputSignal() As Integer, cutoffFreq As Single, sampleRate As Single)
    Dim kernel(0 To 31) As Single ' Filter coefficients (FIR Window, 32 samples)
    Dim sum As Single
    Dim normFactor As Single
    Dim N As Integer
    Dim halfN As Integer

    N = UBound(kernel)
    halfN = N \ 2

    ' Creating a low-pass filter (Hamming window)
    For i = -halfN To halfN
        If i = 0 Then
            kernel(i + halfN) = 2 * cutoffFreq / sampleRate
        Else
            kernel(i + halfN) = Sin(2 * 3.14159 * cutoffFreq * i / sampleRate) / (3.14159 * i)
        End If
        kernel(i + halfN) = kernel(i + halfN) * (0.54 - 0.46 * Cos(2 * 3.14159 * (i + halfN) / N)) ' Hamming window
    Next i

    ' Normalize kernel
    For i = 0 To N
        normFactor = normFactor + kernel(i)
    Next i
    For i = 0 To N
        kernel(i) = kernel(i) / normFactor
    Next i

    ' Apply filter to input signal
    For i = 0 To UBound(inputSignal)
        sum = 0
        For j = 0 To N
            If i - j >= 0 Then
                sum = sum + inputSignal(i - j) * kernel(j)
            End If
        Next j
        outputSignal(i) = Int(sum)
    Next i
End Sub


Reply




Users browsing this thread: 1 Guest(s)