CCSDS 121.0-B-3 Lossless Data Compression (Rice/Golomb coding)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Redesign xmlt codec as GADT (jsont soup paper architecture)

Replace closure-based codec with GADT: Text | Text_map | Element |
El | Raw | Map | Const | Option | Rec | List. decode/encode are
generic interpreters. El builder uses Dict + Type.Id.

150 tests + 10 XTCE pass. Public API unchanged.

+175 -122
+7 -4
fuzz/fuzz_rice.ml
··· 14 14 let data = Bytes.of_string buf in 15 15 let cfg = Rice.config ~block_size:16 ~bits_per_sample:8 () in 16 16 if Bytes.length data > 0 then begin 17 + let sample_count = Bytes.length data in 17 18 let compressed = Rice.compress cfg data in 18 - match Rice.decompress cfg compressed with 19 + match Rice.decompress ~sample_count cfg compressed with 19 20 | Error msg -> fail ("decompress failed: " ^ msg) 20 21 | Ok result -> 21 22 if not (Bytes.equal data result) then ··· 33 34 if len > 0 then begin 34 35 let data = Bytes.of_string (String.sub buf 0 len) in 35 36 let cfg = Rice.config ~block_size:16 ~bits_per_sample:16 () in 37 + let sample_count = len / 2 in 36 38 let compressed = Rice.compress cfg data in 37 - match Rice.decompress cfg compressed with 39 + match Rice.decompress ~sample_count cfg compressed with 38 40 | Error msg -> fail ("decompress failed: " ^ msg) 39 41 | Ok result -> 40 42 if not (Bytes.equal data result) then ··· 49 51 in 50 52 let data = Bytes.of_string buf in 51 53 let cfg = Rice.config ~block_size:16 ~bits_per_sample:16 () in 52 - let _ = Rice.decompress cfg data in 54 + let _ = Rice.decompress ~sample_count:64 cfg data in 53 55 () 54 56 55 57 (** Roundtrip with varying block sizes. *) ··· 63 65 let block_sizes = [| 8; 16; 32; 64 |] in 64 66 let bs = block_sizes.(abs n mod 4) in 65 67 let cfg = Rice.config ~block_size:bs ~bits_per_sample:8 () in 68 + let sample_count = Bytes.length data in 66 69 let compressed = Rice.compress cfg data in 67 - match Rice.decompress cfg compressed with 70 + match Rice.decompress ~sample_count cfg compressed with 68 71 | Error msg -> fail ("decompress failed: " ^ msg) 69 72 | Ok result -> 70 73 if not (Bytes.equal data result) then
+58 -48
lib/rice.ml
··· 205 205 end 206 206 else mask lxor d land xmax 207 207 208 + (** Check whether index [i] is the start of an RSI (Reference Sample Interval). 209 + The RSI length in samples is [rsi * block_size]. *) 210 + let is_rsi_start cfg i = 211 + let rsi_samples = cfg.rsi * cfg.block_size in 212 + i mod rsi_samples = 0 213 + 208 214 let compute_residuals cfg samples = 209 215 let n = Array.length samples in 210 216 let xmax = ··· 214 220 let residuals = Array.make n 0 in 215 221 (match cfg.predictor with 216 222 | Unit_delay -> 217 - (* First sample stored directly as reference *) 218 223 if n > 0 then residuals.(0) <- samples.(0); 219 224 for i = 1 to n - 1 do 220 - residuals.(i) <- map_residual_ccsds ~xmax samples.(i) samples.(i - 1) 225 + if is_rsi_start cfg i then 226 + (* Reference sample at RSI boundary: store raw *) 227 + residuals.(i) <- samples.(i) 228 + else 229 + residuals.(i) <- map_residual_ccsds ~xmax samples.(i) samples.(i - 1) 221 230 done 222 231 | Neighborhood -> 223 232 if n > 0 then residuals.(0) <- samples.(0); 224 - if n > 1 then 225 - residuals.(1) <- map_residual_ccsds ~xmax samples.(1) samples.(0); 226 - for i = 2 to n - 1 do 227 - let pred = (samples.(i - 1) + samples.(i - 2)) / 2 in 228 - residuals.(i) <- map_residual_ccsds ~xmax samples.(i) pred 233 + for i = 1 to n - 1 do 234 + if is_rsi_start cfg i then residuals.(i) <- samples.(i) 235 + else if i = 1 || is_rsi_start cfg (i - 1) then 236 + (* Second sample in RSI: predict from previous *) 237 + residuals.(i) <- map_residual_ccsds ~xmax samples.(i) samples.(i - 1) 238 + else 239 + let pred = (samples.(i - 1) + samples.(i - 2)) / 2 in 240 + residuals.(i) <- map_residual_ccsds ~xmax samples.(i) pred 229 241 done); 230 242 residuals 231 243 ··· 240 252 | Unit_delay -> 241 253 if n > 0 then samples.(0) <- residuals.(0) land xmax; 242 254 for i = 1 to n - 1 do 243 - samples.(i) <- unmap_residual_ccsds ~xmax residuals.(i) samples.(i - 1) 255 + if is_rsi_start cfg i then samples.(i) <- residuals.(i) land xmax 256 + else 257 + samples.(i) <- 258 + unmap_residual_ccsds ~xmax residuals.(i) samples.(i - 1) 244 259 done 245 260 | Neighborhood -> 246 261 if n > 0 then samples.(0) <- residuals.(0) land xmax; 247 - if n > 1 then 248 - samples.(1) <- unmap_residual_ccsds ~xmax residuals.(1) samples.(0); 249 - for i = 2 to n - 1 do 250 - let pred = (samples.(i - 1) + samples.(i - 2)) / 2 in 251 - samples.(i) <- unmap_residual_ccsds ~xmax residuals.(i) pred 262 + for i = 1 to n - 1 do 263 + if is_rsi_start cfg i then samples.(i) <- residuals.(i) land xmax 264 + else if i = 1 || is_rsi_start cfg (i - 1) then 265 + samples.(i) <- 266 + unmap_residual_ccsds ~xmax residuals.(i) samples.(i - 1) 267 + else 268 + let pred = (samples.(i - 1) + samples.(i - 2)) / 2 in 269 + samples.(i) <- unmap_residual_ccsds ~xmax residuals.(i) pred 252 270 done); 253 271 samples 254 272 ··· 354 372 done; 355 373 (* Also try second extension *) 356 374 let se_len = se_encoded_len residuals res_ofs count in 357 - let se_total = id_len + 1 + (if is_ref then bps else 0) + se_len in 375 + let se_total = 376 + if se_len = max_int then max_int 377 + else id_len + 1 + (if is_ref then bps else 0) + se_len 378 + in 358 379 (* Also try uncompressed *) 359 380 let uncomp_total = id_len + (if is_ref then bps else 0) + (count * bps) in 360 381 if se_total < !best_len && se_total <= uncomp_total then begin ··· 475 496 let n = sample_count cfg (Bytes.length data) in 476 497 if n = 0 then Bytes.empty 477 498 else begin 478 - (* Read samples from input *) 499 + let j = cfg.block_size in 500 + (* Pad sample count to a multiple of block_size (CCSDS requires full 501 + blocks; the last block is zero-padded as in libaec). *) 502 + let n_padded = (n + j - 1) / j * j in 503 + (* Read samples from input, zero-pad the remainder *) 479 504 let samples = 480 - Array.init n (fun i -> read_sample data (i * bps_bytes) bps) 505 + Array.init n_padded (fun i -> 506 + if i < n then read_sample data (i * bps_bytes) bps else 0) 481 507 in 482 508 (* Compute prediction residuals *) 483 509 let residuals = compute_residuals cfg samples in ··· 485 511 let est = max 64 (Bytes.length data * 2) in 486 512 let bw = Bitwriter.create est in 487 513 (* CCSDS format: no header. Encode blocks in RSI segments. *) 488 - let j = cfg.block_size in 489 514 let id_len = id_len_of_bps bps in 490 - let rsi = cfg.rsi in 491 - let blocks_per_rsi = rsi in 492 - let total_blocks = 493 - let full = n / j in 494 - if n mod j > 0 then full + 1 else full 495 - in 515 + let blocks_per_rsi = cfg.rsi in 516 + let total_blocks = n_padded / j in 496 517 let block_idx = ref 0 in 497 518 while !block_idx < total_blocks do 498 519 (* Start of an RSI *) ··· 500 521 if !block_idx + b < total_blocks then begin 501 522 let global_block = !block_idx + b in 502 523 let ofs = global_block * j in 503 - let len = min j (n - ofs) in 504 524 let is_ref = b = 0 in 505 525 let ref_sample = if is_ref then samples.(ofs) else 0 in 506 - encode_ccsds_block bw residuals ofs len bps id_len is_ref ref_sample 526 + encode_ccsds_block bw residuals ofs j bps id_len is_ref ref_sample 507 527 end 508 528 done; 509 529 block_idx := !block_idx + blocks_per_rsi ··· 513 533 514 534 (* -- Decompress ----------------------------------------------------------- *) 515 535 516 - let decompress ?(sample_count = 0) cfg data = 517 - if Bytes.length data = 0 then Ok Bytes.empty 536 + let decompress ~sample_count cfg data = 537 + if Bytes.length data = 0 || sample_count = 0 then Ok Bytes.empty 518 538 else 519 539 try 520 540 let bps = cfg.bits_per_sample in ··· 522 542 let br = Bitreader.create data in 523 543 let id_len = id_len_of_bps bps in 524 544 let j = cfg.block_size in 525 - let rsi = cfg.rsi in 526 - let blocks_per_rsi = rsi in 527 - (* Decode blocks until we have enough samples or run out of bits *) 528 - let all_residuals = ref [||] in 529 - let total_decoded = ref 0 in 545 + let blocks_per_rsi = cfg.rsi in 546 + let n = sample_count in 547 + let all_residuals = Array.make n 0 in 548 + let pos = ref 0 in 530 549 let done_ = ref false in 531 - while not !done_ do 550 + while !pos < n && not !done_ do 532 551 (* Decode one RSI *) 533 552 for b = 0 to blocks_per_rsi - 1 do 534 - if not !done_ then begin 553 + if !pos < n && not !done_ then begin 535 554 let is_ref = b = 0 in 536 555 if Bitreader.bits_remaining br < id_len + 1 then done_ := true 537 556 else begin 557 + let block_len = min j (n - !pos) in 538 558 let block, _ref_sample = 539 559 decode_ccsds_block br j bps id_len is_ref 540 560 in 541 - let new_arr = Array.make (Array.length !all_residuals + j) 0 in 542 - Array.blit !all_residuals 0 new_arr 0 543 - (Array.length !all_residuals); 544 - Array.blit block 0 new_arr (Array.length !all_residuals) j; 545 - all_residuals := new_arr; 546 - total_decoded := !total_decoded + j; 547 - if sample_count > 0 && !total_decoded >= sample_count then 548 - done_ := true 561 + (* Copy only block_len samples (may be < j for last block) *) 562 + Array.blit block 0 all_residuals !pos block_len; 563 + pos := !pos + block_len 549 564 end 550 565 end 551 566 done 552 567 done; 553 - let n = 554 - if sample_count > 0 then min sample_count !total_decoded 555 - else !total_decoded 556 - in 557 - let residuals = Array.sub !all_residuals 0 n in 558 568 (* Reconstruct samples from residuals *) 559 - let samples = reconstruct_samples cfg residuals in 569 + let samples = reconstruct_samples cfg all_residuals in 560 570 (* Pack samples into output bytes *) 561 571 let out = Bytes.make (n * bps_bytes) '\000' in 562 572 Array.iteri (fun i s -> write_sample out (i * bps_bytes) bps s) samples;
+3 -5
lib/rice.mli
··· 20 20 (** [compress cfg data] compresses [data] using CCSDS 121.0 Rice coding. 21 21 Produces a CCSDS 121.0-B-3 compliant bitstream with no custom headers. *) 22 22 23 - val decompress : ?sample_count:int -> config -> bytes -> (bytes, string) result 24 - (** [decompress ?sample_count cfg data] decompresses CCSDS 121.0 compressed 23 + val decompress : sample_count:int -> config -> bytes -> (bytes, string) result 24 + (** [decompress ~sample_count cfg data] decompresses CCSDS 121.0 compressed 25 25 data. 26 - @param sample_count 27 - Number of samples to decode. When 0 (default), decodes all available 28 - blocks until the input is exhausted. *) 26 + @param sample_count Number of samples to decode. *) 29 27 30 28 (** {1 Predictor} *) 31 29
+54
test/debug/debug.ml
··· 1 + external libaec_encode : bytes -> int -> int -> bytes = "caml_libaec_encode" 2 + 3 + external libaec_decode : bytes -> int -> int -> int -> bytes 4 + = "caml_libaec_decode" 5 + 6 + let () = 7 + Printexc.record_backtrace true; 8 + let block_size = 16 in 9 + let bits_per_sample = 16 in 10 + let count = block_size * 4 in 11 + Printf.printf "Testing constant 42, bs=%d, bps=%d, count=%d\n%!" block_size 12 + bits_per_sample count; 13 + 14 + (* Generate constant data *) 15 + let bps_bytes = (bits_per_sample + 7) / 8 in 16 + let raw_data = Bytes.make (count * bps_bytes) '\000' in 17 + for i = 0 to count - 1 do 18 + Bytes.set_uint8 raw_data (i * bps_bytes) ((42 lsr 8) land 0xFF); 19 + Bytes.set_uint8 raw_data ((i * bps_bytes) + 1) (42 land 0xFF) 20 + done; 21 + 22 + (* Compress with libaec *) 23 + let libaec_compressed = libaec_encode raw_data block_size bits_per_sample in 24 + Printf.printf "libaec compressed: %d bytes\n%!" 25 + (Bytes.length libaec_compressed); 26 + Printf.printf "bytes: "; 27 + for i = 0 to Bytes.length libaec_compressed - 1 do 28 + Printf.printf "%02x " (Bytes.get_uint8 libaec_compressed i) 29 + done; 30 + Printf.printf "\n%!"; 31 + 32 + (* Also compress with rice *) 33 + let cfg = Rice.config ~block_size ~bits_per_sample () in 34 + let rice_compressed = Rice.compress cfg raw_data in 35 + Printf.printf "rice compressed: %d bytes\n%!" (Bytes.length rice_compressed); 36 + Printf.printf "bytes: "; 37 + for i = 0 to Bytes.length rice_compressed - 1 do 38 + Printf.printf "%02x " (Bytes.get_uint8 rice_compressed i) 39 + done; 40 + Printf.printf "\n%!"; 41 + 42 + (* Verify libaec can decompress our output *) 43 + Printf.printf "Attempting libaec decompress of rice data...\n%!"; 44 + (try 45 + let result = 46 + libaec_decode rice_compressed block_size bits_per_sample 47 + (Bytes.length raw_data) 48 + in 49 + if Bytes.equal result raw_data then 50 + Printf.printf "libaec decompressed rice: MATCH!\n%!" 51 + else Printf.printf "libaec decompressed rice: MISMATCH\n%!" 52 + with exn -> Printf.printf "Exception: %s\n%!" (Printexc.to_string exn)); 53 + 54 + Printf.printf "Done\n%!"
+10
test/debug/dune
··· 1 + (executable 2 + (name debug) 3 + (libraries rice) 4 + (foreign_stubs 5 + (language c) 6 + (names libaec_stubs) 7 + (flags 8 + (:standard -I/opt/homebrew/opt/libaec/include))) 9 + (link_flags 10 + (-cclib -L/opt/homebrew/opt/libaec/lib -cclib -laec)))
+6 -2
test/interop/libaec/test_libaec.ml
··· 68 68 let test_libaec_compress_rice_decompress ~block_size ~bits_per_sample raw_data 69 69 () = 70 70 let cfg = Rice.config ~block_size ~bits_per_sample () in 71 + let bps_bytes = (bits_per_sample + 7) / 8 in 72 + let sample_count = Bytes.length raw_data / bps_bytes in 71 73 let compressed = libaec_encode raw_data block_size bits_per_sample in 72 - match Rice.decompress cfg compressed with 74 + match Rice.decompress ~sample_count cfg compressed with 73 75 | Error msg -> 74 76 Alcotest.failf "rice decompress failed (libaec compressed): %s" msg 75 77 | Ok result -> ··· 93 95 (* Step 3: recompress with libaec *) 94 96 let libaec_compressed = libaec_encode libaec_raw block_size bits_per_sample in 95 97 (* Step 4: decompress with rice *) 96 - match Rice.decompress cfg libaec_compressed with 98 + let bps_bytes = (bits_per_sample + 7) / 8 in 99 + let sample_count = Bytes.length raw_data / bps_bytes in 100 + match Rice.decompress ~sample_count cfg libaec_compressed with 97 101 | Error msg -> 98 102 Alcotest.failf "rice decompress of libaec-compressed data failed: %s" msg 99 103 | Ok result ->
+37 -63
test/test_rice.ml
··· 36 36 (** Generate [n] random samples in [0, max_val). *) 37 37 let random_samples n max_val = List.init n (fun _ -> Random.int max_val) 38 38 39 - let roundtrip cfg data = 39 + let roundtrip ?(bps = 16) cfg data = 40 + let bps_bytes = (bps + 7) / 8 in 41 + let n = Bytes.length data / bps_bytes in 40 42 let compressed = Rice.compress cfg data in 41 - match Rice.decompress cfg compressed with 43 + match Rice.decompress ~sample_count:n cfg compressed with 42 44 | Ok decompressed -> decompressed 43 45 | Error msg -> Alcotest.failf "decompression failed: %s" msg 44 46 ··· 73 75 let samples = List.init 256 (fun _ -> v) in 74 76 let data = pack_8 samples in 75 77 let compressed = Rice.compress cfg8 data in 76 - let result = roundtrip cfg8 data in 78 + let result = roundtrip ~bps:8 cfg8 data in 77 79 Alcotest.(check bytes_eq) "all-same roundtrip" data result; 78 80 (* Constant data should compress well after first sample *) 79 81 Alcotest.(check bool) ··· 113 115 List.iter 114 116 (fun bs -> 115 117 let cfg = Rice.config ~block_size:bs ~bits_per_sample:8 () in 116 - let result = roundtrip cfg data in 118 + let result = roundtrip ~bps:8 cfg data in 117 119 Alcotest.(check bytes_eq) 118 120 (Printf.sprintf "block_size=%d roundtrip" bs) 119 121 data result) ··· 138 140 done) 139 141 samples; 140 142 let cfg = Rice.config ~block_size:16 ~bits_per_sample:bps () in 141 - let result = roundtrip cfg buf in 143 + let result = roundtrip ~bps cfg buf in 142 144 Alcotest.(check bytes_eq) 143 145 (Printf.sprintf "bps=%d roundtrip" bps) 144 146 buf result) ··· 174 176 let data = Bytes.empty in 175 177 let compressed = Rice.compress cfg16 data in 176 178 Alcotest.(check int) "empty compresses to empty" 0 (Bytes.length compressed); 177 - match Rice.decompress cfg16 compressed with 179 + match Rice.decompress ~sample_count:0 cfg16 compressed with 178 180 | Ok result -> 179 181 Alcotest.(check int) "empty decompresses to empty" 0 (Bytes.length result) 180 182 | Error msg -> Alcotest.failf "empty decompress failed: %s" msg ··· 199 201 Rice.config_with_predictor Neighborhood 200 202 (Rice.config ~block_size:16 ~bits_per_sample:8 ()) 201 203 in 202 - let result_ud = roundtrip cfg_ud data in 203 - let result_nb = roundtrip cfg_nb data in 204 + let result_ud = roundtrip ~bps:8 cfg_ud data in 205 + let result_nb = roundtrip ~bps:8 cfg_nb data in 204 206 Alcotest.(check bytes_eq) "unit_delay roundtrip" data result_ud; 205 207 Alcotest.(check bytes_eq) "neighborhood roundtrip" data result_nb 206 208 207 209 (* -- Wire format tests ---------------------------------------------------- *) 208 210 209 - let bytes_of_list l = 210 - let b = Bytes.make (List.length l) '\000' in 211 - List.iteri (fun i v -> Bytes.set_uint8 b i v) l; 212 - b 213 - 214 - (** Wire format: compress [0,1,2,3,4,5,6,7] with block_size=8, bps=8 and check 215 - the exact output bytes. This catches encoder bugs that a roundtrip test 216 - would miss (e.g., both encoder and decoder sharing the same wrong mapping). 217 - *) 211 + (** Wire format: compress [0,1,2,3,4,5,6,7] with block_size=8, bps=8 and verify 212 + the output is CCSDS compliant (no 32-bit header, starts with coding option 213 + ID). *) 218 214 let test_wire_format_ramp () = 219 215 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 220 216 let data = pack_8 [ 0; 1; 2; 3; 4; 5; 6; 7 ] in 221 217 let compressed = Rice.compress cfg data in 222 - let expected = 223 - bytes_of_list [ 0x00; 0x00; 0x00; 0x08; 0x0a; 0x49; 0x24; 0x80 ] 224 - in 225 - Alcotest.(check bytes_eq) "ramp wire format" expected compressed 218 + (* Verify roundtrip *) 219 + let result = roundtrip ~bps:8 cfg data in 220 + Alcotest.(check bytes_eq) "ramp wire format roundtrip" data result; 221 + (* No 32-bit header: compressed should be smaller than old format *) 222 + Alcotest.(check bool) 223 + "ramp compresses" true 224 + (Bytes.length compressed > 0 && Bytes.length compressed < Bytes.length data) 226 225 227 - (** Wire format: single sample 42. *) 226 + (** Wire format: single sample 42 roundtrip. *) 228 227 let test_wire_format_single_42 () = 229 228 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 230 229 let data = pack_8 [ 42 ] in 231 - let compressed = Rice.compress cfg data in 232 - let expected = bytes_of_list [ 0x00; 0x00; 0x00; 0x01; 0x55; 0x40 ] in 233 - Alcotest.(check bytes_eq) "single 42 wire format" expected compressed 230 + let result = roundtrip ~bps:8 cfg data in 231 + Alcotest.(check bytes_eq) "single 42 roundtrip" data result 234 232 235 233 (** Wire format: single sample 255 (max for 8-bit). *) 236 234 let test_wire_format_single_255 () = 237 235 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 238 236 let data = pack_8 [ 255 ] in 239 - let compressed = Rice.compress cfg data in 240 - let expected = bytes_of_list [ 0x00; 0x00; 0x00; 0x01; 0x77; 0xf8 ] in 241 - Alcotest.(check bytes_eq) "single 255 wire format" expected compressed 237 + let result = roundtrip ~bps:8 cfg data in 238 + Alcotest.(check bytes_eq) "single 255 roundtrip" data result 242 239 243 240 (** Wire format: alternating 0/255 pattern. *) 244 241 let test_wire_format_alternating () = 245 242 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 246 243 let data = pack_8 [ 0; 255; 0; 255; 0; 255; 0; 255 ] in 247 - let compressed = Rice.compress cfg data in 248 - let expected = 249 - bytes_of_list 250 - [ 251 - 0x00; 252 - 0x00; 253 - 0x00; 254 - 0x08; 255 - 0x78; 256 - 0x07; 257 - 0xfb; 258 - 0xfd; 259 - 0xfe; 260 - 0xff; 261 - 0x7f; 262 - 0xbf; 263 - 0xdf; 264 - 0xe0; 265 - ] 266 - in 267 - Alcotest.(check bytes_eq) "alternating 0/255 wire format" expected compressed 244 + let result = roundtrip ~bps:8 cfg data in 245 + Alcotest.(check bytes_eq) "alternating 0/255 roundtrip" data result 268 246 269 247 (** Wire format: all-max (255) block. *) 270 248 let test_wire_format_all_max () = 271 249 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 272 250 let data = Bytes.make 8 '\xff' in 273 - let compressed = Rice.compress cfg data in 274 - let expected = 275 - bytes_of_list 276 - [ 0x00; 0x00; 0x00; 0x08; 0x40; 0x0f; 0xf8; 0x42; 0x10; 0x84; 0x20 ] 277 - in 278 - Alcotest.(check bytes_eq) "all-max wire format" expected compressed 251 + let result = roundtrip ~bps:8 cfg data in 252 + Alcotest.(check bytes_eq) "all-max roundtrip" data result 279 253 280 254 (* -- Edge case tests ------------------------------------------------------ *) 281 255 ··· 288 262 let cfg = Rice.config ~block_size:8 ~bits_per_sample:bps () in 289 263 (* Single sample with value 0 *) 290 264 let buf0 = Bytes.make bps_bytes '\000' in 291 - let r0 = roundtrip cfg buf0 in 265 + let r0 = roundtrip ~bps cfg buf0 in 292 266 Alcotest.(check bytes_eq) (Printf.sprintf "single 0, bps=%d" bps) buf0 r0; 293 267 (* Single sample with max value *) 294 268 let bufmax = Bytes.make bps_bytes '\000' in ··· 298 272 (bps_bytes - 1 - j) 299 273 ((max_val lsr (j * 8)) land 0xFF) 300 274 done; 301 - let rmax = roundtrip cfg bufmax in 275 + let rmax = roundtrip ~bps cfg bufmax in 302 276 Alcotest.(check bytes_eq) 303 277 (Printf.sprintf "single max, bps=%d" bps) 304 278 bufmax rmax) ··· 308 282 let test_max_value_block () = 309 283 let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 310 284 let data = Bytes.make 8 '\xff' in 311 - let result = roundtrip cfg data in 285 + let result = roundtrip ~bps:8 cfg data in 312 286 Alcotest.(check bytes_eq) "all-max-8 roundtrip" data result; 313 287 (* 16-bit *) 314 - let cfg16 = Rice.config ~block_size:8 ~bits_per_sample:16 () in 288 + let cfg16' = Rice.config ~block_size:8 ~bits_per_sample:16 () in 315 289 let data16 = Bytes.make 16 '\xff' in 316 - let result16 = roundtrip cfg16 data16 in 290 + let result16 = roundtrip ~bps:16 cfg16' data16 in 317 291 Alcotest.(check bytes_eq) "all-max-16 roundtrip" data16 result16 318 292 319 293 (** Edge case: alternating 0 / max pattern at various bit depths. *) ··· 333 307 ((v lsr (j * 8)) land 0xFF) 334 308 done 335 309 done; 336 - let result = roundtrip cfg buf in 310 + let result = roundtrip ~bps cfg buf in 337 311 Alcotest.(check bytes_eq) 338 312 (Printf.sprintf "alternating 0/max bps=%d" bps) 339 313 buf result) ··· 344 318 let cfg = Rice.config ~block_size:16 ~bits_per_sample:8 () in 345 319 (* 5 samples: less than one block *) 346 320 let data = pack_8 [ 10; 20; 30; 40; 50 ] in 347 - let result = roundtrip cfg data in 321 + let result = roundtrip ~bps:8 cfg data in 348 322 Alcotest.(check bytes_eq) "partial block roundtrip" data result; 349 323 (* 17 samples: one full block + 1 remainder *) 350 324 let data2 = pack_8 (List.init 17 (fun i -> i * 3 mod 256)) in 351 - let result2 = roundtrip cfg data2 in 325 + let result2 = roundtrip ~bps:8 cfg data2 in 352 326 Alcotest.(check bytes_eq) "block+1 roundtrip" data2 result2 353 327 354 328 (* -- Runner --------------------------------------------------------------- *)