diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 276cc11..7d8ce5c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,16 +6,10 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
     - name: Setup OCaml
-      uses: avsm/setup-ocaml@v1.0
-    - name: Install depext module
-      run: opam install -y depext
+      uses: avsm/setup-ocaml@v2
     - name: Pin locally
       run: opam pin -y add --no-action .
     - name: Install locally
-      run: opam depext -y -i alsa
-    - name: Build locally
-      run: eval $(opam env) && dune build
-    - name: Run tests locally
-      run: eval $(opam env) && dune runtest
+      run: opam install -y -t alsa
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
new file mode 100644
index 0000000..abd7b7e
--- /dev/null
+++ b/.github/workflows/doc.yml
@@ -0,0 +1,26 @@
+name: Build doc
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build_doc:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v2
+    - name: Setup OCaml
+      uses: avsm/setup-ocaml@v2
+    - name: Pin locally
+      run: opam pin -y add -n .
+    - name: Install locally
+      run: opam install -y odoc alsa
+    - name: Build doc
+      run: opam exec dune build @doc
+    - name: Deploy doc
+      uses: JamesIves/github-pages-deploy-action@4.1.6
+      with:
+        branch: gh-pages
+        folder: _build/default/_doc/_html
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 40499dc..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: c
-sudo: false
-addons:
-  apt:
-    sources:
-    - avsm
-    packages:
-    - opam
-
-script: bash -ex .travis-ci.sh
diff --git a/CHANGES.md b/CHANGES.md
index 3e4f132..a36abf7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,8 @@
+0.3.1 (unreleased)
+=====
+
+- Add `writei_floatn`.
+
 0.3.0 (2020-08-08)
 =====
 
diff --git a/examples/sine/sine.ml b/examples/sine/sine.ml
index 4eed807..96c2952 100644
--- a/examples/sine/sine.ml
+++ b/examples/sine/sine.ml
@@ -16,7 +16,7 @@ let () =
   let period_size = Pcm.get_period_size params in
   Printf.printf "samplerate: %d, buffer size: %d, period size: %d\n%!" samplerate buffer_size period_size;
   Pcm.prepare dev;
-  let buf = Bigarray.Array1.create Bigarray.Float32 Bigarray.C_layout (channels * period_size) in
+  let buf = Array.init channels (fun _ -> Array.make period_size 0.) in
   let t = ref 0. in
   while true do
     Printf.printf "time: %f\r%!" !t;
@@ -24,8 +24,8 @@ let () =
       let x = sin (2. *. Float.pi *. !t *. 440.) in
       t := !t +. 1. /. float samplerate;
       for c = 0 to channels - 1 do
-        buf.{channels*i+c} <- x
+        buf.(c).(i) <- x
       done
     done;
-    ignore (Pcm.writei_float_ba dev channels buf)
+    ignore (Pcm.writei_floatn dev buf 0 period_size)
   done
diff --git a/src/alsa.ml b/src/alsa.ml
index c06b037..ef765da 100644
--- a/src/alsa.ml
+++ b/src/alsa.ml
@@ -115,6 +115,9 @@ module Pcm = struct
   external writen_float : handle -> float array array -> int -> int -> int
     = "ocaml_snd_pcm_writen_float"
 
+  external writei_floatn : handle -> float array array -> int -> int -> int
+    = "ocaml_snd_pcm_writei_floatn"
+
   external readn_float_ba :
     handle ->
     (float, Bigarray.float32_elt, Bigarray.c_layout) Bigarray.Array1.t array ->
diff --git a/src/alsa.mli b/src/alsa.mli
index 99e8643..d68938e 100644
--- a/src/alsa.mli
+++ b/src/alsa.mli
@@ -19,10 +19,10 @@
 *)
 
 (**
-  * Interface with the alsa drivers.
-  *
-  * @author Samuel Mimram
-  *)
+   Interface with the alsa drivers.
+
+   @author Samuel Mimram
+*)
 
 (** Get the ALSA sound library version in ASCII format. *)
 val get_version : unit -> string
@@ -80,7 +80,11 @@ module Pcm : sig
     | Async  (** Asynchronous notification (not supported yet). *)
     | Non_blocking  (** Non blocking I/O. *)
 
+  (** Open given device (use ["defaut"] for default one) with given streams and
+      modes. *)
   val open_pcm : string -> stream list -> mode list -> handle
+
+  (** Close device. *)
   val close : handle -> unit
 
   (** Prepare PCM for use. *)
@@ -89,13 +93,14 @@ module Pcm : sig
   (** Resume from suspend, no samples are lost. *)
   val resume : handle -> unit
 
-  (** Recover the stream state from an error or suspend.
-    * This a high-level helper function building on other functions.
-    * This functions handles Interrupted, Buffer_xrun and Suspended 
-    * exceptions trying to prepare given stream for next I/O. 
-    * Raises the given exception when not recognized/used. *)
+  (** Recover the stream state from an error or suspend. This a high-level
+      helper function building on other functions. This functions handles
+      Interrupted, Buffer_xrun and Suspended exceptions trying to prepare given
+      stream for next I/O. Raises the given exception when not
+      recognized/used. *)
   val recover : ?verbose:bool -> handle -> exn -> unit
 
+  (** Start the PCM. *)
   val start : handle -> unit
 
   (** Stop a PCM preserving pending frames. *)
@@ -105,30 +110,36 @@ module Pcm : sig
   val drop : handle -> unit
 
   (** [pause hnd pause] pauses (when [pause] is [true]) or resume (when [pause]
-    * is [false]) a PCM. *)
+      is [false]) a PCM. *)
   val pause : handle -> bool -> unit
 
   val reset : handle -> unit
 
   (** Wait for a PCM to become ready. The second argument is the timeout in
-    * milliseconds (negative for infinite). Returns [false] if a timeout
-    * occured. *)
+      milliseconds (negative for infinite). Returns [false] if a timeout
+      occured. *)
   val wait : handle -> int -> bool
 
   (** [readi handle buf ofs len] reads [len] interleaved {i frames} in [buf]
-    * starting at offset [ofs] (in bytes). It returns the actual number of
-    * frames read. *)
+      starting at offset [ofs] (in bytes). It returns the actual number of
+      frames read. *)
   val readi : handle -> bytes -> int -> int -> int
 
   (** [writei handle buf ofs len] writes [len] interleaved {i frames} of [buf]
-    * starting at offset [ofs] (in bytes). *)
+      starting at offset [ofs] (in bytes). *)
   val writei : handle -> bytes -> int -> int -> int
 
+  (** Read non-interleaved frames. *)
   val readn : handle -> bytes array -> int -> int -> int
+
+  (** Write non-interleaved frames. *)
   val writen : handle -> bytes array -> int -> int -> int
   val readn_float : handle -> float array array -> int -> int -> int
   val writen_float : handle -> float array array -> int -> int -> int
 
+  (** Write in interleaved way non-interleaved frames. *)
+  val writei_floatn : handle -> float array array -> int -> int -> int
+
   val readn_float_ba :
     handle ->
     (float, Bigarray.float32_elt, Bigarray.c_layout) Bigarray.Array1.t array ->
diff --git a/src/alsa_stubs.c b/src/alsa_stubs.c
index 238c76f..459d471 100644
--- a/src/alsa_stubs.c
+++ b/src/alsa_stubs.c
@@ -470,6 +470,32 @@ CAMLprim value ocaml_snd_pcm_writen_float(value handle_, value fbuf, value ofs_,
   CAMLreturn(Val_int(ret));
 }
 
+CAMLprim value ocaml_snd_pcm_writei_floatn(value handle_, value fbuf, value ofs_, value len_)
+{
+  CAMLparam4(handle_, fbuf, ofs_, len_);
+  int len = Int_val(len_);
+  int ofs = Int_val(ofs_);
+  int chans = Wosize_val(fbuf);
+  snd_pcm_t *handle = Pcm_handle_val(handle_);
+  float *buf;
+  int c, i;
+  snd_pcm_sframes_t ret;
+
+  buf = malloc(chans * len * sizeof(float));
+  for(c = 0; c < chans; c++)
+    for(i = 0; i < len; i++)
+      buf[i*chans+c] = Double_field(Field(fbuf, c), i + ofs);
+
+  caml_enter_blocking_section();
+  ret = snd_pcm_writei(handle, (void*)buf, len);
+  caml_leave_blocking_section();
+
+  free(buf);
+  check_for_err(ret);
+
+  CAMLreturn(Val_int(ret));
+}
+
 CAMLprim value ocaml_snd_pcm_readn_float_ba(value handle_, value dbuf)
 {
   CAMLparam2(handle_, dbuf);
@@ -747,8 +773,10 @@ CAMLprim value ocaml_snd_pcm_set_params(value handle, value params)
   CAMLparam2(handle, params);
 
   check_for_err(snd_pcm_hw_params(Pcm_handle_val(handle), Hw_params_val(params)));
-  /* TODO: compute real value */
-  Frame_size_val(handle) = 4;
+
+  unsigned int channels;
+  check_for_err(snd_pcm_hw_params_get_channels(Hw_params_val(params), &channels));
+  Frame_size_val(handle) = 2*channels;
 
   CAMLreturn(Val_unit);
 }