Last active
May 4, 2021 08:34
-
-
Save Artoria2e5/64a2b0a03121e2c9af0b7943bfebf8e4 to your computer and use it in GitHub Desktop.
Pretend that your 32-bit floating-point WAV is 32-bit integer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
This cursed piece of shit lets you pretend your 32-bit floating-point | |
WAV is actually 32-bit integer, so FLAC may take it. Now the only encoder | |
that does 32-bit integer is some SVN build of FLAKE I have no intention | |
of compiling, so I can't test how badly it compresses. | |
What I can tell you is that WavPack hates it. It compressess down to about | |
84% of the original, which is worse than 7-zip. On the other hand the original | |
f32 version goes down to 58%. The test file is decoded from Open Goldberg, | |
Aria, AAC. | |
The good news is that the reverse conversion does work. There is probably a | |
better way to go by this if we exploit the fact that all floats involved are | |
in [-1, 1]. | |
""" | |
import pathlib | |
from scipy.io import wavfile | |
import numpy as np | |
import struct | |
from typing import Union, BinaryIO, Callable, List | |
from functools import partial | |
handle = Union[str, BinaryIO] | |
def f2i_32(f: np.float32) -> np.int32: | |
""" | |
Turn a float into an int, preserving sort order. | |
Need a subtraction to appease signed int. | |
See: http://stereopsis.com/radix.html | |
""" | |
b = struct.pack(">f", f) | |
i: int = struct.unpack(">I", b)[0] | |
mask = -int(i >> 31) | 0x80000000 | |
return np.int32(((i ^ mask) & 0xFFFFFFFF) - 0x80000000) | |
def i2f_32(i: np.int32) -> np.float32: | |
""" | |
Inverse of ``f2i_32``. | |
""" | |
i = (i + 0x80000000) & 0xFFFFFFFF | |
mask = (((i >> 31) - 1) | 0x80000000) & 0xFFFFFFFF | |
i ^= mask | |
b = struct.pack(">I", i) | |
return np.float32(struct.unpack(">f", b)[0]) | |
def wav_samp( | |
mapping: Callable[[np.number], np.number], | |
infile: handle, | |
outfile: handle, | |
) -> None: | |
(rate, data) = wavfile.read(infile) | |
wavfile.write(outfile, rate, np.vectorize(mapping)(data)) | |
wav_f2i_32 = partial(wav_samp, f2i_32) | |
wav_i2f_32 = partial(wav_samp, i2f_32) | |
def do_file(wav_map: Callable[[str, str], None], file: str, suf: str) -> None: | |
pf = pathlib.PurePath(file) | |
nf = pf.with_suffix(f".{suf}{pf.suffix}") | |
wav_map(file, str(nf)) | |
def main(argv: List[str]) -> int: | |
# No time for argparse. I am sorry. | |
WAV_MAPS = { | |
"f2i_32": wav_f2i_32, | |
"i2f_32": wav_i2f_32, | |
} | |
wmap = WAV_MAPS[argv[1]] | |
for i in argv[2:]: | |
do_file(wmap, i, argv[1]) | |
return 0 | |
if __name__ == "__main__": | |
import sys | |
sys.exit(main(sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment