diff options
Diffstat (limited to 'libs/audiographer/src')
-rw-r--r-- | libs/audiographer/src/gdither/gdither.cc | 474 | ||||
-rw-r--r-- | libs/audiographer/src/gdither/gdither.h | 92 | ||||
-rw-r--r-- | libs/audiographer/src/gdither/gdither_types.h | 48 | ||||
-rw-r--r-- | libs/audiographer/src/gdither/gdither_types_internal.h | 74 | ||||
-rw-r--r-- | libs/audiographer/src/gdither/noise.h | 38 | ||||
-rw-r--r-- | libs/audiographer/src/routines.cc | 7 | ||||
-rw-r--r-- | libs/audiographer/src/sample_format_converter.cc | 190 | ||||
-rw-r--r-- | libs/audiographer/src/sndfile_base.cc | 56 | ||||
-rw-r--r-- | libs/audiographer/src/sndfile_reader.cc | 67 | ||||
-rw-r--r-- | libs/audiographer/src/sndfile_writer.cc | 73 | ||||
-rw-r--r-- | libs/audiographer/src/sr_converter.cc | 218 | ||||
-rw-r--r-- | libs/audiographer/src/utils.cc | 14 |
12 files changed, 1351 insertions, 0 deletions
diff --git a/libs/audiographer/src/gdither/gdither.cc b/libs/audiographer/src/gdither/gdither.cc new file mode 100644 index 0000000000..966da47b06 --- /dev/null +++ b/libs/audiographer/src/gdither/gdither.cc @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "gdither_types_internal.h" +#include "gdither.h" +#include "noise.h" + +/* this monstrosity is necessary to get access to lrintf() and random(). + whoever is writing the glibc headers <cmath> and <cstdlib> should be + hauled off to a programmer re-education camp. for the rest of + their natural lives. or longer. <paul@linuxaudiosystems.com> +*/ + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 +#ifdef __cplusplus +#include <cmath> +#else +#include <math.h> +#endif + +#undef __USE_SVID +#define __USE_SVID 1 +#ifdef __cplusplus +#include <cstdlib> +#else +#include <stdlib.h> +#endif + +#include <sys/types.h> + +/* Lipshitz's minimally audible FIR, only really works for 46kHz-ish signals */ +static const float shaped_bs[] = { 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f }; + +/* Some useful constants */ +#define MAX_U8 255 +#define MIN_U8 0 +#define SCALE_U8 128.0f + +#define MAX_S16 32767 +#define MIN_S16 -32768 +#define SCALE_S16 32768.0f + +#define MAX_S24 8388607 +#define MIN_S24 -8388608 +#define SCALE_S24 8388608.0f + +GDither gdither_new(GDitherType type, uint32_t channels, + + GDitherSize bit_depth, int dither_depth) +{ + GDither s; + + s = (GDither)calloc(1, sizeof(struct GDither_s)); + s->type = type; + s->channels = channels; + s->bit_depth = (int)bit_depth; + + if (dither_depth <= 0 || dither_depth > (int)bit_depth) { + dither_depth = (int)bit_depth; + } + s->dither_depth = dither_depth; + + s->scale = (float)(1LL << (dither_depth - 1)); + if (bit_depth == GDitherFloat || bit_depth == GDitherDouble) { + s->post_scale_fp = 1.0f / s->scale; + s->post_scale = 0; + } else { + s->post_scale_fp = 0.0f; + s->post_scale = 1 << ((int)bit_depth - dither_depth); + } + + switch (bit_depth) { + case GDither8bit: + /* Unsigned 8 bit */ + s->bias = 1.0f; + s->clamp_u = 255; + s->clamp_l = 0; + break; + case GDither16bit: + /* Signed 16 bit */ + s->bias = 0.0f; + s->clamp_u = 32767; + s->clamp_l = -32768; + break; + case GDither32bit: + /* Signed 24 bit, in upper 24 bits of 32 bit word */ + s->bias = 0.0f; + s->clamp_u = 8388607; + s->clamp_l = -8388608; + break; + case GDitherFloat: + /* normalised float */ + s->bias = 0.0f; + s->clamp_u = lrintf(s->scale); + s->clamp_l = lrintf(-s->scale); + break; + case GDitherDouble: + /* normalised float */ + s->bias = 0.0f; + s->clamp_u = lrintf(s->scale); + s->clamp_l = lrintf(-s->scale); + break; + case 23: + /* special performance test case */ + s->scale = SCALE_S24; + s->post_scale = 256; + s->bias = 0.0f; + s->clamp_u = 8388607; + s->clamp_l = -8388608; + break; + default: + /* Not a bit depth we can handle */ + free(s); + + return NULL; + break; + } + + switch (type) { + case GDitherNone: + case GDitherRect: + /* No state */ + break; + + case GDitherTri: + /* The last whitenoise sample */ + s->tri_state = (float *) calloc(channels, sizeof(float)); + break; + + case GDitherShaped: + /* The error from the last few samples encoded */ + s->shaped_state = (GDitherShapedState*) + calloc(channels, sizeof(GDitherShapedState)); + break; + } + + return s; +} + +void gdither_free(GDither s) +{ + if (s) { + free(s->tri_state); + free(s->shaped_state); + free(s); + } +} + +inline static void gdither_innner_loop(const GDitherType dt, + const uint32_t stride, const float bias, const float scale, + + const uint32_t post_scale, const int bit_depth, + const uint32_t channel, const uint32_t length, float *ts, + + GDitherShapedState *ss, float const *x, void *y, const int clamp_u, + + const int clamp_l) +{ + uint32_t pos, i; + uint8_t *o8 = (uint8_t*) y; + int16_t *o16 = (int16_t*) y; + int32_t *o32 = (int32_t*) y; + float tmp, r, ideal; + int64_t clamped; + + i = channel; + for (pos = 0; pos < length; pos++, i += stride) { + tmp = x[i] * scale + bias; + + switch (dt) { + case GDitherNone: + break; + case GDitherRect: + tmp -= GDITHER_NOISE; + break; + case GDitherTri: + r = GDITHER_NOISE - 0.5f; + tmp -= r - ts[channel]; + ts[channel] = r; + break; + case GDitherShaped: + /* Save raw value for error calculations */ + ideal = tmp; + + /* Run FIR and add white noise */ + ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f; + tmp += ss->buffer[ss->phase] * shaped_bs[0] + + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK] + * shaped_bs[1] + + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK] + * shaped_bs[2] + + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK] + * shaped_bs[3] + + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK] + * shaped_bs[4]; + + /* Roll buffer and store last error */ + ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK; + ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal; + break; + } + + clamped = lrintf(tmp); + if (clamped > clamp_u) { + clamped = clamp_u; + } else if (clamped < clamp_l) { + clamped = clamp_l; + } + + switch (bit_depth) { + case GDither8bit: + o8[i] = (u_int8_t) (clamped * post_scale); + break; + case GDither16bit: + o16[i] = (int16_t) (clamped * post_scale); + break; + case GDither32bit: + o32[i] = (int32_t) (clamped * post_scale); + break; + } + } +} + +/* floating pint version of the inner loop function */ +inline static void gdither_innner_loop_fp(const GDitherType dt, + const uint32_t stride, const float bias, const float scale, + + const float post_scale, const int bit_depth, + const uint32_t channel, const uint32_t length, float *ts, + + GDitherShapedState *ss, float const *x, void *y, const int clamp_u, + + const int clamp_l) +{ + uint32_t pos, i; + float *oflt = (float*) y; + double *odbl = (double*) y; + float tmp, r, ideal; + double clamped; + + i = channel; + for (pos = 0; pos < length; pos++, i += stride) { + tmp = x[i] * scale + bias; + + switch (dt) { + case GDitherNone: + break; + case GDitherRect: + tmp -= GDITHER_NOISE; + break; + case GDitherTri: + r = GDITHER_NOISE - 0.5f; + tmp -= r - ts[channel]; + ts[channel] = r; + break; + case GDitherShaped: + /* Save raw value for error calculations */ + ideal = tmp; + + /* Run FIR and add white noise */ + ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f; + tmp += ss->buffer[ss->phase] * shaped_bs[0] + + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK] + * shaped_bs[1] + + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK] + * shaped_bs[2] + + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK] + * shaped_bs[3] + + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK] + * shaped_bs[4]; + + /* Roll buffer and store last error */ + ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK; + ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal; + break; + } + + clamped = rintf(tmp); + if (clamped > clamp_u) { + clamped = clamp_u; + } else if (clamped < clamp_l) { + clamped = clamp_l; + } + + switch (bit_depth) { + case GDitherFloat: + oflt[i] = (float) (clamped * post_scale); + break; + case GDitherDouble: + odbl[i] = (double) (clamped * post_scale); + break; + } + } +} + +#define GDITHER_CONV_BLOCK 512 + +void gdither_run(GDither s, uint32_t channel, uint32_t length, + double const *x, void *y) +{ + float conv[GDITHER_CONV_BLOCK]; + uint32_t i, pos; + char *ycast = (char *)y; + + int step; + + switch (s->bit_depth) { + case GDither8bit: + step = 1; + break; + case GDither16bit: + step = 2; + break; + case GDither32bit: + case GDitherFloat: + step = 4; + break; + case GDitherDouble: + step = 8; + break; + default: + step = 0; + break; + } + + pos = 0; + while (pos < length) { + for (i=0; (i + pos) < length && i < GDITHER_CONV_BLOCK; i++) { + conv[i] = x[pos + i]; + } + gdither_runf(s, channel, i, conv, ycast + s->channels * step); + pos += i; + } +} + +void gdither_runf(GDither s, uint32_t channel, uint32_t length, + float const *x, void *y) +{ + uint32_t pos, i; + float tmp; + int64_t clamped; + GDitherShapedState *ss = NULL; + + if (!s || channel >= s->channels) { + return; + } + + if (s->shaped_state) { + ss = s->shaped_state + channel; + } + + if (s->type == GDitherNone && s->bit_depth == 23) { + int32_t *o32 = (int32_t*) y; + + for (pos = 0; pos < length; pos++) { + i = channel + (pos * s->channels); + tmp = x[i] * 8388608.0f; + + clamped = lrintf(tmp); + if (clamped > 8388607) { + clamped = 8388607; + } else if (clamped < -8388608) { + clamped = -8388608; + } + + o32[i] = (int32_t) (clamped * 256); + } + + return; + } + + /* some common case handling code - looks a bit wierd, but it allows + * the compiler to optimise out the branches in the inner loop */ + if (s->bit_depth == 8 && s->dither_depth == 8) { + switch (s->type) { + case GDitherNone: + gdither_innner_loop(GDitherNone, s->channels, 128.0f, SCALE_U8, + 1, 8, channel, length, NULL, NULL, x, y, + MAX_U8, MIN_U8); + break; + case GDitherRect: + gdither_innner_loop(GDitherRect, s->channels, 128.0f, SCALE_U8, + 1, 8, channel, length, NULL, NULL, x, y, + MAX_U8, MIN_U8); + break; + case GDitherTri: + gdither_innner_loop(GDitherTri, s->channels, 128.0f, SCALE_U8, + 1, 8, channel, length, s->tri_state, + NULL, x, y, MAX_U8, MIN_U8); + break; + case GDitherShaped: + gdither_innner_loop(GDitherShaped, s->channels, 128.0f, SCALE_U8, + 1, 8, channel, length, NULL, + ss, x, y, MAX_U8, MIN_U8); + break; + } + } else if (s->bit_depth == 16 && s->dither_depth == 16) { + switch (s->type) { + case GDitherNone: + gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S16, + 1, 16, channel, length, NULL, NULL, x, y, + MAX_S16, MIN_S16); + break; + case GDitherRect: + gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S16, + 1, 16, channel, length, NULL, NULL, x, y, + MAX_S16, MIN_S16); + break; + case GDitherTri: + gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S16, + 1, 16, channel, length, s->tri_state, + NULL, x, y, MAX_S16, MIN_S16); + break; + case GDitherShaped: + gdither_innner_loop(GDitherShaped, s->channels, 0.0f, + SCALE_S16, 1, 16, channel, length, NULL, + ss, x, y, MAX_S16, MIN_S16); + break; + } + } else if (s->bit_depth == 32 && s->dither_depth == 24) { + switch (s->type) { + case GDitherNone: + gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S24, + 256, 32, channel, length, NULL, NULL, x, + y, MAX_S24, MIN_S24); + break; + case GDitherRect: + gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S24, + 256, 32, channel, length, NULL, NULL, x, + y, MAX_S24, MIN_S24); + break; + case GDitherTri: + gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S24, + 256, 32, channel, length, s->tri_state, + NULL, x, y, MAX_S24, MIN_S24); + break; + case GDitherShaped: + gdither_innner_loop(GDitherShaped, s->channels, 0.0f, SCALE_S24, + 256, 32, channel, length, + NULL, ss, x, y, MAX_S24, MIN_S24); + break; + } + } else if (s->bit_depth == GDitherFloat || s->bit_depth == GDitherDouble) { + gdither_innner_loop_fp(s->type, s->channels, s->bias, s->scale, + s->post_scale_fp, s->bit_depth, channel, length, + s->tri_state, ss, x, y, s->clamp_u, s->clamp_l); + } else { + /* no special case handling, just process it from the struct */ + + gdither_innner_loop(s->type, s->channels, s->bias, s->scale, + s->post_scale, s->bit_depth, channel, + length, s->tri_state, ss, x, y, s->clamp_u, + s->clamp_l); + } +} + +/* vi:set ts=8 sts=4 sw=4: */ diff --git a/libs/audiographer/src/gdither/gdither.h b/libs/audiographer/src/gdither/gdither.h new file mode 100644 index 0000000000..d2b2657f32 --- /dev/null +++ b/libs/audiographer/src/gdither/gdither.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef GDITHER_H +#define GDITHER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "gdither_types.h" + +/* Create and initialise a state structure, takes a dither type, a number of + * channels and a bit depth as input + * + * The Dither type is one of + * + * GDitherNone - straight nearest neighbour rounding. Theres no pressing + * reason to do this at 8 or 16 bit, but you might want to at 24, for some + * reason. At the lest it will save you writing int->float conversion code, + * which is arder than it sounds. + * + * GDitherRect - mathematically most accurate, lowest noise floor, but not + * that good for audio. It is the fastest though. + * + * GDitherTri - a happy medium between Rectangular and Shaped, reasonable + * noise floor, not too obvious, quite fast. + * + * GDitherShaped - should have the least audible impact, but has the highest + * noise floor, fairly CPU intensive. Not advisible if your going to apply + * any frequency manipulation afterwards. + * + * channels, sets the number of channels in the output data, output data will + * be written interleaved into the area given to gdither_run(). Set to 1 + * if you are not working with interleaved buffers. + * + * bit depth, sets the bit width of the output sample data, it can be one of: + * + * GDither8bit - 8 bit unsiged + * GDither16bit - 16 bit signed + * GDither32bit - 24+bits in upper bits of a 32 bit word + * GDitherFloat - IEEE floating point (32bits) + * GDitherDouble - Double precision IEEE floating point (64bits) + * + * dither_depth, set the number of bits before the signal will be truncated to, + * eg. 16 will produce an output stream with 16bits-worth of signal. Setting to + * zero or greater than the width of the output format will dither to the + * maximum precision allowed by the output format. + */ +GDither gdither_new(GDitherType type, uint32_t channels, + + GDitherSize bit_depth, int dither_depth); + +/* Frees memory used by gdither_new. + */ +void gdither_free(GDither s); + +/* Applies dithering to the supplied signal. + * + * channel is the channel number you are processing (0 - channles-1), length is + * the length of the input, in samples, x is the input samples (float), y is + * where the output samples will be written, it should have the approaprate + * type for the chosen bit depth + */ +void gdither_runf(GDither s, uint32_t channel, uint32_t length, + float const *x, void *y); + +/* see gdither_runf, vut input argument is double format */ +void gdither_run(GDither s, uint32_t channel, uint32_t length, + double const *x, void *y); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/audiographer/src/gdither/gdither_types.h b/libs/audiographer/src/gdither/gdither_types.h new file mode 100644 index 0000000000..bcc0097d7f --- /dev/null +++ b/libs/audiographer/src/gdither/gdither_types.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef GDITHER_TYPES_H +#define GDITHER_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + GDitherNone = 0, + GDitherRect, + GDitherTri, + GDitherShaped +} GDitherType; + +typedef enum { + GDither8bit = 8, + GDither16bit = 16, + GDither32bit = 32, + GDitherFloat = 25, + GDitherDouble = 54 +} GDitherSize; + +typedef void *GDither; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/audiographer/src/gdither/gdither_types_internal.h b/libs/audiographer/src/gdither/gdither_types_internal.h new file mode 100644 index 0000000000..6cb0c48af9 --- /dev/null +++ b/libs/audiographer/src/gdither/gdither_types_internal.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef GDITHER_TYPES_H +#define GDITHER_TYPES_H + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define GDITHER_SH_BUF_SIZE 8 +#define GDITHER_SH_BUF_MASK 7 + +/* this must agree with whats in gdither_types.h */ +typedef enum { + GDitherNone = 0, + GDitherRect, + GDitherTri, + GDitherShaped +} GDitherType; + +typedef enum { + GDither8bit = 8, + GDither16bit = 16, + GDither32bit = 32, + GDitherFloat = 25, + GDitherDouble = 54 +} GDitherSize; + +typedef struct { + uint32_t phase; + float buffer[GDITHER_SH_BUF_SIZE]; +} GDitherShapedState; + +typedef struct GDither_s { + GDitherType type; + uint32_t channels; + uint32_t bit_depth; + uint32_t dither_depth; + float scale; + uint32_t post_scale; + float post_scale_fp; + float bias; + + int clamp_u; + + int clamp_l; + float *tri_state; + GDitherShapedState *shaped_state; +} *GDither; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/audiographer/src/gdither/noise.h b/libs/audiographer/src/gdither/noise.h new file mode 100644 index 0000000000..96a582ef9b --- /dev/null +++ b/libs/audiographer/src/gdither/noise.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2000-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef NOISE_H +#define NOISE_H + +/* Can be overrriden with any code that produces whitenoise between 0.0f and + * 1.0f, eg (random() / (float)RAND_MAX) should be a good source of noise, but + * its expensive */ +#ifndef GDITHER_NOISE +#define GDITHER_NOISE gdither_noise() +#endif + +inline static float gdither_noise() +{ + static uint32_t rnd = 23232323; + rnd = (rnd * 196314165) + 907633515; + + return rnd * 2.3283064365387e-10f; +} + +#endif diff --git a/libs/audiographer/src/routines.cc b/libs/audiographer/src/routines.cc new file mode 100644 index 0000000000..b97653be75 --- /dev/null +++ b/libs/audiographer/src/routines.cc @@ -0,0 +1,7 @@ +#include "audiographer/routines.h" + +namespace AudioGrapher +{ +Routines::compute_peak_t Routines::_compute_peak = &Routines::default_compute_peak; +Routines::apply_gain_to_buffer_t Routines::_apply_gain_to_buffer = &Routines::default_apply_gain_to_buffer; +} diff --git a/libs/audiographer/src/sample_format_converter.cc b/libs/audiographer/src/sample_format_converter.cc new file mode 100644 index 0000000000..5b2d3d6e8c --- /dev/null +++ b/libs/audiographer/src/sample_format_converter.cc @@ -0,0 +1,190 @@ +#include "audiographer/sample_format_converter.h" + +#include "gdither/gdither.h" +#include "audiographer/exception.h" + +#include <boost/format.hpp> + +#include <cstring> + +namespace AudioGrapher +{ + +template <typename TOut> +SampleFormatConverter<TOut>::SampleFormatConverter (uint32_t channels) : + channels (channels), + dither (0), + data_out_size (0), + data_out (0), + clip_floats (false) +{ +} + +template <> +void +SampleFormatConverter<float>::init (nframes_t max_frames, int type, int data_width) +{ + if (data_width != 32) { throw Exception (*this, "Unsupported data width"); } + init_common (max_frames); + dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width); +} + +template <> +void +SampleFormatConverter<int32_t>::init (nframes_t max_frames, int type, int data_width) +{ + if(data_width < 24) { throw Exception (*this, "Use SampleFormatConverter<int16_t> for data widths < 24"); } + + init_common (max_frames); + + if (data_width == 24) { + dither = gdither_new ((GDitherType) type, channels, GDither32bit, data_width); + } else if (data_width == 32) { + dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width); + } else { + throw Exception (*this, "Unsupported data width"); + } +} + +template <> +void +SampleFormatConverter<int16_t>::init (nframes_t max_frames, int type, int data_width) +{ + if (data_width != 16) { throw Exception (*this, "Unsupported data width"); } + init_common (max_frames); + dither = gdither_new ((GDitherType) type, channels, GDither16bit, data_width); +} + +template <> +void +SampleFormatConverter<uint8_t>::init (nframes_t max_frames, int type, int data_width) +{ + if (data_width != 8) { throw Exception (*this, "Unsupported data width"); } + init_common (max_frames); + dither = gdither_new ((GDitherType) type, channels, GDither8bit, data_width); +} + +template <typename TOut> +void +SampleFormatConverter<TOut>::init_common (nframes_t max_frames ) +{ + reset(); + if (max_frames > data_out_size) { + + delete[] data_out; + + data_out = new TOut[max_frames]; + data_out_size = max_frames; + } +} + +template <typename TOut> +SampleFormatConverter<TOut>::~SampleFormatConverter () +{ + reset(); +} + +template <typename TOut> +void +SampleFormatConverter<TOut>::reset() +{ + if (dither) { + gdither_free (dither); + dither = 0; + } + + delete[] data_out; + data_out_size = 0; + data_out = 0; + + clip_floats = false; +} + +/* Basic const version of process() */ +template <typename TOut> +void +SampleFormatConverter<TOut>::process (ProcessContext<float> const & c_in) +{ + float const * const data = c_in.data(); + nframes_t const frames = c_in.frames(); + + check_frame_count (frames); + + /* Do conversion */ + + for (uint32_t chn = 0; chn < channels; ++chn) { + gdither_runf (dither, chn, frames / channels, data, data_out); + } + + /* Write forward */ + + ProcessContext<TOut> c_out(c_in, data_out); + output (c_out); +} + +/* Basic non-const version of process(), calls the const one */ +template<typename TOut> +void +SampleFormatConverter<TOut>::process (ProcessContext<float> & c_in) +{ + process (static_cast<ProcessContext<float> const &> (c_in)); +} + +/* template specialization for float, in-place processing (non-const) */ +template<> +void +SampleFormatConverter<float>::process (ProcessContext<float> & c_in) +{ + nframes_t frames = c_in.frames(); + float * data = c_in.data(); + + if (clip_floats) { + for (nframes_t x = 0; x < frames; ++x) { + if (data[x] > 1.0f) { + data[x] = 1.0f; + } else if (data[x] < -1.0f) { + data[x] = -1.0f; + } + } + } + + output (c_in); +} + +/* template specialized const version, copies the data, and calls the non-const version */ +template<> +void +SampleFormatConverter<float>::process (ProcessContext<float> const & c_in) +{ + // Make copy of data and pass it to non-const version + nframes_t frames = c_in.frames(); + check_frame_count (frames); + memcpy (data_out, c_in.data(), frames * sizeof(float)); + + ProcessContext<float> c (c_in, data_out); + process (c); +} + +template<typename TOut> +void +SampleFormatConverter<TOut>::check_frame_count(nframes_t frames) +{ + if (frames % channels != 0) { + throw Exception (*this, boost::str (boost::format ( + "Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels") + % frames % channels)); + } + + if (frames > data_out_size) { + throw Exception (*this, boost::str (boost::format ( + "Too many frames given to process(), %1% instad of %2%") + % frames % data_out_size)); + } +} + +template class SampleFormatConverter<uint8_t>; +template class SampleFormatConverter<int16_t>; +template class SampleFormatConverter<int32_t>; +template class SampleFormatConverter<float>; + +} // namespace diff --git a/libs/audiographer/src/sndfile_base.cc b/libs/audiographer/src/sndfile_base.cc new file mode 100644 index 0000000000..8d12f9341b --- /dev/null +++ b/libs/audiographer/src/sndfile_base.cc @@ -0,0 +1,56 @@ +#include "audiographer/sndfile_base.h" +#include "audiographer/exception.h" + +#include <boost/format.hpp> + +namespace AudioGrapher +{ + +using std::string; +using boost::str; +using boost::format; + +/* SndfileWriterBase */ + +SndfileBase::SndfileBase (ChannelCount channels, nframes_t samplerate, int format, string const & path) + : path (path) +{ + char errbuf[256]; + + sf_info.channels = channels; + sf_info.samplerate = samplerate; + sf_info.format = format; + + if (!sf_format_check (&sf_info)) { + throw Exception (*this, "Invalid format in constructor"); + } + + if (path.length() == 0) { + throw Exception (*this, "No output file specified"); + } + + /* TODO add checks that the directory path exists, and also + check if we are overwriting an existing file... + */ + + // Open file + if (path.compare ("temp")) { + if ((sndfile = sf_open (path.c_str(), SFM_WRITE, &sf_info)) == 0) { + sf_error_str (0, errbuf, sizeof (errbuf) - 1); + throw Exception (*this, str (boost::format ("Cannot open output file \"%1%\" (%2%)") % path % errbuf)); + } + } else { + FILE * file; + if (!(file = tmpfile ())) { + throw Exception (*this, "Cannot open tempfile"); + } + sndfile = sf_open_fd (fileno(file), SFM_RDWR, &sf_info, true); + } +} + +SndfileBase::~SndfileBase () +{ + sf_close (sndfile); +} + +} // namespace diff --git a/libs/audiographer/src/sndfile_reader.cc b/libs/audiographer/src/sndfile_reader.cc new file mode 100644 index 0000000000..0508110314 --- /dev/null +++ b/libs/audiographer/src/sndfile_reader.cc @@ -0,0 +1,67 @@ +#include "audiographer/sndfile_reader.h" + +#include <boost/format.hpp> + +#include "audiographer/exception.h" + +namespace AudioGrapher +{ + +template<typename T> +SndfileReader<T>::SndfileReader (ChannelCount channels, nframes_t samplerate, int format, std::string path) + : SndfileBase (channels, samplerate, format, path) +{ + init (); +} + +template<typename T> +nframes_t +SndfileReader<T>::seek (nframes_t frames, SeekType whence) +{ + return sf_seek (sndfile, frames, whence); +} + +template<typename T> +nframes_t +SndfileReader<T>::read (ProcessContext<T> & context) +{ + if (context.channels() != sf_info.channels) { + throw Exception (*this, boost::str (boost::format ( + "ProcessContext given to read() has a wrong amount of channels: %1% instead of %2%") + % context.channels() % sf_info.channels)); + } + + nframes_t frames_read = (*read_func) (sndfile, context.data(), context.frames()); + if (frames_read < context.frames()) { + context.set_flag (ProcessContext<T>::EndOfInput); + } + output (context); + return frames_read; +} + +template<> +void +SndfileReader<short>::init() +{ + read_func = &sf_read_short; +} + +template<> +void +SndfileReader<int>::init() +{ + read_func = &sf_read_int; +} + +template<> +void +SndfileReader<float>::init() +{ + read_func = &sf_read_float; +} + +template class SndfileReader<short>; +template class SndfileReader<int>; +template class SndfileReader<float>; + +} // namespace
\ No newline at end of file diff --git a/libs/audiographer/src/sndfile_writer.cc b/libs/audiographer/src/sndfile_writer.cc new file mode 100644 index 0000000000..d12d6b943b --- /dev/null +++ b/libs/audiographer/src/sndfile_writer.cc @@ -0,0 +1,73 @@ +#include "audiographer/sndfile_writer.h" +#include "audiographer/exception.h" + +#include <cstring> + +#include <boost/format.hpp> + +namespace AudioGrapher +{ + +using std::string; +using boost::str; +using boost::format; + +template <typename T> +SndfileWriter<T>::SndfileWriter (ChannelCount channels, nframes_t samplerate, int format, string const & path) : + SndfileBase (channels, samplerate, format, path) +{ + // init write function + init (); +} + +template <> +void +SndfileWriter<float>::init () +{ + write_func = &sf_write_float; +} + +template <> +void +SndfileWriter<int>::init () +{ + write_func = &sf_write_int; +} + +template <> +void +SndfileWriter<short>::init () +{ + write_func = &sf_write_short; +} + +template <typename T> +void +SndfileWriter<T>::process (ProcessContext<T> const & c) +{ + if (c.channels() != sf_info.channels) { + throw Exception (*this, str (boost::format( + "Wrong number of channels given to process(), %1% instead of %2%") + % c.channels() % sf_info.channels)); + } + + char errbuf[256]; + nframes_t written = (*write_func) (sndfile, c.data(), c.frames()); + if (written != c.frames()) { + sf_error_str (sndfile, errbuf, sizeof (errbuf) - 1); + throw Exception (*this, str ( format("Could not write data to output file (%1%)") % errbuf)); + } + + if (c.has_flag(ProcessContext<T>::EndOfInput)) { + sf_write_sync (sndfile); + //#ifdef HAVE_SIGCPP + FileWritten (path); + //#endif + } +} + +template class SndfileWriter<short>; +template class SndfileWriter<int>; +template class SndfileWriter<float>; + +} // namespace diff --git a/libs/audiographer/src/sr_converter.cc b/libs/audiographer/src/sr_converter.cc new file mode 100644 index 0000000000..c61b3d0728 --- /dev/null +++ b/libs/audiographer/src/sr_converter.cc @@ -0,0 +1,218 @@ +#include "audiographer/sr_converter.h" +#include "audiographer/exception.h" + +#include <cmath> +#include <cstring> +#include <boost/format.hpp> + +#define ENABLE_DEBUG 0 + +#if ENABLE_DEBUG + #include <iostream> + #define DEBUG(str) std::cout << str << std::endl; +#else + #define DEBUG(str) +#endif + +namespace AudioGrapher +{ +using boost::format; +using boost::str; + +SampleRateConverter::SampleRateConverter (uint32_t channels) + : active (false) + , channels (channels) + , max_frames_in(0) + , leftover_data (0) + , leftover_frames (0) + , max_leftover_frames (0) + , data_out (0) + , data_out_size (0) + , src_state (0) +{ +} + +void +SampleRateConverter::init (nframes_t in_rate, nframes_t out_rate, int quality) +{ + reset(); + + if (in_rate == out_rate) { + src_data.src_ratio = 1; + return; + } + + active = true; + int err; + if ((src_state = src_new (quality, channels, &err)) == 0) { + throw Exception (*this, str (format ("Cannot initialize sample rate converter: %1%") % src_strerror (err))); + } + + src_data.src_ratio = (double) out_rate / (double) in_rate; +} + +SampleRateConverter::~SampleRateConverter () +{ + reset(); +} + +nframes_t +SampleRateConverter::allocate_buffers (nframes_t max_frames) +{ + if (!active) { return max_frames; } + + nframes_t max_frames_out = (nframes_t) ceil (max_frames * src_data.src_ratio); + if (data_out_size < max_frames_out) { + + delete[] data_out; + data_out = new float[max_frames_out]; + src_data.data_out = data_out; + + max_leftover_frames = 4 * max_frames; + leftover_data = (float *) realloc (leftover_data, max_leftover_frames * sizeof (float)); + if (!leftover_data) { + throw Exception (*this, "A memory allocation error occured"); + } + + max_frames_in = max_frames; + data_out_size = max_frames_out; + } + + return max_frames_out; +} + +void +SampleRateConverter::process (ProcessContext<float> const & c) +{ + if (!active) { + output (c); + return; + } + + nframes_t frames = c.frames(); + float * in = const_cast<float *> (c.data()); // TODO check if this is safe! + + if (frames > max_frames_in) { + throw Exception (*this, str (format ( + "process() called with too many frames, %1% instead of %2%") + % frames % max_frames_in)); + } + + if (frames % channels != 0) { + throw Exception (*this, boost::str (boost::format ( + "Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels") + % frames % channels)); + } + + int err; + bool first_time = true; + + do { + src_data.output_frames = data_out_size / channels; + src_data.data_out = data_out; + + if (leftover_frames > 0) { + + /* input data will be in leftover_data rather than data_in */ + + src_data.data_in = leftover_data; + + if (first_time) { + + /* first time, append new data from data_in into the leftover_data buffer */ + + memcpy (&leftover_data [leftover_frames * channels], in, frames * sizeof(float)); + src_data.input_frames = frames + leftover_frames; + } else { + + /* otherwise, just use whatever is still left in leftover_data; the contents + were adjusted using memmove() right after the last SRC call (see + below) + */ + + src_data.input_frames = leftover_frames; + } + + } else { + src_data.data_in = in; + src_data.input_frames = frames / channels; + } + + first_time = false; + + DEBUG ("data_in: " << src_data.data_in); + DEBUG ("input_frames: " << src_data.input_frames); + DEBUG ("data_out: " << src_data.data_out); + DEBUG ("output_frames: " << src_data.output_frames); + + if ((err = src_process (src_state, &src_data)) != 0) { + throw Exception (*this, str (format ("An error occured during sample rate conversion: %1%") % src_strerror (err))); + } + + leftover_frames = src_data.input_frames - src_data.input_frames_used; + + if (leftover_frames > 0) { + if (leftover_frames > max_leftover_frames) { + throw Exception(*this, "leftover frames overflowed"); + } + memmove (leftover_data, (char *) &src_data.data_in[src_data.input_frames_used * channels], + leftover_frames * channels * sizeof(float)); + } + + ProcessContext<float> c_out (c, data_out, src_data.output_frames_gen * channels); + if (!src_data.end_of_input || leftover_frames) { + c_out.remove_flag (ProcessContext<float>::EndOfInput); + } + output (c_out); + + DEBUG ("src_data.output_frames_gen: " << src_data.output_frames_gen << ", leftover_frames: " << leftover_frames); + + if (src_data.output_frames_gen == 0 && leftover_frames) { throw Exception (*this, boost::str (boost::format ( + "No output frames genereated with %1% leftover frames") + % leftover_frames)); } + + } while (leftover_frames > frames); + + // src_data.end_of_input has to be checked to prevent infinite recursion + if (!src_data.end_of_input && c.has_flag(ProcessContext<float>::EndOfInput)) { + set_end_of_input (c); + } +} + +void SampleRateConverter::set_end_of_input (ProcessContext<float> const & c) +{ + src_data.end_of_input = true; + + float f; + ProcessContext<float> const dummy (c, &f, 0, channels); + + /* No idea why this has to be done twice for all data to be written, + * but that just seems to be the way it is... + */ + process (dummy); + process (dummy); +} + + +void SampleRateConverter::reset () +{ + active = false; + max_frames_in = 0; + src_data.end_of_input = false; + + if (src_state) { + src_delete (src_state); + } + + leftover_frames = 0; + max_leftover_frames = 0; + if (leftover_data) { + free (leftover_data); + } + + data_out_size = 0; + delete [] data_out; + data_out = 0; +} + +} // namespace diff --git a/libs/audiographer/src/utils.cc b/libs/audiographer/src/utils.cc new file mode 100644 index 0000000000..018fad3113 --- /dev/null +++ b/libs/audiographer/src/utils.cc @@ -0,0 +1,14 @@ +#include "audiographer/utils.h" + +using namespace AudioGrapher; + +char const * Utils::zeros = 0; +unsigned long Utils::num_zeros = 0; + +void +Utils::free_resources() +{ + num_zeros = 0; + delete [] zeros; + zeros = 0; +}
\ No newline at end of file |