From 52ca8a39caa3479d1cc32d43f8e398f3c2de2a5a Mon Sep 17 00:00:00 2001 From: Paul Brossier Date: Fri, 24 Jan 2014 13:22:00 -0300 Subject: [PATCH] src/io/sink_wavwrite.c: add native basic wav writer --- python/lib/generator.py | 1 + src/aubio.h | 1 + src/io/sink.c | 11 ++ src/io/sink_wavwrite.c | 208 ++++++++++++++++++++++++++++++++++++++ src/io/sink_wavwrite.h | 81 +++++++++++++++ tests/src/io/test-sink_wavwrite.c | 53 ++++++++++ wscript | 1 + 7 files changed, 356 insertions(+) create mode 100644 src/io/sink_wavwrite.c create mode 100644 src/io/sink_wavwrite.h create mode 100644 tests/src/io/test-sink_wavwrite.c diff --git a/python/lib/generator.py b/python/lib/generator.py index 867aeae1..195c2efb 100755 --- a/python/lib/generator.py +++ b/python/lib/generator.py @@ -57,6 +57,7 @@ def generate_object_files(output_path): 'sink', 'sink_apple_audio', 'sink_sndfile', + 'sink_wavwrite', 'source', 'source_apple_audio', 'source_sndfile', diff --git a/src/aubio.h b/src/aubio.h index 6a5c6ca2..c615dd93 100644 --- a/src/aubio.h +++ b/src/aubio.h @@ -196,6 +196,7 @@ extern "C" #include "io/source_wavread.h" #include "io/sink_sndfile.h" #include "io/sink_apple_audio.h" +#include "io/sink_wavwrite.h" #include "io/audio_unit.h" #include "onset/peakpicker.h" #include "pitch/pitchmcomb.h" diff --git a/src/io/sink.c b/src/io/sink.c index 1923c92f..0f635403 100644 --- a/src/io/sink.c +++ b/src/io/sink.c @@ -28,6 +28,9 @@ #ifdef HAVE_SNDFILE #include "io/sink_sndfile.h" #endif +#ifdef HAVE_WAVWRITE +#include "io/sink_wavwrite.h" +#endif typedef void (*aubio_sink_do_t)(aubio_sink_t * s, fvec_t * data, uint_t write); #if 0 @@ -66,6 +69,14 @@ aubio_sink_t * new_aubio_sink(char_t * uri, uint_t samplerate) { return s; } #endif /* HAVE_SNDFILE */ +#if HAVE_WAVWRITE + s->sink = (void *)new_aubio_sink_wavwrite(uri, samplerate); + if (s->sink) { + s->s_do = (aubio_sink_do_t)(aubio_sink_wavwrite_do); + s->s_del = (del_aubio_sink_t)(del_aubio_sink_wavwrite); + return s; + } +#endif /* HAVE_WAVWRITE */ AUBIO_ERROR("sink: failed creating aubio sink with %s\n", uri); AUBIO_FREE(s); return NULL; diff --git a/src/io/sink_wavwrite.c b/src/io/sink_wavwrite.c new file mode 100644 index 00000000..5c1f380d --- /dev/null +++ b/src/io/sink_wavwrite.c @@ -0,0 +1,208 @@ +/* + Copyright (C) 2014 Paul Brossier + + 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 . + +*/ + + +#include "config.h" + +#ifdef HAVE_WAVWRITE + +#include "aubio_priv.h" +#include "sink_wavwrite.h" +#include "fvec.h" + +#include + +#define MAX_CHANNELS 6 +#define MAX_SIZE 4096 + +#define FLOAT_TO_SHORT(x) (short)(x * 32768) + +// swap endian of a short +#define SWAPS(x) ((x & 0xff) << 8) | ((x & 0xff00) >> 8) + +// swap host to little endian +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define HTOLES(x) SWAPS(x) +#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define HTOLES(x) x +#else +#define HTOLES(x) SWAPS(htons(x)) +#endif + +struct _aubio_sink_wavwrite_t { + char_t *path; + uint_t samplerate; + uint_t channels; + uint_t bitspersample; + uint_t total_frames_written; + + FILE *fid; + + uint_t max_size; + + uint_t scratch_size; + unsigned short *scratch_data; +}; + +unsigned char *write_little_endian (unsigned int s, unsigned char *str, unsigned int length); +unsigned char *write_little_endian (unsigned int s, unsigned char *str, unsigned int length) { + uint_t i; + for (i = 0; i < length; i++) { + str[i] = s >> (i * 8); + } + return str; +} + +aubio_sink_wavwrite_t * new_aubio_sink_wavwrite(char_t * path, uint_t samplerate) { + aubio_sink_wavwrite_t * s = AUBIO_NEW(aubio_sink_wavwrite_t); + unsigned char buf[5]; + uint_t byterate, blockalign; + + if (path == NULL) { + AUBIO_ERR("sink_wavwrite: Aborted opening null path\n"); + goto beach; + } + if ((sint_t)samplerate < 0) { + AUBIO_ERR("sink_wavwrite: Can not create %s with samplerate %d\n", path, samplerate); + goto beach; + } + + s->path = path; + s->samplerate = samplerate; + s->max_size = MAX_SIZE; + s->channels = 1; + s->bitspersample = 16; + s->total_frames_written = 0; + + /* set output format */ + s->fid = fopen((const char *)path, "wb"); + if (!s->fid) { + AUBIO_ERR("sink_wavwrite: could not open %s (%s)\n", s->path, strerror(errno)); + goto beach; + } + + // ChunkID + fwrite("RIFF", 4, 1, s->fid); + + // ChunkSize (0 for now, actual size will be written in _close) + fwrite(write_little_endian(0, buf, 4), 4, 1, s->fid); + + // Format + fwrite("WAVE", 4, 1, s->fid); + + // Subchunk1ID + fwrite("fmt ", 4, 1, s->fid); + + // Subchunk1Size + fwrite(write_little_endian(16, buf, 4), 4, 1, s->fid); + + // AudioFormat + fwrite(write_little_endian(1, buf, 2), 2, 1, s->fid); + + // NumChannels + fwrite(write_little_endian(s->channels, buf, 2), 2, 1, s->fid); + + // SampleRate + fwrite(write_little_endian(s->samplerate, buf, 4), 4, 1, s->fid); + + // ByteRate + byterate = s->samplerate * s->channels * s->bitspersample / 8; + fwrite(write_little_endian(byterate, buf, 4), 4, 1, s->fid); + + // BlockAlign + blockalign = s->channels * s->bitspersample / 8; + fwrite(write_little_endian(blockalign, buf, 2), 2, 1, s->fid); + + // BitsPerSample + fwrite(write_little_endian(s->bitspersample, buf, 2), 2, 1, s->fid); + + // Subchunk2ID + fwrite("data", 4, 1, s->fid); + + // Subchunk1Size (0 for now, actual size will be written in _close) + fwrite(write_little_endian(0, buf, 4), 4, 1, s->fid); + + s->scratch_size = s->max_size * s->channels; + /* allocate data for de/interleaving reallocated when needed. */ + if (s->scratch_size >= MAX_SIZE * MAX_CHANNELS) { + AUBIO_ERR("sink_wavwrite: %d x %d exceeds maximum buffer size %d\n", + s->max_size, s->channels, MAX_CHANNELS * MAX_CHANNELS); + AUBIO_FREE(s); + return NULL; + } + s->scratch_data = AUBIO_ARRAY(unsigned short,s->scratch_size); + + return s; + +beach: + AUBIO_ERR("sink_wavwrite: can not write %s at samplerate %dHz\n", + s->path, s->samplerate); + del_aubio_sink_wavwrite(s); + return NULL; +} + + +void aubio_sink_wavwrite_do(aubio_sink_wavwrite_t *s, fvec_t * write_data, uint_t write){ + uint_t i = 0, written_frames = 0; + + if (write > s->max_size) { + AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, " + "but only %d can be written at a time\n", write, s->path, s->max_size); + write = s->max_size; + } + + for (i = 0; i < write; i++) { + s->scratch_data[i] = HTOLES(FLOAT_TO_SHORT(write_data->data[i])); + } + written_frames = fwrite(s->scratch_data, 2, write, s->fid); + + if (written_frames != write) { + AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, " + "but only %d could be written\n", write, s->path, written_frames); + } + s->total_frames_written += written_frames; + return; +} + +void aubio_sink_wavwrite_close(aubio_sink_wavwrite_t * s) { + uint_t data_size = s->total_frames_written * s->bitspersample * s->channels / 8; + unsigned char buf[5]; + if (!s->fid) return; + // ChunkSize + fseek(s->fid, 4, SEEK_SET); + fwrite(write_little_endian(data_size + 36, buf, 4), 4, 1, s->fid); + // Subchunk2Size + fseek(s->fid, 40, SEEK_SET); + fwrite(write_little_endian(data_size, buf, 4), 4, 1, s->fid); + // close file + if (fclose(s->fid)) { + AUBIO_ERR("sink_wavwrite: Error closing file %s (%s)\n", s->path, strerror(errno)); + } + s->fid = NULL; +} + +void del_aubio_sink_wavwrite(aubio_sink_wavwrite_t * s){ + if (!s) return; + aubio_sink_wavwrite_close(s); + AUBIO_FREE(s->scratch_data); + AUBIO_FREE(s); +} + +#endif /* HAVE_WAVWRITE */ diff --git a/src/io/sink_wavwrite.h b/src/io/sink_wavwrite.h new file mode 100644 index 00000000..9f73144b --- /dev/null +++ b/src/io/sink_wavwrite.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2014 Paul Brossier + + 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 . + +*/ + +#ifndef _AUBIO_SINK_WAVWRITE_H +#define _AUBIO_SINK_WAVWRITE_H + +/** \file + + Write to file using [libsndfile](http://www.mega-nerd.com/libsndfile/) + + Avoid including this file directly! Prefer using ::aubio_sink_t instead to + make your code portable. + + To read from file, use ::aubio_source_t. + + \example io/test-sink_wavwrite.c + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _aubio_sink_wavwrite_t aubio_sink_wavwrite_t; + +/** + + create new ::aubio_sink_wavwrite_t + + \param uri the file path or uri to write to + \param samplerate sample rate to write the file at + + \return newly created ::aubio_sink_wavwrite_t + + Creates a new sink object. + +*/ +aubio_sink_wavwrite_t * new_aubio_sink_wavwrite(char_t * uri, uint_t samplerate); + +/** + + write monophonic vector of length hop_size to sink + + \param s sink, created with ::new_aubio_sink_wavwrite + \param write_data ::fvec_t samples to write to sink + \param write number of frames to write + +*/ +void aubio_sink_wavwrite_do(aubio_sink_wavwrite_t * s, fvec_t * write_data, uint_t write); + +/** + + close sink and cleanup memory + + \param s sink, created with ::new_aubio_sink_wavwrite + +*/ +void del_aubio_sink_wavwrite(aubio_sink_wavwrite_t * s); + +#ifdef __cplusplus +} +#endif + +#endif /* _AUBIO_SINK_WAVWRITE_H */ diff --git a/tests/src/io/test-sink_wavwrite.c b/tests/src/io/test-sink_wavwrite.c new file mode 100644 index 00000000..49f309bc --- /dev/null +++ b/tests/src/io/test-sink_wavwrite.c @@ -0,0 +1,53 @@ +#define AUBIO_UNSTABLE 1 +#include +#include "utils_tests.h" + +// this file uses the unstable aubio api, please use aubio_sink instead +// see src/io/sink.h and tests/src/sink/test-sink.c + +int main (int argc, char **argv) +{ + sint_t err = 0; + + if (argc < 3) { + err = 2; + PRINT_ERR("not enough arguments\n"); + PRINT_MSG("usage: %s [samplerate]\n", argv[0]); + return err; + } + +#ifdef HAVE_WAVWRITE + uint_t samplerate = 44100; + uint_t hop_size = 512; + uint_t n_frames = 0, read = 0; + + char_t *source_path = argv[1]; + char_t *sink_path = argv[2]; + if ( argc == 4 ) samplerate = atoi(argv[3]); + + fvec_t *vec = new_fvec(hop_size); + aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size); + if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i); + aubio_sink_wavwrite_t *o = new_aubio_sink_wavwrite(sink_path, samplerate); + + if (!i || !o) { err = 1; goto beach; } + + do { + aubio_source_do(i, vec, &read); + aubio_sink_wavwrite_do(o, vec, read); + n_frames += read; + } while ( read == hop_size ); + + PRINT_MSG("%d frames read from %s\n written to %s at %dHz\n", + n_frames, source_path, sink_path, samplerate); + +beach: + del_aubio_source(i); + del_aubio_sink_wavwrite(o); + del_fvec(vec); +#else + err = 3; + PRINT_ERR("aubio was not compiled with aubio_sink_wavwrite\n"); +#endif /* HAVE_WAVWRITE */ + return err; +} diff --git a/wscript b/wscript index e9bed7fe..566e29b8 100644 --- a/wscript +++ b/wscript @@ -229,6 +229,7 @@ def configure(ctx): ctx.msg('Checking for all libav libraries', 'not found', color = 'YELLOW') ctx.define('HAVE_WAVREAD', 1) + ctx.define('HAVE_WAVWRITE', 1) # use memcpy hacks if (ctx.options.enable_memcpy == True): -- 2.11.0