(*
 * Copyright 2003 Savonet team
 *
 * This file is part of OCaml-Vorbis.
 *
 * OCaml-Vorbis is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * OCaml-Vorbis is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OCaml-Vorbis; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *)

(**
  * An wav to ogg converter using OCaml-Vorbis.
  *
  * @author Samuel Mimram, and many others...
  *)

open Vorbis
open Unix


let src = ref ""
let dst = ref ""

let buflen = ref 1024

let input_string chan len =
  let ans = String.create len in
    (* TODO: check length *)
    ignore (input chan ans 0 len) ;
    ans

let input_int chan =
  let buf = input_string chan 4 in
    (int_of_char buf.[0])
    + (int_of_char buf.[1]) lsl 8
    + (int_of_char buf.[2]) lsl 16
    + (int_of_char buf.[3]) lsl 24

let input_short chan =
  let buf = input_string chan 2 in
    (int_of_char buf.[0]) + (int_of_char buf.[1]) lsl 8

let bitrate = ref 128000
let usage = "usage: wav2ogg [options] source destination"

let _ =
  Arg.parse
    [
      "--bitrate", Arg.Int (fun b -> bitrate := b * 1000),
      "Bitrate, in kilobits per second, defaults to 128kbps" ;
      "--buflen", Arg.Int (fun i -> buflen := i),
      "Size of chunks successively encoded"
    ]
    (
      let pnum = ref (-1) in
        (fun s -> incr pnum; match !pnum with
           | 0 -> src := s
           | 1 -> dst := s
           | _ -> Printf.eprintf "Error: too many arguments\n"; exit 1
        )
    ) usage;
  if !src = "" || !dst = "" then
    (
      Printf.printf "%s\n" usage;
      exit 1
    );
  let ic = open_in_bin !src in
  let oc = open_out_bin !dst in
    (* TODO: improve! *)
    if input_string ic 4 <> "RIFF" then invalid_arg "No RIFF tag";
    ignore (input_string ic 4);
    if input_string ic 4 <> "WAVE" then invalid_arg "No WAVE tag";
    if input_string ic 4 <> "fmt " then invalid_arg "No fmt tag";
    let _ = input_int ic in
    let _ = input_short ic in (* TODO: should be 1 *)
    let channels = input_short ic in
    let infreq = input_int ic in
    let _ = input_int ic in (* bytes / s *)
    let _ = input_short ic in (* block align *)
    let bits = input_short ic in
    let fos buf =
      let len = String.length buf / (2 * channels) in
      let ans = Array.init channels (fun _ -> Array.create len 0.) in
        for i = 0 to len - 1 do
          for c = 0 to channels - 1 do
            let n =
              int_of_char buf.[2 * channels * i + 2 * c]
              + int_of_char buf.[2 * channels * i + 2 * c + 1] lsl 8
            in
            let n =
              if n land 1 lsl 15 = 0 then
                n
              else
                (n land 0b111111111111111) - 32768
            in
              ans.(c).(i) <- float n /. 32768.;
              ans.(c).(i) <- max (-1.) (min 1. ans.(c).(i))
          done;
        done;
        ans
    in
    let enc = Encoder.create channels infreq (-1) !bitrate (-1) in
    let os = Ogg.Stream.create () in
    let encode buf =
      let fbuf = fos buf in
        Encoder.encode_buffer_float enc os fbuf 0 (Array.length fbuf.(0))
    in
    let start = Unix.time () in
      Printf.printf
        "Input detected: PCM WAVE %d channels, %d Hz, %d bits\n%!"
        channels infreq bits;
      Printf.printf
        "Encoding to: OGG %d channels, %d Hz, %d kbps\nPlease wait...\n%!"
        channels infreq !bitrate;
      Encoder.headerout enc os ["ARTIST", "test"];
      if input_string ic 4 <> "data" then invalid_arg "No data tag";
      (* This ensures the actual audio data will start on a new page, as per
       * spec. *)
      let ph,pb = Ogg.Stream.flush_page os in
      output_string oc (ph ^ pb);
      let buflen = !buflen in
      let buf = String.create buflen in
        begin try while true do
            try
              really_input ic buf 0 buflen;
              encode buf;
              while true do
                let ph,pb = Ogg.Stream.get_page os in
                output_string oc (ph ^ pb)
              done
            with Ogg.Not_enough_data -> ()
        done;
        with End_of_file -> () end;
        Encoder.end_of_stream enc os;
        begin
          try
            while true do
              let ph,pb = Ogg.Stream.get_page os in
              output_string oc (ph ^ pb)
            done
          with Ogg.Not_enough_data -> ()
        end;
        close_in ic;
        close_out oc;
        Printf.printf "Finished in %.0f seconds.\n" ((Unix.time ())-.start);
        Gc.full_major ()
