├── .rustfmt.toml ├── .cargo └── config.toml ├── test ├── Timing Results.ods ├── compile-pgo.sh ├── test_wavs.sh ├── bench.sh └── timings.csv ├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── src ├── utils.rs ├── lib.rs ├── bin │ ├── wav_to_str.rs │ └── x3.rs ├── error.rs ├── bytewriter.rs ├── encodefile.rs ├── crc.rs ├── bytereader.rs ├── x3.rs ├── bitreader.rs ├── bitpacker.rs ├── decodefile.rs ├── decoder.rs └── encoder.rs ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── Cargo.toml ├── README.md └── LICENSE /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width=120 2 | tab_spaces=2 -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags=["-C","target-cpu=native"] -------------------------------------------------------------------------------- /test/Timing Results.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiphi75/x3-rust/HEAD/test/Timing Results.ods -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | workings/ 6 | 7 | test/underwater-sound-samples 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": ["std"], 3 | "rust-analyzer.cargo.noDefaultFeatures": false, 4 | "rust-analyzer.cargo.allTargets": true 5 | } -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | #[cold] 3 | pub const fn cold() {} 4 | 5 | #[inline(always)] 6 | #[allow(unused)] 7 | pub const fn likely(b: bool) -> bool { 8 | if !b { 9 | cold(); 10 | } 11 | b 12 | } 13 | 14 | #[inline(always)] 15 | pub const fn _unlikely(b: bool) -> bool { 16 | if b { 17 | cold(); 18 | } 19 | b 20 | } 21 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu 3 | { 4 | "name": "Ubuntu", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "build": { 7 | "dockerfile": "Dockerfile", 8 | "context": "." 9 | }, 10 | "customizations": { 11 | "vscode": { 12 | "extensions": [ 13 | "rust-lang.rust-analyzer" 14 | ] 15 | } 16 | }, 17 | "workspaceFolder": "/workspaces/x3-rust" 18 | } 19 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | LABEL Name=x3rust Version=0.0.1 3 | 4 | # Install dependencies, this includes dependencies for benchmarking and testing. 5 | RUN apt update && \ 6 | apt dist-upgrade -y && \ 7 | apt install -y build-essential curl git flac time bc llvm-20 8 | 9 | # Install Rust 10 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 11 | ENV PATH="/root/.cargo/bin:${PATH}" 12 | 13 | # This repo is used for bechmarks 14 | RUN git config --global --add safe.directory /workspaces/x3-rust/test/underwater-sound-samples 15 | 16 | ENTRYPOINT ["sh"] 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x3" 3 | description = "A CPU efficient audio encoder/decoder using the X3 codec." 4 | version = "0.3.1" 5 | authors = ["Simon M. Werner "] 6 | license = "GPL-3.0-or-later" 7 | readme = "README.md" 8 | edition = "2024" 9 | repository = "https://github.com/psiphi75/x3-rust" 10 | keywords = ["audio", "compression"] 11 | categories = ["compression", "embedded", "encoding", "multimedia::audio"] 12 | 13 | [profile.release] 14 | lto = "fat" 15 | codegen-units = 1 16 | panic = "abort" 17 | # debug = true # Enable debug for `perf` profiling 18 | 19 | [build-dependencies] 20 | clippy = { version = "^0.0.302", optional = true } 21 | 22 | [dependencies] 23 | byteorder = { version = "1.3.4", default-features = false } 24 | hound = "3.5.1" 25 | quick-xml = "0.38" 26 | clap = "2.33.1" 27 | chrono = "0.4.15" 28 | 29 | [features] 30 | # default = ["std"] 31 | std = [] 32 | alloc = [] 33 | 34 | [[bin]] 35 | name = "x3" 36 | path = "src/bin/x3.rs" 37 | required-features = ["std"] 38 | 39 | [[bin]] 40 | name = "wav_to_str" 41 | path = "src/bin/wav_to_str.rs" 42 | required-features = ["std"] -------------------------------------------------------------------------------- /test/compile-pgo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copied from: https://doc.rust-lang.org/rustc/profile-guided-optimization.html 4 | # 5 | # BEFORE 6 | # Algorithm Compression speed (MB/s) Decompression speed (MB/s) 7 | # x3a 105.98 433.81 8 | # flac 191.32 243.84 9 | # 10 | # AFTER (with PGO) 11 | # Algorithm Compression speed (MB/s) Decompression speed (MB/s) 12 | # x3a 139.72 384.61 13 | # flac 184.69 230.29 14 | 15 | set -euo pipefail 16 | 17 | # STEP 0: Start with a clean slate 18 | TEMP_DIR=$(mktemp -d) 19 | trap "rm -rf ${TEMP_DIR}" 0 2 3 15 20 | 21 | # STEP 1: Build the instrumented binaries 22 | RUSTFLAGS="-Cprofile-generate=${TEMP_DIR}" cargo build --release --bin x3 --features=std 23 | 24 | # STEP 2: Run the instrumented binaries with some typical data 25 | X3=../target/release/x3 26 | for file in $(find . -name *.wav); do 27 | ${X3} --input $file --output $file.x3a > /dev/null 28 | ${X3} --input $file.x3a --output $file.x3a.wav > /dev/null 29 | rm $file.x3a $file.x3a.wav 30 | break 31 | done 32 | 33 | # STEP 3: Merge the `.profraw` files into a `.profdata` file 34 | llvm-profdata-20 merge -o ${TEMP_DIR}/merged.profdata ${TEMP_DIR} 35 | 36 | # STEP 4: Use the `.profdata` file for guiding optimizations 37 | RUSTFLAGS="-Cprofile-use=${TEMP_DIR}/merged.profdata" cargo build --release --bin x3 --features=std 38 | 39 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | #![no_std] 22 | 23 | #[cfg(feature = "std")] 24 | extern crate std; 25 | 26 | #[cfg(any(feature = "alloc", feature = "std"))] 27 | extern crate alloc; 28 | 29 | extern crate byteorder; 30 | extern crate hound; 31 | 32 | pub mod bitpacker; 33 | pub mod bitreader; 34 | pub mod bytereader; 35 | pub mod bytewriter; 36 | pub mod crc; 37 | #[cfg(feature = "std")] 38 | pub mod decodefile; 39 | pub mod decoder; 40 | #[cfg(feature = "std")] 41 | pub mod encodefile; 42 | pub mod encoder; 43 | pub mod error; 44 | mod utils; 45 | pub mod x3; 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug x3 test", 11 | "cargo": { 12 | "args": ["test", "--no-run", "--lib", "test_decode_x3a_file"] 13 | }, 14 | "program": "${cargo:program}", 15 | "args": [] 16 | }, 17 | { 18 | "type": "lldb", 19 | "request": "launch", 20 | "name": "Debug 'x3'", 21 | "cargo": { 22 | "args": ["test", "--no-run", "--lib", "--package=x3"], 23 | "filter": { 24 | "kind": "lib" 25 | } 26 | }, 27 | "args": [], 28 | "cwd": "${workspaceFolder}" 29 | }, 30 | { 31 | "type": "lldb", 32 | "request": "launch", 33 | "name": "Debug 'x3' cli-DEcode", 34 | "program": "${workspaceFolder}/target/debug/x3", 35 | "args": ["--input", "/home/simon/Projects/x3/test/files/LI192.x3a", "--output", "test.wav"], 36 | "cwd": "${workspaceFolder}", 37 | "env": { 38 | "RUST_BACKTRACE": "1" 39 | } 40 | }, 41 | { 42 | "type": "lldb", 43 | "request": "launch", 44 | "name": "Debug 'x3' cli-ENcode (x3a)", 45 | "program": "${workspaceFolder}/target/debug/x3", 46 | "args": ["--input", "${workspaceFolder}/x3-matlab/test.wav", "--output", "test.x3a"], 47 | "cwd": "${workspaceFolder}", 48 | "env": { 49 | "RUST_BACKTRACE": "1" 50 | } 51 | }, 52 | { 53 | "type": "lldb", 54 | "request": "launch", 55 | "name": "Debug 'wav_to_str", 56 | "program": "${workspaceFolder}/target/debug/wav_to_str", 57 | "args": ["--wav", "${workspaceFolder}/test/test.wav"], 58 | "cwd": "${workspaceFolder}", 59 | "env": { 60 | "RUST_BACKTRACE": "1" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/bin/wav_to_str.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | extern crate clap; 23 | /// 24 | /// This is just a test helper, it outputs the wav data a string 25 | /// 26 | // externs 27 | extern crate hound; 28 | 29 | use clap::{App, Arg}; 30 | 31 | fn main() { 32 | let matches = App::new("wav_to_str") 33 | .version("0.1.0") 34 | .author("Simon Werner ") 35 | .about("wav_to_str - output a wav a string.") 36 | .arg( 37 | Arg::with_name("wav") 38 | .short("w") 39 | .long("wav") 40 | .value_name("FILE") 41 | .help("The input file, a .wavfile") 42 | .required(true) 43 | .takes_value(true), 44 | ) 45 | .get_matches(); 46 | 47 | let wav_filename = matches.value_of("wav").unwrap(); 48 | let mut reader = hound::WavReader::open(wav_filename).unwrap(); 49 | 50 | println!("bits_per_sample: {}", reader.spec().bits_per_sample); 51 | println!("channels: {}", reader.spec().channels); 52 | println!("sample_rate: {}", reader.spec().sample_rate); 53 | 54 | let samples = reader.samples::().map(|x| x.unwrap()).collect::>(); 55 | let mut n = 0; 56 | for sample in samples { 57 | if n == 16 { 58 | println!(); 59 | n = 0; 60 | } 61 | n += 1; 62 | print!("{} ", sample); 63 | } 64 | println!(); 65 | } 66 | -------------------------------------------------------------------------------- /test/test_wavs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ############################################################################# 4 | # # 5 | # Rust implementation of the X3 lossless audio compression protocol. # 6 | # # 7 | # Copyright (C) 2019 Simon M. Werner # 8 | # # 9 | # This program is free software; you can redistribute it and/or modify # 10 | # it under the terms of the GNU General Public License as published by # 11 | # the Free Software Foundation, either version 3 of the License, or # 12 | # (at your option) any later version. # 13 | # # 14 | # This program is distributed in the hope that it will be useful, # 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 17 | # GNU General Public License for more details. # 18 | # # 19 | # You should have received a copy of the GNU General Public License # 20 | # along with this program. If not, see . # 21 | # # 22 | ############################################################################# 23 | 24 | # 25 | # These tests will compress all the files in the given directory, 26 | # decompress them, and test the original with the decompressed version. 27 | # This validates end-to-end compression/decompression. 28 | # 29 | 30 | set -e # exit on error 31 | 32 | TARGET=release 33 | X3=../target/${TARGET}/x3 34 | W2S=../target/${TARGET}/wav_to_str 35 | 36 | SOUND_DIR=$1 37 | if [ -z ${SOUND_DIR} ] || [ ! -d ${SOUND_DIR} ]; then 38 | echo 39 | echo "Usage:" 40 | echo " test_wavs.sh [DIRECTORY]" 41 | echo 42 | exit 1 43 | fi 44 | 45 | # build it 46 | cargo build --${TARGET} --bin="x3" --features="std" 47 | cargo build --${TARGET} --bin="wav_to_str" --features="std" 48 | 49 | 50 | TEMP_X3A=$(mktemp).x3a 51 | trap "rm -f $TEMP_X3A" 0 2 3 15 52 | 53 | TEMP_WAV=$(mktemp).wav 54 | trap "rm -f $TEMP_WAV" 0 2 3 15 55 | 56 | TEMP_WAV_STR_ORIG=${TEMP_WAV}.raw-orig 57 | trap "rm -f $TEMP_WAV_STR_ORIG" 0 2 3 15 58 | 59 | TEMP_WAV_STR_TEST=${TEMP_WAV}.raw-test 60 | trap "rm -f $TEMP_WAV_STR_TEST" 0 2 3 15 61 | 62 | 63 | SOUNDS=$(ls $SOUND_DIR/*.wav) 64 | for WAV in ${SOUNDS} 65 | do 66 | 67 | echo "Testing $WAV" 68 | 69 | echo " Encoding to ${TEMP_X3A}" 70 | $X3 --input $WAV --output $TEMP_X3A > /dev/null 71 | 72 | echo " Decoding ${TEMP_X3A} to ${TEMP_WAV}" 73 | $X3 --input $TEMP_X3A --output $TEMP_WAV > /dev/null 74 | 75 | echo " Checking" 76 | $W2S --wav $WAV > "${TEMP_WAV_STR_ORIG}" 77 | $W2S --wav $TEMP_WAV > "${TEMP_WAV_STR_TEST}" 78 | WAV_DIFF=$(cmp "${TEMP_WAV_STR_ORIG}" "${TEMP_WAV_STR_TEST}") 79 | if [ -n "${WAV_DIFF}" ]; then 80 | vbindiff "${WAV}" "${TEMP_WAV}" 81 | echo " TEST FAILED" 82 | echo ${WAV_DIFF} 83 | exit 1 84 | fi 85 | 86 | echo " Ok" 87 | echo 88 | 89 | done -------------------------------------------------------------------------------- /src/bin/x3.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | extern crate clap; 23 | extern crate x3; 24 | 25 | use clap::{App, Arg}; 26 | 27 | #[derive(PartialEq, Eq)] 28 | enum AudioFiles { 29 | X3a, // .x3a 30 | Wav, // .wav 31 | } 32 | 33 | fn get_filetype(filename: &str) -> AudioFiles { 34 | if filename.ends_with(".x3a") { 35 | return AudioFiles::X3a; 36 | } 37 | if filename.ends_with(".wav") { 38 | return AudioFiles::Wav; 39 | } 40 | panic!("Invalid audio file, expecting a '.wav' or '.x3a' file: {}", filename); 41 | } 42 | 43 | fn main() { 44 | let matches = App::new("x3") 45 | .version("0.3.0") 46 | .author("Simon Werner ") 47 | .about("x3 - efficient lossless compression for low entropy audio wav files.") 48 | .arg( 49 | Arg::with_name("input") 50 | .short("i") 51 | .long("input") 52 | .value_name("FILE") 53 | .help("The input file, a .wav or .x3a file") 54 | .required(true) 55 | .takes_value(true), 56 | ) 57 | .arg( 58 | Arg::with_name("output") 59 | .short("o") 60 | .long("output") 61 | .value_name("FILE") 62 | .help("The output file, a .wav or .x3a file") 63 | .required(true) 64 | .takes_value(true), 65 | ) 66 | .get_matches(); 67 | 68 | let in_file = matches.value_of("input").unwrap(); 69 | let out_file = matches.value_of("output").unwrap(); 70 | 71 | let in_type = get_filetype(in_file); 72 | let out_type = get_filetype(out_file); 73 | 74 | if in_type == out_type { 75 | panic!("Input must be different file type than output."); 76 | } 77 | 78 | match in_type { 79 | AudioFiles::Wav => x3::encodefile::wav_to_x3a(in_file, out_file).unwrap(), 80 | AudioFiles::X3a => x3::decodefile::x3a_to_wav(in_file, out_file).unwrap(), 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # X3 Lossless Audio Compression for Rust 2 | 3 | X3 is a simple and effective lossless audio compressor for low entropy sound. It is based on 4 | [Shorten]() and has some of the features 5 | of [FLAC](https://xiph.org/flac/) but is designed specifically for use in underwater sound 6 | recording tags and buoys. It is much faster than FLAC, but does not acheive the compression 7 | ratio. The name comes from the compression factor that it usually achieves, i.e., 3 times. 8 | The algorithm is described in a paper in the [Journal of the Acoustical Society of 9 | America 133:1387-1398, 2013](http://link.aip.org/link/?JAS/133/1387). 10 | 11 | This repository is a Rust port of the Matlab code from the 12 | [original authors](https://www.soundtags.org/dtags/audio_compression/). 13 | 14 | ## Code usage 15 | 16 | ### Encode and decode .wav <-> .x3a 17 | 18 | ```rust 19 | 20 | use x3::encodefile::wav_to_x3a; 21 | use x3::decodefile::x3a_to_wav; 22 | 23 | // Convert .wav to .x3a 24 | wav_to_x3a("/path/to/input_file.x3a", "/path/to/output_file.wav").unwrap(); 25 | 26 | // Convert .x3a to .wav 27 | x3a_to_wav("/path/to/input_file.wav", "/path/to/output_file.x3a").unwrap(); 28 | 29 | ``` 30 | 31 | ### Encode an array of wav data 32 | 33 | ```rust 34 | 35 | let wav: Vec = /* you need to add your wav data */; 36 | 37 | // Can only handle signed 16 bit data with one channel. 38 | let params = x3::Parameters::default(); 39 | let sample_rate = 44100; 40 | let num_samples = wav.len(); 41 | 42 | // Create the channel data 43 | let first_channel = x3::Channel::new(0, &wav, sample_rate, params); 44 | 45 | // Create the output data 46 | let x3_len = num_samples * 2; 47 | let mut x3_out = vec![0u8; x3_len]; 48 | let bp = &mut BitPacker::new(&mut x3_out); // Packer where x3 compressed data is stored. 49 | 50 | encoder::encode(&[&first_channel], bp).unwrap(); 51 | 52 | // Get the bytes 53 | let x3_bytes = bp.as_bytes().unwrap(); 54 | 55 | ``` 56 | 57 | ## Comand line usage 58 | 59 | Building the package will create the `x3` binary executable. You can convert files 60 | to/from x3a/wav. 61 | 62 | Example: 63 | 64 | ```sh 65 | 66 | # Convert from x3a to wav 67 | ./x3 --input /path/to/file.x3a --output /path/to/file.wav 68 | 69 | # Convert from wav to x3a 70 | ./x3 --input /path/to/file.wav --output /path/to/file.x3a 71 | ``` 72 | 73 | ## TODO 74 | 75 | The following items need to be worked on: 76 | 77 | - Inform user if there were issues with decoding frames. 78 | - Error handling could be better. 79 | - Add multiple channel capability - currently we can only handle mono. 80 | - Peformance, it is currently slower than the flac encoder. 81 | - Not very memory efficient for encoding files. It will allocate all the memory upfront. 82 | - Seperate reading and decoding. Read frames in advance, maybe in a different way. 83 | 84 | ## License 85 | 86 | Rust implementation of the X3 lossless audio compression protocol. 87 | 88 | Copyright (C) 2019 Simon M. Werner 89 | 90 | This program is free software; you can redistribute it and/or modify 91 | it under the terms of the GNU General Public License as published by 92 | the Free Software Foundation; either version 3 of the License, or 93 | (at your option) any later version. 94 | 95 | This program is distributed in the hope that it will be useful, 96 | but WITHOUT ANY WARRANTY; without even the implied warranty of 97 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 98 | GNU General Public License for more details. 99 | 100 | You should have received a copy of the GNU General Public License 101 | along with this program; if not, see . 102 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | pub type Result = core::result::Result; 23 | 24 | // We derive `Debug` because all types should probably derive `Debug`. 25 | // This gives us a reasonable human readable description of `CliError` values. 26 | #[derive(Debug)] 27 | pub enum X3Error { 28 | #[cfg(feature = "std")] 29 | Io(std::io::Error), 30 | Hound(hound::Error), 31 | BitPack(crate::bitpacker::BitPackError), 32 | 33 | // Custom X3 Errors 34 | InvalidEncodingThresh, // Threshold must be less than or equal to code.offset 35 | OutOfBoundsInverse, // The value is out-of-bounds for the .inv array. 36 | MoreThanOneChannel, // FIXME: We need to support more than one channel 37 | 38 | // X3 Archive Header errors 39 | ArchiveHeaderXMLInvalid, // XML is poorly structured 40 | ArchiveHeaderXMLRiceCode, // XML has invalid rice code 41 | ArchiveHeaderXMLInvalidKey, // Invalid archive key 'X3ARHIV' 42 | 43 | // Frame issues 44 | FrameLength, // The frame is too long 45 | 46 | // Frame header issues 47 | FrameHeaderInvalidKey, // The frame header is missing 'x3' 48 | FrameHeaderInvalidPayloadLen, // The payload length reaches beyond the end of the available data 49 | FrameHeaderInvalidHeaderCRC, 50 | FrameHeaderInvalidPayloadCRC, 51 | 52 | // Decoding issues 53 | FrameDecodeInvalidBlockLength, // The block length is bad 54 | FrameDecodeInvalidIndex, // Invalid rice code encountered, index out of range 55 | FrameDecodeInvalidNTOGO, // Invalid ntogo 56 | FrameDecodeInvalidFType, // Invalid ftype 57 | FrameDecodeInvalidRiceCode, // The Rice codes are invalid 58 | FrameDecodeInvalidBPF, // The BPF decoder blew up, an invalid value was reached. 59 | FrameDecodeUnexpectedEnd, // The BitReader has less bytes than the size of the header, but still expects a frame. 60 | 61 | ByteWriterInsufficientMemory, 62 | } 63 | 64 | #[cfg(feature = "std")] 65 | impl From for X3Error { 66 | fn from(err: std::io::Error) -> X3Error { 67 | X3Error::Io(err) 68 | } 69 | } 70 | 71 | impl From for X3Error { 72 | fn from(err: hound::Error) -> X3Error { 73 | X3Error::Hound(err) 74 | } 75 | } 76 | 77 | impl From for X3Error { 78 | fn from(err: crate::bitpacker::BitPackError) -> X3Error { 79 | X3Error::BitPack(err) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/bytewriter.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Result, X3Error}; 2 | 3 | #[cfg(not(feature="std"))] 4 | pub enum SeekFrom{ 5 | Start(u64), 6 | End(i64), 7 | Current(i64), 8 | } 9 | 10 | /// 11 | /// Generic trait with all functions required to write to underlying seekable memory 12 | /// structure 13 | /// 14 | pub trait ByteWriter { 15 | fn align(&mut self)-> Result; 16 | // Writing 17 | fn write_all(&mut self, value: impl AsRef<[u8]>) -> Result<()>; 18 | fn flush(&mut self)-> Result<()>; 19 | // seeking 20 | fn seek(&mut self, pos: SeekFrom)-> Result; 21 | fn stream_position(&mut self)-> Result; 22 | } 23 | 24 | /// 25 | /// Wrapper struct implementing ByteWriter trait to an underlying memory slice 26 | /// 27 | pub struct SliceByteWriter<'a> { 28 | slice: &'a mut [u8], 29 | p_byte: usize, 30 | stream_length: usize, 31 | } 32 | 33 | impl<'a> SliceByteWriter<'a> { 34 | pub fn new(slice: &'a mut [u8])-> Self{ 35 | SliceByteWriter { 36 | slice, 37 | p_byte: 0, 38 | stream_length: 0, 39 | } 40 | } 41 | } 42 | 43 | impl<'a> ByteWriter for SliceByteWriter<'a> { 44 | fn align(&mut self) -> Result { 45 | let residual = self.p_byte % N; 46 | if residual == 0 { 47 | /* Nothing to do */ 48 | return Ok(0); 49 | } 50 | let zero_array = [0u8; N]; 51 | self.write_all(&zero_array[residual..])?; 52 | Ok(N-residual) 53 | 54 | } 55 | 56 | fn flush(&mut self)-> Result<()> { 57 | Ok(()) 58 | } 59 | 60 | fn seek(&mut self, pos: SeekFrom)-> Result { 61 | let abs_pos = match pos { 62 | SeekFrom::Current(pos) => { 63 | ((self.p_byte as i64) + pos) as usize 64 | }, 65 | SeekFrom::Start(pos) => { 66 | pos as usize 67 | }, 68 | SeekFrom::End(pos) => { 69 | (self.stream_length as i64 + pos) as usize 70 | } 71 | }; 72 | if abs_pos > self.slice.len() { 73 | return Err(X3Error::ByteWriterInsufficientMemory) 74 | } 75 | self.p_byte = abs_pos; 76 | if self.p_byte > self.stream_length { 77 | self.stream_length = self.p_byte; 78 | } 79 | Ok(self.p_byte as u64) 80 | } 81 | 82 | fn stream_position(&mut self)-> Result { 83 | Ok(self.p_byte as u64) 84 | } 85 | 86 | fn write_all(&mut self, value: impl AsRef<[u8]>) -> Result<()> { 87 | let value = value.as_ref(); 88 | if value.len() > self.slice[self.p_byte..].len() { 89 | return Err(X3Error::ByteWriterInsufficientMemory); 90 | } self.slice[self.p_byte..self.p_byte + value.len()] 91 | .copy_from_slice(value); 92 | 93 | self.p_byte += value.len(); 94 | if self.p_byte > self.stream_length { 95 | self.stream_length = self.p_byte; 96 | } 97 | 98 | Ok(()) 99 | } 100 | } 101 | 102 | 103 | #[cfg(feature = "std")] 104 | pub use stream_byte_writer::*; 105 | #[cfg(feature = "std")] 106 | pub mod stream_byte_writer{ 107 | pub use std::io::{Write, Seek, SeekFrom}; 108 | use crate::bytewriter::ByteWriter; 109 | use crate::error::X3Error; 110 | 111 | /// 112 | /// Wrapper Struct implementing ByteWriter trait for any underlying Seek + Write stream 113 | /// (e.g. io::File, io:BufWriter, io::Cursor, etc...) 114 | /// 115 | pub struct StreamByteWriter<'a, W> 116 | where W: Write + Seek 117 | { 118 | writer: &'a mut W, 119 | } 120 | 121 | impl<'a, W> StreamByteWriter<'a, W> 122 | where W: Write + Seek 123 | { 124 | pub fn new(writer:&'a mut W) -> Self { 125 | StreamByteWriter { 126 | writer, 127 | } 128 | } 129 | } 130 | 131 | impl<'a, W> ByteWriter for StreamByteWriter<'a, W> 132 | where W: Write + Seek 133 | { 134 | fn align(&mut self)-> crate::error::Result { 135 | let position = self.writer.stream_position().map_err(X3Error::from)?; 136 | let residual = (position as usize) % N; 137 | if residual == 0 { 138 | /* Nothing to do */ 139 | return Ok(0); 140 | } 141 | let zero_array = [0u8; N]; 142 | self.write_all(&zero_array[residual..])?; 143 | Ok( N - residual) 144 | } 145 | 146 | fn flush(&mut self)-> crate::error::Result<()> { 147 | self.writer.flush().map_err(X3Error::Io) 148 | } 149 | 150 | fn seek(&mut self, pos: SeekFrom)-> crate::error::Result { 151 | self.writer.seek(pos).map_err(X3Error::from) 152 | } 153 | 154 | fn stream_position(&mut self)-> crate::error::Result { 155 | self.writer.stream_position().map_err(X3Error::from) 156 | } 157 | 158 | fn write_all(&mut self, value: impl AsRef<[u8]>) -> crate::error::Result<()> { 159 | let value = value.as_ref(); 160 | self.writer.write_all(value).map_err(X3Error::from)?; 161 | 162 | Ok(()) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/encodefile.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | // std 23 | use std::format; 24 | use std::fs::File; 25 | use std::io::BufWriter; 26 | use std::path; 27 | 28 | // externs 29 | use crate::hound; 30 | 31 | // this crate 32 | use crate::bytewriter::{ByteWriter, SeekFrom, StreamByteWriter}; 33 | use crate::crc::crc16; 34 | use crate::encoder; 35 | use crate::error; 36 | use crate::x3; 37 | 38 | use error::X3Error; 39 | 40 | /// 41 | /// Convert a .wav file to an .x3a (X3 Archive) file. 42 | /// 43 | /// ### Arguments 44 | /// 45 | /// * `wav_filename` - the input wav file to read. 46 | /// * `x3a_filename` - the output X3A file. It will be overwritten. 47 | /// 48 | pub fn wav_to_x3a>(wav_filename: P, x3a_filename: P) -> Result<(), X3Error> { 49 | let mut reader = hound::WavReader::open(wav_filename).unwrap(); 50 | 51 | // Can only handle 16 bit data 52 | assert_eq!(reader.spec().bits_per_sample, 16); 53 | 54 | // FIXME: We want to be able to handle multiple channels 55 | assert_eq!(reader.spec().channels, 1); 56 | 57 | let params = x3::Parameters::default(); 58 | let sample_rate = reader.spec().sample_rate; 59 | 60 | let samples = reader.samples::().map(|x| x.unwrap()); 61 | let mut first_channel = x3::IterChannel::new(0, samples, sample_rate, params); 62 | 63 | // Open output file 64 | // Note (MSH): BufWriter is not necessary but should improve performance as 65 | // underlying BitPacker struct performs many single byte writes. 66 | let x3_output_file = File::create(x3a_filename)?; 67 | let mut x3_buffered_writer = BufWriter::new(x3_output_file); 68 | let mut x3_output_writer = StreamByteWriter::new(&mut x3_buffered_writer); 69 | // let mut x3_output_writer = StreamByteWriter::new(&mut x3_output_file); // if not using BufWriter 70 | 71 | // Output file header 72 | create_archive_header(&first_channel, &mut x3_output_writer)?; 73 | 74 | encoder::encode(&mut [&mut first_channel], &mut x3_output_writer)?; 75 | 76 | Ok(()) 77 | } 78 | 79 | // 80 | // Write to the BitPacker output. 81 | // 82 | fn create_archive_header(ch: &x3::IterChannel, writer: &mut W) -> Result<(), X3Error> 83 | where 84 | I: Iterator, 85 | { 86 | // 87 | writer.write_all(x3::Archive::ID)?; 88 | 89 | // Make space for the header 90 | let frame_header_pos = writer.stream_position()?; 91 | writer.seek(SeekFrom::Current(x3::FrameHeader::LENGTH as i64))?; 92 | 93 | // Pad to make sure XML section ends on word boundary 94 | const PADDING: &str = " "; 95 | 96 | let xml: &str = &[ 97 | "", 98 | "", 99 | "", 100 | &format!("{}", ch.sample_rate), 101 | "wav", 102 | "", 103 | &format!("{}", ch.params.block_len), 104 | &format!( 105 | "RICE{},RICE{},RICE{},BFP", 106 | ch.params.codes[0], ch.params.codes[1], ch.params.codes[2] 107 | ), 108 | "DIFF", 109 | "16", 110 | &format!( 111 | "{},{},{}", 112 | ch.params.thresholds[0], ch.params.thresholds[1], ch.params.thresholds[2] 113 | ), 114 | "", 115 | "", 116 | PADDING 117 | ] 118 | .concat(); 119 | let xml_bytes = xml.as_bytes(); 120 | // 121 | let mut payload_len = xml_bytes.len(); 122 | 123 | // Align to the nearest word 124 | if payload_len % 2 == 1 { 125 | payload_len -= 1; 126 | } 127 | let xml_bytes = &xml_bytes[0..payload_len]; 128 | let payload_crc = crc16(xml_bytes); 129 | writer.write_all(xml_bytes)?; 130 | 131 | // 132 | // Write the header details 133 | let return_position = writer.stream_position()?; 134 | writer.seek(SeekFrom::Start(frame_header_pos))?; 135 | let frame_header = encoder::write_frame_header(0, 0, payload_len, payload_crc); 136 | writer.write_all(frame_header)?; 137 | writer.seek(SeekFrom::Start(return_position))?; 138 | Ok(()) 139 | } 140 | 141 | // #[cfg(test)] 142 | // mod tests { 143 | // use crate::encodefile::wav_to_x3a; 144 | 145 | // #[test] 146 | // fn test_encodefile() { 147 | // wav_to_x3a("~/../../../sounds/15s/NO96_15s.wav", "~/test.wav").unwrap(); 148 | // } 149 | // } 150 | -------------------------------------------------------------------------------- /src/crc.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | const CRC_TABLE: [u16; 256] = [ 23 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 24 | 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 25 | 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 26 | 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 27 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 28 | 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 29 | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 30 | 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 31 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 32 | 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 33 | 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 34 | 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 35 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 36 | 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 37 | 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 38 | 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 39 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 40 | 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 41 | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 42 | ]; 43 | 44 | pub fn update_crc16(crc: u16, data: &u8) -> u16 { 45 | let lookup: usize = (data ^ (crc >> 8) as u8) as usize; 46 | (crc << 8) ^ CRC_TABLE[lookup] 47 | } 48 | 49 | pub fn crc16(data: &[u8]) -> u16 { 50 | let mut crc: u16 = 0xffff; // initial CRC value 51 | 52 | // calculate the CRC over the data bytes 53 | for d in data { 54 | crc = update_crc16(crc, d); 55 | } 56 | 57 | crc 58 | } 59 | 60 | 61 | // 62 | // 63 | // ####### 64 | // # ###### #### ##### #### 65 | // # # # # # 66 | // # ##### #### # #### 67 | // # # # # # 68 | // # # # # # # # 69 | // # ###### #### # #### 70 | // 71 | // 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use crate::crc::crc16; 76 | 77 | #[test] 78 | fn test_crc() { 79 | let header: [u8; 20] = [ 80 | 0x78, 0x33, // 'x3' 81 | 0x01, 0x01, // Source id 82 | 0x27, 0x10, // n bytes 83 | 0x19, 0xd0, // n samples 84 | 0x00, 0x00, // time 85 | 0x00, 0x00, // .. 86 | 0x00, 0x00, // .. 87 | 0x00, 0x00, // .. 88 | 0xad, 0xdb, // Header crc 89 | 0x6f, 0x61, // Payload crc 90 | ]; 91 | 92 | assert_eq!(0xaddb, crc16(&header[0..16])); 93 | 94 | let payload: [u8; 150] = [ 95 | 0xf2, 0x2b, 0xf4, 0x86, 0xb0, 0xe1, 0x6e, 0xca, 0x9a, 0x35, 0x29, 0xa7, 0x51, 0xcd, 0xee, 0xd5, 0xc9, 0x30, 0x94, 96 | 0x21, 0x38, 0xda, 0x56, 0x97, 0x84, 0x44, 0x93, 0xd9, 0x44, 0x60, 0xb4, 0x9c, 0x57, 0x34, 0xd2, 0x1d, 0x2b, 0x69, 97 | 0x11, 0xe9, 0xd6, 0x9a, 0x46, 0xc4, 0x2d, 0xc2, 0x3e, 0x26, 0x25, 0x42, 0xd8, 0xcd, 0xd2, 0xfb, 0x66, 0x6a, 0xe7, 98 | 0x7b, 0xa0, 0x57, 0x8b, 0x20, 0x42, 0xd2, 0x67, 0xf6, 0x67, 0xfa, 0xe5, 0x5a, 0xd6, 0x19, 0x17, 0x19, 0x79, 0xf5, 99 | 0xfc, 0xdb, 0x38, 0xb3, 0x9b, 0x86, 0x5f, 0xcd, 0x2f, 0xa5, 0xf5, 0x3a, 0xcc, 0x62, 0x6e, 0xa3, 0x93, 0xeb, 0x43, 100 | 0xb6, 0x29, 0xaa, 0x62, 0xc5, 0x07, 0xa0, 0xfd, 0x13, 0xdd, 0x40, 0x24, 0x2f, 0x49, 0xc4, 0x85, 0xfa, 0xcf, 0xd2, 101 | 0x83, 0x14, 0x2d, 0x3a, 0x33, 0x0e, 0x4e, 0xf8, 0x11, 0x7a, 0xfc, 0x80, 0x3e, 0xf4, 0x6e, 0x2b, 0x48, 0x63, 0x80, 102 | 0x36, 0xfd, 0x09, 0xec, 0x09, 0x2f, 0x58, 0x36, 0x08, 0x34, 0x0f, 0xb8, 0x1f, 0x60, 0x3f, 0x17, 0xc5, 103 | ]; 104 | 105 | assert_eq!(2073, crc16(&payload)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/bytereader.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | // externs 23 | use crate::bitpacker::BitPackError; 24 | use crate::byteorder::{BigEndian, ByteOrder, LittleEndian}; 25 | 26 | // 27 | // ###### ###### 28 | // # # # # ##### ###### # # ###### ## ##### ###### ##### 29 | // # # # # # # # # # # # # # # # # 30 | // ###### # # # ##### ###### ##### # # # # ##### # # 31 | // # # # # # # # # ###### # # # ##### 32 | // # # # # # # # # # # # # # # # 33 | // ###### # # ###### # # ###### # # ##### ###### # # 34 | // 35 | 36 | /// 37 | /// BitReader allows individual bits to be read from an array of bytes. 38 | /// 39 | pub struct ByteReader<'a> { 40 | array: &'a [u8], 41 | p_byte: usize, // Byte pointer 42 | } 43 | 44 | impl<'a> ByteReader<'a> { 45 | pub fn new(array: &'a [u8]) -> ByteReader<'a> { 46 | ByteReader { array, p_byte: 0 } 47 | } 48 | 49 | pub fn reset(&mut self) { 50 | self.p_byte = 0; 51 | } 52 | 53 | pub fn set_pos(&mut self, p_byte: usize) { 54 | self.p_byte = p_byte; 55 | } 56 | 57 | pub fn get_pos(&self) -> usize { 58 | self.p_byte 59 | } 60 | 61 | pub fn find_le_u16(&mut self, word: u16) -> bool { 62 | if self.p_byte >= self.array.len() { 63 | return false; 64 | } 65 | 66 | let b0 = (word >> 8) as u8; 67 | let b1 = (word & 255) as u8; 68 | 69 | for i in self.p_byte..self.array.len() - 1 { 70 | let a0 = self.array[i]; 71 | let a1 = self.array[i + 1]; 72 | if a0 == b0 && a1 == b1 { 73 | return true; 74 | } 75 | self.p_byte += 1; 76 | } 77 | false 78 | } 79 | 80 | /// 81 | /// Get the number of bytes remaining in the ByteReader buffer. 82 | /// 83 | #[inline(always)] 84 | pub fn remaining_bytes(&self) -> Result { 85 | if self.p_byte > self.array.len() { 86 | Err(BitPackError::ArrayEndReached) 87 | } else { 88 | Ok(self.array.len() - self.p_byte) 89 | } 90 | } 91 | 92 | /// 93 | /// This operates together with `write_packed_bits`. It increments the 94 | /// `p_byte` value by `n_bytes`. 95 | /// 96 | #[inline(always)] 97 | pub fn inc_counter(&mut self, n_bytes: usize) -> Result<(), BitPackError> { 98 | if self.p_byte + n_bytes >= self.array.len() { 99 | return Err(BitPackError::ArrayEndReached); 100 | } 101 | self.p_byte += n_bytes; 102 | 103 | Ok(()) 104 | } 105 | 106 | /// 107 | /// This operates together with `write_packed_bits`. It decrements the 108 | /// `p_byte` value by `n_bytes`. 109 | /// 110 | #[inline(always)] 111 | pub fn dec_counter(&mut self, n_bytes: usize) -> Result<(), BitPackError> { 112 | if n_bytes > self.p_byte { 113 | return Err(BitPackError::BoundaryReached); 114 | } 115 | self.p_byte -= n_bytes; 116 | 117 | Ok(()) 118 | } 119 | 120 | /// 121 | /// Read `buf.len()` bytes and write them to buf. 122 | /// 123 | /// ### Arguments 124 | /// * `buf` - The array where the bytes will be written to. 125 | /// 126 | pub fn read(&mut self, buf: &mut [u8]) -> Result { 127 | let bytes_written = if buf.len() > self.remaining_bytes()? { 128 | self.remaining_bytes()? 129 | } else { 130 | buf.len() 131 | }; 132 | 133 | for (i, p_buf) in buf[..bytes_written].iter_mut().enumerate() { 134 | *p_buf = self.array[self.p_byte + i]; 135 | } 136 | self.p_byte += bytes_written; 137 | 138 | Ok(bytes_written) 139 | } 140 | 141 | /// 142 | /// Read the next two bytes as big-endian u16. 143 | /// 144 | #[inline(always)] 145 | pub fn read_u8(&mut self) -> Result { 146 | let value = self.array[self.p_byte]; 147 | self.p_byte += 1; 148 | Ok(value) 149 | } 150 | 151 | /// 152 | /// Read the next two bytes as big-endian u16. 153 | /// 154 | #[inline(always)] 155 | pub fn read_be_u16(&mut self) -> Result { 156 | let value = BigEndian::read_u16(&self.array[self.p_byte..]); 157 | self.p_byte += 2; 158 | Ok(value) 159 | } 160 | 161 | /// 162 | /// Read the next two bytes as big-endian i16. 163 | /// 164 | #[inline(always)] 165 | pub fn read_be_i16(&mut self) -> Result { 166 | let value = BigEndian::read_i16(&self.array[self.p_byte..]); 167 | self.p_byte += 2; 168 | Ok(value) 169 | } 170 | 171 | /// 172 | /// Read the next two bytes as little-endian i16. 173 | /// 174 | #[inline(always)] 175 | pub fn read_le_i16(&mut self) -> Result { 176 | let value = LittleEndian::read_i16(&self.array[self.p_byte..]); 177 | self.p_byte += 2; 178 | Ok(value) 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /test/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################# 4 | # # 5 | # Rust implementation of the X3 lossless audio compression protocol. # 6 | # # 7 | # Copyright (C) 2019 Simon M. Werner # 8 | # # 9 | # This program is free software; you can redistribute it and/or modify # 10 | # it under the terms of the GNU General Public License as published by # 11 | # the Free Software Foundation, either version 3 of the License, or # 12 | # (at your option) any later version. # 13 | # # 14 | # This program is distributed in the hope that it will be useful, # 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 17 | # GNU General Public License for more details. # 18 | # # 19 | # You should have received a copy of the GNU General Public License # 20 | # along with this program. If not, see . # 21 | # # 22 | ############################################################################# 23 | 24 | # 25 | # These tests will compress all the files in the given directory, 26 | # decompress them, and test the original with the decompressed version. 27 | # This validates end-to-end compression/decompression. 28 | # 29 | 30 | set -euo pipefail 31 | 32 | FLAC="$(which flac) --totally-silent --force" 33 | cargo build --release --bin x3 --features=std 34 | 35 | X3=../target/release/x3 36 | if [[ ! -x $X3 ]]; then 37 | echo "x3 binary not found at $X3" 38 | exit 1 39 | fi 40 | 41 | TIME="$(which time) -f %e,%M" # GNU Time, this is not the usual bash/shell time command 42 | if [[ -z $(which time) ]]; then 43 | echo "GNU Time not found" 44 | exit 1 45 | fi 46 | 47 | command -v flac >/dev/null 2>&1 || { echo "flac is required"; exit 1; } 48 | command -v $X3 >/dev/null 2>&1 || { echo "x3 is required"; exit 1; } 49 | 50 | TEMP_DIR=$(mktemp -d) 51 | trap "rm -rf $TEMP_DIR" 0 2 3 15 52 | 53 | # Minimal portable workspace root (script is in test/) 54 | THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 55 | SAMPLES_REPO="https://github.com/psiphi75/underwater-sound-samples" 56 | CLONE_DIR="${THIS_DIR}/underwater-sound-samples" 57 | SAMPLES_DIR="${CLONE_DIR}/samples" 58 | 59 | # Ensure required tools 60 | command -v git >/dev/null 2>&1 || { echo "git is required"; exit 1; } 61 | command -v flac >/dev/null 2>&1 || { echo "flac is required"; exit 1; } 62 | 63 | 64 | # Clone or pull the samples repo 65 | if [ -d "${CLONE_DIR}/.git" ]; then 66 | git -C "${CLONE_DIR}" fetch --all --tags 67 | git -C "${CLONE_DIR}" pull --ff-only || git -C "${CLONE_DIR}" pull 68 | else 69 | git clone --depth 1 "$SAMPLES_REPO" "${CLONE_DIR}" 70 | fi 71 | 72 | 73 | 74 | # Decompress .flac -> .wav 75 | shopt -s nullglob 76 | FLAC_FILES=(${SAMPLES_DIR}/*.flac) 77 | if [ ${#FLAC_FILES[@]} -eq 0 ]; then 78 | echo "No .flac files found in ${SAMPLES_DIR}" 79 | exit 1 80 | fi 81 | 82 | for f in "${FLAC_FILES[@]}"; do 83 | base="$(basename "$f" .flac)" 84 | out="${SAMPLES_DIR}/${base}.wav" 85 | if [[ ! -f ${out} ]]; then 86 | flac -d -s -f -o "$out" "$f" 87 | fi 88 | cp "$out" $TEMP_DIR 89 | done 90 | 91 | function bench_wav_to_x3a { 92 | ${TIME} ${X3} --input $1 --output $2 93 | } 94 | 95 | function bench_x3a_to_wav { 96 | ${TIME} ${X3} --input $1 --output $2 97 | } 98 | 99 | function bench_wav_to_flac { 100 | ${TIME} ${FLAC} $1 --output-name="$2" 101 | } 102 | 103 | function bench_flac_to_wav { 104 | ${TIME} ${FLAC} --decode $1 --output-name="$2" 105 | } 106 | 107 | function bench_algo { 108 | local in_file_ext=$1 109 | local out_file_ext=$2 110 | local algorithm="${in_file_ext}_to_${out_file_ext}" 111 | local bench_sh="bench_${algorithm}" 112 | 113 | if [[ ${in_file_ext} == "wav" ]]; then 114 | local algo=${out_file_ext} 115 | local type="comp" 116 | else 117 | local algo=${in_file_ext} 118 | local type="dec" 119 | fi 120 | 121 | # Do the benchmark for all the audio files 122 | for in_base_file in $FILE_LIST 123 | do 124 | local in_file="${in_base_file}.${in_file_ext}" 125 | local out_file="${in_base_file}.${out_file_ext}" 126 | 127 | local orig_size=$(stat -c%s -- "${in_file}") 128 | Totalsize["$algo"|"$type"]=$((${Totalsize["$algo"|"$type"]} + ${orig_size})) 129 | 130 | # Run the benchmark 131 | local result="$(${bench_sh} ${in_file} ${out_file} 2>&1 > /dev/null)" 132 | local elapsed=${result%%,*} 133 | Totaltime["$algo"|"$type"]=$(echo "$elapsed + ${Totaltime[$algo|$type]}" | bc -l) 134 | local comp_size=$(stat -c%s -- "$out_file") 135 | 136 | # Choose size to use for MB/s: if input is wav use original size, else use compressed size 137 | if [ "${in_file_ext}" == "wav" ]; then 138 | Origsize[$algo]=$((Origsize[$algo] + "$orig_size")) 139 | fi 140 | 141 | echo "$(basename ${in_file}),${algorithm},${orig_size},${elapsed},${comp_size}" 142 | 143 | done 144 | } 145 | 146 | declare -A Totalsize 147 | declare -A Totaltime 148 | declare -A Origsize 149 | Totalsize["x3a"|"dec"]=0 150 | Totalsize["x3a"|"comp"]=0 151 | Totalsize["flac"|"dec"]=0 152 | Totalsize["flac"|"comp"]=0 153 | Totaltime["x3a"|"dec"]=0 154 | Totaltime["x3a"|"comp"]=0 155 | Totaltime["flac"|"dec"]=0 156 | Totaltime["flac"|"comp"]=0 157 | Origsize["x3a"]=0 158 | Origsize["x3a"]=0 159 | 160 | 161 | FILE_LIST="$(find "${TEMP_DIR}" -name "*.wav" -print0 | while IFS= read -r -d '' f; do printf '%s\n' "${f%.wav}"; done)" 162 | echo "File,Algorithm,File Size (B),Time,Max Mem Usage (kB),Compressed Size (B)" 163 | bench_algo wav x3a 164 | bench_algo x3a wav 165 | bench_algo wav flac 166 | bench_algo flac wav 167 | 168 | echo -e "\n" 169 | 170 | echo "Algorithm,Compression ratio,Compression speed (MB/s),Decompression speed (MB/s)" 171 | for a in x3a flac; do 172 | declare -A mbps 173 | for t in comp dec; do 174 | mbps[$t]=$(echo "${Origsize[$a]}/${Totaltime[$a|$t]}/1024/1024" | bc -l) 175 | done 176 | comp_ratio=$(echo "${Totalsize[$a|'dec']}/${Totalsize[$a|'comp']}" | bc -l) 177 | 178 | echo "$a,${comp_ratio},${mbps['comp']},${mbps['dec']}" 179 | done -------------------------------------------------------------------------------- /src/x3.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | use crate::error::X3Error; 23 | 24 | pub struct Decoder<'a> { 25 | pub channels: &'a [Channel<'a>], 26 | pub x3_inp: &'a mut [u8], 27 | } 28 | 29 | pub struct Channel<'a> { 30 | pub id: u16, // The channel number 31 | pub wav: &'a [i16], // The raw wave data 32 | pub sample_rate: u32, // The sample rate in Hz 33 | pub params: Parameters, // X3 encoding parameters 34 | } 35 | 36 | impl<'a> Channel<'a> { 37 | pub fn new(id: u16, wav: &'a [i16], sample_rate: u32, params: Parameters) -> Self { 38 | Channel { 39 | id, 40 | wav, 41 | sample_rate, 42 | params, 43 | } 44 | } 45 | } 46 | 47 | pub struct IterChannel 48 | where 49 | I: Iterator, 50 | { 51 | pub id: u16, // The channel number 52 | pub wav: I, // Raw sample iterator 53 | pub sample_rate: u32, // The sample rate in Hz 54 | pub params: Parameters, // X3 encoding parameters 55 | } 56 | 57 | impl IterChannel 58 | where 59 | I: Iterator, 60 | { 61 | pub fn new(id: u16, wav: impl IntoIterator, sample_rate: u32, params: Parameters) -> Self { 62 | IterChannel { 63 | id, 64 | wav: wav.into_iter(), 65 | sample_rate, 66 | params, 67 | } 68 | } 69 | } 70 | pub struct X3aSpec { 71 | /// The number of samples per second. 72 | pub sample_rate: u32, 73 | 74 | /// The parameters for the stream 75 | pub params: Parameters, 76 | 77 | /// The number of channels in use 78 | pub channels: u8, 79 | } 80 | 81 | pub struct Parameters { 82 | pub block_len: usize, 83 | pub blocks_per_frame: usize, 84 | pub codes: [usize; 3], 85 | pub thresholds: [usize; 3], 86 | pub rice_codes: [&'static RiceCode; 3], 87 | } 88 | 89 | impl Parameters { 90 | pub const MAX_BLOCK_LENGTH: usize = 60; 91 | pub const WAV_BIT_SIZE: usize = 16; 92 | 93 | pub const DEFAULT_BLOCK_LENGTH: usize = 20; 94 | pub const DEFAULT_RICE_CODES: [usize; 3] = [0, 1, 3]; 95 | pub const DEFAULT_THRESHOLDS: [usize; 3] = [3, 8, 20]; 96 | pub const DEFAULT_BLOCKS_PER_FRAME: usize = 500; 97 | 98 | pub fn new( 99 | block_len: usize, 100 | blocks_per_frame: usize, 101 | codes: [usize; 3], 102 | thresholds: [usize; 3], 103 | ) -> Result { 104 | let rice_codes = RiceCodes::get(codes); 105 | 106 | // setup the codes 107 | for k in 0..2 { 108 | let rc = rice_codes[k]; 109 | if thresholds[k] > rc.offset { 110 | return Err(X3Error::InvalidEncodingThresh); 111 | } 112 | } 113 | 114 | Ok(Parameters { 115 | block_len, 116 | blocks_per_frame, 117 | codes, 118 | thresholds, 119 | rice_codes, 120 | }) 121 | } 122 | } 123 | 124 | impl Default for Parameters { 125 | fn default() -> Self { 126 | Parameters { 127 | block_len: Self::DEFAULT_BLOCK_LENGTH, 128 | blocks_per_frame: Self::DEFAULT_BLOCKS_PER_FRAME, 129 | codes: Self::DEFAULT_RICE_CODES, 130 | thresholds: Self::DEFAULT_THRESHOLDS, 131 | rice_codes: RiceCodes::get(Self::DEFAULT_RICE_CODES), 132 | } 133 | } 134 | } 135 | 136 | pub struct Archive {} 137 | impl Archive { 138 | /// 139 | pub const ID: &'static [u8] = &[0x58, 0x33, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56]; // 'X3ARCHIV' 140 | pub const ID_LEN: usize = 8; 141 | } 142 | 143 | pub struct Frame {} 144 | impl Frame { 145 | pub const MAX_LENGTH: usize = 0x7fe0; 146 | } 147 | 148 | pub struct FrameHeader { 149 | /// Source Id of the stream 150 | pub source_id: u8, 151 | 152 | /// The number of samples in the frame 153 | pub samples: u16, 154 | 155 | /// The number of channels 156 | pub channels: u8, 157 | 158 | /// The length of the frame (bytes) 159 | pub payload_len: usize, 160 | 161 | /// The CRC16 value for the payload 162 | pub payload_crc: u16, 163 | } 164 | 165 | impl FrameHeader { 166 | /// The length of the header 167 | pub const LENGTH: usize = 20; 168 | 169 | /// Fixed key marks the boundary of the frame 'x3' 170 | pub const KEY: u16 = 30771; // "x3" 171 | pub const KEY_BUF: &'static [u8] = &[0x78, 0x33]; // "x3" 172 | 173 | /// The location of various bytes in the header 174 | pub const P_KEY: usize = 0; 175 | pub const P_SOURCE_ID: usize = 2; 176 | pub const P_CHANNELS: usize = 3; 177 | pub const P_SAMPLES: usize = 4; 178 | pub const P_PAYLOAD_SIZE: usize = 6; 179 | pub const P_TIME: usize = 8; 180 | 181 | /// CRC of the encoded payload, all the frames 182 | pub const P_HEADER_CRC: usize = 16; 183 | pub const P_PAYLOAD_CRC: usize = 18; 184 | } 185 | 186 | #[allow(dead_code)] 187 | pub struct RiceCode { 188 | pub nsubs: usize, // number of subcode (suffix) bits 189 | pub offset: usize, // table offset 190 | pub code: &'static [usize], 191 | pub num_bits: &'static [usize], 192 | pub inv: &'static [i16], 193 | pub inv_len: usize, // The length of inv that is used for this. 194 | } 195 | 196 | pub struct RiceCodes {} 197 | 198 | // The inverse rice code lookup table is the same for all rice codes, although the lower valued 199 | // rice codes need less than what is provided here 200 | const INV_RICE_CODE: &[i16] = &[ 201 | 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8, 8, -9, 9, -10, 10, -11, 11, -12, 12, -13, 13, -14, 14, -15, 202 | 15, -16, 16, -17, 17, -18, 18, -19, 19, -20, 20, -21, 21, -22, 22, -23, 23, -24, 24, -25, 25, -26, 26, -27, 27, -28, 203 | 28, -29, 29, -30, 204 | ]; 205 | 206 | impl RiceCodes { 207 | const CODE: [RiceCode; 4] = [ 208 | RiceCode { 209 | nsubs: 0, 210 | offset: 6, 211 | code: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 212 | num_bits: &[12, 10, 8, 6, 4, 2, 1, 3, 5, 7, 9, 11, 13, 15], 213 | inv: INV_RICE_CODE, 214 | inv_len: 16, 215 | }, 216 | RiceCode { 217 | nsubs: 1, 218 | offset: 11, 219 | code: &[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], 220 | num_bits: &[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 221 | inv: INV_RICE_CODE, 222 | inv_len: 26, 223 | }, 224 | RiceCode { 225 | nsubs: 2, 226 | offset: 20, 227 | code: &[ 228 | 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 4, 6, 4, 6, 4, 6, 4, 6, 4, 6, 4, 6, 4, 6, 4, 6, 4, 229 | 6, 4, 6, 230 | ], 231 | num_bits: &[ 232 | 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 233 | 10, 11, 11, 12, 12, 234 | ], 235 | inv: INV_RICE_CODE, 236 | inv_len: 44, 237 | }, 238 | RiceCode { 239 | nsubs: 3, 240 | offset: 28, 241 | code: &[ 242 | 15, 13, 11, 9, 15, 13, 11, 9, 15, 13, 11, 9, 15, 13, 11, 9, 15, 13, 11, 9, 15, 13, 11, 9, 15, 13, 11, 9, 8, 10, 243 | 12, 14, 8, 10, 12, 14, 8, 10, 12, 14, 8, 10, 12, 14, 8, 10, 12, 14, 8, 10, 12, 14, 8, 10, 12, 14, 244 | ], 245 | num_bits: &[ 246 | 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 247 | 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 248 | ], 249 | inv: INV_RICE_CODE, 250 | inv_len: 60, 251 | }, 252 | ]; 253 | 254 | pub fn get(code_list: [usize; 3]) -> [&'static RiceCode; 3] { 255 | [ 256 | &RiceCodes::CODE[code_list[0]], 257 | &RiceCodes::CODE[code_list[1]], 258 | &RiceCodes::CODE[code_list[2]], 259 | ] 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/bitreader.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | // externs 23 | use crate::byteorder::{BigEndian, ByteOrder}; 24 | 25 | // TODO: Can we get this from std::u32? 26 | const BIT_LEN: usize = 32; 27 | const BYTES_PER_WORD: usize = 4; 28 | 29 | #[inline(always)] 30 | fn read_word(array: &[u8], idx: usize) -> (u32, usize) { 31 | if array.len() - idx >= BYTES_PER_WORD { 32 | let word = BigEndian::read_u32(&array[idx..]); 33 | (word, BYTES_PER_WORD) 34 | } else { 35 | // We are at the end of the array 36 | let remaining_idx = array.len() - idx; 37 | let mut word = 0u32; 38 | if remaining_idx >= 1 { 39 | word |= (array[idx] as u32) << (3 * 8); 40 | } 41 | if remaining_idx >= 2 { 42 | word |= (array[idx + 1] as u32) << (2 * 8); 43 | } 44 | if remaining_idx == 3 { 45 | word |= (array[idx + 2] as u32) << (8); 46 | } 47 | (word, remaining_idx) 48 | } 49 | } 50 | 51 | pub struct BitReader<'a> { 52 | array: &'a [u8], 53 | 54 | /// Byte pointer to the byte within the array 55 | idx: usize, 56 | 57 | /// Leading byte, such that we don't need to read the array the whole time 58 | leading_word: u32, 59 | 60 | /// The remaining number of bits to process in the word 61 | rem_bit: usize, 62 | } 63 | 64 | impl<'a> BitReader<'a> { 65 | pub fn new(array: &'a [u8]) -> Self { 66 | let (leading_word, idx) = read_word(array, 0); 67 | Self { 68 | array, 69 | idx, 70 | leading_word, 71 | rem_bit: idx * 8, 72 | } 73 | } 74 | 75 | /// Increment the bits, load a new byte if required. 76 | #[inline(always)] 77 | pub fn inc_bits(&mut self, n: usize) { 78 | debug_assert!(n < BIT_LEN); 79 | 80 | if n < self.rem_bit { 81 | self.leading_word <<= n; 82 | self.rem_bit -= n; 83 | } else if n > self.rem_bit { 84 | let rem = n - self.rem_bit; 85 | self.get_next(); 86 | self.rem_bit = BIT_LEN - rem; 87 | self.leading_word <<= rem; 88 | } else { 89 | // n == self.rem_bit 90 | self.get_next(); 91 | } 92 | } 93 | 94 | /// 95 | /// Read the n number of bites in a packed bit array. 96 | /// 97 | /// ### Arguments 98 | /// 99 | /// * `num_bits` - The number of bits to read. 100 | /// 101 | /// ### Returns 102 | /// 103 | /// * The unsigned value returned. 104 | /// 105 | #[inline(always)] 106 | pub fn read_nbits(&mut self, n: usize) -> u32 { 107 | if n <= self.rem_bit { 108 | let result = self.leading_word >> (BIT_LEN - n); 109 | self.inc_bits(n); 110 | result 111 | } else { 112 | let rem = n - self.rem_bit; 113 | let mut result = self.leading_word >> (BIT_LEN - n); 114 | self.inc_bits(self.rem_bit); 115 | result |= self.leading_word >> (BIT_LEN - rem); 116 | self.inc_bits(rem); 117 | result 118 | } 119 | } 120 | 121 | /// 122 | /// Read the number of zeros in a packed bit array. Loads a new byte if needed. 123 | /// 124 | /// ### Returns 125 | /// 126 | /// * the number of consectutive zeros found in the array. 127 | /// 128 | #[inline(always)] 129 | pub fn count_zero_bits(&mut self) -> usize { 130 | let mut count = self.leading_word.leading_zeros() as usize; 131 | if count > self.rem_bit { 132 | count = match self.peek_next() { 133 | Some((word, _)) => self.rem_bit + word.leading_zeros() as usize, 134 | None => self.rem_bit, 135 | }; 136 | } 137 | self.inc_bits(count); 138 | count 139 | } 140 | 141 | /// 142 | /// Get the next byte. 143 | /// 144 | /// ### Returns 145 | /// 146 | /// * the next byte 147 | /// 148 | #[inline(always)] 149 | fn get_next(&mut self) -> u32 { 150 | match self.peek_next() { 151 | Some((word, diff_idx)) => { 152 | self.leading_word = word; 153 | self.idx += diff_idx; 154 | self.rem_bit = diff_idx * 8; 155 | word 156 | } 157 | None => { 158 | self.leading_word = 0; 159 | self.rem_bit = 0; 160 | 0 161 | } 162 | } 163 | } 164 | 165 | /// 166 | /// Peek at the next byte, without incrementing our pointer. 167 | /// 168 | #[inline(always)] 169 | fn peek_next(&self) -> Option<(u32, usize)> { 170 | if self.idx >= self.array.len() { 171 | None 172 | } else { 173 | Some(read_word(self.array, self.idx)) 174 | } 175 | } 176 | } 177 | 178 | // 179 | // 180 | // ####### 181 | // # ###### #### ##### #### 182 | // # # # # # 183 | // # ##### #### # #### 184 | // # # # # # 185 | // # # # # # # # 186 | // # ###### #### # #### 187 | // 188 | // 189 | 190 | #[cfg(test)] 191 | mod tests { 192 | use crate::bitreader::BitReader; 193 | 194 | #[test] 195 | fn test_bitreader_init() { 196 | let inp_arr: &mut [u8] = &mut [0x00, 0x0f, 0xf0, 0x00]; 197 | let br = BitReader::new(inp_arr); 198 | 199 | assert_eq!(32, br.rem_bit); 200 | assert_eq!(0x000ff000, br.leading_word); 201 | } 202 | 203 | #[test] 204 | fn test_bitreader_init_short() { 205 | let inp_arr: &mut [u8] = &mut [0x00, 0x0f, 0xf0]; 206 | let br = BitReader::new(inp_arr); 207 | 208 | assert_eq!(24, br.rem_bit); 209 | assert_eq!(0x000ff000, br.leading_word); 210 | } 211 | 212 | #[test] 213 | fn test_count_zero_bits() { 214 | let inp_arr: &mut [u8] = &mut [0x00, 0x0f, 0xf0, 0x00]; 215 | let mut br = BitReader::new(inp_arr); 216 | 217 | // Read first 12 zero 218 | { 219 | let zeros = br.count_zero_bits(); 220 | assert_eq!(12, zeros); 221 | assert_eq!(20, br.rem_bit); 222 | assert_eq!(0xff000000, br.leading_word); 223 | } 224 | 225 | // Next there are no zeros 226 | { 227 | let zeros = br.count_zero_bits(); 228 | assert_eq!(0, zeros); 229 | assert_eq!(20, br.rem_bit); 230 | assert_eq!(0xff000000, br.leading_word); 231 | } 232 | 233 | // Skip some bits 234 | { 235 | let byte = br.read_nbits(7); 236 | assert_eq!(0x7f, byte); 237 | assert_eq!(13, br.rem_bit); 238 | assert_eq!(0x80000000, br.leading_word); 239 | } 240 | { 241 | let byte = br.read_nbits(1); 242 | assert_eq!(0x01, byte); 243 | assert_eq!(12, br.rem_bit); 244 | assert_eq!(0x00000000, br.leading_word); 245 | } 246 | 247 | // Read to the end 248 | { 249 | let zeros = br.count_zero_bits(); 250 | assert_eq!(12, zeros); 251 | 252 | assert_eq!(0, br.rem_bit); 253 | assert_eq!(0x00000000, br.leading_word); 254 | } 255 | } 256 | 257 | #[test] 258 | fn test_bitreader_long_array() { 259 | let inp_arr: &mut [u8] = &mut [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01]; 260 | 261 | let mut br = BitReader::new(inp_arr); 262 | 263 | // Basic load 264 | { 265 | assert_eq!(32, br.rem_bit); 266 | assert_eq!(0b00000001001000110100010101100111, br.leading_word); 267 | } 268 | 269 | // Load next bytes 270 | { 271 | let byte = br.read_nbits(20); 272 | assert_eq!(0b00000001001000110100, byte); 273 | assert_eq!(12, br.rem_bit); 274 | assert_eq!(0b010101100111 << 20, br.leading_word); 275 | } 276 | 277 | // Load next bits 278 | { 279 | let bit = br.read_nbits(1); 280 | assert_eq!(0b0, bit); 281 | assert_eq!(0b10101100111000000000000000000000, br.leading_word); 282 | 283 | let bit = br.read_nbits(1); 284 | assert_eq!(0b1, bit); 285 | assert_eq!(0b01011001110000000000000000000000, br.leading_word); 286 | } 287 | { 288 | let bits = br.read_nbits(5); 289 | assert_eq!(0b01011, bits); 290 | assert_eq!(0b00111000000000000000000000000000, br.leading_word); 291 | 292 | let bits = br.read_nbits(6); 293 | assert_eq!(0b001111, bits); 294 | assert_eq!(0b00010011010101111001101111011110, br.leading_word); 295 | 296 | let bits = br.read_nbits(31); 297 | assert_eq!(0x09abcdef, bits); 298 | assert_eq!(0x01000000, br.leading_word); 299 | 300 | let bits = br.read_nbits(8); 301 | assert_eq!(0x01, bits); 302 | assert_eq!(0, br.leading_word); 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /test/timings.csv: -------------------------------------------------------------------------------- 1 | id,in file,test,in file size (bytes),test params,time,max mem usage (kB),out file size (bytes),,,,,,,,,,,,,,, 2 | 01-Mascalito-Rhythmical_Disorder.wav::wav_to_x3a,01-Mascalito-Rhythmical_Disorder.wav,wav_to_x3a,38243566,,0.49,67952,28773934,,Timings 1,,,,,,,,,,,,, 3 | 01-Saluberrimae-Trapped_Inside.wav::wav_to_x3a,01-Saluberrimae-Trapped_Inside.wav,wav_to_x3a,28929646,,0.4,52828,22344256,,X3 crate v0.2.1,,,,,,,,,,,,, 4 | 03-Mascalito-Filthy_Summer.wav::wav_to_x3a,03-Mascalito-Filthy_Summer.wav,wav_to_x3a,41855474,,0.52,72848,30012254,,,,,,,,,,Encoding,,,Decoding,, 5 | 03-Saluberrimae-Jennifer_Connelly.wav::wav_to_x3a,03-Saluberrimae-Jennifer_Connelly.wav,wav_to_x3a,46710766,,0.58,81700,34294846,,Enc Key,Dec Key,Algorthm,in file,Original,Duration,Compressed,Compression,Time,Rate,Memory,Time,Rate,Memory 6 | 04-Saluberrimae-Time_Is_A_Flat_Circle.wav::wav_to_x3a,04-Saluberrimae-Time_Is_A_Flat_Circle.wav,wav_to_x3a,38666926,,0.49,67756,28064162,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,01-Mascalito-Rhythmical_Disorder,"38,243,566",433.6,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 7 | EH120.wav::wav_to_x3a,EH120.wav,wav_to_x3a,72000044,,0.91,91956,19613060,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,01-Saluberrimae-Trapped_Inside,"28,929,646",328,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 8 | GI16.wav::wav_to_x3a,GI16.wav,wav_to_x3a,9600044,,0.12,15148,3184374,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,03-Mascalito-Filthy_Summer,"41,855,474",474.55,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 9 | GI60.wav::wav_to_x3a,GI60.wav,wav_to_x3a,36000044,,0.43,49804,12391742,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,03-Saluberrimae-Jennifer_Connelly,"46,710,766",529.6,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 10 | GR48.wav::wav_to_x3a,GR48.wav,wav_to_x3a,28800044,,0.35,39276,8589252,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,04-Saluberrimae-Time_Is_A_Flat_Circle,"38,666,926",438.4,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 11 | LI192.wav::wav_to_x3a,LI192.wav,wav_to_x3a,115200042,,1.44,148920,34532616,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,EH120,"72,000,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 12 | NO96.wav::wav_to_x3a,NO96.wav,wav_to_x3a,57600044,,0.7,72568,14304970,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,GI16,"9,600,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 13 | PI240.wav::wav_to_x3a,PI240.wav,wav_to_x3a,144000044,,1.59,163152,20340626,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,GI60,"36,000,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 14 | pts-trondheim-3.wav::wav_to_x3a,pts-trondheim-3.wav,wav_to_x3a,40952044,,0.53,74300,32394872,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,GR48,"28,800,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 15 | STstreamTest.bin.wav::wav_to_x3a,STstreamTest.bin.wav,wav_to_x3a,970892,,0.01,3612,178930,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,LI192,"115,200,042",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 16 | ::,,,,,,,,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,NO96,"57,600,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 17 | ::,,,,,,,,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,PI240,"144,000,044",300,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 18 | ::,,,,,,,,,.wav::wav_to_flac,.flac::flac_to_wav,FLAC,pts-trondheim-3,"40,952,044",464.31,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 19 | ::,,,,,,,,,,,,Total,"698,558,728",4768.46,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A 20 | Where:::,Where:,,,,,,,,,,,,,,,,,,,,, 21 | DIRECTORY is the directory with the source files::,DIRECTORY is the directory with the source files,,,,,,,,,,,,,,,,,,,,, 22 | ALGORITHM is the algorithm to test::one of: wav_to_x3a,ALGORITHM is the algorithm to test,one of: wav_to_x3a,x3a_to_wav,wav_to_flac,flac_to_wav,,,,,,,,,,,,,,,,, 23 | in file::test,in file,test,in file size (bytes),test params,time,max mem usage (kB),out file size (bytes),,,,,,,,,,,,,,, 24 | 01-Mascalito-Rhythmical_Disorder.x3a::x3a_to_wav,01-Mascalito-Rhythmical_Disorder.x3a,x3a_to_wav,28773934,,0.64,68152,38243566,,,,,,,,,,,,,,, 25 | 01-Saluberrimae-Trapped_Inside.x3a::x3a_to_wav,01-Saluberrimae-Trapped_Inside.x3a,x3a_to_wav,22344256,,0.48,52796,28929646,,,,,,,,,,,,,,, 26 | 03-Mascalito-Filthy_Summer.x3a::x3a_to_wav,03-Mascalito-Filthy_Summer.x3a,x3a_to_wav,30012254,,0.69,72884,41855474,,,,,,,,,,,,,,, 27 | 03-Saluberrimae-Jennifer_Connelly.x3a::x3a_to_wav,03-Saluberrimae-Jennifer_Connelly.x3a,x3a_to_wav,34294846,,0.77,81696,46710766,,,,,,,,,,,,,,, 28 | 04-Saluberrimae-Time_Is_A_Flat_Circle.x3a::x3a_to_wav,04-Saluberrimae-Time_Is_A_Flat_Circle.x3a,x3a_to_wav,28064162,,0.63,67748,38666926,,,,,,,,,,,,,,, 29 | EH120.x3a::x3a_to_wav,EH120.x3a,x3a_to_wav,19613060,,1.38,91964,72000044,,,,,,,,,,,,,,, 30 | GI16.x3a::x3a_to_wav,GI16.x3a,x3a_to_wav,3184374,,0.17,15188,9600044,,,,,,,,,,,,,,, 31 | GI60.x3a::x3a_to_wav,GI60.x3a,x3a_to_wav,12391742,,0.63,50184,36000044,,,,,,,,,,,,,,, 32 | GR48.x3a::x3a_to_wav,GR48.x3a,x3a_to_wav,8589252,,0.52,39200,28800044,,,,,,,,,,,,,,, 33 | LI192.x3a::x3a_to_wav,LI192.x3a,x3a_to_wav,34532616,,2.16,148912,115200042,,,,,,,,,,,,,,, 34 | NO96.x3a::x3a_to_wav,NO96.x3a,x3a_to_wav,14304970,,1.07,72632,57600044,,,,,,,,,,,,,,, 35 | PI240.x3a::x3a_to_wav,PI240.x3a,x3a_to_wav,20340626,,2.41,162996,144000044,,,,,,,,,,,,,,, 36 | pts-trondheim-3.x3a::x3a_to_wav,pts-trondheim-3.x3a,x3a_to_wav,32394872,,0.7,74272,40952044,,,,,,,,,,,,,,, 37 | STstreamTest.bin.x3a::x3a_to_wav,STstreamTest.bin.x3a,x3a_to_wav,178930,,0.01,3676,970892,,,,,,,,,,,,,,, 38 | in file::test,in file,test,in file size (bytes),test params,time,max mem usage (kB),out file size (bytes),,,,,,,,,,,,,,, 39 | 01-Mascalito-Rhythmical_Disorder.x3a::x3a_to_wav,01-Mascalito-Rhythmical_Disorder.x3a,x3a_to_wav,28773934,,0.64,68104,38243566,,,,,,,,,,,,,,, 40 | 01-Saluberrimae-Trapped_Inside.x3a::x3a_to_wav,01-Saluberrimae-Trapped_Inside.x3a,x3a_to_wav,22344256,,0.48,52724,28929646,,,,,,,,,,,,,,, 41 | 03-Mascalito-Filthy_Summer.x3a::x3a_to_wav,03-Mascalito-Filthy_Summer.x3a,x3a_to_wav,30012254,,0.69,72676,41855474,,,,,,,,,,,,,,, 42 | 03-Saluberrimae-Jennifer_Connelly.x3a::x3a_to_wav,03-Saluberrimae-Jennifer_Connelly.x3a,x3a_to_wav,34294846,,0.77,81668,46710766,,,,,,,,,,,,,,, 43 | 04-Saluberrimae-Time_Is_A_Flat_Circle.x3a::x3a_to_wav,04-Saluberrimae-Time_Is_A_Flat_Circle.x3a,x3a_to_wav,28064162,,0.63,67772,38666926,,,,,,,,,,,,,,, 44 | EH120.x3a::x3a_to_wav,EH120.x3a,x3a_to_wav,19613060,,1.38,91964,72000044,,,,,,,,,,,,,,, 45 | GI16.x3a::x3a_to_wav,GI16.x3a,x3a_to_wav,3184374,,0.17,15276,9600044,,,,,,,,,,,,,,, 46 | GI60.x3a::x3a_to_wav,GI60.x3a,x3a_to_wav,12391742,,0.62,50128,36000044,,,,,,,,,,,,,,, 47 | GR48.x3a::x3a_to_wav,GR48.x3a,x3a_to_wav,8589252,,0.53,39368,28800044,,,,,,,,,,,,,,, 48 | LI192.x3a::x3a_to_wav,LI192.x3a,x3a_to_wav,34532616,,2.17,148952,115200042,,,,,,,,,,,,,,, 49 | NO96.x3a::x3a_to_wav,NO96.x3a,x3a_to_wav,14304970,,1.06,72724,57600044,,,,,,,,,,,,,,, 50 | PI240.x3a::x3a_to_wav,PI240.x3a,x3a_to_wav,20340626,,2.44,163096,144000044,,,,,,,,,,,,,,, 51 | pts-trondheim-3.x3a::x3a_to_wav,pts-trondheim-3.x3a,x3a_to_wav,32394872,,0.7,74308,40952044,,,,,,,,,,,,,,, 52 | STstreamTest.bin.x3a::x3a_to_wav,STstreamTest.bin.x3a,x3a_to_wav,178930,,0.01,3684,970892,,,,,,,,,,,,,,, 53 | in file::test,in file,test,in file size (bytes),test params,time,max mem usage (kB),out file size (bytes),,,,,,,,,,,,,,, 54 | 01-Mascalito-Rhythmical_Disorder.x3a::x3a_to_wav,01-Mascalito-Rhythmical_Disorder.x3a,x3a_to_wav,28773934,,0.64,67908,38243566,,,,,,,,,,,,,,, 55 | 01-Saluberrimae-Trapped_Inside.x3a::x3a_to_wav,01-Saluberrimae-Trapped_Inside.x3a,x3a_to_wav,22344256,,0.49,52632,28929646,,,,,,,,,,,,,,, 56 | 03-Mascalito-Filthy_Summer.x3a::x3a_to_wav,03-Mascalito-Filthy_Summer.x3a,x3a_to_wav,30012254,,0.69,72756,41855474,,,,,,,,,,,,,,, 57 | 03-Saluberrimae-Jennifer_Connelly.x3a::x3a_to_wav,03-Saluberrimae-Jennifer_Connelly.x3a,x3a_to_wav,34294846,,0.77,81696,46710766,,,,,,,,,,,,,,, 58 | 04-Saluberrimae-Time_Is_A_Flat_Circle.x3a::x3a_to_wav,04-Saluberrimae-Time_Is_A_Flat_Circle.x3a,x3a_to_wav,28064162,,0.63,67664,38666926,,,,,,,,,,,,,,, 59 | EH120.x3a::x3a_to_wav,EH120.x3a,x3a_to_wav,19613060,,1.38,91956,72000044,,,,,,,,,,,,,,, 60 | GI16.x3a::x3a_to_wav,GI16.x3a,x3a_to_wav,3184374,,0.17,14968,9600044,,,,,,,,,,,,,,, 61 | GI60.x3a::x3a_to_wav,GI60.x3a,x3a_to_wav,12391742,,0.63,50092,36000044,,,,,,,,,,,,,,, 62 | GR48.x3a::x3a_to_wav,GR48.x3a,x3a_to_wav,8589252,,0.51,39204,28800044,,,,,,,,,,,,,,, 63 | LI192.x3a::x3a_to_wav,LI192.x3a,x3a_to_wav,34532616,,2.14,148784,115200042,,,,,,,,,,,,,,, 64 | NO96.x3a::x3a_to_wav,NO96.x3a,x3a_to_wav,14304970,,1.06,73036,57600044,,,,,,,,,,,,,,, 65 | PI240.x3a::x3a_to_wav,PI240.x3a,x3a_to_wav,20340626,,2.42,162960,144000044,,,,,,,,,,,,,,, 66 | pts-trondheim-3.x3a::x3a_to_wav,pts-trondheim-3.x3a,x3a_to_wav,32394872,,0.69,74368,40952044,,,,,,,,,,,,,,, 67 | STstreamTest.bin.x3a::x3a_to_wav,STstreamTest.bin.x3a,x3a_to_wav,178930,,0.02,3728,970892,,,,,,,,,,,,,,, 68 | in file::test,in file,test,in file size (bytes),test params,time,max mem usage (kB),out file size (bytes),,,,,,,,,,,,,,, 69 | 01-Mascalito-Rhythmical_Disorder.wav::wav_to_x3a,01-Mascalito-Rhythmical_Disorder.wav,wav_to_x3a,38243566,,0.49,68016,28773934,,,,,,,,,,,,,,, 70 | 01-Saluberrimae-Trapped_Inside.wav::wav_to_x3a,01-Saluberrimae-Trapped_Inside.wav,wav_to_x3a,28929646,,0.37,52656,22344256,,,,,,,,,,,,,,, 71 | 03-Mascalito-Filthy_Summer.wav::wav_to_x3a,03-Mascalito-Filthy_Summer.wav,wav_to_x3a,41855474,,0.52,72760,30012254,,,,,,,,,,,,,,, 72 | 03-Saluberrimae-Jennifer_Connelly.wav::wav_to_x3a,03-Saluberrimae-Jennifer_Connelly.wav,wav_to_x3a,46710766,,0.58,81656,34294846,,,,,,,,,,,,,,, 73 | 04-Saluberrimae-Time_Is_A_Flat_Circle.wav::wav_to_x3a,04-Saluberrimae-Time_Is_A_Flat_Circle.wav,wav_to_x3a,38666926,,0.48,67656,28064162,,,,,,,,,,,,,,, 74 | EH120.wav::wav_to_x3a,EH120.wav,wav_to_x3a,72000044,,0.9,91988,19613060,,,,,,,,,,,,,,, 75 | GI16.wav::wav_to_x3a,GI16.wav,wav_to_x3a,9600044,,0.12,15272,3184374,,,,,,,,,,,,,,, 76 | GI60.wav::wav_to_x3a,GI60.wav,wav_to_x3a,36000044,,0.43,49840,12391742,,,,,,,,,,,,,,, 77 | GR48.wav::wav_to_x3a,GR48.wav,wav_to_x3a,28800044,,0.35,38860,8589252,,,,,,,,,,,,,,, 78 | LI192.wav::wav_to_x3a,LI192.wav,wav_to_x3a,115200042,,1.45,148844,34532616,,,,,,,,,,,,,,, 79 | NO96.wav::wav_to_x3a,NO96.wav,wav_to_x3a,57600044,,0.7,72772,14304970,,,,,,,,,,,,,,, 80 | PI240.wav::wav_to_x3a,PI240.wav,wav_to_x3a,144000044,,1.62,163080,20340626,,,,,,,,,,,,,,, 81 | pts-trondheim-3.wav::wav_to_x3a,pts-trondheim-3.wav,wav_to_x3a,40952044,,0.6,74344,32394872,,,,,,,,,,,,,,, 82 | STstreamTest.bin.wav::wav_to_x3a,STstreamTest.bin.wav,wav_to_x3a,970892,,0.01,3620,178930,,,,,,,,,,,,,,, 83 | -------------------------------------------------------------------------------- /src/bitpacker.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2020 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | use crate::crc::update_crc16; 23 | // 24 | // ###### ###### 25 | // # # # ##### # # ## #### # # ###### ##### 26 | // # # # # # # # # # # # # # # # 27 | // ###### # # ###### # # # #### ##### # # 28 | // # # # # # ###### # # # # ##### 29 | // # # # # # # # # # # # # # # 30 | // ###### # # # # # #### # # ###### # # 31 | // 32 | use crate::error::{Result, X3Error}; 33 | use crate::bytewriter::{ByteWriter, SeekFrom}; 34 | 35 | #[derive(Debug)] 36 | pub enum BitPackError { 37 | NotByteAligned, // The bytes are not aligned. 38 | BoundaryReached, // The soft boundary has been reached. 39 | ArrayEndReached, // The end of the array has been reached. 40 | ExceededBitBoundary, // More bits were read than we expected 41 | } 42 | 43 | /// 44 | /// BitPacker allows individual bits to be written to an array of bytes. 45 | /// 46 | pub struct BitPacker<'a, W: ByteWriter> { 47 | writer: &'a mut W, 48 | // Bit pointer 49 | scratch_byte: u8, 50 | p_bit: usize, 51 | // Len and CRC 52 | byte_len: usize, 53 | crc: u16, 54 | } 55 | 56 | impl<'a, W: ByteWriter> Drop for BitPacker<'a, W> { 57 | fn drop(&mut self) { 58 | if self.p_bit != 0 { 59 | self.flush().unwrap(); 60 | } 61 | } 62 | } 63 | 64 | impl<'a, W: ByteWriter> BitPacker<'a, W> { 65 | pub fn new(writer: &'a mut W) -> BitPacker<'a, W> { 66 | BitPacker { 67 | writer, 68 | scratch_byte: 0, 69 | p_bit: 0, 70 | byte_len: 0, 71 | crc: 0xffff, 72 | } 73 | } 74 | 75 | pub fn crc(&self) -> u16 { 76 | self.crc 77 | } 78 | 79 | fn flush(&mut self) -> Result<()> { 80 | self.crc = update_crc16(self.crc, &self.scratch_byte); 81 | self.byte_len += 1; 82 | self.writer.write_all([self.scratch_byte])?; 83 | self.scratch_byte = 0; 84 | self.p_bit = 0; 85 | Ok(()) 86 | } 87 | 88 | pub fn len(&self) -> usize { 89 | self.byte_len 90 | } 91 | 92 | pub fn is_empty(&self) -> bool { 93 | self.byte_len == 0 && self.p_bit == 0 94 | } 95 | 96 | /// 97 | /// This operates together with `write_packed_bits`. It only increments the 98 | /// `p_bit` value by 1, also incrementing `p_byte` where necessary. 99 | /// 100 | /// Note: The bit pointer must be byte aligned. 101 | /// 102 | /// ### Arguments 103 | /// 104 | /// * `n_bytes` - The number of bytes to increment. 105 | pub fn inc_counter_n_bytes(&mut self, n_bytes: usize) -> Result<()> { 106 | if self.p_bit != 0 { 107 | return Err(X3Error::BitPack(BitPackError::NotByteAligned)); 108 | } 109 | self.writer.seek(SeekFrom::Current(n_bytes as i64))?; 110 | Ok(()) 111 | } 112 | 113 | /// 114 | /// Align the packing to the next word, but only if we aren't already aligned. 115 | /// 116 | pub fn word_align(&mut self) -> Result<()> { 117 | if self.p_bit != 0 { 118 | self.flush()?; 119 | } 120 | while 0 != (self.writer.stream_position()? % 2) { 121 | self.flush()?; 122 | } 123 | Ok(()) 124 | } 125 | 126 | /// 127 | /// Pack array value into the byte array. Starting at `p_byte` position of the array and `p_bit` bit offset. 128 | /// 129 | /// ### Arguments 130 | /// 131 | /// * `value` - The bits that will be written. 132 | /// * `num_bits` - The number of bits in `value` that should be written. 133 | /// 134 | #[inline(always)] 135 | pub fn write_bits(&mut self, mut value: usize, num_bits: usize)-> Result<()> { 136 | let rem_bit = 8 - self.p_bit; 137 | let mask = (1 << num_bits) - 1; 138 | value &= mask; 139 | 140 | if num_bits == rem_bit { 141 | self.scratch_byte |= value as u8; 142 | self.flush()?; 143 | } else if num_bits < rem_bit { 144 | let shift_l = rem_bit - num_bits; 145 | self.scratch_byte |= (value << shift_l) as u8; 146 | self.p_bit += num_bits; 147 | } else { 148 | let shift_r = num_bits - rem_bit; 149 | self.scratch_byte |= (value >> shift_r) as u8; 150 | self.flush()?; 151 | 152 | self.write_bits(value, shift_r)?; 153 | } 154 | Ok(()) 155 | } 156 | 157 | /// 158 | /// This operates together with `write_packed_bits`. It allows zero values to be written. Although 159 | /// these are never actually written to the array, the offsets are just managed. 160 | /// 161 | /// ### Arguments 162 | /// 163 | /// * `num_zeros` - The number of zeros that should be written. 164 | /// 165 | #[inline(always)] 166 | pub fn write_packed_zeros(&mut self, num_zeros: usize) -> Result<()> { 167 | self.write_bits(0, num_zeros) 168 | } 169 | } 170 | 171 | // 172 | // 173 | // ####### 174 | // # ###### #### ##### #### 175 | // # # # # # 176 | // # ##### #### # #### 177 | // # # # # # 178 | // # # # # # # # 179 | // # ###### #### # #### 180 | // 181 | // 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use crate::bitpacker::BitPacker; 186 | use crate::bytewriter::SliceByteWriter; 187 | 188 | #[test] 189 | fn test_write_packed_bits() { 190 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 191 | { 192 | let writer = &mut SliceByteWriter::new(inp_arr); 193 | let mut bp = BitPacker::new(writer); 194 | let _ = bp.write_bits(0x0,9); 195 | let _ = bp.write_bits(0x3, 2); 196 | } 197 | assert_eq!(&[0x00, 0x60, 0x00], inp_arr); 198 | 199 | let inp_arr: &mut [u8] = &mut [0xff, 0x80, 0x00]; 200 | { 201 | let writer = &mut SliceByteWriter::new(inp_arr); 202 | let mut bp = BitPacker::new(writer); 203 | let _ = bp.write_bits(0x1ff,9); 204 | let _ = bp.write_bits(0x3, 2); 205 | } 206 | assert_eq!(&[0xff, 0xE0, 0x00], inp_arr); 207 | 208 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 209 | { 210 | let writer = &mut SliceByteWriter::new(inp_arr); 211 | let mut bp = BitPacker::new(writer); 212 | let _ = bp.write_bits(0,13); 213 | let _ = bp.write_bits(0x1ff, 9); 214 | } 215 | assert_eq!(&[0x00, 0x07, 0xfc], inp_arr); 216 | 217 | 218 | let inp_arr: &mut [u8] = &mut [0xff, 0xfc, 0x00]; 219 | { 220 | let writer = &mut SliceByteWriter::new(inp_arr); 221 | let mut bp = BitPacker::new(writer); 222 | let _ = bp.write_bits(0x1fff,13); 223 | let _ = bp.write_bits(0x1ff, 9); 224 | } 225 | assert_eq!(&[0xff, 0xff, 0xfc], inp_arr); 226 | 227 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 228 | { 229 | let writer = &mut SliceByteWriter::new(inp_arr); 230 | let mut bp = BitPacker::new(writer); 231 | let _ = bp.write_bits(0,6); 232 | let _ = bp.write_bits(0x1f27b, 17); 233 | } 234 | assert_eq!(&[0x03, 0xe4, 0xf6], inp_arr); 235 | 236 | let inp_arr: &mut [u8] = &mut [0xfe, 0x00, 0x00]; 237 | { 238 | let writer = &mut SliceByteWriter::new(inp_arr); 239 | let mut bp = BitPacker::new(writer); 240 | let _ = bp.write_bits(0x3f,6); 241 | let _ = bp.write_bits(0x1f27b, 17); 242 | } 243 | assert_eq!(&[0xff, 0xe4, 0xf6], inp_arr); 244 | 245 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 246 | { 247 | let writer = &mut SliceByteWriter::new(inp_arr); 248 | let mut bp = BitPacker::new(writer); 249 | let _ = bp.write_bits(0,12); 250 | let _ = bp.write_bits(0x9, 4); 251 | } 252 | assert_eq!(&[0x00, 0x09, 0x00], inp_arr); 253 | 254 | let inp_arr: &mut [u8] = &mut [0xf0, 0x00, 0x00]; 255 | { 256 | let writer = &mut SliceByteWriter::new(inp_arr); 257 | let mut bp = BitPacker::new(writer); 258 | let _ = bp.write_bits(0xf,4); 259 | let _ = bp.write_bits(0xffffbe81, 16); 260 | } 261 | assert_eq!(&[0xfb, 0xe8, 0x10], inp_arr); 262 | 263 | 264 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 265 | { 266 | let writer = &mut SliceByteWriter::new(inp_arr); 267 | let mut bp = BitPacker::new(writer); 268 | let _ = bp.write_bits(0,9); 269 | let _ = bp.write_bits(0xfffffffc, 6); 270 | } 271 | assert_eq!(&[0x00, 0x78, 0x00], inp_arr); 272 | 273 | let inp_arr: &mut [u8] = &mut [0x00, 0x00, 0x00]; 274 | { 275 | let writer = &mut SliceByteWriter::new(inp_arr); 276 | let mut bp = BitPacker::new(writer); 277 | let _ = bp.write_bits(0,10); 278 | let _ = bp.write_bits(0xfffffffc, 6); 279 | } 280 | assert_eq!(&[0x00, 0x3c, 0x00], inp_arr); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/decodefile.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | // std 23 | use std::fs::File; 24 | use std::io::{prelude::*, BufReader}; 25 | use std::path; 26 | use std::println; 27 | use std::string::String; 28 | use std::vec; 29 | use std::vec::Vec; 30 | 31 | // externs 32 | use crate::hound; 33 | 34 | // this crate 35 | use crate::decoder; 36 | use crate::error; 37 | use crate::{crc, x3}; 38 | 39 | use crate::x3::{FrameHeader, X3aSpec}; 40 | use error::X3Error; 41 | use quick_xml::events::Event; 42 | use quick_xml::Reader; 43 | 44 | pub const X3_READ_BUFFER_SIZE: usize = 1024 * 24; 45 | pub const X3_WRITE_BUFFER_SIZE: usize = X3_READ_BUFFER_SIZE * 8; 46 | 47 | pub struct X3aReader { 48 | reader: BufReader, 49 | spec: X3aSpec, 50 | remaing_bytes: usize, 51 | read_buf: [u8; X3_READ_BUFFER_SIZE], 52 | 53 | /// The count of errors. 54 | /// TODO: Count each type of error 55 | frame_errors: usize, 56 | } 57 | 58 | impl X3aReader { 59 | pub fn open>(filename: P) -> Result { 60 | let file = File::open(filename).unwrap(); 61 | let mut remaing_bytes = file.metadata()?.len() as usize; 62 | let mut reader = BufReader::with_capacity(64 * 1024, file); 63 | 64 | let (spec, header_size) = read_archive_header(&mut reader)?; 65 | remaing_bytes -= header_size; 66 | 67 | Ok(Self { 68 | reader, 69 | spec, 70 | remaing_bytes, 71 | read_buf: [0u8; X3_READ_BUFFER_SIZE], 72 | frame_errors: 0, 73 | }) 74 | } 75 | 76 | pub fn spec(&self) -> &X3aSpec { 77 | &self.spec 78 | } 79 | 80 | fn read_bytes(&mut self, mut buf_len: usize) -> std::io::Result<()> { 81 | if self.remaing_bytes < buf_len { 82 | buf_len = self.remaing_bytes; 83 | } 84 | self.remaing_bytes -= buf_len; 85 | self.reader.read_exact(&mut self.read_buf[0..buf_len]) 86 | } 87 | 88 | fn read_frame_header(&mut self) -> Result { 89 | self.read_bytes(x3::FrameHeader::LENGTH)?; 90 | decoder::read_frame_header(&self.read_buf[0..x3::FrameHeader::LENGTH]) 91 | } 92 | 93 | fn read_frame_payload(&mut self, header: &FrameHeader) -> Result<(), X3Error> { 94 | self.read_bytes(header.payload_len)?; 95 | 96 | let payload = &self.read_buf[0..header.payload_len]; 97 | let crc = crc::crc16(payload); 98 | if crc != header.payload_crc { 99 | return Err(X3Error::FrameHeaderInvalidPayloadCRC); 100 | } 101 | 102 | Ok(()) 103 | } 104 | 105 | pub fn decode_next_frame(&mut self, wav_buf: &mut [i16; X3_WRITE_BUFFER_SIZE]) -> Result, X3Error> { 106 | // We have reached the end of the file 107 | if self.remaing_bytes <= x3::FrameHeader::LENGTH { 108 | return Ok(None); 109 | } 110 | 111 | // Get the header details 112 | let frame_header = self.read_frame_header()?; 113 | let samples = frame_header.samples as usize; 114 | if self.remaing_bytes < frame_header.payload_len { 115 | return Ok(None); 116 | } 117 | 118 | if frame_header.payload_len > X3_READ_BUFFER_SIZE { 119 | // Payload is larger than the available buffer size 120 | return Err(X3Error::FrameHeaderInvalidPayloadLen); 121 | } 122 | 123 | // Get the Payload 124 | self.read_frame_payload(&frame_header)?; 125 | let x3_bytes = &mut self.read_buf[0..frame_header.payload_len]; 126 | 127 | // Do the decoding 128 | match decoder::decode_frame(x3_bytes, wav_buf, &self.spec.params, samples) { 129 | Ok(result) => Ok(result), 130 | Err(err) => { 131 | self.frame_errors += 1; 132 | println!("Frame error: {:?}", err); 133 | Ok(None) 134 | } 135 | } 136 | } 137 | } 138 | 139 | /// 140 | /// Read the from in the input buffer. 141 | /// 142 | fn read_archive_header(reader: &mut BufReader) -> Result<(X3aSpec, usize), X3Error> { 143 | // 144 | { 145 | let mut arc_header = [0u8; x3::Archive::ID.len()]; 146 | reader.read_exact(&mut arc_header)?; 147 | if !arc_header.eq(x3::Archive::ID) { 148 | return Err(X3Error::ArchiveHeaderXMLInvalidKey); 149 | } 150 | } 151 | 152 | // 153 | let header = { 154 | let mut header_buf = [0u8; x3::FrameHeader::LENGTH]; 155 | reader.read_exact(&mut header_buf)?; 156 | decoder::read_frame_header(&header_buf)? 157 | }; 158 | 159 | // Get the payload 160 | let mut payload: Vec = vec![0; header.payload_len]; 161 | reader.read_exact(&mut payload)?; 162 | let xml = String::from_utf8_lossy(&payload); 163 | 164 | let (sample_rate, params) = parse_xml(&xml)?; 165 | 166 | let header_size = x3::FrameHeader::LENGTH + payload.len(); 167 | 168 | Ok(( 169 | X3aSpec { 170 | sample_rate, 171 | params, 172 | channels: header.channels, 173 | }, 174 | header_size, 175 | )) 176 | } 177 | 178 | /// 179 | /// Convert an .x3a (X3 Archive) file to a .wav file. 180 | /// 181 | /// Note: the x3a can contain some meta data of the recording that may be lost, such as the time 182 | /// of the recording and surplus XML payload data that has been embedded into the X3A header. 183 | /// 184 | /// ### Arguments 185 | /// 186 | /// * `x3a_filename` - the input X3A file to decode. 187 | /// * `wav_filename` - the output wav file to write to. It will be overwritten. 188 | /// 189 | pub fn x3a_to_wav>(x3a_filename: P, wav_filename: P) -> Result<(), X3Error> { 190 | let mut x3a_reader = X3aReader::open(x3a_filename)?; 191 | 192 | let x3_spec = x3a_reader.spec(); 193 | let spec = hound::WavSpec { 194 | channels: 1, //x3_spec.channels as u16, 195 | sample_rate: x3_spec.sample_rate, 196 | bits_per_sample: 16, 197 | sample_format: hound::SampleFormat::Int, 198 | }; 199 | 200 | let mut writer = hound::WavWriter::create(wav_filename, spec)?; 201 | let mut wav = [0i16; X3_WRITE_BUFFER_SIZE]; 202 | 203 | while let Some(samples) = x3a_reader.decode_next_frame(&mut wav)? { 204 | write_samples(&mut writer, &wav, samples)?; 205 | } 206 | 207 | Ok(()) 208 | } 209 | 210 | fn write_samples( 211 | writer: &mut hound::WavWriter>, 212 | buf: &[i16], 213 | num_samples: usize, 214 | ) -> Result<(), X3Error> { 215 | let mut fast_writer = writer.get_i16_writer(num_samples as u32); 216 | for s in buf.iter().take(num_samples) { 217 | unsafe { 218 | fast_writer.write_sample_unchecked(*s); 219 | } 220 | } 221 | fast_writer.flush()?; 222 | Ok(()) 223 | } 224 | 225 | /// 226 | /// Parse the XML header that contains the parameters for the wav output. 227 | /// 228 | fn parse_xml(xml: &str) -> Result<(u32, x3::Parameters), X3Error> { 229 | let mut reader = Reader::from_str(xml); 230 | reader.config_mut().trim_text(true); 231 | 232 | let mut buf = Vec::new(); 233 | let mut fs = Vec::with_capacity(3); 234 | let mut bl = Vec::with_capacity(3); 235 | let mut codes = Vec::with_capacity(3); 236 | let mut th = Vec::with_capacity(3); 237 | 238 | // The `Reader` does not implement `Iterator` because it outputs borrowed data (`Cow`s) 239 | loop { 240 | match reader.read_event_into(&mut buf) { 241 | Ok(Event::Start(ref e)) => match e.name().as_ref() { 242 | b"FS" => fs.push(reader.read_text(e.name()).unwrap()), 243 | b"BLKLEN" => bl.push(reader.read_text(e.name()).unwrap()), 244 | b"CODES" => codes.push(reader.read_text(e.name()).unwrap()), 245 | b"T" => th.push(reader.read_text(e.name()).unwrap()), 246 | _ => (), 247 | }, 248 | Ok(Event::Eof) => break, // exits the loop when reaching end of file 249 | Err(e) => { 250 | println!( 251 | "Error reading X3 Archive header (XML) at position {}: {:?}", 252 | reader.buffer_position(), 253 | e 254 | ); 255 | return Err(X3Error::ArchiveHeaderXMLInvalid); 256 | } 257 | _ => (), // There are several other `Event`s we do not consider here 258 | } 259 | 260 | // if we don't keep a borrow elsewhere, we can clear the buffer to keep memory usage low 261 | buf.clear(); 262 | } 263 | println!("sample rate: {}", fs[0]); 264 | println!("block length: {}", bl[0]); 265 | println!("Rice codes: {}", codes[0]); 266 | println!("thresholds: {}", th[0]); 267 | 268 | let sample_rate = fs[0].parse::().unwrap(); 269 | let block_len = bl[0].parse::().unwrap(); 270 | let mut rice_code_ids = Vec::new(); 271 | for word in codes[0].split(',') { 272 | match word { 273 | "RICE0" => rice_code_ids.push(0), 274 | "RICE1" => rice_code_ids.push(1), 275 | "RICE2" => rice_code_ids.push(2), 276 | "RICE3" => rice_code_ids.push(3), 277 | "BFP" => (), 278 | _ => return Err(X3Error::ArchiveHeaderXMLRiceCode), 279 | }; 280 | } 281 | let thresholds: Vec = th[0].split(',').map(|s| s.parse::().unwrap()).collect(); 282 | 283 | let mut rc_array: [usize; 3] = [0; 3]; 284 | let mut th_array: [usize; 3] = [0; 3]; 285 | 286 | #[allow(clippy::manual_memcpy)] 287 | for i in 0..3 { 288 | rc_array[i] = rice_code_ids[i]; 289 | th_array[i] = thresholds[i]; 290 | } 291 | let params = x3::Parameters::new( 292 | block_len as usize, 293 | x3::Parameters::DEFAULT_BLOCKS_PER_FRAME, 294 | rc_array, 295 | th_array, 296 | )?; 297 | 298 | Ok((sample_rate, params)) 299 | } 300 | 301 | // 302 | // 303 | // ####### 304 | // # ###### #### ##### #### 305 | // # # # # # 306 | // # ##### #### # #### 307 | // # # # # # 308 | // # # # # # # # 309 | // # ###### #### # #### 310 | // 311 | // 312 | 313 | #[cfg(test)] 314 | mod tests { 315 | // use crate::decodefile::x3a_to_wav; 316 | 317 | // #[test] 318 | // fn test_decode_x3a_file() { 319 | // x3a_to_wav("~/tmp/test.x3a", "~/tmp/test.wav").unwrap(); 320 | // } 321 | } 322 | -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * * 3 | * Rust implementation of the X3 lossless audio compression protocol. * 4 | * * 5 | * Copyright (C) 2019 Simon M. Werner * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | **************************************************************************/ 21 | 22 | use crate::bitreader::BitReader; 23 | use crate::crc; 24 | use crate::error; 25 | use crate::x3::{self, FrameHeader}; 26 | 27 | use byteorder::{BigEndian, ByteOrder}; 28 | use error::X3Error; 29 | 30 | pub enum FrameTest { 31 | IsFrame, 32 | EndOfBuffer, 33 | NotFrame, 34 | } 35 | 36 | pub fn decode_frame( 37 | x3_bytes: &mut [u8], 38 | wav_buf: &mut [i16], 39 | params: &x3::Parameters, 40 | samples: usize, 41 | ) -> Result, X3Error> { 42 | let mut last_wav = BigEndian::read_i16(x3_bytes); 43 | let mut p_wav = 0; 44 | wav_buf[p_wav] = last_wav; 45 | p_wav += 1; 46 | let br = &mut BitReader::new(&x3_bytes[2..]); 47 | let mut remaining_samples = samples - 1; 48 | 49 | while remaining_samples > 0 { 50 | let block_len = core::cmp::min(remaining_samples, params.block_len); 51 | decode_block(br, &mut wav_buf[p_wav..(p_wav + block_len)], &mut last_wav, params)?; 52 | 53 | remaining_samples -= block_len; 54 | p_wav += block_len; 55 | } 56 | 57 | Ok(Some(p_wav)) 58 | } 59 | 60 | /// 61 | /// Parse the frame header and return the payload. The Frame header and payload 62 | /// contain CRCs, theses will be checked and errors returned if the CRC does not 63 | /// match. 64 | /// 65 | /// ### Arguments 66 | /// 67 | /// * `br` - the data to decode as a BitReader. 68 | /// 69 | pub fn read_frame_header(bytes: &[u8]) -> Result { 70 | if bytes.len() < FrameHeader::LENGTH { 71 | return Err(X3Error::FrameDecodeUnexpectedEnd); 72 | } 73 | // Calc header CRC 74 | let header_crc = crc::crc16(&bytes[0..FrameHeader::P_HEADER_CRC]); 75 | let expected_header_crc = BigEndian::read_u16(&bytes[FrameHeader::P_HEADER_CRC..]); 76 | if expected_header_crc != header_crc { 77 | return Err(X3Error::FrameHeaderInvalidHeaderCRC); 78 | } 79 | 80 | // Read 81 | let x3_archive_key = BigEndian::read_u16(&bytes[0..2]); 82 | if x3_archive_key != FrameHeader::KEY { 83 | return Err(X3Error::FrameHeaderInvalidKey); 84 | } 85 | 86 | // 87 | // Currently just skip it 88 | let source_id = bytes[FrameHeader::P_SOURCE_ID]; 89 | 90 | // 91 | let channels = bytes[FrameHeader::P_CHANNELS]; 92 | if channels > 1 { 93 | return Err(X3Error::MoreThanOneChannel); 94 | } 95 | 96 | // 97 | let samples = BigEndian::read_u16(&bytes[FrameHeader::P_SAMPLES..]); 98 | 99 | // 100 | let payload_len = BigEndian::read_u16(&bytes[FrameHeader::P_PAYLOAD_SIZE..]) as usize; 101 | if payload_len >= x3::Frame::MAX_LENGTH { 102 | return Err(X3Error::FrameLength); 103 | } 104 | 105 | //