Index: python/lib/moresetuptools.py
===================================================================
--- python/lib/moresetuptools.py	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ python/lib/moresetuptools.py	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -80,4 +80,5 @@
                 'libswresample', 'libavresample',
                 'sndfile',
+                'hdf5',
                 #'fftw3f',
                ]
@@ -104,4 +105,7 @@
         ext.define_macros += [('HAVE_FFTW3F', 1)]
         ext.define_macros += [('HAVE_FFTW3', 1)]
+    if 'hdf5' in ext.libraries:
+        ext.libraries += ['hdf5_hl']
+        ext.define_macros += [('HAVE_HDF5', 1)]
 
     # add accelerate on darwin
Index: scripts/get_crepe.sh
===================================================================
--- scripts/get_crepe.sh	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ scripts/get_crepe.sh	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,16 @@
+#! /bin/bash
+
+CREPEURL=https://github.com/marl/crepe/blob/models/model-tiny.h5.bz2?raw=true
+MODELTARGET=crepe-model-tiny.h5
+SHA512=91df10316092de0d9c35ae0eaa8f6cceb49fb01f54dc74f9f1196f7f569a3f885242d1abded56c9825180552a602d9e76f2021d7cfb55cfbefb2f84c8a9f4715
+
+
+function checkmodelsum () {
+  (echo $SHA512  $MODELTARGET | sha512sum -c - ) || (echo $MODELTARGET checksum did not match?!; exit 1)
+}
+
+function fetchmodel () {
+  wget -qO- $CREPEURL | bunzip2 - > $MODELTARGET
+}
+
+( [ -f "$MODELTARGET" ] || fetchmodel ) && checkmodelsum
Index: src/ai/activation.c
===================================================================
--- src/ai/activation.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/activation.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,21 @@
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "ai/tensor.h"
+
+void aubio_activation_relu(aubio_tensor_t *t)
+{
+  uint_t i;
+  AUBIO_ASSERT(t);
+  for (i = 0; i < t->size; i++) {
+    t->buffer[i] = MAX(0, t->buffer[i]);
+  }
+}
+
+void aubio_activation_sigmoid(aubio_tensor_t *t)
+{
+  uint_t i;
+  AUBIO_ASSERT(t);
+  for (i = 0; i < t->size; i++) {
+    t->buffer[i] = 1. / (1. + EXP( - t->buffer[i] ));
+  }
+}
Index: src/ai/activation.h
===================================================================
--- src/ai/activation.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/activation.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,55 @@
+
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_ACTIVATION_H
+#define AUBIO_ACTIVATION_H
+
+/** \file
+
+  Activation functions.
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Rectifier linear unit (ReLU)
+
+  \param t input tensor on which to compute the activation function (in-place)
+
+  Upon return, each value \f$x\f$ of the tensor \f$t\f$ will be set to
+  \f$max(0, x)\f$.
+
+*/
+void aubio_activation_relu(aubio_tensor_t *t);
+
+/** compute sigmoid activation
+
+  \param t input tensor on which to compute the activation function (in-place)
+
+  Upon return, each value \f$x\f$ of the tensor \f$t\f$ will be set to
+  \f$\frac{1}{1 + e^{-x}}\f$.
+
+*/
+void aubio_activation_sigmoid(aubio_tensor_t *t);
+
+#endif
Index: src/ai/batchnorm.c
===================================================================
--- src/ai/batchnorm.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/batchnorm.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,189 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "batchnorm.h"
+
+struct _aubio_batchnorm_t {
+  uint_t n_outputs;
+  fvec_t *gamma;
+  fvec_t *beta;
+  fvec_t *moving_mean;
+  fvec_t *moving_variance;
+};
+
+static void aubio_batchnorm_debug(aubio_batchnorm_t *c,
+    aubio_tensor_t *input_tensor);
+
+aubio_batchnorm_t *new_aubio_batchnorm(void)
+{
+  aubio_batchnorm_t *c = AUBIO_NEW(aubio_batchnorm_t);
+  // note; no input parameter, so no other possible failure
+  return c;
+}
+
+static void aubio_batchnorm_reset(aubio_batchnorm_t *c) {
+  AUBIO_ASSERT(c);
+  if (c->gamma)
+    del_fvec(c->gamma);
+  if (c->beta)
+    del_fvec(c->beta);
+  if (c->moving_mean)
+    del_fvec(c->moving_mean);
+  if (c->moving_variance)
+    del_fvec(c->moving_variance);
+}
+
+void del_aubio_batchnorm(aubio_batchnorm_t* c) {
+  aubio_batchnorm_reset(c);
+  AUBIO_FREE(c);
+}
+
+void aubio_batchnorm_debug(aubio_batchnorm_t *c, aubio_tensor_t *input_tensor)
+{
+  AUBIO_DBG("batchnorm: %15s -> %s (%d params) (4 * (%d,))\n",
+      aubio_tensor_get_shape_string(input_tensor),
+      aubio_tensor_get_shape_string(input_tensor), // same output shape
+      c->n_outputs, 4 * c->n_outputs);
+}
+
+uint_t aubio_batchnorm_get_output_shape(aubio_batchnorm_t *c,
+    aubio_tensor_t *input, uint_t *shape)
+{
+  uint_t i;
+
+  AUBIO_ASSERT(c && input && shape);
+
+  for (i = 0; i < input->ndim; i++) {
+    shape[i] = input->shape[i];
+  }
+
+  aubio_batchnorm_reset(c);
+
+  c->n_outputs = input->shape[input->ndim - 1];
+
+  c->gamma = new_fvec(c->n_outputs);
+  c->beta = new_fvec(c->n_outputs);
+  c->moving_mean = new_fvec(c->n_outputs);
+  c->moving_variance = new_fvec(c->n_outputs);
+
+  if (!c->gamma || !c->beta || !c->moving_mean || !c->moving_variance)
+  {
+    aubio_batchnorm_reset(c);
+    return AUBIO_FAIL;
+  }
+
+  aubio_batchnorm_debug(c, input);
+
+  return AUBIO_OK;
+}
+
+void aubio_batchnorm_do(aubio_batchnorm_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  smpl_t s;
+  uint_t i, j;
+  uint_t ii = 0;
+  uint_t length = activations->shape[activations->ndim - 1];
+  uint_t height = activations->size / length;
+
+  AUBIO_ASSERT(c);
+  AUBIO_ASSERT_EQUAL_SHAPE(input_tensor, activations);
+  AUBIO_ASSERT(length == c->n_outputs);
+  AUBIO_ASSERT(height * length == activations->size);
+
+  for (i = 0; i < height; i++) {
+    for (j = 0; j < length; j++) {
+      s = input_tensor->buffer[ii + j];
+      s -= c->moving_mean->data[j];
+      s *= c->gamma->data[j];
+      s /= SQRT(c->moving_variance->data[j] + 1.e-4);
+      s += c->beta->data[j];
+      activations->buffer[ii + j] = s;
+    }
+    ii += length;
+  }
+}
+
+uint_t aubio_batchnorm_set_gamma(aubio_batchnorm_t *t, fvec_t *gamma)
+{
+  AUBIO_ASSERT(t && t->gamma);
+  AUBIO_ASSERT(gamma);
+  if (t->gamma->length != gamma->length) return AUBIO_FAIL;
+  fvec_copy(gamma, t->gamma);
+  return AUBIO_OK;
+}
+
+uint_t aubio_batchnorm_set_beta(aubio_batchnorm_t *t, fvec_t *beta)
+{
+  AUBIO_ASSERT(t && t->beta && beta);
+  if (t->beta->length != beta->length)
+    return AUBIO_FAIL;
+  fvec_copy(beta, t->beta);
+  return AUBIO_OK;
+}
+
+uint_t aubio_batchnorm_set_moving_mean(aubio_batchnorm_t *t,
+    fvec_t *moving_mean)
+{
+  AUBIO_ASSERT(t && t->moving_mean);
+  AUBIO_ASSERT(moving_mean);
+  if (t->moving_mean->length != moving_mean->length)
+    return AUBIO_FAIL;
+  fvec_copy(moving_mean, t->moving_mean);
+  return AUBIO_OK;
+}
+
+uint_t aubio_batchnorm_set_moving_variance(aubio_batchnorm_t *t,
+    fvec_t *moving_variance)
+{
+  AUBIO_ASSERT(t && t->moving_variance);
+  AUBIO_ASSERT(moving_variance);
+  if (t->moving_variance->length != moving_variance->length)
+    return AUBIO_FAIL;
+  fvec_copy(moving_variance, t->moving_variance);
+  return AUBIO_OK;
+}
+
+fvec_t *aubio_batchnorm_get_gamma(aubio_batchnorm_t *t)
+{
+  AUBIO_ASSERT(t && t->gamma);
+  return t->gamma;
+}
+
+fvec_t *aubio_batchnorm_get_beta(aubio_batchnorm_t *t)
+{
+  AUBIO_ASSERT(t && t->beta);
+  return t->beta;
+}
+
+fvec_t *aubio_batchnorm_get_moving_mean(aubio_batchnorm_t *t)
+{
+  AUBIO_ASSERT(t && t->moving_mean);
+  return t->moving_mean;
+}
+
+fvec_t *aubio_batchnorm_get_moving_variance(aubio_batchnorm_t *t)
+{
+  AUBIO_ASSERT(t && t->moving_variance);
+  return t->moving_variance;
+}
Index: src/ai/batchnorm.h
===================================================================
--- src/ai/batchnorm.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/batchnorm.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,179 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_BATCHNORM_H
+#define AUBIO_BATCHNORM_H
+
+/** \file
+
+ Batch normalization layer.
+
+ References
+ ----------
+
+ Ioffe, Sergey; Szegedy, Christian. "Batch Normalization: Accelerating Deep
+ Network Training by Reducing Internal Covariate Shift", available online
+ at https://arxiv.org/pdf/1502.03167.pdf
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** batch normalization layer */
+typedef struct _aubio_batchnorm_t aubio_batchnorm_t;
+
+/** create a new batch normalization layer
+
+  This layer takes no parameters. The number of output channels will be
+  determined as the inner-most dimension of the input tensor when calling
+  ::aubio_batchnorm_get_output_shape.
+
+*/
+aubio_batchnorm_t *new_aubio_batchnorm(void);
+
+/** get output shape of the layer
+
+  \param t      ::aubio_batchnorm_t layer
+  \param input  input tensor
+  \param shape  output shape
+
+  \return 0 on success, non-zero otherwise
+
+  This function determines the number of output channels required and allocate
+  the vectors of weights. The ouptut shape of this layer is identical to the
+  input shape.
+
+*/
+uint_t aubio_batchnorm_get_output_shape(aubio_batchnorm_t *t,
+        aubio_tensor_t *input, uint_t *shape);
+
+/** get a pointer to the gamma vector
+
+  \param t  ::aubio_batchnorm_t layer
+
+  \return   pointer to `fvec_t` holding the gamma parameters
+
+  When called after ::aubio_batchnorm_get_output_shape, this function will
+  return a pointer to the vector allocated to hold the `gamma` weights.
+
+  A NULL pointer will be returned if ::aubio_batchnorm_get_output_shape has not
+  been called yet.
+
+*/
+fvec_t *aubio_batchnorm_get_gamma(aubio_batchnorm_t *t);
+
+/** get a pointer to the beta vector
+
+  \param t  ::aubio_batchnorm_t layer
+
+  \return   pointer to `fvec_t` holding the beta parameters
+*/
+fvec_t *aubio_batchnorm_get_beta(aubio_batchnorm_t *t);
+
+/** get a pointer to the moving mean vector
+
+  \param t  ::aubio_batchnorm_t layer
+
+  \return   pointer to `fvec_t` holding the moving mean parameters
+
+*/
+fvec_t *aubio_batchnorm_get_moving_mean(aubio_batchnorm_t *t);
+
+/** get a pointer to the moving variance vector
+
+  \param t  ::aubio_batchnorm_t layer
+
+  \return   pointer to `fvec_t` holding the moving variance parameters
+
+*/
+fvec_t *aubio_batchnorm_get_moving_variance(aubio_batchnorm_t *t);
+
+/** set gamma vector of batchnorm layer
+
+  \param t      ::aubio_batchnorm_t layer
+  \param gamma  ::fvec_t containing the weights
+
+  \return   0 on success, non-zero otherwise.
+
+  This function will copy the content of an existing vector into
+  the corresponding vector of weights in `t`.
+
+  Note: to spare a copy and load directly the data in `t`,
+  ::aubio_batchnorm_get_gamma can be used instead.
+
+*/
+uint_t aubio_batchnorm_set_gamma(aubio_batchnorm_t *t, fvec_t *gamma);
+
+/** set beta vector of a batchnorm layer
+
+  \param t      ::aubio_batchnorm_t layer
+  \param beta   ::fvec_t containing the weights
+
+  \return   0 on success, non-zero otherwise.
+
+*/
+uint_t aubio_batchnorm_set_beta(aubio_batchnorm_t *t, fvec_t *beta);
+
+/** set moving mean vector of batchnorm layer
+
+  \param t              ::aubio_batchnorm_t layer
+  \param moving_mean    ::fvec_t containing the weights
+
+  \return   0 on success, non-zero otherwise.
+
+*/
+uint_t aubio_batchnorm_set_moving_mean(aubio_batchnorm_t *t,
+        fvec_t *moving_mean);
+
+/** set moving variance vector of batchnorm layer
+
+  \param t                  ::aubio_batchnorm_t layer
+  \param moving_variance    ::fvec_t containing the weights
+
+  \return   0 on success, non-zero otherwise.
+
+*/
+uint_t aubio_batchnorm_set_moving_variance(aubio_batchnorm_t *t,
+        fvec_t *moving_variance);
+
+/** compute batch normalization layer
+
+  \param t             ::aubio_batchnorm_t layer
+  \param input_tensor  input tensor
+  \param activations   output tensor
+
+*/
+void aubio_batchnorm_do(aubio_batchnorm_t *t, aubio_tensor_t *input_tensor,
+        aubio_tensor_t *activations);
+
+/** delete batch normalization layer
+
+  \param t  ::aubio_batchnorm_t layer to delete
+
+*/
+void del_aubio_batchnorm(aubio_batchnorm_t *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_BATCHNORM_H */
Index: src/ai/conv1d.c
===================================================================
--- src/ai/conv1d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/conv1d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,366 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "conv1d.h"
+
+typedef enum
+{
+  PAD_SAME = 0,
+  PAD_VALID = 1,
+  PAD_CAUSAL = 2, // TODO (1d only, for dilated convolution)
+} aubio_conv1d_padding_type;
+
+struct _aubio_conv1d_t {
+  // define internals here
+  uint_t n_filters;
+  uint_t kernel_shape;     // kernel sizes
+  uint_t stride_shape[1];  // stride sizes
+
+  aubio_conv1d_padding_type padding_mode;
+
+  // these will be set after calling get_output_shape
+  aubio_tensor_t *kernel;
+  fvec_t *bias;
+  uint_t output_shape[2];  // shape of output
+  uint_t padding_start;    // left padding
+
+#if defined(HAVE_BLAS)
+  aubio_tensor_t *padded_input;
+#endif
+};
+
+static
+void aubio_conv1d_debug(aubio_conv1d_t *c, aubio_tensor_t *input_tensor);
+
+aubio_conv1d_t *new_aubio_conv1d(uint_t n_filters, uint_t kernel_shape[1])
+{
+  aubio_conv1d_t *c = AUBIO_NEW(aubio_conv1d_t);
+
+  // validate input parameters
+  AUBIO_GOTO_FAILURE((sint_t)n_filters >= 1);
+  AUBIO_GOTO_FAILURE((sint_t)kernel_shape[0] >= 1);
+
+  // set internal variables
+  c->n_filters = n_filters;
+  c->kernel_shape = kernel_shape[0];
+
+  // default to padding_mode="valid"
+  c->padding_mode = PAD_VALID;
+  // set default stride_shape to (1)
+  uint_t stride_shape[1] = {1};
+  aubio_conv1d_set_stride(c, stride_shape);
+
+  return c;
+
+failure:
+  del_aubio_conv1d(c);
+  return NULL;
+}
+
+void del_aubio_conv1d(aubio_conv1d_t *c)
+{
+  AUBIO_ASSERT(c);
+  // destroy internals here
+  if (c->kernel) {
+    del_aubio_tensor(c->kernel);
+  }
+  if (c->bias)
+    del_fvec(c->bias);
+#if defined(HAVE_BLAS)
+  if (c->padded_input) del_aubio_tensor(c->padded_input);
+#endif
+  AUBIO_FREE(c);
+}
+
+
+uint_t aubio_conv1d_set_stride(aubio_conv1d_t *c, uint_t stride[1])
+{
+  if ((sint_t)stride[0] < 1) return AUBIO_FAIL;
+  c->stride_shape[0] = stride[0];
+  return AUBIO_OK;
+}
+
+uint_t *aubio_conv1d_get_stride(aubio_conv1d_t *c)
+{
+  return c->stride_shape;
+}
+
+uint_t aubio_conv1d_get_output_shape(aubio_conv1d_t *c,
+    aubio_tensor_t *input_tensor,
+    uint_t *shape)
+{
+  uint_t output_shape[2] = {0, c->n_filters};
+  uint_t padding_shape = 0;  // total amount of padding
+  uint_t padding_start = 0;
+
+  // check input parameters
+  AUBIO_ASSERT(input_tensor);
+  AUBIO_ASSERT(shape);
+
+  // reset output array
+  shape[0] = 0;
+  shape[1] = 0;
+
+  switch (c->padding_mode) {
+    case PAD_SAME:
+      // compute output shape
+      output_shape[0] = (uint_t)CEIL(input_tensor->shape[0]
+          / (smpl_t)c->stride_shape[0]);
+
+      padding_shape = (output_shape[0] - 1) * c->stride_shape[0] +
+        c->kernel_shape - input_tensor->shape[0];
+
+      padding_start = FLOOR(padding_shape / 2);
+      break;
+    case PAD_VALID:
+      output_shape[0] = (input_tensor->shape[0] - c->kernel_shape + 1)
+        / c->stride_shape[0];
+
+      padding_start = 0;
+      break;
+    case PAD_CAUSAL:
+      // TODO
+      return AUBIO_FAIL;
+    default:
+      return AUBIO_FAIL;
+  }
+
+  uint_t kernel_shape[3];
+  kernel_shape[0] = c->kernel_shape; // filter length
+  kernel_shape[1] = input_tensor->shape[1]; // channels
+  kernel_shape[2] = c->n_filters; // outputs
+
+  if (c->kernel) del_aubio_tensor(c->kernel);
+  if (c->bias) del_fvec(c->bias);
+
+  c->kernel = new_aubio_tensor(3, kernel_shape);
+  if (!c->kernel) return AUBIO_FAIL;
+  c->bias = new_fvec(c->n_filters);
+
+  // set internals upon success
+  c->output_shape[0] = output_shape[0];
+  c->output_shape[1] = output_shape[1];
+
+#if defined(HAVE_BLAS)
+  if (c->padded_input) del_aubio_tensor(c->padded_input);
+  uint_t padded_shape[2] = {input_tensor->shape[0] + padding_shape,
+    input_tensor->shape[1]};
+  c->padded_input = new_aubio_tensor(2, padded_shape);
+#endif
+
+  c->padding_start = padding_start;
+
+  // set output
+  shape[0] = output_shape[0];
+  shape[1] = output_shape[1];
+
+  aubio_conv1d_debug(c, input_tensor);
+
+  return AUBIO_OK;
+}
+
+void aubio_conv1d_debug(aubio_conv1d_t *c, aubio_tensor_t *input_tensor)
+{
+  // print some info
+  AUBIO_ASSERT(c);
+  uint_t n_params = (c->kernel->shape[0] * c->kernel->shape[2] + 1)
+    * c->kernel->shape[1];
+  AUBIO_DBG("conv1d:    %15s -> (%d, %d) (%d params)"
+      " (weigths=(%d, %d, %d), stride=(%d,), pad_start=(%d,))\n",
+    aubio_tensor_get_shape_string(input_tensor),
+    c->output_shape[0], c->output_shape[1],
+    n_params,
+    c->kernel->shape[0], c->kernel->shape[1], c->kernel->shape[2],
+    c->stride_shape[0],
+    -c->padding_start);
+}
+
+uint_t aubio_conv1d_check_output_shape(aubio_conv1d_t *c,
+    aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  // fetch output_shape if it hasn't been done before
+  if (c->output_shape[0] == 0 ||
+      c->output_shape[1] == 0) {
+    if (!aubio_conv1d_get_output_shape(c, input_tensor, c->output_shape)) {
+      return AUBIO_FAIL;
+    }
+  }
+
+  // check we have as many filters as expected activation outputs
+  if (activations->shape[1] != c->n_filters) return AUBIO_FAIL;
+  if (activations->shape[1] != c->kernel->shape[2]) return AUBIO_FAIL;
+  if (input_tensor->shape[1] != c->kernel->shape[1]) return AUBIO_FAIL;
+
+  // check tensor activations has the expected sizes
+  if (c->output_shape[0] != activations->shape[0]) return AUBIO_FAIL;
+  if (c->output_shape[1] != activations->shape[1]) return AUBIO_FAIL;
+  return AUBIO_OK;
+}
+
+#if !defined(HAVE_BLAS)
+void aubio_conv1d_do(aubio_conv1d_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  uint_t i, j, k, a;
+  uint_t stride_a, kk;
+  sint_t x;
+  smpl_t s, w, bias, acc;
+
+  AUBIO_ASSERT(c && input_tensor && activations);
+  // check we have the correct output activation sizes
+  if (aubio_conv1d_check_output_shape(c, input_tensor, activations))
+  {
+    AUBIO_ERR("conv1d: check_output_shape failed\n");
+    return;
+  }
+
+  // for each kernel filter k
+  for (i = 0; i < activations->shape[1]; i++) {
+    // get bias
+    bias = c->bias->data[i];
+    stride_a = 0; // j * c->stride_shape
+    // for each output
+    for (j = 0; j < activations->shape[0]; j++) {
+      // reset output
+      acc = 0;
+      // compute convolution for one kernel
+      for (a = 0; a < c->kernel_shape; a++) {
+        x = stride_a + a - c->padding_start;
+        if ((x > -1) && (x < (sint_t)input_tensor->shape[0])) {
+          kk = 0;
+          // for each input channel
+          for (k = 0; k < input_tensor->shape[1]; k++) {
+            // get kernel weight
+            w = c->kernel->data[a][kk + i];
+            // get input sample
+            s = input_tensor->data[x][k];
+            acc += w * s;
+            kk += c->kernel->shape[2];
+          }
+        }
+      }
+      stride_a += c->stride_shape[0];
+      // apply bias
+      activations->data[j][i] = acc + bias;
+    }
+  }
+}
+
+#else /* HAVE_BLAS */
+
+// blas implementation
+//
+//  uses gemv on the padded input to compute each output elements at once
+//
+// TODO
+//  - avoid copy when padding_start == 0
+//  - optimize copying using tensor helpers
+
+void aubio_conv1d_do(aubio_conv1d_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  uint_t i, j;
+
+  uint_t sdot_size = c->kernel->shape[0] * c->kernel->shape[1];
+  uint_t input_stride = c->stride_shape[0] * c->padded_input->shape[1];
+
+  AUBIO_ASSERT(c && input_tensor && activations);
+  if (aubio_conv1d_check_output_shape(c, input_tensor, activations))
+  {
+    AUBIO_ERR("conv1d: check_output_shape failed\n");
+    return;
+  }
+
+  // copy input to padded version
+  for (j = 0; j < input_tensor->shape[0]; j++) {
+    for (i = 0; i < input_tensor->shape[1]; i++) {
+      c->padded_input->data[j + c->padding_start][i] =
+        input_tensor->data[j][i];
+    }
+  }
+
+  // for each output
+  for (j = 0; j < activations->shape[0]; j++) {
+    // for each row of activation output
+    aubio_cblas__gemv(CblasRowMajor, CblasTrans,
+        sdot_size, c->kernel->shape[2], 1.,
+        c->kernel->buffer, c->kernel->shape[2],
+        c->padded_input->buffer + j  * input_stride, 1, 0.,
+        activations->buffer + j * activations->shape[1], 1);
+  }
+  for (j = 0; j < activations->shape[0]; j++) {
+    // for each kernel filter k
+    for (i = 0; i < activations->shape[1]; i++) {
+      activations->data[j][i] += c->bias->data[i];
+    }
+  }
+}
+#endif /* HAVE_BLAS */
+
+uint_t aubio_conv1d_set_padding_mode(aubio_conv1d_t *c,
+    const char_t *padding_mode)
+{
+  AUBIO_ASSERT(c && padding_mode);
+  if (strncasecmp(padding_mode, "same", PATH_MAX) == 0) {
+    c->padding_mode = PAD_SAME;
+  } else if (strncasecmp(padding_mode, "valid", PATH_MAX) == 0) {
+    c->padding_mode = PAD_VALID;
+  } else {
+    return AUBIO_FAIL;
+  }
+  return AUBIO_OK;
+}
+
+aubio_tensor_t *aubio_conv1d_get_kernel(aubio_conv1d_t* c)
+{
+  AUBIO_ASSERT(c && c->kernel);
+  return c->kernel;
+}
+
+uint_t aubio_conv1d_set_kernel(aubio_conv1d_t *c, aubio_tensor_t *kernel)
+{
+  AUBIO_ASSERT(c && kernel);
+  if (aubio_tensor_have_same_shape(c->kernel, kernel)) {
+    aubio_tensor_copy(kernel, c->kernel);
+    return AUBIO_OK;
+  }
+  return AUBIO_FAIL;
+}
+
+uint_t aubio_conv1d_set_bias(aubio_conv1d_t *c, fvec_t *bias)
+{
+  AUBIO_ASSERT(c && bias);
+  if (bias->length == c->bias->length) {
+    fvec_copy(bias, c->bias);
+    return AUBIO_OK;
+  }
+  return AUBIO_FAIL;
+}
+
+fvec_t *aubio_conv1d_get_bias(aubio_conv1d_t* c)
+{
+  AUBIO_ASSERT(c && c->bias);
+  return c->bias;
+}
Index: src/ai/conv1d.h
===================================================================
--- src/ai/conv1d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/conv1d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,184 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_CONV1D_H
+#define AUBIO_CONV1D_H
+
+/** \file
+
+  Convolutional layer (1D)
+
+  Standard implementation of a 1D convolutional layer, partly optimized for
+  CPU.
+
+  Note
+  ----
+  Only the forward pass is implemented for now.
+
+  References
+  ----------
+  Vincent Dumoulin, Francesco Visin - A guide to convolution arithmetic for
+  deep learning. https://github.com/vdumoulin/conv_arithmetic
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** conv1d layer */
+typedef struct _aubio_conv1d_t aubio_conv1d_t;
+
+/** create a new conv1d layer
+
+  \param n_filters number of filters
+  \param kernel_shape length of each filter
+
+  \return new conv1d layer
+
+*/
+aubio_conv1d_t *new_aubio_conv1d(uint_t n_filters, uint_t kernel_shape[1]);
+
+/** set padding mode
+
+  \param c                  layer
+  \param padding_mode       padding mode
+
+  \return 0 on success, non-zero otherwise
+
+  Available padding: "same", and "valid".
+
+  Todo:
+  - add causal mode
+
+*/
+uint_t aubio_conv1d_set_padding_mode(aubio_conv1d_t *c,
+    const char_t *padding_mode);
+
+/** set stride
+
+  \param c      layer
+  \param stride array of length 1 containing the stride parameter
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_conv1d_set_stride(aubio_conv1d_t *c, uint_t stride[1]);
+
+/** get current stride settings
+
+  \param t  layer
+
+  \return   array of length 1 containing the stride parameter
+
+*/
+uint_t *aubio_conv1d_get_stride(aubio_conv1d_t* t);
+
+/** get output shape
+
+  \param t                  layer
+  \param input_tensor       input tensor
+  \param shape              output shape
+
+  \return 0 on success, non-zero otherwise
+
+  Upon return, `shape` will be filled with the output shape of the layer. This
+  function should be called after ::aubio_conv1d_set_stride or
+  ::aubio_conv1d_set_padding_mode, and before ::aubio_conv1d_get_kernel or
+  ::aubio_conv1d_get_bias.
+
+*/
+uint_t aubio_conv1d_get_output_shape(aubio_conv1d_t *t,
+        aubio_tensor_t *input_tensor, uint_t *shape);
+
+/** get kernel weights
+
+  \param t ::aubio_conv1d_t layer
+
+  \return tensor of weights
+
+  When called after ::aubio_conv1d_get_output_shape, this function will return
+  a pointer to the tensor holding the weights of this layer.
+
+*/
+aubio_tensor_t *aubio_conv1d_get_kernel(aubio_conv1d_t *t);
+
+/** get biases
+
+  \param t layer
+
+  \return vector of biases
+
+  When called after ::aubio_conv1d_get_output_shape, this function will return
+  a pointer to the vector holding the biases.
+
+*/
+fvec_t *aubio_conv1d_get_bias(aubio_conv1d_t *t);
+
+/** set kernel weights
+
+  \param t          layer
+  \param kernel     kernel weights
+
+  \return 0 on success, non-zero otherwise
+
+  Copy kernel weights into internal layer memory.  This function should be
+  called after ::aubio_conv1d_get_output_shape.
+
+*/
+uint_t aubio_conv1d_set_kernel(aubio_conv1d_t *t, aubio_tensor_t *kernel);
+
+/** set biases
+
+  \param t          layer
+  \param bias       biases
+
+  \return 0 on success, non-zero otherwise
+
+  Copy vector of biases into internal layer memory. This function should be
+  called after ::aubio_conv1d_get_output_shape.
+
+*/
+uint_t aubio_conv1d_set_bias(aubio_conv1d_t *t, fvec_t *bias);
+
+/** compute layer output
+
+  \param t              layer
+  \param input_tensor   input tensor
+  \param output_tensor  output tensor
+
+  Perform 1D convolution.
+
+*/
+void aubio_conv1d_do(aubio_conv1d_t *t, aubio_tensor_t *input_tensor,
+        aubio_tensor_t *output_tensor);
+
+/** destroy conv1d layer
+
+  \param t  layer
+
+*/
+void del_aubio_conv1d(aubio_conv1d_t *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_CONV1D_H */
Index: src/ai/conv2d.c
===================================================================
--- src/ai/conv2d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/conv2d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,509 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "conv2d.h"
+
+typedef enum
+{
+  PAD_SAME = 0,   // same, aka half mode
+  PAD_VALID = 1   // valid, aka no padding
+} aubio_conv2d_padding_t;
+
+struct _aubio_conv2d_t {
+  // define internals here
+  uint_t n_filters;
+  uint_t kernel_shape[2];     // kernel sizes
+  uint_t stride_shape[2];     // stride sizes
+
+  aubio_conv2d_padding_t padding_mode;
+
+  // these will be set after calling get_output_shape
+  aubio_tensor_t *kernel;
+  fvec_t *bias;
+  uint_t output_shape[3];     // shape of output
+  uint_t padding_start[2];    // {top, left} padding
+
+#if defined(HAVE_BLAS)
+  aubio_tensor_t *padded_input;
+#endif
+};
+
+static void aubio_conv2d_debug(aubio_conv2d_t *c, aubio_tensor_t *input_tensor);
+
+aubio_conv2d_t *new_aubio_conv2d(uint_t n_filters, uint_t kernel_shape[2])
+{
+  aubio_conv2d_t *c = AUBIO_NEW(aubio_conv2d_t);
+
+  // validate input parameters
+  AUBIO_GOTO_FAILURE((sint_t)n_filters >= 1);
+  AUBIO_GOTO_FAILURE((sint_t)kernel_shape[0] >= 1);
+  AUBIO_GOTO_FAILURE((sint_t)kernel_shape[1] >= 1);
+
+  // set internal variables
+  c->n_filters = n_filters;
+  c->kernel_shape[0] = kernel_shape[0];
+  c->kernel_shape[1] = kernel_shape[1];
+
+  // default to padding_mode="valid"
+  c->padding_mode = PAD_VALID;
+  // set default stride_shape to {1, 1}
+  {
+    uint_t default_stride[2] = {1, 1};
+    aubio_conv2d_set_stride(c, default_stride);
+  }
+
+  return c;
+
+failure:
+  del_aubio_conv2d(c);
+  return NULL;
+}
+
+void del_aubio_conv2d(aubio_conv2d_t *c)
+{
+  AUBIO_ASSERT(c);
+  if (c->kernel)
+    del_aubio_tensor(c->kernel);
+  if (c->bias)
+    del_fvec(c->bias);
+#if defined(HAVE_BLAS)
+  if (c->padded_input)
+    del_aubio_tensor(c->padded_input);
+#endif
+  AUBIO_FREE(c);
+}
+
+
+uint_t aubio_conv2d_set_stride(aubio_conv2d_t *c,
+    uint_t stride[2])
+{
+  if ((sint_t)stride[0] < 1) return AUBIO_FAIL;
+  if ((sint_t)stride[1] < 1) return AUBIO_FAIL;
+  c->stride_shape[0] = stride[0];
+  c->stride_shape[1] = stride[1];
+  return AUBIO_OK;
+}
+
+uint_t *aubio_conv2d_get_stride(aubio_conv2d_t *c)
+{
+  return c->stride_shape;
+}
+
+uint_t aubio_conv2d_get_output_shape(aubio_conv2d_t *c,
+    aubio_tensor_t *input_tensor,
+    uint_t *shape)
+{
+  uint_t output_shape[3] = {0, 0, c->n_filters};
+  uint_t padding_start[2] = {0, 0};
+  // total amount of padding
+  uint_t padding_shape[2] = {0, 0};
+
+  // check input parameters
+  AUBIO_ASSERT(input_tensor);
+  AUBIO_ASSERT(shape);
+
+  // reset output array
+  shape[0] = 0;
+  shape[1] = 0;
+  shape[2] = 0;
+
+  switch (c->padding_mode) {
+    case PAD_SAME:
+      // compute output shape
+      output_shape[0] = (uint_t)CEIL(input_tensor->shape[0]
+          / (smpl_t)c->stride_shape[0]);
+      output_shape[1] = (uint_t)CEIL(input_tensor->shape[1]
+          / (smpl_t)c->stride_shape[1]);
+
+      padding_shape[0] = (output_shape[0] - 1) * c->stride_shape[0]
+        + c->kernel_shape[0] - input_tensor->shape[0];
+      padding_shape[1] = (output_shape[1] - 1) * c->stride_shape[1]
+        + c->kernel_shape[1] - input_tensor->shape[1];
+
+      padding_start[0] = FLOOR(padding_shape[0] / 2);
+      padding_start[1] = FLOOR(padding_shape[1] / 2);
+
+      break;
+    case PAD_VALID:
+      output_shape[0] = (input_tensor->shape[0] - c->kernel_shape[0] + 1)
+        / c->stride_shape[0];
+      output_shape[1] = (input_tensor->shape[1] - c->kernel_shape[1] + 1)
+        / c->stride_shape[1];
+
+      padding_start[0] = 0;
+      padding_start[1] = 0;
+
+      break;
+    //case PAD_CAUSAL:
+    //  // TODO
+    //  return AUBIO_FAIL;
+    default:
+      return AUBIO_FAIL;
+  }
+
+  uint_t kernel_shape[4];
+  kernel_shape[0] = c->kernel_shape[0];
+  kernel_shape[1] = c->kernel_shape[1];
+  kernel_shape[2] = input_tensor->shape[2];
+  kernel_shape[3] = c->n_filters;
+
+  if (c->kernel) del_aubio_tensor(c->kernel);
+  if (c->bias) del_fvec(c->bias);
+
+  c->kernel = new_aubio_tensor(4, kernel_shape);
+  if (!c->kernel) return AUBIO_FAIL;
+  c->bias = new_fvec(c->n_filters);
+
+  // set internals upon success
+  c->output_shape[0] = output_shape[0];
+  c->output_shape[1] = output_shape[1];
+  c->output_shape[2] = output_shape[2];
+
+  c->padding_start[0] = padding_start[0];
+  c->padding_start[1] = padding_start[1];
+
+  // set output
+  shape[0] = output_shape[0];
+  shape[1] = output_shape[1];
+  shape[2] = output_shape[2];
+
+
+#if defined(HAVE_BLAS)
+  // im2col padding
+  padding_shape[0] = output_shape[0] * output_shape[1];
+  padding_shape[1] = c->kernel_shape[0] * c->kernel_shape[1]
+    * input_tensor->shape[2];
+  c->padded_input = new_aubio_tensor(2, padding_shape);
+  if (!c-> padded_input) {
+    AUBIO_MSG("conv2d: failed creating padded_input with shape (%d, %d, %d)\n",
+        padding_shape);
+    return AUBIO_FAIL;
+  }
+#endif
+
+  aubio_conv2d_debug(c, input_tensor);
+
+  return AUBIO_OK;
+}
+
+void aubio_conv2d_debug(aubio_conv2d_t *c, aubio_tensor_t *input_tensor)
+{
+  // print some info
+  AUBIO_ASSERT(c);
+  uint_t n_params = (c->kernel->shape[0] * c->kernel->shape[2] + 1)
+    * c->kernel->shape[1] * c->kernel->shape[3];
+
+  const char_t *tensor_str = aubio_tensor_get_shape_string(input_tensor);
+  //AUBIO_DBG("conv2d: kernel_shape_str %s\n", kernel_shape_str);
+  AUBIO_DBG("conv2d:    %15s -> (%d, %d, %d)",
+    tensor_str,
+    c->output_shape[0], c->output_shape[1], c->output_shape[2]);
+  tensor_str = aubio_tensor_get_shape_string(c->kernel);
+  AUBIO_DBG(" (n_params=%d, kernel_shape=(%d, %d),"
+      " weigths=%s, stride (%d, %d), pad_start [%d, %d])\n",
+    n_params, c->kernel_shape[0], c->kernel_shape[1],
+    tensor_str,
+    c->stride_shape[0], c->stride_shape[1],
+    -c->padding_start[0], -c->padding_start[1]);
+}
+
+uint_t aubio_conv2d_check_output_shape(aubio_conv2d_t *c,
+    aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  // fetch output_shape if it hasn't been done before
+  if (c->output_shape[0] == 0 ||
+      c->output_shape[1] == 0 ||
+      c->output_shape[2] == 0) {
+    if (!aubio_conv2d_get_output_shape(c, input_tensor, c->output_shape)) {
+      return AUBIO_FAIL;
+    }
+  }
+
+  // check we have as many filters as expected activation outputs
+  if (activations->shape[2] != c->n_filters) return AUBIO_FAIL;
+  if (activations->shape[2] != c->kernel->shape[3]) return AUBIO_FAIL;
+  if (input_tensor->shape[2] != c->kernel->shape[2]) return AUBIO_FAIL;
+
+  // check tensor activations has the expected sizes
+  if (c->output_shape[0] != activations->shape[0]) return AUBIO_FAIL;
+  if (c->output_shape[1] != activations->shape[1]) return AUBIO_FAIL;
+  if (c->output_shape[2] != activations->shape[2]) return AUBIO_FAIL;
+  return AUBIO_OK;
+}
+
+#if !defined(HAVE_BLAS)
+void aubio_conv2d_do(aubio_conv2d_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  uint_t i, j, k, l, a, b;
+  uint_t stride_a, stride_b;
+  sint_t x, y;
+  smpl_t s, w, bias, acc;
+  uint_t jj, ll, bb, yy;
+
+  uint_t k_stride1 = c->kernel->shape[3];
+  uint_t k_stride2 = c->kernel->shape[2] * k_stride1;
+
+  AUBIO_ASSERT(c && input_tensor && activations);
+  // check we have the correct output activation sizes
+  if (aubio_conv2d_check_output_shape(c, input_tensor, activations))
+  {
+    AUBIO_ERR("conv2d: check_output_shape failed\n");
+    return;
+  }
+
+  // for each kernel filter k
+  for (i = 0; i < activations->shape[2]; i++) {
+    // get bias
+    bias = c->bias->data[i];
+    stride_b = 0; // == j * c->stride_shape[1]
+    jj = 0; // == j * activations->shape[2]
+    // for each output y
+    for (j = 0; j < activations->shape[1]; j++) {
+      // for each output x
+      stride_a = 0; // k * c->stride_shape[0]
+      for (k = 0; k < activations->shape[0]; k++) {
+        // reset output
+        acc = 0;
+        // compute convolution for one kernel
+        for (a = 0; a < c->kernel_shape[0]; a++) {
+          x = stride_a + a - c->padding_start[0];
+          if ((x < 0) || (x > (sint_t)input_tensor->shape[0] - 1))
+            continue; // padding with 0.
+          bb = 0; // == b * k_stride2
+          for (b = 0; b < c->kernel_shape[1]; b++) {
+            y = stride_b + b - c->padding_start[1];
+            if ((y < 0) || (y > (sint_t)input_tensor->shape[1] - 1))
+              continue; // padding with 0.
+            yy = y * input_tensor->shape[2];
+            ll = bb + i; // + l * k_stride1
+            // for each input channel
+            for (l = 0; l < input_tensor->shape[2]; l++) {
+              // get kernel weight
+              w = c->kernel->data[a][ll];
+              // get input sample
+              s = input_tensor->data[x][yy + l];
+              acc += w * s;
+              ll += k_stride1;
+            }
+            bb += k_stride2;
+          }
+        }
+        stride_a += c->stride_shape[0];
+        // apply bias
+        acc += bias;
+        // set output activation
+        activations->data[k][jj + i] = acc;
+      }
+      stride_b += c->stride_shape[1];
+      jj += activations->shape[2];
+    }
+  }
+}
+
+#else /* HAVE_BLAS */
+
+void aubio_conv2d_copy_to_padded(aubio_conv2d_t *o,
+    aubio_tensor_t *input_tensor, aubio_tensor_t *padded_input)
+{
+  // naive implementation of im2col
+  uint_t i, j, k, l, m;
+  uint_t stride_4 = o->kernel->shape[2];
+  uint_t stride_3 = o->kernel->shape[1] * stride_4;
+  uint_t stride_2 = o->kernel->shape[0] * stride_3;
+  uint_t stride_1 = o->output_shape[1] * stride_2;
+  uint_t stride_in_2 = input_tensor->shape[2];
+  uint_t stride_in_1 = input_tensor->shape[1] * stride_in_2;
+
+  AUBIO_ASSERT(padded_input->size ==
+      o->output_shape[0] * o->output_shape[1]
+      * o->kernel_shape[0] * o->kernel_shape[1]
+      * input_tensor->shape[2]);
+  AUBIO_ASSERT(input_tensor->shape[2] == o->kernel->shape[2]);
+
+  for (i = 0; i < o->output_shape[0]; i++)
+  {
+    for (j = 0; j <  o->output_shape[1]; j++)
+    {
+      for (k = 0; k < o->kernel->shape[0]; k++)
+      {
+        for (l = 0; l < o->kernel->shape[1]; l++)
+        {
+          for (m = 0; m < o->kernel->shape[2]; m++)
+          {
+            uint_t read_i = i * o->stride_shape[0] + k;
+            uint_t read_j = j * o->stride_shape[1] + l;
+            if (read_i < o->padding_start[0])
+              continue;
+            else if (read_i - o->padding_start[0] >= input_tensor->shape[0])
+              continue;
+            if (read_j < o->padding_start[1])
+              continue;
+            else if (read_j - o->padding_start[1] >= input_tensor->shape[1])
+              continue;
+
+            sint_t idx =
+              ((read_i - o->padding_start[0])) * stride_in_1
+              + ((read_j - o->padding_start[1])) * stride_in_2
+              + m;
+            padded_input->buffer[i * stride_1
+              + j * stride_2
+              + k * stride_3
+              + l * stride_4
+              + m]
+              = input_tensor->buffer[idx];
+          }
+        }
+      }
+    }
+  }
+}
+
+void aubio_conv2d_do(aubio_conv2d_t *o, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations)
+{
+  uint_t i, j;
+  smpl_t bias;
+  aubio_tensor_t *padded_input = o->padded_input;
+  aubio_tensor_t *kernel = o->kernel;
+
+  AUBIO_ASSERT(o && input_tensor && activations);
+  // check we have the correct output activation sizes
+  if (aubio_conv2d_check_output_shape(o, input_tensor, activations))
+  {
+    AUBIO_ERR("conv2d: check_output_shape failed\n");
+    return;
+  }
+
+  uint_t M = padded_input->shape[0];
+  uint_t K = padded_input->size/padded_input->shape[0];
+  uint_t N = kernel->size / K;
+
+  // check sizes
+  AUBIO_ASSERT(M * K == padded_input->size);
+  AUBIO_ASSERT(N * K == kernel->size);
+  AUBIO_ASSERT(M * N == activations->size);
+
+  // copy input to im2col sliding window version
+  aubio_conv2d_copy_to_padded(o, input_tensor, padded_input);
+
+  aubio_cblas__gemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
+      M,                    // M
+      N,                    // N
+      K,                    // K
+      1.F,                  // alpha
+      padded_input->buffer, // M x K matrix
+      K,                    // K (2nd dim of A)
+      kernel->buffer,       // K x N matrix
+      N,                    // N
+      0.F,                  // beta
+      activations->buffer,  // M x N matrix
+      N);                   // N (2nd dim of C)
+
+
+  // apply bias
+  for (i = 0; i < activations->shape[2]; i++) {
+    bias = o->bias->data[i];
+    for (j = 0; j < activations->shape[0] * activations->shape[1]; j++)
+    {
+      activations->buffer[j * activations->shape[2] + i] += bias;
+    }
+  }
+}
+#endif
+
+void aubio_conv2d_do_backwards(aubio_conv2d_t *c,
+    /*aubio_tensor_t *old_gradients,*/
+    aubio_tensor_t *gradients)
+{
+  uint_t i, j, k, a, b;
+  AUBIO_ASSERT(c && gradients);
+  // TODO
+  // for each kernel filter k
+  for (i = 0; i < c->n_filters; i++) {
+    // for each input column
+    for (j = 0; j < gradients->shape[1]; j++) {
+      // for each input row
+      for (k = 0; k < gradients->shape[2]; k++) {
+        for (a = 0; a < c->kernel_shape[0]; a++) {
+          for (b = 0; b < c->kernel_shape[1]; b++) {
+#if 0
+            smpl_t grad = gradients->data[i]->data[a][b];
+            smpl_t oldgrad = old_gradients->data[i]->data[a][b];
+            smpl_t m = (grad - oldgrad * momentum);
+            w -= lr * m - lr * decay * w;
+#endif
+          }
+        }
+      }
+    }
+  }
+}
+
+uint_t aubio_conv2d_set_padding_mode(aubio_conv2d_t *c,
+    const char_t *padding_mode)
+{
+  AUBIO_ASSERT(c && padding_mode);
+  if (strncasecmp(padding_mode, "same", PATH_MAX) == 0) {
+    c->padding_mode = PAD_SAME;
+  } else if (strncasecmp(padding_mode, "valid", PATH_MAX) == 0) {
+    c->padding_mode = PAD_VALID;
+  } else {
+    return AUBIO_FAIL;
+  }
+  return AUBIO_OK;
+}
+
+uint_t aubio_conv2d_set_kernel(aubio_conv2d_t *c, aubio_tensor_t *kernel)
+{
+  AUBIO_ASSERT(c && kernel);
+  if (aubio_tensor_have_same_shape(kernel, c->kernel)) {
+    aubio_tensor_copy(kernel, c->kernel);
+    return AUBIO_OK;
+  }
+  return AUBIO_FAIL;
+}
+
+aubio_tensor_t *aubio_conv2d_get_kernel(aubio_conv2d_t* c)
+{
+  AUBIO_ASSERT(c && c->kernel);
+  return c->kernel;
+}
+
+uint_t aubio_conv2d_set_bias(aubio_conv2d_t *c, fvec_t *bias)
+{
+  AUBIO_ASSERT(c && bias);
+  if (bias->length == c->bias->length) {
+    fvec_copy(bias, c->bias);
+    return AUBIO_OK;
+  }
+  return AUBIO_OK;
+}
+
+fvec_t *aubio_conv2d_get_bias(aubio_conv2d_t* c)
+{
+  AUBIO_ASSERT(c && c->bias);
+  return c->bias;
+}
Index: src/ai/conv2d.h
===================================================================
--- src/ai/conv2d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/conv2d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,181 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_CONV2D_H
+#define AUBIO_CONV2D_H
+
+/** \file
+
+  Convolutional layer (2D)
+
+  Standard implementation of a 2D convolutional layer. partly optimized for
+  CPU.
+
+  Note
+  ----
+  Only the forward pass is implemented for now.
+
+  References
+  ----------
+  Vincent Dumoulin, Francesco Visin - [A guide to convolution arithmetic for
+  deep learning](https://github.com/vdumoulin/conv_arithmetic)
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** conv2d layer */
+typedef struct _aubio_conv2d_t aubio_conv2d_t;
+
+/** create a new conv2d layer
+
+  \param n_filters number of filters
+  \param kernel_shape shape of each filter
+
+  \return new conv2d layer
+
+*/
+aubio_conv2d_t *new_aubio_conv2d(uint_t n_filters, uint_t kernel_shape[2]);
+
+/** set padding mode
+
+  \param c                  layer
+  \param padding_mode       padding mode
+
+  \return 0 on success, non-zero otherwise
+
+  Available padding: "same", and "valid".
+
+*/
+uint_t aubio_conv2d_set_padding_mode(aubio_conv2d_t *c,
+    const char_t *padding_mode);
+
+/** set stride
+
+  \param c      layer
+  \param stride array of length 2 containing the strides
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_conv2d_set_stride(aubio_conv2d_t *c, uint_t stride[2]);
+
+/** get current stride settings
+
+  \param t  layer
+
+  \return   array of length 2 containing the stride in each dimension
+
+*/
+uint_t *aubio_conv2d_get_stride(aubio_conv2d_t* t);
+
+/** get output shape
+
+  \param t                  layer
+  \param input_tensor       input tensor
+  \param shape              output shape
+
+  \return 0 on success, non-zero otherwise
+
+  Upon return, `shape` will be filled with the output shape of the layer. This
+  function should be called after ::aubio_conv2d_set_stride or
+  ::aubio_conv2d_set_padding_mode, and before ::aubio_conv2d_get_kernel or
+  ::aubio_conv2d_get_bias.
+
+*/
+uint_t aubio_conv2d_get_output_shape(aubio_conv2d_t *t,
+        aubio_tensor_t *input_tensor, uint_t *shape);
+
+/** get kernel weights
+
+  \param t  ::aubio_conv2d_t layer
+
+  \return tensor of weights
+
+  When called after ::aubio_conv2d_get_output_shape, this function will return
+  a pointer to the tensor holding the weights of this layer.
+
+*/
+aubio_tensor_t *aubio_conv2d_get_kernel(aubio_conv2d_t *t);
+
+/** get biases
+
+  \param t layer
+
+  \return vector of biases
+
+  When called after ::aubio_conv2d_get_output_shape, this function will return
+  a pointer to the vector holding the biases.
+
+*/
+fvec_t *aubio_conv2d_get_bias(aubio_conv2d_t *t);
+
+/** set kernel weights
+
+  \param t          layer
+  \param kernel     kernel weights
+
+  \return 0 on success, non-zero otherwise
+
+  Copy kernel weights into internal layer memory.  This function should be
+  called after ::aubio_conv2d_get_output_shape.
+
+*/
+uint_t aubio_conv2d_set_kernel(aubio_conv2d_t *t, aubio_tensor_t *kernel);
+
+/** set biases
+
+  \param t          layer
+  \param bias       biases
+
+  \return 0 on success, non-zero otherwise
+
+  Copy vector of biases into internal layer memory. This function should be
+  called after ::aubio_conv2d_get_output_shape.
+
+*/
+uint_t aubio_conv2d_set_bias(aubio_conv2d_t *t, fvec_t *bias);
+
+/** compute layer output
+
+  \param t              layer
+  \param input_tensor   input tensor
+  \param output_tensor  output tensor
+
+  Perform 2D convolution.
+
+*/
+void aubio_conv2d_do(aubio_conv2d_t *t, aubio_tensor_t *input_tensor,
+        aubio_tensor_t *output_tensor);
+
+/** destroy conv2d layer
+
+  \param t  layer
+
+*/
+void del_aubio_conv2d(aubio_conv2d_t *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_CONV2D_H */
Index: src/ai/dense.c
===================================================================
--- src/ai/dense.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/dense.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,109 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "dense.h"
+
+struct _aubio_dense_t {
+  uint_t n_units;
+  fmat_t *weights;
+  fvec_t *bias;
+};
+
+aubio_dense_t *new_aubio_dense(uint_t n_units) {
+  aubio_dense_t *c = AUBIO_NEW(aubio_dense_t);
+
+  AUBIO_GOTO_FAILURE((sint_t)n_units >= 1);
+
+  c->n_units = n_units;
+
+  return c;
+failure:
+  del_aubio_dense(c);
+  return NULL;
+}
+
+void del_aubio_dense(aubio_dense_t *c) {
+  AUBIO_ASSERT(c);
+  if (c->weights)
+    del_fmat(c->weights);
+  if (c->bias)
+    del_fvec(c->bias);
+  AUBIO_FREE(c);
+}
+
+void aubio_dense_debug(aubio_dense_t *c, aubio_tensor_t *input_tensor)
+{
+  uint_t n_params = input_tensor->shape[0] * c->n_units + c->n_units;
+  AUBIO_DBG("dense:     %15s -> (%d,) (%d params)"
+      " (n_units=%d, weights=(%d, %d), bias=(%d,))\n",
+      aubio_tensor_get_shape_string(input_tensor), c->n_units, n_params,
+      c->n_units, c->weights->height, c->weights->length, c->bias->length);
+}
+
+uint_t aubio_dense_get_output_shape(aubio_dense_t *c,
+    aubio_tensor_t *input, uint_t *shape)
+{
+  AUBIO_ASSERT (c && input && shape);
+  AUBIO_ASSERT (input->ndim == 1);
+  shape[0] = c->n_units;
+
+  if (c->weights) del_fmat(c->weights);
+  c->weights = new_fmat(input->shape[0], c->n_units);
+  if (!c->weights) return AUBIO_FAIL;
+
+  if (c->bias) del_fvec(c->bias);
+  c->bias = new_fvec(c->n_units);
+  if (!c->bias) return AUBIO_FAIL;
+
+  aubio_dense_debug(c, input);
+
+  return AUBIO_OK;
+}
+
+fmat_t *aubio_dense_get_weights(aubio_dense_t *c) {
+  return c->weights;
+}
+
+fvec_t *aubio_dense_get_bias(aubio_dense_t *c) {
+  return c->bias;
+}
+
+void aubio_dense_do(aubio_dense_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *activations) {
+  AUBIO_ASSERT(c && input_tensor && activations);
+  AUBIO_ASSERT(input_tensor->ndim == 1);
+  AUBIO_ASSERT(activations->ndim == 1);
+  AUBIO_ASSERT(input_tensor->shape[0] == c->weights->height);
+  AUBIO_ASSERT(activations->shape[0] == c->weights->length);
+
+  fvec_t input_vec;
+  aubio_tensor_as_fvec(input_tensor, &input_vec);
+  fvec_t output_vec;
+  aubio_tensor_as_fvec(activations, &output_vec);
+
+  // compute x.W
+  fvec_matmul(&input_vec, c->weights, &output_vec);
+  // add bias
+  fvec_vecadd(&output_vec, c->bias);
+}
Index: src/ai/dense.h
===================================================================
--- src/ai/dense.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/dense.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,105 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_DENSE_H
+#define AUBIO_DENSE_H
+
+/** \file
+
+ Fully connected layer
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** dense layer */
+typedef struct _aubio_dense_t aubio_dense_t;
+
+/** create a new dense layer
+
+  \param n_units    number of units
+
+  \return new dense layer
+
+*/
+aubio_dense_t *new_aubio_dense(uint_t n_units);
+
+/** get output shape
+
+  \param c      ::aubio_dense_t layer
+  \param input  input tensor
+  \param shape  output shape
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_dense_get_output_shape(aubio_dense_t *c,
+    aubio_tensor_t *input, uint_t *shape);
+
+/** get internal weights
+
+   \param c     dense layer
+
+   \return matrix of weights
+
+   This function should be called after ::aubio_dense_get_output_shape
+   to get a pointer to the internal weight matrix.
+
+*/
+fmat_t *aubio_dense_get_weights(aubio_dense_t *c);
+
+/** get internal biases
+
+   \param c     dense layer
+
+   \return vector of biases
+
+   This function should be called after ::aubio_dense_get_output_shape
+   to get a pointer to the internal biases.
+
+*/
+fvec_t *aubio_dense_get_bias(aubio_dense_t *c);
+
+/** compute forward pass
+
+  \param c          ::aubio_dense_t layer
+  \param input      input tensor
+  \param output     output tensor
+
+  This function computes the output of the dense layer given an input tensor.
+
+*/
+void aubio_dense_do(aubio_dense_t *c, aubio_tensor_t *input,
+    aubio_tensor_t *output);
+
+/** destroy layer
+
+  \param c  layer to destroy
+
+*/
+void del_aubio_dense(aubio_dense_t *c);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_DENSE_H */
Index: src/ai/maxpool1d.c
===================================================================
--- src/ai/maxpool1d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/maxpool1d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,96 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "maxpool1d.h"
+
+#include <float.h> // FLT_MAX
+
+struct _aubio_maxpool1d_t {
+  uint_t pool_size;
+  uint_t stride;
+};
+
+static void aubio_maxpool1d_debug(aubio_maxpool1d_t *c,
+    aubio_tensor_t *input_tensor);
+
+aubio_maxpool1d_t *new_aubio_maxpool1d(uint_t pool_size[1])
+{
+  aubio_maxpool1d_t *c = AUBIO_NEW(aubio_maxpool1d_t);
+
+  AUBIO_GOTO_FAILURE((sint_t)pool_size[0] > 0);
+
+  c->pool_size = pool_size[0];
+
+  c->stride = 1;
+
+  return c;
+
+failure:
+  del_aubio_maxpool1d(c);
+  return NULL;
+}
+
+void del_aubio_maxpool1d(aubio_maxpool1d_t* c) {
+  AUBIO_ASSERT(c);
+  AUBIO_FREE(c);
+}
+
+void aubio_maxpool1d_debug(aubio_maxpool1d_t *c, aubio_tensor_t *input_tensor)
+{
+  AUBIO_DBG("maxpool1d: %15s -> (%d, %d) (no params)"
+      " (pool_size=(%d,), )\n", aubio_tensor_get_shape_string(input_tensor),
+      input_tensor->shape[0] / c->pool_size,
+      input_tensor->shape[1], c->pool_size);
+}
+
+uint_t aubio_maxpool1d_get_output_shape(aubio_maxpool1d_t *c,
+    aubio_tensor_t *input, uint_t *shape)
+{
+  AUBIO_ASSERT(c);
+  AUBIO_ASSERT(shape && sizeof(shape) == 2*sizeof(uint_t));
+  AUBIO_ASSERT(input);
+  shape[0] = input->shape[0] / c->pool_size;
+  shape[1] = input->shape[1];
+
+  aubio_maxpool1d_debug(c, input);
+
+  return AUBIO_OK;
+}
+
+void aubio_maxpool1d_do(aubio_maxpool1d_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *output_tensor)
+{
+  uint_t i, j, a;
+  AUBIO_ASSERT(c && input_tensor && output_tensor);
+
+  for (j = 0; j < output_tensor->shape[1]; j++) {
+    for (i = 0; i < output_tensor->shape[0]; i++) {
+      uint_t stride_i = i * c->pool_size;
+      smpl_t m = input_tensor->data[stride_i][j];
+      for (a = 0; a < c->pool_size; a++) {
+        m = MAX(m, input_tensor->data[stride_i + a][j]);
+      }
+      output_tensor->data[i][j] = m;
+    }
+  }
+}
Index: src/ai/maxpool1d.h
===================================================================
--- src/ai/maxpool1d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/maxpool1d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,80 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_MAXPOOL1D_H
+#define AUBIO_MAXPOOL1D_H
+
+/** \file
+
+  Max pooling layer (1D)
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** maxpool1d layer */
+typedef struct _aubio_maxpool1d_t aubio_maxpool1d_t;
+
+/** create a new maxpool1d layer
+
+  \param pool_size  size of the pooling windows
+
+  \return new ::aubio_maxpool2d_t layer
+
+*/
+aubio_maxpool1d_t *new_aubio_maxpool1d(uint_t pool_size[1]);
+
+/** get output shape
+
+  \param t      layer
+  \param input  input tensor
+  \param shape  output shape
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_maxpool1d_get_output_shape(aubio_maxpool1d_t *t,
+        aubio_tensor_t *input, uint_t *shape);
+
+/** compute layer output
+
+  \param    t               layer
+  \param    input_tensor    input tensor
+  \param    output_tensor   output tensor
+
+*/
+void aubio_maxpool1d_do(aubio_maxpool1d_t *t,
+        aubio_tensor_t *input_tensor,
+        aubio_tensor_t *output_tensor);
+
+/** destroy layer
+
+  \param t      layer to destroy
+
+*/
+void del_aubio_maxpool1d(aubio_maxpool1d_t *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_MAXPOOL1D_H */
Index: src/ai/maxpool2d.c
===================================================================
--- src/ai/maxpool2d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/maxpool2d.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,116 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+#include "maxpool2d.h"
+
+struct _aubio_maxpool2d_t {
+  uint_t pool_size[2];
+  uint_t stride[2];
+};
+
+static void aubio_maxpool2d_debug(aubio_maxpool2d_t *c,
+    aubio_tensor_t *input_tensor);
+
+aubio_maxpool2d_t *new_aubio_maxpool2d(uint_t pool_size[2])
+{
+  aubio_maxpool2d_t *c = AUBIO_NEW(aubio_maxpool2d_t);
+
+  AUBIO_GOTO_FAILURE((sint_t)pool_size[0] > 0);
+  AUBIO_GOTO_FAILURE((sint_t)pool_size[1] > 0);
+
+  c->pool_size[0] = pool_size[0];
+  c->pool_size[1] = pool_size[1];
+
+  c->stride[0] = 1;
+  c->stride[1] = 1;
+
+  return c;
+
+failure:
+  del_aubio_maxpool2d(c);
+  return NULL;
+}
+
+void del_aubio_maxpool2d(aubio_maxpool2d_t* c) {
+  AUBIO_ASSERT(c);
+  AUBIO_FREE(c);
+}
+
+void aubio_maxpool2d_debug(aubio_maxpool2d_t *c, aubio_tensor_t *input_tensor)
+{
+  AUBIO_DBG("maxpool2d: %15s -> (%d, %d, %d)"
+      " (pool_size=(%d, %d))\n",
+      aubio_tensor_get_shape_string(input_tensor),
+      input_tensor->shape[0] / c->pool_size[0],
+      input_tensor->shape[1] / c->pool_size[1],
+      input_tensor->shape[2],
+      c->pool_size[0],
+      c->pool_size[1]);
+}
+
+uint_t aubio_maxpool2d_get_output_shape(aubio_maxpool2d_t *c,
+    aubio_tensor_t *input, uint_t *shape)
+{
+  AUBIO_ASSERT(c);
+  AUBIO_ASSERT(shape && sizeof(shape) == 2*sizeof(uint_t));
+  AUBIO_ASSERT(input);
+  shape[0] = input->shape[0] / c->pool_size[0];
+  shape[1] = input->shape[1] / c->pool_size[1];
+  shape[2] = input->shape[2];
+
+  aubio_maxpool2d_debug(c, input);
+
+  return AUBIO_OK;
+}
+
+void aubio_maxpool2d_do(aubio_maxpool2d_t *c, aubio_tensor_t *input_tensor,
+    aubio_tensor_t *output_tensor)
+{
+  uint_t i, j, k, a, b;
+  AUBIO_ASSERT(c && input_tensor && output_tensor);
+
+  //aubio_maxpool2d_debug(c, input_tensor);
+
+  for (i = 0; i < output_tensor->shape[0]; i++)
+  {
+    for (j = 0; j < output_tensor->shape[1]; j++)
+    {
+      for (k = 0; k < output_tensor->shape[2]; k++)
+      {
+        uint_t stride_i = i * c->pool_size[0];
+        uint_t stride_j = j * c->pool_size[1];
+        smpl_t m = input_tensor->data[stride_i][stride_j
+          * input_tensor->shape[2] + k];
+        for (a = 0; a < c->pool_size[0]; a++) {
+          for (b = 0; b < c->pool_size[1]; b++) {
+            uint_t idx = (stride_j + b) * input_tensor->shape[2] + k;
+            m = MAX(m, input_tensor->data[stride_i + a][idx]);
+          }
+        }
+        output_tensor->data[i][j * output_tensor->shape[2] + k] = m;
+      }
+    }
+  }
+}
Index: src/ai/maxpool2d.h
===================================================================
--- src/ai/maxpool2d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/maxpool2d.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,80 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_MAXPOOL2D_H
+#define AUBIO_MAXPOOL2D_H
+
+/** \file
+
+  Max pooling layer (2D)
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** maxpool2d layer */
+typedef struct _aubio_maxpool2d_t aubio_maxpool2d_t;
+
+/** create a new maxpool2d layer
+
+  \param pool_size  size of the pooling windows
+
+  \return new ::aubio_maxpool2d_t layer
+
+*/
+aubio_maxpool2d_t *new_aubio_maxpool2d(uint_t pool_size[2]);
+
+/** get output shape
+
+  \param t      layer
+  \param input  input tensor
+  \param shape  output shape
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_maxpool2d_get_output_shape(aubio_maxpool2d_t *t,
+        aubio_tensor_t *input, uint_t *shape);
+
+/** compute layer output
+
+  \param    t               layer
+  \param    input_tensor    input tensor
+  \param    output_tensor   output tensor
+
+*/
+void aubio_maxpool2d_do(aubio_maxpool2d_t *t,
+        aubio_tensor_t *input_tensor,
+        aubio_tensor_t *output_tensor);
+
+/** destroy layer
+
+  \param t      layer to destroy
+
+*/
+void del_aubio_maxpool2d(aubio_maxpool2d_t *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_MAXPOOL2D_H */
Index: src/ai/tensor.c
===================================================================
--- src/ai/tensor.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/tensor.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,251 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "aubio_priv.h"
+#include "fmat.h"
+#include "tensor.h"
+
+#define STRN_LENGTH 40
+#if !HAVE_AUBIO_DOUBLE
+#define AUBIO_SMPL_TFMT "% 9.4f"
+#else
+#define AUBIO_SMPL_TFMT "% 9.4lf"
+#endif /* HAVE_AUBIO_DOUBLE */
+
+aubio_tensor_t *new_aubio_tensor(uint_t ndim, uint_t *shape)
+{
+  aubio_tensor_t *c = AUBIO_NEW(aubio_tensor_t);
+  uint_t items_per_row = 1;
+  uint_t i;
+
+  if ((sint_t)ndim <= 0) goto failure;
+  for (i = 0; i < ndim; i++) {
+    if ((sint_t)shape[i] <= 0) goto failure;
+  }
+
+  c->ndim = ndim;
+  c->shape[0] = shape[0];
+  for (i = 1; i < ndim; i++) {
+    c->shape[i] = shape[i];
+    items_per_row *= shape[i];
+  }
+  c->size = items_per_row * shape[0];
+  c->buffer = AUBIO_ARRAY(smpl_t, c->size);
+  c->data = AUBIO_ARRAY(smpl_t*, shape[0]);
+  for (i = 0; i < c->shape[0]; i++) {
+    c->data[i] = c->buffer + i * items_per_row;
+  }
+
+  return c;
+
+failure:
+  del_aubio_tensor(c);
+  return NULL;
+}
+
+void del_aubio_tensor(aubio_tensor_t *c)
+{
+  if (c->data) {
+    if (c->data[0]) {
+      AUBIO_FREE(c->data[0]);
+    }
+    AUBIO_FREE(c->data);
+  }
+  AUBIO_FREE(c);
+}
+
+uint_t aubio_tensor_as_fvec(aubio_tensor_t *c, fvec_t *o) {
+  if (!c || !o) return AUBIO_FAIL;
+  o->length = c->size;
+  o->data = c->buffer;
+  return AUBIO_OK;
+}
+
+uint_t aubio_fvec_as_tensor(fvec_t *o, aubio_tensor_t *c) {
+  if (!o || !c) return AUBIO_FAIL;
+  c->ndim = 1;
+  c->shape[0] = o->length;
+  c->data = &o->data;
+  c->buffer = o->data;
+  c->size = o->length;
+  return AUBIO_OK;
+}
+
+uint_t aubio_tensor_as_fmat(aubio_tensor_t *c, fmat_t *o) {
+  if (!c || !o) return AUBIO_FAIL;
+  o->height = c->shape[0];
+  o->length = c->size / c->shape[0];
+  o->data = c->data;
+  // o was allocated on the stack, data[1] may be NULL
+  AUBIO_WRN("aubio_tensor_as_fmat will not create a usable table of rows\n");
+  return AUBIO_OK;
+}
+
+uint_t aubio_fmat_as_tensor(fmat_t *o, aubio_tensor_t *c) {
+  if (!o || !c) return AUBIO_FAIL;
+  c->ndim = 2;
+  c->shape[0] = o->height;
+  c->shape[1] = o->length;
+  c->size = o->height * o->length;
+  c->data = o->data;
+  c->buffer = o->data[0];
+  return AUBIO_OK;
+}
+
+uint_t aubio_tensor_get_subtensor(aubio_tensor_t *t, uint_t i,
+    aubio_tensor_t *st)
+{
+  uint_t j;
+  if (!t || !st) return AUBIO_FAIL;
+  if (i >= t->shape[0]) {
+    AUBIO_ERR("tensor: index %d out of range, only %d subtensors\n",
+        i, t->shape[0]);
+    return AUBIO_FAIL;
+  }
+  if(t->ndim > 1) {
+    st->ndim = t->ndim - 1;
+    for (j = 0; j < st->ndim; j++) {
+      st->shape[j] = t->shape[j + 1];
+    }
+    for (j = st->ndim; j < AUBIO_TENSOR_MAXDIM; j++) {
+      st->shape[j] = 0;
+    }
+    st->size = t->size / t->shape[0];
+  } else {
+    st->ndim = 1;
+    st->shape[0] = 1;
+    st->size = 1;
+  }
+  // st was allocated on the stack, row indices are lost
+  st->data = NULL;
+  st->buffer = &t->buffer[0] + st->size * i;
+  return AUBIO_OK;
+}
+
+uint_t aubio_tensor_have_same_shape(aubio_tensor_t *a, aubio_tensor_t *b)
+{
+  uint_t n;
+  if (!a || !b)
+    return 0;
+  if (a->ndim != b->ndim)
+    return 0;
+
+  for (n = 0; n < a->ndim; n++) {
+    if (a->shape[n] != b->shape[n]) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+smpl_t aubio_tensor_max(aubio_tensor_t *t)
+{
+  uint_t i;
+  smpl_t max = t->buffer[0];
+  for (i = 0; i < t->size; i++) {
+    max = MAX(t->buffer[i], max);
+  }
+  return max;
+}
+
+const char_t *aubio_tensor_get_shape_string(aubio_tensor_t *t) {
+  uint_t i;
+  if (!t) return NULL;
+  size_t offset = 2;
+  static char_t shape_str[STRN_LENGTH];
+  char_t shape_str_previous[STRN_LENGTH] = "(";
+  for (i = 0; i < t->ndim; i++) {
+    // and space last if not the last one
+    int add_space = (i < t->ndim - 1);
+    // add coma first if this not last, or always if 1d
+    int add_coma = add_space || (t->ndim == 1);
+    int len = snprintf(shape_str, STRN_LENGTH, "%s%d%s%s",
+        shape_str_previous, t->shape[i],
+        add_coma ? "," : "", add_space ? " " : "");
+    strncpy(shape_str_previous, shape_str, len);
+  }
+  snprintf(shape_str, strnlen(shape_str, STRN_LENGTH - offset - 1) + offset,
+      "%s)", shape_str_previous);
+  return shape_str;
+}
+
+static void aubio_tensor_print_subtensor(aubio_tensor_t *t, uint_t depth)
+{
+  uint_t i;
+  AUBIO_MSG("[");
+  for (i = 0; i < t->shape[0]; i ++) {
+    AUBIO_MSG("%*s", i == 0 ? 0 : depth + 1, i == 0 ? "" : " ");
+    if (t->ndim == 1) {
+      AUBIO_MSG(AUBIO_SMPL_TFMT, t->buffer[i]);
+    } else {
+      aubio_tensor_t st;
+      aubio_tensor_get_subtensor(t, i, &st);
+      aubio_tensor_print_subtensor(&st, depth + 1); // recursive call
+    }
+    AUBIO_MSG("%s%s", (i < t->shape[0] - 1) ? "," : "",
+        t->ndim == 1 ? " " : ((i < t->shape[0] - 1) ? "\n" : ""));
+  }
+  AUBIO_MSG("]");
+}
+
+void aubio_tensor_print(aubio_tensor_t *t)
+{
+  AUBIO_MSG("tensor of shape %s\n", aubio_tensor_get_shape_string(t));
+  aubio_tensor_print_subtensor(t, 0);
+  AUBIO_MSG("\n");
+}
+
+void aubio_tensor_matmul(aubio_tensor_t *a, aubio_tensor_t *b,
+    aubio_tensor_t *c)
+{
+  AUBIO_ASSERT (a->shape[0] == c->shape[0]);
+  AUBIO_ASSERT (a->shape[1] == b->shape[0]);
+  AUBIO_ASSERT (b->shape[1] == c->shape[1]);
+#if !defined(HAVE_BLAS)
+  uint_t i, j, k;
+  for (i = 0; i < c->shape[0]; i++) {
+    for (j = 0; j < c->shape[1]; j++) {
+      smpl_t sum = 0.;
+      for (k = 0; k < a->shape[1]; k++) {
+          sum += a->buffer[i * a->shape[1] + k]
+            * b->buffer[k * b->shape[1] + j];
+      }
+      c->buffer[i * c->shape[1] + j] = sum;
+    }
+  }
+#else
+  aubio_cblas__gemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, a->shape[0],
+      b->size/b->shape[0], b->shape[0], 1.F, a->buffer, a->size/a->shape[0],
+      b->buffer,
+      b->size/b->shape[0], 0.F, c->buffer, b->size/b->shape[0]);
+#endif
+}
+
+void aubio_tensor_copy(aubio_tensor_t *s, aubio_tensor_t *t)
+{
+  if (!aubio_tensor_have_same_shape(s, t)) {
+    AUBIO_ERR("tensor: not copying source tensor %s",
+        aubio_tensor_get_shape_string(s));
+    AUBIO_ERR(" to destination tensor %s",
+        aubio_tensor_get_shape_string(t));
+    return;
+  }
+  AUBIO_MEMCPY(t->buffer, s->buffer, s->size);
+}
Index: src/ai/tensor.h
===================================================================
--- src/ai/tensor.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/ai/tensor.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,184 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_TENSOR_H
+#define AUBIO_TENSOR_H
+
+/** \file
+
+  Tensor for real-valued data.
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Maximum number of dimensions
+
+  This is defined as a constant so that ::aubio_tensor_t allocated
+  on the stack can be manipulated.
+
+ */
+#define AUBIO_TENSOR_MAXDIM 10
+
+/** Tensor for real-valued data
+
+  This object holds a tensor of real-valued data, ::smpl_t, with up to
+  AUBIO_TENSOR_MAXDIM dimentsions.
+
+  Todo:
+  - add array of strides
+  - add accessors macros
+  - consider removing data
+
+*/
+typedef struct
+{
+  uint_t ndim;     /**< number of dimensions */
+  uint_t shape[AUBIO_TENSOR_MAXDIM]; /**< dimensions array */
+  uint_t size;     /**< total number of elements */
+  smpl_t *buffer;  /**< buffer of values */
+  smpl_t **data;   /**< pointer to rows, or NULL when subtensor */
+} aubio_tensor_t;
+
+/** create a new tensor
+
+  \param ndim   number of dimensions
+  \param shape  array of dimensions
+
+  \return new ::aubio_tensor_t
+
+*/
+aubio_tensor_t *new_aubio_tensor(uint_t ndim, uint_t *shape);
+
+/** destroy a tensor
+
+  \param c  tensor to destroy
+
+*/
+void del_aubio_tensor(aubio_tensor_t *c);
+
+/** view tensor as a vector
+
+  \param c  tensor to view as ::fvec_t
+  \param o  pointer to use to store view
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_tensor_as_fvec(aubio_tensor_t *c, fvec_t *o);
+
+/** view vector as a tensor
+
+  \param o  ::fvec_t to view as a tensor
+  \param c  pointer to use to store view
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_fvec_as_tensor(fvec_t *o, aubio_tensor_t *c);
+
+/** view tensor as a matrix
+
+  \param c  tensor to view as ::fmat_t
+  \param o  pointer to use to store view
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_tensor_as_fmat(aubio_tensor_t *c, fmat_t *o);
+
+/** view matrix as a tensor
+
+  \param o  ::fmat_t to view as a tensor
+  \param c  pointer to use to store view
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_fmat_as_tensor(fmat_t *o, aubio_tensor_t *c);
+
+/** view i-th row of tensor t as a tensor
+
+  \param t tensor to get maximum from
+  \param i index of row to retrieve
+  \param st subtensor to fill in
+
+  \return 0 on success, non-zero otherwise
+*/
+uint_t aubio_tensor_get_subtensor(aubio_tensor_t *t, uint_t i,
+        aubio_tensor_t *st);
+
+/** find the maximum value of a tensor
+
+  \param t tensor to get maximum from
+
+  \return maximum value of all elements in tensor
+*/
+smpl_t aubio_tensor_max(aubio_tensor_t *t);
+
+/** check if sizes of 2 tensor match
+
+  \param t  first tensor to check size with
+  \param s  second tensor to check size with
+
+  \return 1 if tensors have the same size, 0 otherwise
+*/
+uint_t aubio_tensor_have_same_shape(aubio_tensor_t *t, aubio_tensor_t *s);
+
+/** print the content of a tensor
+
+  \param t  tensor to print
+
+ */
+void aubio_tensor_print(aubio_tensor_t *t);
+
+/** get a string representing the dimensions of this tensor
+
+  \param t  tensor to get shape string from
+
+  \return string of characters containing the dimensions of t
+*/
+const char_t *aubio_tensor_get_shape_string(aubio_tensor_t *t);
+
+/** compute matrix multiplication A x B = C
+
+  \param a  left tensor
+  \param b  right tensor
+  \param c  output tensor
+
+*/
+void aubio_tensor_matmul(aubio_tensor_t *a, aubio_tensor_t *b,
+    aubio_tensor_t *c);
+
+/** copy tensor
+
+  \param s  source tensor
+  \param d  destination tensor
+
+*/
+void aubio_tensor_copy(aubio_tensor_t *s, aubio_tensor_t *d);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_TENSOR_H */
Index: src/aubio_priv.h
===================================================================
--- src/aubio_priv.h	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/aubio_priv.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -134,4 +134,6 @@
 #define aubio_cblas_swap      cblas_sswap
 #define aubio_cblas_dot       cblas_sdot
+#define aubio_cblas__gemv     cblas_sgemv
+#define aubio_cblas__gemm     cblas_sgemm
 #else /* HAVE_AUBIO_DOUBLE */
 #ifdef HAVE_ATLAS
@@ -141,4 +143,6 @@
 #define aubio_cblas_swap      cblas_dswap
 #define aubio_cblas_dot       cblas_ddot
+#define aubio_cblas__gemv     cblas_dgemv
+#define aubio_cblas__gemm     cblas_dgemm
 #endif /* HAVE_AUBIO_DOUBLE */
 #endif /* HAVE_BLAS */
@@ -410,3 +414,14 @@
 #endif /* DEBUG */
 
+// goto to failure if condition x is not true
+#define AUBIO_GOTO_FAILURE(x) if (!(x)) goto failure
+
+#define AUBIO_ASSERT_EQUAL_SHAPE(t1, t2) { \
+    AUBIO_ASSERT(t1 && t2); \
+    AUBIO_ASSERT(t1->ndim == t2->ndim); \
+    uint_t nn; \
+    for (nn = 0; nn < t1->ndim; nn++) \
+      AUBIO_ASSERT(t1->shape[nn] == t2->shape[nn]); \
+    }
+
 #endif /* AUBIO_PRIV_H */
Index: src/fmat.c
===================================================================
--- src/fmat.c	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/fmat.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -24,6 +24,6 @@
 fmat_t * new_fmat (uint_t height, uint_t length) {
   fmat_t * s;
-  uint_t i,j;
-  if ((sint_t)length <= 0 || (sint_t)height <= 0 ) {
+  uint_t i;
+  if ((sint_t)height <= 0 || (sint_t)length <= 0 ) {
     return NULL;
   }
@@ -31,10 +31,10 @@
   s->height = height;
   s->length = length;
+  // array of row pointers
   s->data = AUBIO_ARRAY(smpl_t*,s->height);
-  for (i=0; i< s->height; i++) {
-    s->data[i] = AUBIO_ARRAY(smpl_t, s->length);
-    for (j=0; j< s->length; j++) {
-      s->data[i][j]=0.;
-    }
+  // first row store the full height * length buffer
+  s->data[0] = AUBIO_ARRAY(smpl_t, s->height * s->length);
+  for (i=1; i< s->height; i++) {
+    s->data[i] = s->data[0] + i * s->length;
   }
   return s;
@@ -42,9 +42,9 @@
 
 void del_fmat (fmat_t *s) {
-  uint_t i;
-  for (i=0; i<s->height; i++) {
-    AUBIO_FREE(s->data[i]);
-  }
-  AUBIO_FREE(s->data);
+  AUBIO_ASSERT(s);
+  if (s->data[0])
+    AUBIO_FREE(s->data[0]);
+  if (s->data)
+    AUBIO_FREE(s->data);
   AUBIO_FREE(s);
 }
@@ -78,5 +78,5 @@
   for (i=0; i< s->height; i++) {
     for (j=0; j< s->length; j++) {
-      AUBIO_MSG(AUBIO_SMPL_FMT " ", s->data[i][j]);
+      AUBIO_MSG(AUBIO_SMPL_FMT " ", s->data[0][i * s->length + j]);
     }
     AUBIO_MSG("\n");
@@ -156,22 +156,26 @@
 
 void fmat_vecmul(const fmat_t *s, const fvec_t *scale, fvec_t *output) {
-  uint_t k;
-#if 0
-  assert(s->height == output->length);
-  assert(s->length == scale->length);
-#endif
 #if !defined(HAVE_ACCELERATE) && !defined(HAVE_BLAS)
-  uint_t j;
+  uint_t j, k;
+  AUBIO_ASSERT(s->height == output->length);
+  AUBIO_ASSERT(s->length == scale->length);
   fvec_zeros(output);
   for (j = 0; j < s->length; j++) {
     for (k = 0; k < s->height; k++) {
-      output->data[k] += scale->data[j]
-          * s->data[k][j];
+      output->data[k] += scale->data[j] * s->data[k][j];
     }
   }
 #elif defined(HAVE_BLAS)
+#if 0
   for (k = 0; k < s->height; k++) {
     output->data[k] = aubio_cblas_dot( s->length, scale->data, 1, s->data[k], 1);
   }
+#else
+  aubio_cblas__gemv(CblasColMajor, CblasTrans,
+      s->length, s->height, 1.,
+      s->data[0], s->length,
+      scale->data, 1, 0.,
+      output->data, 1);
+#endif
 #elif defined(HAVE_ACCELERATE)
 #if 0
@@ -179,4 +183,5 @@
   vDSP_mmul (s->data[0], 1, scale->data, 1, output->data, 1, s->height, 1, s->length);
 #else
+  uint_t k;
   for (k = 0; k < s->height; k++) {
     aubio_vDSP_dotpr( scale->data, 1, s->data[k], 1, &(output->data[k]), s->length);
@@ -185,2 +190,63 @@
 #endif
 }
+
+void fvec_matmul(const fvec_t *scale, const fmat_t *s, fvec_t *output) {
+  AUBIO_ASSERT(s->height == scale->length);
+  AUBIO_ASSERT(s->length == output->length);
+#if !defined(HAVE_ACCELERATE) && !defined(HAVE_BLAS)
+  uint_t j, k;
+  fvec_zeros(output);
+  for (k = 0; k < s->height; k++) {
+    for (j = 0; j < s->length; j++) {
+      output->data[j] += s->data[k][j] * scale->data[k];
+    }
+  }
+#elif defined(HAVE_BLAS)
+#if 0
+  for (k = 0; k < s->length; k++) {
+    output->data[k] = aubio_cblas_dot( scale->length, scale->data, 1,
+        &s->data[0][0] + k, s->length);
+  }
+#else
+  aubio_cblas__gemv(CblasColMajor, CblasNoTrans,
+      s->length, s->height, 1.,
+      s->data[0], s->length,
+      scale->data, 1, 0.,
+      output->data, 1);
+#endif
+#elif defined(HAVE_ACCELERATE)
+#if 0
+  // seems slower and less precise (and dangerous?)
+  vDSP_mmul (s->data[0], 1, scale->data, 1, output->data, 1, s->height, 1, s->length);
+#else
+  uint_t k;
+  for (k = 0; k < s->height; k++) {
+    aubio_vDSP_dotpr( scale->data, 1, s->data[k], 1, &(output->data[k]), scale->length);
+  }
+#endif
+#endif
+}
+
+void fmat_matmul(const fmat_t *a, const fmat_t *b, fmat_t *c)
+{
+  AUBIO_ASSERT (a->height == c->height);
+  AUBIO_ASSERT (a->length == b->height);
+  AUBIO_ASSERT (b->length == c->length);
+#if !defined(HAVE_BLAS)
+  uint_t i, j, k;
+  for (i = 0; i < c->height; i++) {
+    for (j = 0; j < c->length; j++) {
+      smpl_t sum = 0.;
+      for (k = 0; k < a->length; k++) {
+          sum += a->data[0][i * a->length + k]
+            * b->data[0][k * b->length + j];
+      }
+      c->data[0][i * c->length + j] = sum;
+    }
+  }
+#else
+  aubio_cblas__gemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, a->height,
+      b->length, b->height, 1.F, a->data[0], a->length, b->data[0],
+      b->length, 0.F, c->data[0], b->length);
+#endif
+}
Index: src/fmat.h
===================================================================
--- src/fmat.h	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/fmat.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -161,8 +161,18 @@
    \param s matrix to compute product with
    \param scale vector to compute product with
-   \param output vector to store restults in
+   \param output vector of results
+
 
 */
 void fmat_vecmul(const fmat_t *s, const fvec_t *scale, fvec_t *output);
+
+/** compute the product of a vector by a matrix
+
+   \param s matrix to compute product with
+   \param scale input to compute product with
+   \param output vector of results
+
+*/
+void fvec_matmul(const fvec_t *scale, const fmat_t *s, fvec_t *output);
 
 #ifdef __cplusplus
Index: src/fvec.c
===================================================================
--- src/fvec.c	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/fvec.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -99,4 +99,12 @@
 }
 
+void fvec_vecadd(fvec_t *s, const fvec_t *bias) {
+  uint_t j;
+  uint_t length = MIN(s->length, bias->length);
+  for (j = 0; j < length; j++) {
+    s->data[j] += bias->data[j];
+  }
+}
+
 void fvec_weight(fvec_t *s, const fvec_t *weight) {
   uint_t length = MIN(s->length, weight->length);
Index: src/fvec.h
===================================================================
--- src/fvec.h	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/fvec.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -144,4 +144,14 @@
 void fvec_rev(fvec_t *s);
 
+/** add a vector b to vector a, modifying a
+
+  \param a  input vector to add b to
+  \param b  input vector of the values to be added to a
+
+  Upon return, he content of a[i] will be set to a[i] + b[i].
+
+*/
+void fvec_vecadd(fvec_t *a, const fvec_t *b);
+
 /** apply weight to vector
 
Index: src/io/file_hdf5.c
===================================================================
--- src/io/file_hdf5.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/io/file_hdf5.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,180 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "aubio_priv.h"
+
+#ifdef HAVE_HDF5
+
+#include "fmat.h"
+#include "ai/tensor.h"
+#include "file_hdf5.h"
+
+#include <hdf5.h>
+#include <hdf5_hl.h>
+
+#if !HAVE_AUBIO_DOUBLE
+#define aubio_H5LTread_dataset_smpl H5LTread_dataset_float
+#else
+#define aubio_H5LTread_dataset_smpl H5LTread_dataset_double
+#endif
+
+#define MAX_DEPTH 100
+
+struct _aubio_file_hdf5_t {
+  const char_t *path;
+  hid_t fid;
+  hid_t datatype;
+};
+
+aubio_file_hdf5_t *new_aubio_file_hdf5(const char_t *path)
+{
+  aubio_file_hdf5_t *f = AUBIO_NEW(aubio_file_hdf5_t);
+
+  f->fid = H5Fopen(path, H5F_ACC_RDONLY, H5P_DEFAULT);
+  if (f->fid <= 0) goto failure;
+
+  // TODO keep a copy
+  f->path = path;
+
+  //AUBIO_DBG("file_hdf5: opened %s\n", f->path);
+  //aubio_file_hdf5_list(f);
+
+  return f;
+
+failure:
+  del_aubio_file_hdf5(f);
+  return NULL;
+}
+
+uint_t aubio_file_hdf5_load_dataset_into_tensor (aubio_file_hdf5_t *f,
+    const char_t *key, aubio_tensor_t *tensor)
+{
+  uint_t i;
+  AUBIO_ASSERT(f && key && tensor);
+  // check arguments
+  if (!f->fid || !key || !tensor)
+    return AUBIO_FAIL;
+  // find key in file
+  hid_t data_id = H5Dopen(f->fid, key, H5P_DEFAULT);
+  if (data_id <= 0) {
+    AUBIO_ERR("file_hdf5: failed getting key %s in %s\n", key, f->path);
+    return AUBIO_FAIL;
+  }
+  // get dimensions
+  hsize_t shape[10];
+  hid_t space = H5Dget_space(data_id);
+  int ndim = H5Sget_simple_extent_dims(space, shape, NULL);
+  if (ndim <= 0) {
+    AUBIO_ERR("file_hdf5: failed to get dims of %s in %s\n", key, f->path);
+    return AUBIO_FAIL;
+  }
+
+  // check output tensor dimension matches
+  AUBIO_ASSERT(ndim == (sint_t)tensor->ndim);
+  for (i = 0; i < (uint_t)ndim; i++) {
+    AUBIO_ASSERT(shape[i] == tensor->shape[i]);
+  }
+
+  if (ndim != (sint_t)tensor->ndim) return AUBIO_FAIL;
+  for (i = 0; i < (uint_t)ndim; i++) {
+    if (shape[i] != tensor->shape[i]) return AUBIO_FAIL;
+  }
+
+  // read data from hdf5 file into tensor buffer
+  smpl_t *buffer = tensor->buffer;
+  herr_t err = aubio_H5LTread_dataset_smpl(f->fid, key, buffer);
+
+  if (err < 0) {
+    return AUBIO_FAIL;
+  }
+
+  //AUBIO_DBG("file_hdf5: loaded : shape %s from key %s\n",
+  //    aubio_tensor_get_shape_string(tensor), key);
+
+  H5Dclose(data_id);
+  return AUBIO_OK;
+}
+
+uint_t aubio_file_hdf5_load_dataset_into_matrix(aubio_file_hdf5_t *f,
+    const char_t *key, fmat_t *mat) {
+  aubio_tensor_t t;
+  if (aubio_fmat_as_tensor (mat, &t)) return AUBIO_FAIL;
+  return aubio_file_hdf5_load_dataset_into_tensor(f, key, &t);
+}
+
+
+uint_t aubio_file_hdf5_load_dataset_into_vector(aubio_file_hdf5_t *f,
+    const char_t *key, fvec_t *vec) {
+  aubio_tensor_t t;
+  if (aubio_fvec_as_tensor (vec, &t)) return AUBIO_FAIL;
+  return aubio_file_hdf5_load_dataset_into_tensor(f, key, &t);
+}
+
+static herr_t aubio_file_hdf5_iterate(hid_t loc_id, const char *name,
+    const H5L_info_t *info UNUSED, void *opdata)
+{
+  H5O_info_t infobuf;
+  const char_t *type_name;
+  uint_t *depth = (uint_t *)opdata;
+  herr_t err = H5Oget_info_by_name(loc_id, name, &infobuf, H5P_DEFAULT);
+  if (err < 0) goto failure;
+  if (*depth > MAX_DEPTH) goto failure;
+  switch (infobuf.type) {
+    case H5O_TYPE_GROUP:
+      type_name = "group";
+      break;
+    case H5O_TYPE_DATASET:
+      type_name = "dataset";
+      break;
+    case H5O_TYPE_NAMED_DATATYPE:
+      type_name = "datatype";
+      break;
+    default:
+      type_name = "unknown";
+      break;
+  }
+  AUBIO_MSG("%*s %s (%s)\n", *depth, "-", name, type_name);
+  if (infobuf.type == H5O_TYPE_GROUP) {
+    uint_t d = *depth + 1;
+    err = H5Literate_by_name(loc_id, name, H5_INDEX_NAME, H5_ITER_NATIVE,
+        NULL, aubio_file_hdf5_iterate, &d, H5P_DEFAULT);
+  }
+failure:
+  return err;
+}
+
+void aubio_file_hdf5_list(aubio_file_hdf5_t *f)
+{
+  uint_t depth = 1;
+  herr_t err = H5Literate(f->fid, H5_INDEX_NAME, H5_ITER_NATIVE,
+      NULL, aubio_file_hdf5_iterate, &depth);
+  if (err < 0)
+    AUBIO_ERR("file_hdf5: failed iterating into %s\n", f->path);
+}
+
+void del_aubio_file_hdf5(aubio_file_hdf5_t *f)
+{
+  AUBIO_ASSERT(f);
+  if (f->fid > 0)
+    H5Fclose(f->fid);
+  AUBIO_FREE(f);
+}
+
+#endif /* HAVE_HDF5 */
Index: src/io/file_hdf5.h
===================================================================
--- src/io/file_hdf5.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/io/file_hdf5.h	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_FILE_HDF5_H
+#define AUBIO_FILE_HDF5_H
+
+typedef struct _aubio_file_hdf5_t aubio_file_hdf5_t;
+
+aubio_file_hdf5_t *new_aubio_file_hdf5(const char_t *path);
+
+void del_aubio_file_hdf5(aubio_file_hdf5_t *f);
+
+void aubio_file_hdf5_list(aubio_file_hdf5_t *f);
+
+uint_t aubio_file_hdf5_load_dataset_into_tensor (aubio_file_hdf5_t *f,
+    const char_t *key, aubio_tensor_t *tensor);
+
+uint_t aubio_file_hdf5_load_dataset_into_matrix(aubio_file_hdf5_t *f,
+    const char_t *key, fmat_t *mat);
+
+uint_t aubio_file_hdf5_load_dataset_into_vector (aubio_file_hdf5_t *f,
+    const char_t *key, fvec_t *vec);
+
+#endif /* AUBIO_FILE_HDF5_H */
Index: src/pitch/pitch.c
===================================================================
--- src/pitch/pitch.c	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/pitch/pitch.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -49,4 +49,5 @@
   aubio_pitcht_yinfast,    /**< `yinfast`, YIN fast */
   aubio_pitcht_specacf,    /**< `specacf`, Spectral autocorrelation */
+  aubio_pitcht_crepe,      /**< `crepe`, convolutional neural network */
   aubio_pitcht_default
     = aubio_pitcht_yinfft, /**< `default` */
@@ -99,4 +100,5 @@
 static void aubio_pitch_do_yinfast (aubio_pitch_t * p, const fvec_t * ibuf, fvec_t * obuf);
 static void aubio_pitch_do_specacf (aubio_pitch_t * p, const fvec_t * ibuf, fvec_t * obuf);
+static void aubio_pitch_do_crepe (aubio_pitch_t * p, const fvec_t * ibuf, fvec_t * obuf);
 
 /* internal functions for frequency conversion */
@@ -104,4 +106,13 @@
 static smpl_t freqconvmidi (smpl_t f, uint_t samplerate, uint_t bufsize);
 static smpl_t freqconvpass (smpl_t f, uint_t samplerate, uint_t bufsize);
+
+typedef struct _aubio_pitch_crepe_t aubio_pitch_crepe_t;
+extern aubio_pitch_crepe_t *new_aubio_pitch_crepe(void);
+extern void aubio_pitch_crepe_do(aubio_pitch_crepe_t *t, fvec_t *input, fvec_t *out);
+extern void del_aubio_pitch_crepe(aubio_pitch_crepe_t *t);
+extern smpl_t aubio_pitch_crepe_get_confidence (aubio_pitch_crepe_t * o);
+uint_t aubio_pitch_crepe_set_tolerance(aubio_pitch_crepe_t * o, smpl_t
+    tolerance);
+smpl_t aubio_pitch_crepe_get_tolerance (aubio_pitch_crepe_t * o);
 
 /* adapter to stack ibuf new samples at the end of buf, and trim `buf` to `bufsize` */
@@ -133,4 +144,6 @@
   else if (strcmp (pitch_mode, "specacf") == 0)
     pitch_type = aubio_pitcht_specacf;
+  else if (strcmp (pitch_mode, "crepe") == 0)
+    pitch_type = aubio_pitcht_crepe;
   else if (strcmp (pitch_mode, "default") == 0)
     pitch_type = aubio_pitcht_default;
@@ -214,4 +227,23 @@
       aubio_pitchspecacf_set_tolerance (p->p_object, 0.85);
       break;
+    case aubio_pitcht_crepe:
+      // TODO add resampling and blocking
+      if (samplerate != 16000) {
+        AUBIO_ERROR("pitch: crepe samplerate must be 16000Hz, got %d\n",
+            samplerate);
+        goto beach;
+      }
+      if (bufsize != 1024) {
+        AUBIO_ERROR("pitch: crepe buffer size must be 1024, got %d\n",
+            bufsize);
+        goto beach;
+      }
+      p->buf = new_fvec (bufsize);
+      p->p_object = new_aubio_pitch_crepe();
+      if (!p->p_object) goto beach;
+      p->detect_cb = aubio_pitch_do_crepe;
+      p->conf_cb = (aubio_pitch_get_conf_t)aubio_pitch_crepe_get_confidence;
+      //aubio_pitch_crepe_set_tolerance (p->p_object, 0.85);
+      break;
     default:
       break;
@@ -260,4 +292,8 @@
       del_fvec (p->buf);
       del_aubio_pitchspecacf (p->p_object);
+      break;
+    case aubio_pitcht_crepe:
+      del_fvec (p->buf);
+      del_aubio_pitch_crepe (p->p_object);
       break;
     default:
@@ -350,4 +386,7 @@
       aubio_pitchyinfast_set_tolerance (p->p_object, tol);
       break;
+    case aubio_pitcht_crepe:
+      aubio_pitch_crepe_set_tolerance (p->p_object, tol);
+      break;
     default:
       break;
@@ -369,4 +408,7 @@
     case aubio_pitcht_yinfast:
       tolerance = aubio_pitchyinfast_get_tolerance (p->p_object);
+      break;
+    case aubio_pitcht_crepe:
+      tolerance = aubio_pitch_crepe_get_tolerance (p->p_object);
       break;
     default:
@@ -479,4 +521,13 @@
 
 void
+aubio_pitch_do_crepe (aubio_pitch_t * p, const fvec_t * ibuf, fvec_t * out)
+{
+  //smpl_t pitch = 0., period;
+  aubio_pitch_slideblock (p, ibuf);
+  aubio_pitch_crepe_do(p->p_object, p->buf, out);
+  out->data[0] = aubio_miditofreq(out->data[0]);
+}
+
+void
 aubio_pitch_do_fcomb (aubio_pitch_t * p, const fvec_t * ibuf, fvec_t * out)
 {
Index: src/pitch/pitch_crepe.c
===================================================================
--- src/pitch/pitch_crepe.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ src/pitch/pitch_crepe.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,521 @@
+/*
+  Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio 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 3 of the License, or
+  (at your option) any later version.
+
+  aubio 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 aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/* CREPE pitch algorithm
+
+ References
+ ----------
+
+ CREPE: A Convolutional Representation for Pitch Estimation Jong Wook Kim,
+ Justin Salamon, Peter Li, Juan Pablo Bello.  Proceedings of the IEEE
+ International Conference on Acoustics, Speech, and Signal Processing (ICASSP),
+ 2018. Available online at https://arxiv.org/abs/1802.06182
+
+ Original implementation available at https://github.com/marl/crepe
+
+*/
+
+#include "aubio_priv.h"
+
+#include "fmat.h"
+#include "ai/tensor.h"
+#include "ai/activation.h"
+#include "ai/conv1d.h"
+#include "ai/maxpool1d.h"
+#include "ai/batchnorm.h"
+#include "ai/dense.h"
+#include "io/file_hdf5.h"
+#include "utils/scale.h"
+
+#define HDF5_FILE_PATH "crepe-model-tiny.h5"
+
+// public prototypes
+typedef struct _aubio_pitch_crepe_t aubio_pitch_crepe_t;
+aubio_pitch_crepe_t *new_aubio_pitch_crepe(void);
+void aubio_pitch_crepe_do(aubio_pitch_crepe_t *t, fvec_t *input, fvec_t *out);
+void del_aubio_pitch_crepe(aubio_pitch_crepe_t *t);
+smpl_t aubio_pitch_crepe_get_confidence (aubio_pitch_crepe_t * o);
+uint_t aubio_pitch_crepe_set_tolerance(aubio_pitch_crepe_t * o, smpl_t
+    tolerance);
+smpl_t aubio_pitch_crepe_get_tolerance (aubio_pitch_crepe_t * o);
+
+// static prototypes
+static uint_t aubio_pitch_crepe_load_params(aubio_pitch_crepe_t *o);
+
+struct _aubio_pitch_crepe_t
+{
+  // number of [conv, maxpool, batchnorm] groups
+  uint_t n_layers;
+  // layers
+  aubio_conv1d_t **conv_layers;
+  aubio_batchnorm_t **batchnorm_layers;
+  aubio_maxpool1d_t **maxpool_layers;
+  aubio_dense_t *dense_layer;
+  // input/output tensors
+  aubio_tensor_t *input_tensor;
+  aubio_tensor_t **conv_output;
+  aubio_tensor_t **batchnorm_output;
+  aubio_tensor_t **maxpool_output;
+  aubio_tensor_t *flattened;
+  aubio_tensor_t *dense_output;
+
+  smpl_t confidence;
+  smpl_t tolerance;
+  aubio_scale_t *scale;
+};
+
+aubio_pitch_crepe_t *new_aubio_pitch_crepe(void)
+{
+  aubio_pitch_crepe_t *o = AUBIO_NEW(aubio_pitch_crepe_t);
+  aubio_tensor_t *block_input;
+  // algorithm constants
+  uint_t input_shape[2] = {1024, 1};
+  uint_t capacity_modes[5] = {4, 8, 16, 24, 32};
+  uint_t n_filters[6] = {32, 4, 4, 4, 8, 16};
+  uint_t widths[6] = {512, 64, 64, 64, 64, 64};
+  uint_t maxpool_stride[1] = {2};
+  uint_t l0_stride[1] = {4};
+  uint_t n_dense = 360;
+
+  // local variables
+  uint_t capacity_mode = 0;
+  uint_t capacity = capacity_modes[capacity_mode];
+  uint_t output_shape[2];
+  uint_t i;
+
+#if defined(HAVE_BLAS) && defined(HAVE_OPENBLAS_CBLAS_H)
+  // workaround to prevent openblas from opening multiple threads, since
+  // the overhead appears to be higher than using a single thread.
+  openblas_set_num_threads(1);
+#endif
+
+  AUBIO_ASSERT (capacity_mode < 5 && (sint_t)capacity_mode >= 0);
+
+  o->n_layers = 6;
+  // create arrays of layers and tensors
+  o->conv_layers = AUBIO_ARRAY(aubio_conv1d_t*, o->n_layers);
+  o->conv_output = AUBIO_ARRAY(aubio_tensor_t*, o->n_layers);
+  o->maxpool_layers = AUBIO_ARRAY(aubio_maxpool1d_t*, o->n_layers);
+  o->maxpool_output = AUBIO_ARRAY(aubio_tensor_t*, o->n_layers);
+  o->batchnorm_layers = AUBIO_ARRAY(aubio_batchnorm_t*, o->n_layers);
+  o->batchnorm_output = AUBIO_ARRAY(aubio_tensor_t*, o->n_layers);
+
+  if (!o->conv_layers || !o->conv_output
+      || !o->maxpool_layers || !o->maxpool_output
+      || !o->batchnorm_layers || !o->batchnorm_output)
+    goto failure;
+
+  // create layers
+  for (i = 0; i < o->n_layers; i++) {
+    uint_t kern_shape[1] = {widths[i]};
+    // create convolutional layers
+    o->conv_layers[i] = new_aubio_conv1d(n_filters[i] * capacity, kern_shape);
+    if (!o->conv_layers[i]) goto failure;
+    // set padding='same'
+    if (aubio_conv1d_set_padding_mode(o->conv_layers[i], "same") != AUBIO_OK) {
+      goto failure;
+    }
+    // set stride of first layer
+    if ((i == 0) && (aubio_conv1d_set_stride(o->conv_layers[0],
+            l0_stride) != AUBIO_OK) ) {
+      goto failure;
+    }
+
+    // create batchnorm layers
+    o->batchnorm_layers[i] = new_aubio_batchnorm();
+    if (!o->batchnorm_layers[i]) goto failure;
+
+    // create maxpool layers
+    o->maxpool_layers[i] = new_aubio_maxpool1d(maxpool_stride);
+    if (!o->maxpool_layers[i]) goto failure;
+  }
+
+  o->dense_layer = new_aubio_dense(n_dense);
+  if (!o->dense_layer) goto failure;
+
+  // create input/output tensors
+  o->input_tensor = new_aubio_tensor(2, input_shape);
+  if (!o->input_tensor) goto failure;
+  block_input = o->input_tensor;
+  for (i = 0; i < o->n_layers; i++) {
+    // get shape of conv1d output and create its tensor
+    if (aubio_conv1d_get_output_shape(o->conv_layers[i],
+          block_input, output_shape))
+      goto failure;
+    o->conv_output[i] = new_aubio_tensor(2, output_shape);
+    if (!o->conv_output[i]) goto failure;
+
+    // get shape of batchnorm output and create its tensor
+    if (aubio_batchnorm_get_output_shape(o->batchnorm_layers[i],
+          o->conv_output[i], output_shape))
+      goto failure;
+    o->batchnorm_output[i] = new_aubio_tensor(2, output_shape);
+    if (!o->batchnorm_output[i]) goto failure;
+
+    // get shape of maxpool1d output and create its tensor
+    if (aubio_maxpool1d_get_output_shape(o->maxpool_layers[i],
+          o->batchnorm_output[i], output_shape))
+      goto failure;
+    o->maxpool_output[i] = new_aubio_tensor(2, output_shape);
+    if (!o->maxpool_output[i]) goto failure;
+
+    // set input for next block
+    block_input = o->maxpool_output[i];
+  }
+
+  uint_t flattened_dim = o->maxpool_output[5]->shape[0];
+  flattened_dim *= o->maxpool_output[5]->shape[1];
+  uint_t dense_input[1] = {flattened_dim};
+  o->flattened = new_aubio_tensor(1, dense_input);
+  if (!o->flattened) goto failure;
+
+  // permute and flatten
+  aubio_tensor_t *permute_input = o->maxpool_output[5];
+  AUBIO_DBG("permute:           (%d, %d) ->"
+      " (%d, %d) (permutation=(2, 1))\n",
+      permute_input->shape[0], permute_input->shape[1],
+      permute_input->shape[1], permute_input->shape[0]);
+  AUBIO_DBG("flatten:           (%d, %d) -> (%d)\n",
+      permute_input->shape[1], permute_input->shape[0],
+      o->flattened->shape[0]);
+
+  if (aubio_dense_get_output_shape(o->dense_layer, o->flattened, output_shape))
+    goto failure;
+  o->dense_output = new_aubio_tensor(1, output_shape);
+  if (!o->dense_output) goto failure;
+
+  AUBIO_ASSERT(n_dense == output_shape[0]);
+
+  if (aubio_pitch_crepe_load_params(o))
+    goto failure;
+
+  // map output units to midi note
+  smpl_t start = 1997.379408437619;
+  smpl_t end = 7180.;
+  o->scale = new_aubio_scale(0., 359., start, start + end);
+  if (!o->scale) goto failure;
+
+  return o;
+
+failure:
+  del_aubio_pitch_crepe(o);
+  return NULL;
+}
+
+void del_aubio_pitch_crepe(aubio_pitch_crepe_t *o)
+{
+  uint_t i;
+  AUBIO_ASSERT(o);
+
+  if (o->input_tensor) {
+    del_aubio_tensor(o->input_tensor);
+  }
+
+  if (o->batchnorm_output) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->batchnorm_output[i])
+        del_aubio_tensor(o->batchnorm_output[i]);
+    }
+    AUBIO_FREE(o->batchnorm_output);
+  }
+
+  if (o->batchnorm_layers) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->batchnorm_layers[i])
+        del_aubio_batchnorm(o->batchnorm_layers[i]);
+    }
+    AUBIO_FREE(o->batchnorm_layers);
+  }
+
+  if (o->maxpool_output) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->maxpool_output[i])
+        del_aubio_tensor(o->maxpool_output[i]);
+    }
+    AUBIO_FREE(o->maxpool_output);
+  }
+
+  if (o->maxpool_layers) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->maxpool_layers[i])
+        del_aubio_maxpool1d(o->maxpool_layers[i]);
+    }
+    AUBIO_FREE(o->maxpool_layers);
+  }
+
+  if (o->conv_output) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->conv_output[i])
+        del_aubio_tensor(o->conv_output[i]);
+    }
+    AUBIO_FREE(o->conv_output);
+  }
+
+  if (o->conv_layers) {
+    for (i = 0; i < o->n_layers; i++) {
+      if (o->conv_layers[i])
+        del_aubio_conv1d(o->conv_layers[i]);
+    }
+    AUBIO_FREE(o->conv_layers);
+  }
+
+  if (o->flattened) {
+    del_aubio_tensor(o->flattened);
+  }
+
+  if (o->dense_layer) {
+    del_aubio_dense(o->dense_layer);
+  }
+
+  if (o->dense_output) {
+    del_aubio_tensor(o->dense_output);
+  }
+
+  if (o->scale) {
+    del_aubio_scale(o->scale);
+  }
+
+  AUBIO_FREE(o);
+}
+
+void aubio_pitch_crepe_do(aubio_pitch_crepe_t *o, fvec_t *input, fvec_t *out)
+{
+  uint_t i;
+  AUBIO_ASSERT(o && input);
+  // copy input to input tensor
+  AUBIO_ASSERT(input->length == o->input_tensor->shape[0]);
+  // normalize frame, removing mean and dividing by std
+  smpl_t mean = fvec_mean(input);
+  fvec_add(input, -mean);
+  smpl_t std = 0.;
+  for (i = 0; i < input->length; i++) {
+    std += SQR(input->data[i]);
+  }
+  std = SQRT(std / (smpl_t)input->length);
+  if (std < 1.e-7) std = 1;
+
+  for (i = 0; i < input->length; i++) {
+    o->input_tensor->data[0][i] = input->data[i] / std;
+  }
+
+  aubio_tensor_t *block_input = o->input_tensor;
+  for (i = 0; i < o->n_layers; i++) {
+    aubio_conv1d_do(o->conv_layers[i], block_input,
+        o->conv_output[i]);
+    // relu activation
+    aubio_activation_relu(o->conv_output[i]);
+    aubio_batchnorm_do(o->batchnorm_layers[i], o->conv_output[i],
+        o->batchnorm_output[i]);
+    aubio_maxpool1d_do(o->maxpool_layers[i], o->batchnorm_output[i],
+        o->maxpool_output[i]);
+    block_input = o->maxpool_output[i];
+  }
+
+  aubio_tensor_t *permute_input = o->maxpool_output[5];
+  // perform flattening (permutation has no effect here, order unchanged)
+  AUBIO_ASSERT (permute_input->size == o->flattened->size);
+  for (i = 0; i < permute_input->size; i++) {
+    o->flattened->data[0][i] = permute_input->data[0][i];
+  }
+
+  // compute dense layer
+  aubio_dense_do(o->dense_layer, o->flattened, o->dense_output);
+
+  // sigmoid activation
+  aubio_activation_sigmoid(o->dense_output);
+
+#if 0
+  // print debug output
+  for (i = 0; i < o->n_layers; i++) {
+    AUBIO_DBG("pitch_crepe: conv1d[%d]    %f\n", i,
+        aubio_tensor_max(o->conv_output[i]));
+    AUBIO_DBG("pitch_crepe: batchnorm[%d] %f\n", i,
+        aubio_tensor_max(o->batchnorm_output[i]));
+    AUBIO_DBG("pitch_crepe: maxpool1d[%d] %f\n", i,
+        aubio_tensor_max(o->maxpool_output[i]));
+  }
+  AUBIO_DBG("pitch_crepe: dense %f\n", aubio_tensor_max(o->dense_output));
+#endif
+
+  // find maximum activation
+  fvec_t activations;
+  aubio_tensor_as_fvec(o->dense_output, &activations);
+  uint_t argmax = fvec_max_elem(&activations);
+  o->confidence = activations.data[argmax];
+
+  // skip frames with no activation at all (e.g. silence)
+  // or with insufficient confidence
+  if ((argmax == activations.length - 1)
+      || (o->confidence < o->tolerance)) {
+    out->data[0] = -100.;
+    o->confidence = 0;
+    return;
+  }
+
+  // perform interpolation across neighbouring outputs
+  sint_t start = MAX(0, (sint_t)argmax - 4);
+  uint_t end = MIN(argmax + 5, activations.length);
+
+  smpl_t prod = 0;
+  smpl_t weight = 0;
+  smpl_t scaling = 0;
+  for (i = start; i < end; i++) {
+    scaling = (smpl_t)(i);
+    prod += activations.data[i] * scaling;
+    weight += activations.data[i];
+  }
+  out->data[0] = prod / weight;
+
+  // map output units to midi output
+  aubio_scale_do(o->scale, out);
+
+  // convert cents to midi
+  out->data[0] /= 100.;
+
+  // final bias (f_ref = 10Hz -> 3.48 midi)
+  out->data[0] += 3.486821174621582;
+}
+
+smpl_t aubio_pitch_crepe_get_confidence (aubio_pitch_crepe_t* o)
+{
+  return o->confidence;
+}
+
+uint_t aubio_pitch_crepe_set_tolerance(aubio_pitch_crepe_t * o,
+    smpl_t tolerance)
+{
+  if (o->tolerance < 0 || o->tolerance > 1) return AUBIO_FAIL;
+  o->tolerance = tolerance;
+  return AUBIO_OK;
+}
+
+smpl_t aubio_pitch_crepe_get_tolerance (aubio_pitch_crepe_t * o)
+{
+  return o->tolerance;
+}
+
+uint_t aubio_pitch_crepe_load_params(aubio_pitch_crepe_t *o)
+{
+#ifdef HAVE_HDF5
+  uint_t i;
+  aubio_tensor_t *k = NULL;
+  fvec_t *vec = NULL;
+
+  AUBIO_ASSERT(o);
+
+  aubio_file_hdf5_t *hdf5 = new_aubio_file_hdf5(HDF5_FILE_PATH);
+  if (!hdf5) return AUBIO_FAIL;
+
+  // get kernels
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d/conv%d_3/kernel:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    k = aubio_conv1d_get_kernel(o->conv_layers[i]);
+
+    // push dimension
+    k->shape[3] = k->shape[2]; k->shape[2] = k->shape[1]; k->shape[1] = 1;
+    k->ndim += 1;
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_tensor(hdf5, key, k))
+      return AUBIO_FAIL;
+    // pop dimension
+    k->shape[1] = k->shape[2]; k->shape[2] = k->shape[3]; k->shape[3] = 0;
+    k->ndim -= 1;
+  }
+
+  // get bias vectors
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d/conv%d_3/bias:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    vec = aubio_conv1d_get_bias(o->conv_layers[i]);
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, vec))
+      return AUBIO_FAIL;
+  }
+
+  // batchnorm
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d-BN/conv%d-BN_3/gamma:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    // get kernel matrix
+    vec = aubio_batchnorm_get_gamma(o->batchnorm_layers[i]);
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, vec))
+      return AUBIO_FAIL;
+  }
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d-BN/conv%d-BN_3/beta:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    // get kernel matrix
+    vec = aubio_batchnorm_get_beta(o->batchnorm_layers[i]);
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, vec))
+      return AUBIO_FAIL;
+  }
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d-BN/conv%d-BN_3/moving_mean:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    // get kernel matrix
+    vec = aubio_batchnorm_get_moving_mean(o->batchnorm_layers[i]);
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, vec))
+      return AUBIO_FAIL;
+  }
+  for (i = 0; i < o->n_layers; i++) {
+    char_t *fmt_key = "/conv%d-BN/conv%d-BN_3/moving_variance:0";
+    char_t key[PATH_MAX];
+    snprintf(key, sizeof(key), fmt_key, i+1, i+1);
+    // get kernel matrix
+    vec = aubio_batchnorm_get_moving_variance(o->batchnorm_layers[i]);
+    // load params from hdf5 into kernel tensor
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, vec))
+      return AUBIO_FAIL;
+  }
+
+  // dense layer
+  {
+    char_t *key = "/classifier/classifier_3/kernel:0";
+    fmat_t *d = aubio_dense_get_weights(o->dense_layer);
+    if (aubio_file_hdf5_load_dataset_into_matrix(hdf5, key, d))
+      return AUBIO_FAIL;
+
+    key = "/classifier/classifier_3/bias:0";
+    fvec_t *v = aubio_dense_get_bias(o->dense_layer);
+    if (aubio_file_hdf5_load_dataset_into_vector(hdf5, key, v))
+      return AUBIO_FAIL;
+  }
+
+  if (hdf5) {
+    del_aubio_file_hdf5(hdf5);
+  }
+
+  return AUBIO_OK;
+#else
+  AUBIO_ASSERT(o);
+  AUBIO_ERR("pitch_crepe: hdf5 support was not built in, failed loading"
+      " crepe model\n");
+  return AUBIO_FAIL;
+#endif
+}
Index: src/wscript_build
===================================================================
--- src/wscript_build	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ src/wscript_build	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -13,4 +13,5 @@
 uselib += ['AVUTIL']
 uselib += ['BLAS']
+uselib += ['HDF5', 'HDF5_HL']
 
 source = ctx.path.ant_glob('*.c **/*.c')
Index: tests/src/ai/test-tensor.c
===================================================================
--- tests/src/ai/test-tensor.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
+++ tests/src/ai/test-tensor.c	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -0,0 +1,338 @@
+#include "aubio.h"
+#include "utils_tests.h"
+
+#include "ai/tensor.h"
+
+int test_1d(void)
+{
+  uint_t dims[1] = {11};
+  aubio_tensor_t *c = new_aubio_tensor(1, dims);
+  fvec_t a;
+  aubio_tensor_t cp;
+
+  assert(c);
+
+  c->data[1][0] = 1.;
+  c->data[0][9] = 1.;
+  c->data[1][1] = 1.;
+
+  aubio_tensor_print(c);
+
+  PRINT_MSG(" created from fvec_t \n");
+  // view tensor as fvec
+  assert (aubio_tensor_as_fvec(c, &a) == 0);
+  fvec_print(&a);
+  // view fvec as tensor
+  assert (aubio_fvec_as_tensor(&a, &cp) == 0);
+  aubio_tensor_print(&cp);
+
+  // wrong input
+  assert (aubio_tensor_as_fvec(NULL, &a) != 0);
+  assert (aubio_tensor_as_fvec(c, NULL) != 0);
+
+  assert (aubio_fvec_as_tensor(NULL, &cp) != 0);
+  assert (aubio_fvec_as_tensor(&a, NULL) != 0);
+
+  del_aubio_tensor(c);
+  return 0;
+}
+
+int test_2d(void)
+{
+  uint_t dims[2] = {3, 2};
+  aubio_tensor_t *c = new_aubio_tensor(2, dims);
+  fmat_t b;
+
+  assert (c);
+  c->data[0][1] = 2.;
+  c->data[1][0] = 1.;
+
+  aubio_tensor_print(c);
+  assert (aubio_tensor_as_fmat(c, &b) == 0);
+  fmat_print(&b);
+
+  PRINT_MSG(" created from fmat_t\n");
+  fmat_t *m = new_fmat(dims[0], dims[1]);
+  aubio_tensor_t cp;
+
+  fmat_ones(m);
+  m->data[0][1] = 0;
+  fmat_print(m);
+  assert (aubio_fmat_as_tensor(m, &cp) == 0);
+  aubio_tensor_print(&cp);
+
+  // view tensor as fvec
+  fvec_t vp;
+  assert (aubio_tensor_as_fvec(&cp, &vp) == 0);
+  fvec_print (&vp);
+
+  assert (aubio_tensor_as_fmat(NULL, &b) != 0);
+  assert (aubio_tensor_as_fmat(c, NULL) != 0);
+
+  assert (aubio_fmat_as_tensor (NULL, &cp) != 0);
+  assert (aubio_fmat_as_tensor (m, NULL) != 0);
+
+  del_fmat(m);
+  del_aubio_tensor(c);
+
+  return 0;
+}
+
+int test_3d(void)
+{
+  uint_t dims[3] = {3, 2, 3};
+  aubio_tensor_t *c = new_aubio_tensor(3, dims);
+  assert (c);
+
+  c->data[0][0 * 3 + 0] = 1;
+  c->data[1][0 * 3 + 1] = 2;
+  c->data[1][1 * 3 + 1] = 2;
+  c->data[2][1 * 3 + 2] = 3;
+
+  aubio_tensor_print(c);
+
+  // view tensor as fmat
+  fmat_t vm;
+  assert (aubio_tensor_as_fmat(c, &vm) == 0);
+  fmat_print (&vm);
+
+  del_aubio_tensor(c);
+  return 0;
+}
+
+int test_4d(void)
+{
+  uint_t d1 = 3, d2 = 2, d3 = 2, d4 = 4;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  aubio_tensor_t *c = new_aubio_tensor(4, dims);
+
+  c->data[0][0] = 1;
+  c->data[1][1 * d3 * d4 + 1 * d4 + 2] = 2;
+  c->data[0][2 * d2 * d3 * d4 + 1 * d3 * d4 + 1 * d4 + 3] = c->size;
+
+  aubio_tensor_print(c);
+
+  del_aubio_tensor(c);
+  return 0;
+}
+
+int test_sizes(void)
+{
+  uint_t d1 = 3, d2 = 2, d3 = 2, d4 = 4;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  aubio_tensor_t *a = new_aubio_tensor(4, dims);
+  aubio_tensor_t *b = new_aubio_tensor(3, dims);
+
+  assert (!aubio_tensor_have_same_shape(a, b));
+
+  del_aubio_tensor(b);
+  dims[2] += 1;
+  b = new_aubio_tensor(4, dims);
+  assert (!aubio_tensor_have_same_shape(a, b));
+  del_aubio_tensor(b);
+  dims[2] -= 1;
+
+  dims[0] -= 1;
+  dims[1] += 1;
+  b = new_aubio_tensor(4, dims);
+  assert (!aubio_tensor_have_same_shape(a, b));
+  del_aubio_tensor(b);
+
+  dims[0] += 1;
+  dims[1] -= 1;
+  b = new_aubio_tensor(4, dims);
+  assert (aubio_tensor_have_same_shape(a, b));
+
+  assert (!aubio_tensor_have_same_shape(NULL, b));
+  assert (!aubio_tensor_have_same_shape(a, NULL));
+
+  del_aubio_tensor(a);
+  del_aubio_tensor(b);
+  return 0;
+}
+
+int test_wrong_args(void)
+{
+  uint_t dims[3] = {3, 2, 1};
+  assert (new_aubio_tensor(0, dims) == NULL);
+  dims[1] = -1;
+  assert (new_aubio_tensor(2, dims) == NULL);
+  return 0;
+}
+
+int test_subtensor(void) {
+  uint_t i;
+  uint_t d1 = 4, d2 = 2, d3 = 2, d4 = 3;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  uint_t ndim = 3;
+  aubio_tensor_t *t = new_aubio_tensor(ndim, dims);
+
+  assert (t != NULL);
+  assert (t->data != NULL);
+
+  for (i = 0; i < t->size; i++) {
+    t->data[0][i] = (smpl_t)i;
+  }
+
+  aubio_tensor_print(t);
+
+  aubio_tensor_t st;
+
+  PRINT_MSG(" getting subtensor 1\n");
+  assert (aubio_tensor_get_subtensor(t, 1, &st) == 0);
+  assert (st.ndim = ndim - 1);
+  assert (st.shape[0] == d2);
+  assert (st.shape[1] == d3);
+  assert (st.shape[2] == 0);
+  assert (st.buffer[0] == 4);
+  assert (st.buffer[3] == 7);
+  assert (st.data == NULL);
+  aubio_tensor_print(&st);
+
+  PRINT_MSG(" check subtensor 3\n");
+  assert (aubio_tensor_get_subtensor(t, 3, &st) == 0);
+  aubio_tensor_print(&st);
+
+  aubio_tensor_t sst;
+
+  PRINT_MSG(" check subtensor 1 of subtensor 3\n");
+  assert (aubio_tensor_get_subtensor(&st, 1, &sst) == 0);
+  assert (sst.ndim = ndim - 2);
+  assert (sst.shape[0] == d3);
+  assert (sst.shape[1] == 0);
+  assert (sst.buffer[0] == 14);
+  assert (sst.buffer[1] == 15);
+  aubio_tensor_print(&sst);
+
+  // can get a single element as a tensor
+  assert (aubio_tensor_get_subtensor(&sst, 1, &st) == 0);
+  assert (st.buffer[0] == sst.buffer[1]);
+  assert (&st.buffer[0] == &sst.buffer[1]);
+  aubio_tensor_print(&st);
+
+  // getting wrong tensors
+  assert (aubio_tensor_get_subtensor(NULL, 0, &sst) != 0);
+  assert (aubio_tensor_get_subtensor(&st, -2, &sst) != 0);
+  assert (aubio_tensor_get_subtensor(&st, 2, &sst) != 0);
+  assert (aubio_tensor_get_subtensor(&st, 0, NULL) != 0);
+
+  del_aubio_tensor(t);
+  return 0;
+}
+
+int test_subtensor_tricks (void)
+{
+  uint_t d1 = 4, d2 = 2, d3 = 2, d4 = 3;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  uint_t ndim = 3;
+  aubio_tensor_t *t = new_aubio_tensor(ndim, dims);
+  // manually delete content
+  free(t->data[0]);
+  t->data[0] = NULL;
+
+  del_aubio_tensor(t);
+  return 0;
+}
+
+int test_maxtensor(void)
+{
+  uint_t d1 = 3, d2 = 2, d3 = 2, d4 = 4;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  aubio_tensor_t *c = new_aubio_tensor(4, dims);
+
+  c->data[0][0] = -200;
+  c->data[1][1 * d3 * d4 + 1 * d4 + 2] = 2;
+  c->data[0][2 * d2 * d3 * d4 + 1 * d3 * d4 + 1 * d4 + 3] = c->size;
+
+  assert (aubio_tensor_max(c) == c->size);
+
+  del_aubio_tensor(c);
+  return 0;
+}
+
+int test_get_shape_string(void)
+{
+  uint_t d1 = 3, d2 = 2, d3 = 2, d4 = 4;
+  uint_t dims[4] = {d1, d2, d3, d4};
+  aubio_tensor_t *c = new_aubio_tensor(4, dims);
+
+  c->data[0][0] = -200;
+  c->data[1][1 * d3 * d4 + 1 * d4 + 2] = 2;
+  c->data[0][2 * d2 * d3 * d4 + 1 * d3 * d4 + 1 * d4 + 3] = c->size;
+
+  const char_t *shape_string = aubio_tensor_get_shape_string(c);
+  PRINT_MSG("found shape string %s\n", shape_string);
+
+  del_aubio_tensor(c);
+
+  assert (aubio_tensor_get_shape_string(NULL) == NULL);
+
+  return 0;
+}
+
+int test_matmul(void)
+{
+  uint_t m = 3, n = 2, k = 4;
+  uint_t input_shape[2] =  {m, k};
+  uint_t kernel_shape[2] = {k, n};
+  uint_t output_shape[2] = {m, n};
+
+  aubio_tensor_t *input_tensor = new_aubio_tensor(2, input_shape);
+  aubio_tensor_t *kernel_tensor = new_aubio_tensor(2, kernel_shape);
+  aubio_tensor_t *output_tensor = new_aubio_tensor(2, output_shape);
+
+  input_tensor->data[0][0] = 1;
+  input_tensor->data[1][1] = 1;
+  input_tensor->data[2][0] = -1;
+  input_tensor->data[2][1] = 1;
+  uint_t i;
+  for (i = 0; i < kernel_tensor->size; i++) {
+    kernel_tensor->buffer[i] = (smpl_t)i + 1.;
+  }
+
+  aubio_tensor_matmul(input_tensor, kernel_tensor, output_tensor);
+
+  PRINT_MSG("input: ");
+  aubio_tensor_print(input_tensor);
+  PRINT_MSG("kernel: ");
+  aubio_tensor_print(kernel_tensor);
+  PRINT_MSG("output: ");
+  aubio_tensor_print(output_tensor);
+
+  assert (output_tensor->data[0][0] == kernel_tensor->data[0][0]);
+  assert (output_tensor->data[0][1] == kernel_tensor->data[0][1]);
+  assert (output_tensor->data[1][0] == kernel_tensor->data[1][0]);
+  assert (output_tensor->data[1][1] == kernel_tensor->data[1][1]);
+  assert (output_tensor->data[2][0] == 2);
+  assert (output_tensor->data[2][1] == 2);
+
+  del_aubio_tensor(output_tensor);
+  del_aubio_tensor(kernel_tensor);
+  del_aubio_tensor(input_tensor);
+  return 0;
+}
+int main(void) {
+  PRINT_MSG("testing 1d tensors\n");
+  assert (test_1d() == 0);
+  PRINT_MSG("testing 2d tensors\n");
+  assert (test_2d() == 0);
+  PRINT_MSG("testing 3d tensors\n");
+  assert (test_3d() == 0);
+  PRINT_MSG("testing 4d tensors\n");
+  assert (test_4d() == 0);
+  PRINT_MSG("testing aubio_tensor_have_same_shape\n");
+  assert (test_sizes() == 0);
+  PRINT_MSG("testing new_aubio_tensor with wrong arguments\n");
+  assert (test_wrong_args() == 0);
+  PRINT_MSG("testing subtensors\n");
+  assert (test_subtensor() == 0);
+  PRINT_MSG("testing subtensors\n");
+  assert (test_subtensor_tricks() == 0);
+  PRINT_MSG("testing max\n");
+  assert (test_maxtensor() == 0);
+  PRINT_MSG("testing get_shape_string\n");
+  assert (test_get_shape_string() == 0);
+  PRINT_MSG("testing matmul\n");
+  assert (test_matmul() == 0);
+  return 0;
+}
Index: wscript
===================================================================
--- wscript	(revision 63fc1dfb73714cac40869e4469cb6eac1b37beab)
+++ wscript	(revision 36ab428a0e74594d35d253aaed00dca87f766460)
@@ -88,8 +88,8 @@
             help_str = 'use CoreFoundation (darwin only) (auto)',
             help_disable_str = 'do not use CoreFoundation framework')
-    add_option_enable_disable(ctx, 'blas', default = False,
+    add_option_enable_disable(ctx, 'blas', default = None,
             help_str = 'use BLAS acceleration library (no)',
             help_disable_str = 'do not use BLAS library')
-    add_option_enable_disable(ctx, 'atlas', default = False,
+    add_option_enable_disable(ctx, 'atlas', default = None,
             help_str = 'use ATLAS acceleration library (no)',
             help_disable_str = 'do not use ATLAS library')
@@ -100,4 +100,8 @@
             help_str = 'compile with source_wavwrite (default)',
             help_disable_str = 'do not compile source_wavwrite')
+
+    add_option_enable_disable(ctx, 'hdf5', default = None,
+            help_str = 'use libhdf5 (default)',
+            help_disable_str = 'do not use libhdf5')
 
     add_option_enable_disable(ctx, 'docs', default = None,
@@ -463,4 +467,10 @@
     if (ctx.options.enable_memcpy == True):
         ctx.define('HAVE_MEMCPY_HACKS', 1)
+
+    if (ctx.options.enable_hdf5 != False):
+        ctx.check_cfg(package='hdf5', args='--cflags --libs',
+                uselib_store='HDF5', mandatory=ctx.options.enable_hdf5)
+        ctx.check(lib=['hdf5_hl'], use = ['HDF5'],
+                uselib_store='HDF5_HL', mandatory=ctx.options.enable_hdf5)
 
     # write configuration header
