package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.audio_wav/Wav.ml.html

Source file Wav.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Wav.mli *)

(* [channels] interleaved: the frames, each a sample per channel *)
let to_string_channels (channels : Signal.t list) : string =
  let c = List.length channels in
  let n = match channels with [] -> 0 | s :: _ -> Array.length s in
  let b = Buffer.create (44 + (2 * c * n)) in
  let u32 v = Buffer.add_int32_le b (Int32.of_int v) and u16 v = Buffer.add_uint16_le b v in
  Buffer.add_string b "RIFF";
  u32 (36 + (2 * c * n));
  Buffer.add_string b "WAVEfmt ";
  u32 16;
  u16 1;
  u16 c;
  u32 Signal.rate;
  u32 (2 * c * Signal.rate);
  u16 (2 * c);
  u16 16;
  Buffer.add_string b "data";
  u32 (2 * c * n);
  for i = 0 to n - 1 do
    List.iter (fun s -> Buffer.add_int16_le b (Signal.to_int16 s.(i))) channels
  done;
  Buffer.contents b

let to_string (samples : Signal.t) : string = to_string_channels [ samples ]

(* the chunks after "RIFF" size "WAVE": each a 4-letter name, a size,
 * its bytes (padded to an even size); "fmt " and "data" are the two we
 * need, and a file may have others (LIST, fact, ...) before, between,
 * after *)
let chunks (s : string) : (string * int * int) list =
  let rec go i acc =
    if i + 8 > String.length s then List.rev acc
    else
      let size = Int32.to_int (String.get_int32_le s (i + 4)) in
      go (i + 8 + size + (size land 1)) ((String.sub s i 4, i + 8, size) :: acc)
  in
  go 12 []

let of_string (s : string) : (Signal.t, string) result =
  if String.length s < 12 || String.sub s 0 4 <> "RIFF" || String.sub s 8 4 <> "WAVE" then Error "not a WAV file"
  else
    let cs = chunks s in
    match (List.find_opt (fun (n, _, _) -> n = "fmt ") cs, List.find_opt (fun (n, _, _) -> n = "data") cs) with
    | Some (_, fmt, _), Some (_, data, size) ->
        let u16 i = String.get_uint16_le s (fmt + i) in
        let channels = u16 2 and rate = Int32.to_int (String.get_int32_le s (fmt + 4)) in
        if u16 0 <> 1 || u16 14 <> 16 || (channels <> 1 && channels <> 2) then Error "not 16-bit PCM, mono or stereo"
        else
          let size = min size (String.length s - data) in
          let frames = size / (2 * channels) in
          let sample i c = float_of_int (String.get_int16_le s (data + (2 * ((i * channels) + c)))) /. 32767. in
          (* stereo mixed down: a sound here is mono until panned *)
          let mono =
            Array.init frames (fun i -> if channels = 1 then sample i 0 else (sample i 0 +. sample i 1) /. 2.)
          in
          Ok (Resample.to_rate Linear rate mono)
    | _ -> Error "no fmt or no data chunk"

let write (path : string) (samples : Signal.t) : unit =
  Out_channel.with_open_bin path (fun oc -> Out_channel.output_string oc (to_string samples))

let read (path : string) : (Signal.t, string) result = of_string (In_channel.with_open_bin path In_channel.input_all)

let write_stereo (path : string) (s : Signal.stereo) : unit =
  Out_channel.with_open_bin path (fun oc -> Out_channel.output_string oc (to_string_channels [ s.left; s.right ]))