(*
 * Copyright 2008 Savonet team
 *
 * This file is part of ocaml-speex.
 *
 * ocaml-speex 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-speex 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-speex; 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-speex.
  *
  * @author Samuel Mimram, Romain Beauxis, and many others...
  *)

open Speex
open Unix


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

let input_string chan len =
  let ans = String.create len in
    really_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 usage = "usage: wav2speex [options] source destination"
let float = ref false
let mode =  ref Wideband
let fpp = ref 5
let vbr = ref false
let quality = ref 7

let _ =
  let f = Printf.sprintf in
  let string_of_mode m = 
    match m with
      | Narrowband     -> "narrowband"
      | Wideband       -> "wideband"
      | Ultra_wideband -> "ultra-wideband"
  in
  Arg.parse
    [
      "--float", Arg.Unit (fun b -> float := true ),
      f "Use floats for decoding. Default: %b" !float;
      "--mode", Arg.String 
        (fun b -> 
          match b with
            | "narrowband"     -> mode := Narrowband
            | "wideband"       -> mode := Wideband
            | "ultra-wideband" -> mode := Ultra_wideband
            | _ -> failwith "unkown mode"),
      f "Encoding mode, one of \"narrowband\", \"wideband\" or \"ultra-wideband\". \
         Default: %s" (string_of_mode !mode);
      "--frame_per_packet", Arg.Int (fun b -> fpp := b ),
      f "Frames per Ogg packet. Default: %i" !fpp;
      "--vbr", Arg.Unit (fun b -> vbr := true ),
      f "Encode in vbr mode. Default: %b" !vbr;
      "--quality", Arg.Int (fun b -> quality := b ),
      f "Encoding bitrate, in Kbps. Default: %i" !quality;
    ]
    (
      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 ibits = input_short ic in
    if input_string ic 4 <> "data" then invalid_arg "No data tag";
    let _ = input_int ic in (* datalen *)
    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) <- max (-32768) (min 32767 n);
          done;
        done;
        ans
    in
    let enc = Encoder.init !mode !fpp in
    if not !vbr then
      Encoder.set enc SPEEX_SET_QUALITY !quality
    else
      Encoder.set enc SPEEX_SET_VBR_QUALITY !quality;
    Encoder.set enc SPEEX_SET_SAMPLING_RATE infreq;
    let ivbr = 
      if !vbr then 1 else 0 
    in
    Encoder.set enc SPEEX_SET_VBR ivbr;
    let fsize = Encoder.get enc SPEEX_GET_FRAME_SIZE in
    let os = Ogg.Stream.create () in
    let header = Header.init ~rate:infreq ~nb_channels:channels 
                             ~mode:(!mode) ~vbr:(!vbr) 
                             ~frames_per_packet:(!fpp) 
                             () in
    Header.encode_header header [] os;
    output_string oc (Ogg.Stream.flush os);
    let start = Unix.time () in
    let smode = 
      match !mode with
        | Narrowband     -> "narrowband"
        | Wideband       -> "wideband"
        | Ultra_wideband -> "ultra-wideband"
    in
      Printf.printf
        "Input detected: PCM WAVE %d channels, %d Hz, %d bits\n%!"
        channels infreq ibits;
      Printf.printf
        "Encoding to: SPEEX %d channels, %d Hz, %s, VBR: %s\nPlease wait...\n%!"
        channels infreq smode (string_of_bool !vbr);
        begin try while true do
          let buflen = 2*fsize*channels in
          let buf = String.create buflen in
          let feed () =
            really_input ic buf 0 buflen;
            let fbuf = fos buf in
            assert(Array.length (fbuf.(0)) = fsize);
            fbuf
          in
          let (h,v) =
            if channels = 1 then
              let feed () = 
                let frame = feed () in
                frame.(0)
              in
              if !float then
                let feed () = 
                  Array.map float_of_int (feed ())
                in 
                Encoder.encode_page enc os feed
              else
                Encoder.encode_page_int enc os feed   
            else
              if !float then
                let feed () = 
                  Array.map (fun x -> Array.map float_of_int x) (feed ())
                in
                Encoder.encode_page_stereo enc os feed
              else
                Encoder.encode_page_int_stereo enc os feed
          in
          output_string oc (h ^ v);
        done;
        with End_of_file -> () end;
        Encoder.eos enc os;
        output_string oc (Ogg.Stream.flush os);
        close_in ic;
        close_out oc;
        Printf.printf "Finished in %.0f seconds.\n" ((Unix.time ())-.start);
        Gc.full_major ()
