Feb 13, 2022 12:04 PM
(This post was last modified: Feb 13, 2022 12:09 PM by MiguelSanchez.)
Compression today, I'll write up the decompression tomorrow, if I've explained this properly you shouldn't need it.
Extrema Compression V0.1
Introduction:
ExtremaCompression is a low-loss compression method for audio.
Unlike MP3, OGG and other common standards the results still vary widely proptional to frequency.
Higher frequency oscillations will be encoded with minimal loss, but also minimal compression, while low frequencies, will compress very small but the quality may deviate from the original product.
How does it work:
Possibly the most intuitive form of compression, the algorithm simply the wave into segments from a high peak to a low peak or low peak to a high peak, finds the closest matching shape (out of 255 precomputed lines/curves) and replaces each segment with a 5 byte approximation segment.
The Filetype:
The intermediate file is simply an encoded bytestring to be read 5 bytes at a time.
Each 5 byte sample can represent two things, a curve, or silence.
If the first byte in the sample is a 0 it represents silence, any other value and it's a curve.
-Silence
Byte 1: Always 0
Byte 2 to 5: A 4-bit unsigned-long representing how many samples to remain silent.*
-Curve
Byte 1: Unsigned-char, ordinal number representing which shape is used, not zero-indexed (i.e. 1 is the first item, 2 is the second.)
Byte 2 & 3: Unsigned-Short representing the length of the curve in samples*
Byte 4 & 5: Signed-Short representing the position of the speaker at the end of the curve. The start of the curve will be the end of the last curve.
*Sample rate is 44100. That means half a second is 22050samples. 2 seconds 88200samples, and 10ms is 441 samples. So a 440Hz note should oscillate 440 times every 44100 frames (every 100.22 frames.)
Compression Step by step:
First we:
-initialise everything needed for the loop,
and
-load the first frame to determine the start direction.
Open the file inputFile, define which cstruct to use depending on the sample width of the wave, unsigned-long for PCM32, unsigned-short for PCM16. (optional: Print the wave properties)
-set the Ythreshold (the minimum amount of change required to be considered a movement, until this amount is surpassed the wave is considered still)
-Initialise the output file,
-Read and unpack the first sample from the input file.
-if the first frame exceeds the threshold set the lastdirection to 1 or -1, else lastdirection=0 #1 is up, -1 is down, 0 is still
-Initialise the variable last (the last value) as the previous frames value (defined as first)
-Make a list containing only the first value, this will be used to store the current segment.
-Initalise a variable to keep count of how many frames have elapsed (I called it n)
and start the loop.
The loop interior:
Load a single frame.
If no frame is available break the loop
If the frame isn't empty then unpack it into the variable currentval divided by magnitude
if currentval exceeds last by at least YThreshold, set the direction to 1, otherwise if last exceeds currentval by more than YThreshold set direction to -1. Otherwise its 0.
now that the direction has been resolved, we compare it with the last direction,
If they're the same, append currentval to current (the current segment)
and set last to currentval and proceed to the next iteration.
-In the event the direction has changed since the last sample (the else condition above) then
-Increase N by 1 (there's a new output)
-If the last direction was 0 (stillness) then make some bytes representing the just elapsed silence. First the value 0 to signify it's silence and not a curve <1 byte unsigned-char> then len(current) <4 byte unsigned-long>
-write the silence bytes to OutFile
If it's not a silence it's a curve so run Curves.closet(current) to compare the contents of current with all 255 precomputed curves, to find the closest match. (This can be very slow, so we often just check 8 to 64 evenly spaced points along the curve, depending on the length)
Our whole section is as simple as (The closest shaped curve <number 1..255>, the length of the current segment, and where it ends)
These 3 values are packed to an unsigned char, an unsigned short, and a signed short respectively, and in that order, and written to outfile, after that the resources are all freed from python.
Before the next iteration replace lastdirection with direction, replace current with [currentval] and replace last with currentval.
Every 1024 frames flush the OutFile (everything we sent to file.write() will actually be visible in the directory now, if you want a peak before it's done)
Once the loop is broken by the end of file, close both files, and return 1 to indicate a success.
I need a break. It's 5am, I'm off to bed. Obviously the next step is to import it somewhere faster. I'm getting great results though. I'll ellaborate tomorrow. This took way too long to type. I hope someone reads it.
Much love to anyone making it this far <3
There's a lot more to this, this is part 1.
More to come for sure, not all in python but the prototypes will usually be.
Extrema Compression V0.1
Introduction:
ExtremaCompression is a low-loss compression method for audio.
Unlike MP3, OGG and other common standards the results still vary widely proptional to frequency.
Higher frequency oscillations will be encoded with minimal loss, but also minimal compression, while low frequencies, will compress very small but the quality may deviate from the original product.
How does it work:
Possibly the most intuitive form of compression, the algorithm simply the wave into segments from a high peak to a low peak or low peak to a high peak, finds the closest matching shape (out of 255 precomputed lines/curves) and replaces each segment with a 5 byte approximation segment.
The Filetype:
The intermediate file is simply an encoded bytestring to be read 5 bytes at a time.
Each 5 byte sample can represent two things, a curve, or silence.
If the first byte in the sample is a 0 it represents silence, any other value and it's a curve.
-Silence
Byte 1: Always 0
Byte 2 to 5: A 4-bit unsigned-long representing how many samples to remain silent.*
-Curve
Byte 1: Unsigned-char, ordinal number representing which shape is used, not zero-indexed (i.e. 1 is the first item, 2 is the second.)
Byte 2 & 3: Unsigned-Short representing the length of the curve in samples*
Byte 4 & 5: Signed-Short representing the position of the speaker at the end of the curve. The start of the curve will be the end of the last curve.
*Sample rate is 44100. That means half a second is 22050samples. 2 seconds 88200samples, and 10ms is 441 samples. So a 440Hz note should oscillate 440 times every 44100 frames (every 100.22 frames.)
Compression Step by step:
First we:
-initialise everything needed for the loop,
and
-load the first frame to determine the start direction.
Code
def RealtimeCompress(inputFile,outputFile):
WaveFile=openwave(inputFile)
samplewidth=WaveFile.getsampwidth()
cstruct="<l" if samplewidth==4 else "<h"
magnitude=2**(8*samplewidth-1)-1
channels=WaveFile.getnchannels()
print("sample width: %s"%samplewidth)
print("channels: %s"%channels)
print("magnitude: %s"%magnitude)Code
YThreshold=50/magnitude
OutFile=open(outputFile,"wb")
firstframe=WaveFile.readframes(1)[0:samplewidth]
first=unpack(cstruct,firstframe)[0]/magnitude
lastdirection=1 if first>YThreshold else -1 if first<-YThreshold else 0
#1 is up, -1 down, 0 still-Initialise the output file,
-Read and unpack the first sample from the input file.
-if the first frame exceeds the threshold set the lastdirection to 1 or -1, else lastdirection=0 #1 is up, -1 is down, 0 is still
Code
last=first
current=[first]
n=0
while 1:-Make a list containing only the first value, this will be used to store the current segment.
-Initalise a variable to keep count of how many frames have elapsed (I called it n)
and start the loop.
The loop interior:
Code
frame=WaveFile.readframes(1)[0:samplewidth]
if frame==b'':
#END OF FILE
breakIf no frame is available break the loop
Code
else:
currentval=unpack(cstruct,frame)[0]/magnitude
direction=1 if currentval>last+YThreshold else -1 if currentval<last-YThreshold else 0
#determine directionif currentval exceeds last by at least YThreshold, set the direction to 1, otherwise if last exceeds currentval by more than YThreshold set direction to -1. Otherwise its 0.
Code
if direction==lastdirection:
current.append(currentval)
last=currentvalIf they're the same, append currentval to current (the current segment)
and set last to currentval and proceed to the next iteration.
Code
else:
n+=1
##MAKE SECTION
if lastdirection==0:
#ADD SILENCE BYTES
silencebytes=pack("B",0)+pack("L",len(current)) #holybytes
OutFile.write(silencebytes)
del silencebytes-In the event the direction has changed since the last sample (the else condition above) then
-Increase N by 1 (there's a new output)
-If the last direction was 0 (stillness) then make some bytes representing the just elapsed silence. First the value 0 to signify it's silence and not a curve <1 byte unsigned-char> then len(current) <4 byte unsigned-long>
-write the silence bytes to OutFile
Code
else:
#ADD SECTION
curvenum,score=Curves.closest(current)#compareCurve
section=(curvenum,len(current),current[-1])
#write-out section
curvenum=pack("B",section[0]+1)
xval=pack("H",int(section[1]))
yval=pack("h",int(section[2]*(2**15-1)))
curvebytes=curvenum+xval+yval
OutFile.write(curvebytes)
del curvebytes,xval,yval,curvenum,section,scoreOur whole section is as simple as (The closest shaped curve <number 1..255>, the length of the current segment, and where it ends)
These 3 values are packed to an unsigned char, an unsigned short, and a signed short respectively, and in that order, and written to outfile, after that the resources are all freed from python.
Code
lastdirection=direction
current=[currentval]
last=currentvalCode
if n==1024:
OutFile.flush()
n=0Code
OutFile.close()
WaveFile.close()
return 1I need a break. It's 5am, I'm off to bed. Obviously the next step is to import it somewhere faster. I'm getting great results though. I'll ellaborate tomorrow. This took way too long to type. I hope someone reads it.
Much love to anyone making it this far <3
There's a lot more to this, this is part 1.
More to come for sure, not all in python but the prototypes will usually be.


![[Image: zzzscreen.gif]](https://trogramming.com/blog/wp-content/uploads/2022/03/zzzscreen.gif)