/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net * Licence for this file: LGPL v2.1 See LICENCE for details. */ #include #include #include #include #include "soxr.h" #include "data-io.h" #include "internal.h" #if AVUTIL_FOUND #include #endif #if WITH_DEV_TRACE #include #include int _soxr_trace_level; void _soxr_trace(char const * fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } #endif char const * soxr_version(void) { return "libsoxr-" SOXR_THIS_VERSION_STR; } typedef void sample_t; /* float or double */ typedef void (* fn_t)(void); typedef fn_t control_block_t[10]; #define resampler_input (*(sample_t * (*)(void *, sample_t * samples, size_t n))p->control_block[0]) #define resampler_process (*(void (*)(void *, size_t))p->control_block[1]) #define resampler_output (*(sample_t const * (*)(void *, sample_t * samples, size_t * n))p->control_block[2]) #define resampler_flush (*(void (*)(void *))p->control_block[3]) #define resampler_close (*(void (*)(void *))p->control_block[4]) #define resampler_delay (*(double (*)(void *))p->control_block[5]) #define resampler_sizes (*(void (*)(size_t * shared, size_t * channel))p->control_block[6]) #define resampler_create (*(char const * (*)(void * channel, void * shared, double io_ratio, soxr_quality_spec_t * q_spec, soxr_runtime_spec_t * r_spec, double scale))p->control_block[7]) #define resampler_set_io_ratio (*(void (*)(void *, double io_ratio, size_t len))p->control_block[8]) #define resampler_id (*(char const * (*)(void))p->control_block[9]) typedef void * resampler_t; /* For one channel. */ typedef void * resampler_shared_t; /* Between channels. */ typedef void (* deinterleave_t)(sample_t * * dest, soxr_datatype_t data_type, void const * * src0, size_t n, unsigned ch); typedef size_t (* interleave_t)(soxr_datatype_t data_type, void * * dest, sample_t const * const * src, size_t, unsigned, unsigned long *); struct soxr { unsigned num_channels; double io_ratio; soxr_error_t error; soxr_quality_spec_t q_spec; soxr_io_spec_t io_spec; soxr_runtime_spec_t runtime_spec; void * input_fn_state; soxr_input_fn_t input_fn; size_t max_ilen; resampler_shared_t shared; resampler_t * resamplers; control_block_t control_block; deinterleave_t deinterleave; interleave_t interleave; void * * channel_ptrs; size_t clips; unsigned long seed; int flushing; }; #if WITH_CR32 || WITH_CR32S || WITH_CR64 || WITH_CR64S #include "filter.h" #else #define lsx_to_3dB(x) ((x)/(x)) #endif soxr_quality_spec_t soxr_quality_spec(unsigned long recipe, unsigned long flags) { soxr_quality_spec_t spec, * p = &spec; unsigned q = recipe & 0xf; /* TODO: move to soxr-lsr.c: */ unsigned quality = q > SOXR_LSR2Q+2? SOXR_VHQ : q > SOXR_LSR2Q? SOXR_QQ : q; double rej; memset(p, 0, sizeof(*p)); if (quality > SOXR_PRECISIONQ) { p->e = "invalid quality type"; return spec; } flags |= quality < SOXR_LSR0Q ? RESET_ON_CLEAR : 0; p->phase_response = "\62\31\144"[(recipe & 0x30)>>4]; p->stopband_begin = 1; p->precision = quality == SOXR_QQ ? 0 : quality <= SOXR_16_BITQ ? 16 : quality <= SOXR_32_BITQ ? 4 + quality * 4 : quality <= SOXR_LSR2Q ? 55 - quality * 4 : /* TODO: move to soxr-lsr.c */ 0; rej = p->precision * linear_to_dB(2.); p->flags = flags; if (quality <= SOXR_32_BITQ || quality == SOXR_PRECISIONQ) { #define LOW_Q_BW0 (1385 / 2048.) /* 0.67625 rounded to be a FP exact. */ p->passband_end = quality == 1? LOW_Q_BW0 : 1 - .05 / lsx_to_3dB(rej); if (quality <= 2) p->flags &= ~SOXR_ROLLOFF_NONE, p->flags |= SOXR_ROLLOFF_MEDIUM; } else { /* TODO: move to soxr-lsr.c */ static float const bw[] = {.931f, .832f, .663f}; p->passband_end = bw[quality - SOXR_LSR0Q]; if (quality == SOXR_LSR2Q) { p->flags &= ~SOXR_ROLLOFF_NONE; p->flags |= SOXR_ROLLOFF_LSR2Q | SOXR_PROMOTE_TO_LQ; } } if (recipe & SOXR_STEEP_FILTER) p->passband_end = 1 - .01 / lsx_to_3dB(rej); return spec; } char const * soxr_engine(soxr_t p) { return resampler_id(); } size_t * soxr_num_clips(soxr_t p) { return &p->clips; } soxr_error_t soxr_error(soxr_t p) { return p->error; } soxr_runtime_spec_t soxr_runtime_spec(unsigned num_threads) { soxr_runtime_spec_t spec, * p = &spec; memset(p, 0, sizeof(*p)); p->log2_min_dft_size = 10; p->log2_large_dft_size = 17; p->coef_size_kbytes = 400; p->num_threads = num_threads; return spec; } soxr_io_spec_t soxr_io_spec( soxr_datatype_t itype, soxr_datatype_t otype) { soxr_io_spec_t spec, * p = &spec; memset(p, 0, sizeof(*p)); if ((itype | otype) >= SOXR_SPLIT * 2) p->e = "invalid io datatype(s)"; else { p->itype = itype; p->otype = otype; p->scale = 1; } return spec; } #if (WITH_CR32S && WITH_CR32) || (WITH_CR64S && WITH_CR64) #if defined __GNUC__ && defined __x86_64__ #define CPUID(type, eax_, ebx_, ecx_, edx_) \ __asm__ __volatile__ ( \ "cpuid \n\t" \ : "=a" (eax_), "=b" (ebx_), "=c" (ecx_), "=d" (edx_) \ : "a" (type), "c" (0)); #elif defined __GNUC__ && defined __i386__ #define CPUID(type, eax_, ebx_, ecx_, edx_) \ __asm__ __volatile__ ( \ "mov %%ebx, %%edi \n\t" \ "cpuid \n\t" \ "xchg %%edi, %%ebx \n\t" \ : "=a" (eax_), "=D" (ebx_), "=c" (ecx_), "=d" (edx_) \ : "a" (type), "c" (0)); #elif defined _M_X64 && defined _MSC_VER && _MSC_VER > 1500 void __cpuidex(int CPUInfo[4], int info_type, int ecxvalue); #pragma intrinsic(__cpuidex) #define CPUID(type, eax_, ebx_, ecx_, edx_) do { \ int regs[4]; \ __cpuidex(regs, type, 0); \ eax_ = regs[0], ebx_ = regs[1], ecx_ = regs[2], edx_ = regs[3]; \ } while(0) #elif defined _M_X64 && defined _MSC_VER void __cpuidex(int CPUInfo[4], int info_type); #pragma intrinsic(__cpuidex) #define CPUID(type, eax_, ebx_, ecx_, edx_) do { \ int regs[4]; \ __cpuidex(regs, type); \ eax_ = regs[0], ebx_ = regs[1], ecx_ = regs[2], edx_ = regs[3]; \ } while(0) #elif defined _M_IX86 && defined _MSC_VER #define CPUID(type, eax_, ebx_, ecx_, edx_) \ __asm pushad \ __asm mov eax, type \ __asm xor ecx, ecx \ __asm cpuid \ __asm mov eax_, eax \ __asm mov ebx_, ebx \ __asm mov ecx_, ecx \ __asm mov edx_, edx \ __asm popad #endif #endif #if WITH_CR32S && WITH_CR32 static bool cpu_has_simd32(void) { #if defined __x86_64__ || defined _M_X64 return true; #elif defined __i386__ || defined _M_IX86 enum {SSE = 1 << 25, SSE2 = 1 << 26}; unsigned eax_, ebx_, ecx_, edx_; CPUID(1, eax_, ebx_, ecx_, edx_); return (edx_ & (SSE|SSE2)) != 0; #elif defined AV_CPU_FLAG_NEON return !!(av_get_cpu_flags() & AV_CPU_FLAG_NEON); #else return false; #endif } static bool should_use_simd32(void) { char const * e; return ((e = getenv("SOXR_USE_SIMD" )))? !!atoi(e) : ((e = getenv("SOXR_USE_SIMD32")))? !!atoi(e) : cpu_has_simd32(); } #else #define should_use_simd32() true #endif #if WITH_CR64S && WITH_CR64 #if defined __GNUC__ #define XGETBV(type, eax_, edx_) \ __asm__ __volatile__ ( \ ".byte 0x0f, 0x01, 0xd0\n" \ : "=a"(eax_), "=d"(edx_) : "c" (type)); #elif defined _M_X64 && defined _MSC_FULL_VER && _MSC_FULL_VER >= 160040219 #include #define XGETBV(type, eax_, edx_) do { \ union {uint64_t x; uint32_t y[2];} a = {_xgetbv(0)}; \ eax_ = a.y[0], edx_ = a.y[1]; \ } while(0) #elif defined _M_IX86 && defined _MSC_VER #define XGETBV(type, eax_, edx_) \ __asm pushad \ __asm mov ecx, type \ __asm _emit 0x0f \ __asm _emit 0x01 \ __asm _emit 0xd0 \ __asm mov eax_, eax \ __asm mov edx_, edx \ __asm popad #else #define XGETBV(type, eax_, edx_) eax_ = edx_ = 0 #endif static bool cpu_has_simd64(void) { enum {OSXSAVE = 1 << 27, AVX = 1 << 28}; unsigned eax_, ebx_, ecx_, edx_; CPUID(1, eax_, ebx_, ecx_, edx_); if ((ecx_ & (OSXSAVE|AVX)) == (OSXSAVE|AVX)) { XGETBV(0, eax_, edx_); return (eax_ & 6) == 6; } return false; } static bool should_use_simd64(void) { char const * e; return ((e = getenv("SOXR_USE_SIMD" )))? !!atoi(e) : ((e = getenv("SOXR_USE_SIMD64")))? !!atoi(e) : cpu_has_simd64(); } #else #define should_use_simd64() true #endif extern control_block_t _soxr_rate32_cb, _soxr_rate32s_cb, _soxr_rate64_cb, _soxr_rate64s_cb, _soxr_vr32_cb; static void runtime_num(char const * env_name, int min, int max, unsigned * field) { char const * e = getenv(env_name); if (e) { int i = atoi(e); if (i >= min && i <= max) *field = (unsigned)i; } } static void runtime_flag(char const * env_name, unsigned n_bits, unsigned n_shift, unsigned long * flags) { char const * e = getenv(env_name); if (e) { int i = atoi(e); unsigned long mask = (1UL << n_bits) - 1; if (i >= 0 && i <= (int)mask) *flags &= ~(mask << n_shift), *flags |= ((unsigned long)i << n_shift); } } soxr_t soxr_create( double input_rate, double output_rate, unsigned num_channels, soxr_error_t * error0, soxr_io_spec_t const * io_spec, soxr_quality_spec_t const * q_spec, soxr_runtime_spec_t const * runtime_spec) { double io_ratio = output_rate!=0? input_rate!=0? input_rate / output_rate : -1 : input_rate!=0? -1 : 0; static const float datatype_full_scale[] = {1, 1, 65536.*32768, 32768}; soxr_t p = 0; soxr_error_t error = 0; #if WITH_DEV_TRACE #define _(x) (char)(sizeof(x)>=10? 'a'+(char)(sizeof(x)-10):'0'+(char)sizeof(x)) char const * e = getenv("SOXR_TRACE"); _soxr_trace_level = e? atoi(e) : 0; { static char const arch[] = {_(char), _(short), _(int), _(long), _(long long) , ' ', _(float), _(double), _(long double) , ' ', _(int *), _(int (*)(int)) , ' ', HAVE_BIGENDIAN ? 'B' : 'L' #if defined _OPENMP , ' ', 'O', 'M', 'P' #endif , 0}; #undef _ lsx_debug("arch: %s", arch); } #endif if (q_spec && q_spec->e) error = q_spec->e; else if (io_spec && (io_spec->itype | io_spec->otype) >= SOXR_SPLIT * 2) error = "invalid io datatype(s)"; if (!error && !(p = calloc(sizeof(*p), 1))) error = "malloc failed"; if (p) { control_block_t * control_block; p->q_spec = q_spec? *q_spec : soxr_quality_spec(SOXR_HQ, 0); if (q_spec) { /* Backwards compatibility with original API: */ if (p->q_spec.passband_end > 2) p->q_spec.passband_end /= 100; if (p->q_spec.stopband_begin > 2) p->q_spec.stopband_begin = 2 - p->q_spec.stopband_begin / 100; } p->io_ratio = io_ratio; p->num_channels = num_channels; if (io_spec) p->io_spec = *io_spec; else p->io_spec.scale = 1; p->runtime_spec = runtime_spec? *runtime_spec : soxr_runtime_spec(1); runtime_num("SOXR_MIN_DFT_SIZE", 8, 15, &p->runtime_spec.log2_min_dft_size); runtime_num("SOXR_LARGE_DFT_SIZE", 8, 20, &p->runtime_spec.log2_large_dft_size); runtime_num("SOXR_COEFS_SIZE", 100, 800, &p->runtime_spec.coef_size_kbytes); runtime_num("SOXR_NUM_THREADS", 0, 64, &p->runtime_spec.num_threads); runtime_flag("SOXR_COEF_INTERP", 2, 0, &p->runtime_spec.flags); runtime_flag("SOXR_STRICT_BUF", 1, 2, &p->runtime_spec.flags); runtime_flag("SOXR_NOSMALLINTOPT", 1, 3, &p->runtime_spec.flags); p->io_spec.scale *= datatype_full_scale[p->io_spec.otype & 3] / datatype_full_scale[p->io_spec.itype & 3]; p->seed = (unsigned long)time(0) ^ (unsigned long)(size_t)p; #if WITH_CR32 || WITH_CR32S || WITH_VR32 if (0 #if WITH_VR32 || ((!WITH_CR32 && !WITH_CR32S) || (p->q_spec.flags & SOXR_VR)) #endif #if WITH_CR32 || WITH_CR32S || !(WITH_CR64 || WITH_CR64S) || (p->q_spec.precision <= 20 && !(p->q_spec.flags & SOXR_DOUBLE_PRECISION)) #endif ) { p->deinterleave = (deinterleave_t)_soxr_deinterleave_f; p->interleave = (interleave_t)_soxr_interleave_f; control_block = #if WITH_VR32 ((!WITH_CR32 && !WITH_CR32S) || (p->q_spec.flags & SOXR_VR))? &_soxr_vr32_cb : #endif #if WITH_CR32S !WITH_CR32 || should_use_simd32()? &_soxr_rate32s_cb : #endif &_soxr_rate32_cb; } #if WITH_CR64 || WITH_CR64S else #endif #endif #if WITH_CR64 || WITH_CR64S { p->deinterleave = (deinterleave_t)_soxr_deinterleave; p->interleave = (interleave_t)_soxr_interleave; control_block = #if WITH_CR64S !WITH_CR64 || should_use_simd64()? &_soxr_rate64s_cb : #endif &_soxr_rate64_cb; } #endif memcpy(&p->control_block, control_block, sizeof(p->control_block)); if (p->num_channels && io_ratio!=0) error = soxr_set_io_ratio(p, io_ratio, 0); } if (error) soxr_delete(p), p = 0; if (error0) *error0 = error; return p; } soxr_error_t soxr_set_input_fn(soxr_t p, soxr_input_fn_t input_fn, void * input_fn_state, size_t max_ilen) { p->input_fn_state = input_fn_state; p->input_fn = input_fn; p->max_ilen = max_ilen? max_ilen : (size_t)-1; return 0; } static void soxr_delete0(soxr_t p) { unsigned i; if (p->resamplers) for (i = 0; i < p->num_channels; ++i) { if (p->resamplers[i]) resampler_close(p->resamplers[i]); free(p->resamplers[i]); } free(p->resamplers); free(p->channel_ptrs); free(p->shared); memset(p, 0, sizeof(*p)); } double soxr_delay(soxr_t p) { return (p && !p->error && p->resamplers)? resampler_delay(p->resamplers[0]) : 0; } static soxr_error_t fatal_error(soxr_t p, soxr_error_t error) { soxr_delete0(p); return p->error = error; } static soxr_error_t initialise(soxr_t p) { unsigned i; size_t shared_size, channel_size; resampler_sizes(&shared_size, &channel_size); p->channel_ptrs = calloc(sizeof(*p->channel_ptrs), p->num_channels); p->shared = calloc(shared_size, 1); p->resamplers = calloc(sizeof(*p->resamplers), p->num_channels); if (!p->shared || !p->channel_ptrs || !p->resamplers) return fatal_error(p, "malloc failed"); for (i = 0; i < p->num_channels; ++i) { soxr_error_t error; if (!(p->resamplers[i] = calloc(channel_size, 1))) return fatal_error(p, "malloc failed"); error = resampler_create( p->resamplers[i], p->shared, p->io_ratio, &p->q_spec, &p->runtime_spec, p->io_spec.scale); if (error) return fatal_error(p, error); } return 0; } soxr_error_t soxr_set_num_channels(soxr_t p, unsigned num_channels) { if (!p) return "invalid soxr_t pointer"; if (num_channels == p->num_channels) return p->error; if (!num_channels) return "invalid # of channels"; if (p->resamplers) return "# of channels can't be changed"; p->num_channels = num_channels; return soxr_set_io_ratio(p, p->io_ratio, 0); } soxr_error_t soxr_set_io_ratio(soxr_t p, double io_ratio, size_t slew_len) { unsigned i; soxr_error_t error; if (!p) return "invalid soxr_t pointer"; if ((error = p->error)) return error; if (!p->num_channels) return "must set # channels before O/I ratio"; if (io_ratio <= 0) return "I/O ratio out-of-range"; if (!p->channel_ptrs) { p->io_ratio = io_ratio; return initialise(p); } if (p->control_block[8]) { for (i = 0; !error && i < p->num_channels; ++i) resampler_set_io_ratio(p->resamplers[i], io_ratio, slew_len); return error; } return fabs(p->io_ratio - io_ratio) < 1e-15? 0 : "varying O/I ratio is not supported with this quality level"; } void soxr_delete(soxr_t p) { if (p) soxr_delete0(p), free(p); } soxr_error_t soxr_clear(soxr_t p) /* TODO: this, properly. */ { if (p) { struct soxr tmp = *p; soxr_delete0(p); memset(p, 0, sizeof(*p)); p->input_fn = tmp.input_fn; p->runtime_spec = tmp.runtime_spec; p->q_spec = tmp.q_spec; p->io_spec = tmp.io_spec; p->num_channels = tmp.num_channels; p->input_fn_state = tmp.input_fn_state; memcpy(p->control_block, tmp.control_block, sizeof(p->control_block)); p->deinterleave = tmp.deinterleave; p->interleave = tmp.interleave; return (p->q_spec.flags & RESET_ON_CLEAR)? soxr_set_io_ratio(p, tmp.io_ratio, 0) : 0; } return "invalid soxr_t pointer"; } static void soxr_input_1ch(soxr_t p, unsigned i, soxr_cbuf_t src, size_t len) { sample_t * dest = resampler_input(p->resamplers[i], NULL, len); (*p->deinterleave)(&dest, p->io_spec.itype, &src, len, 1); } static size_t soxr_input(soxr_t p, void const * in, size_t len) { bool separated = !!(p->io_spec.itype & SOXR_SPLIT); unsigned i; if (!p || p->error) return 0; if (!in && len) {p->error = "null input buffer pointer"; return 0;} if (!len) { p->flushing = true; return 0; } if (separated) for (i = 0; i < p->num_channels; ++i) soxr_input_1ch(p, i, ((soxr_cbufs_t)in)[i], len); else { for (i = 0; i < p->num_channels; ++i) p->channel_ptrs[i] = resampler_input(p->resamplers[i], NULL, len); (*p->deinterleave)( (sample_t **)p->channel_ptrs, p->io_spec.itype, &in, len, p->num_channels); } return len; } static size_t soxr_output_1ch(soxr_t p, unsigned i, soxr_buf_t dest, size_t len, bool separated) { sample_t const * src; if (p->flushing) resampler_flush(p->resamplers[i]); resampler_process(p->resamplers[i], len); src = resampler_output(p->resamplers[i], NULL, &len); if (separated) p->clips += (p->interleave)(p->io_spec.otype, &dest, &src, len, 1, (p->io_spec.flags & SOXR_NO_DITHER)? 0 : &p->seed); else p->channel_ptrs[i] = (void /* const */ *)src; return len; } static size_t soxr_output_no_callback(soxr_t p, soxr_buf_t out, size_t len) { unsigned u; size_t done = 0; bool separated = !!(p->io_spec.otype & SOXR_SPLIT); #if defined _OPENMP int i; if (!p->runtime_spec.num_threads && p->num_channels > 1) #pragma omp parallel for for (i = 0; i < (int)p->num_channels; ++i) { size_t done1; done1 = soxr_output_1ch(p, (unsigned)i, ((soxr_bufs_t)out)[i], len, separated); if (!i) done = done1; } else #endif for (u = 0; u < p->num_channels; ++u) done = soxr_output_1ch(p, u, ((soxr_bufs_t)out)[u], len, separated); if (!separated) p->clips += (p->interleave)(p->io_spec.otype, &out, (sample_t const * const *)p->channel_ptrs, done, p->num_channels, (p->io_spec.flags & SOXR_NO_DITHER)? 0 : &p->seed); return done; } size_t soxr_output(soxr_t p, void * out, size_t len0) { size_t odone, odone0 = 0, olen = len0, osize, idone; size_t ilen = min(p->max_ilen, (size_t)ceil((double)olen *p->io_ratio)); void const * in = out; /* Set to !=0, so that caller may leave unset. */ bool was_flushing; if (!p || p->error) return 0; if (!out && len0) {p->error = "null output buffer pointer"; return 0;} do { odone = soxr_output_no_callback(p, out, olen); odone0 += odone; if (odone0 == len0 || !p->input_fn || p->flushing) break; osize = soxr_datatype_size(p->io_spec.otype) * p->num_channels; out = (char *)out + osize * odone; olen -= odone; idone = p->input_fn(p->input_fn_state, &in, ilen); was_flushing = p->flushing; if (!in) p->error = "input function reported failure"; else soxr_input(p, in, idone); } while (odone || idone || (!was_flushing && p->flushing)); return odone0; } static size_t soxr_i_for_o(soxr_t p, size_t olen, size_t ilen) { size_t result; #if 0 if (p->runtime_spec.flags & SOXR_STRICT_BUFFERING) result = rate_i_for_o(p->resamplers[0], olen); else #endif result = (size_t)ceil((double)olen * p->io_ratio); return min(result, ilen); } #if 0 static size_t soxr_o_for_i(soxr_t p, size_t ilen, size_t olen) { size_t result = (size_t)ceil((double)ilen / p->io_ratio); return min(result, olen); } #endif soxr_error_t soxr_process(soxr_t p, void const * in , size_t ilen0, size_t * idone0, void * out, size_t olen , size_t * odone0) { size_t ilen, idone, odone = 0; unsigned u; bool flush_requested = false; if (!p) return "null pointer"; if (!in) flush_requested = true, ilen = ilen0 = 0; else { if ((ptrdiff_t)ilen0 < 0) flush_requested = true, ilen0 = ~ilen0; if (idone0 && (1 || flush_requested)) ilen = soxr_i_for_o(p, olen, ilen0); else ilen = ilen0/*, olen = soxr_o_for_i(p, ilen, olen)*/; } p->flushing |= ilen == ilen0 && flush_requested; if (!out && !in) idone = ilen; else if (p->io_spec.itype & p->io_spec.otype & SOXR_SPLIT) { /* Both i & o */ #if defined _OPENMP int i; if (!p->runtime_spec.num_threads && p->num_channels > 1) #pragma omp parallel for for (i = 0; i < (int)p->num_channels; ++i) { size_t done; if (in) soxr_input_1ch(p, (unsigned)i, ((soxr_cbufs_t)in)[i], ilen); done = soxr_output_1ch(p, (unsigned)i, ((soxr_bufs_t)out)[i], olen, true); if (!i) odone = done; } else #endif for (u = 0; u < p->num_channels; ++u) { if (in) soxr_input_1ch(p, u, ((soxr_cbufs_t)in)[u], ilen); odone = soxr_output_1ch(p, u, ((soxr_bufs_t)out)[u], olen, true); } idone = ilen; } else { idone = ilen? soxr_input (p, in , ilen) : 0; odone = soxr_output(p, out, olen); } if (idone0) *idone0 = idone; if (odone0) *odone0 = odone; return p->error; } soxr_error_t soxr_oneshot( double irate, double orate, unsigned num_channels, void const * in , size_t ilen, size_t * idone, void * out, size_t olen, size_t * odone, soxr_io_spec_t const * io_spec, soxr_quality_spec_t const * q_spec, soxr_runtime_spec_t const * runtime_spec) { soxr_t resampler; soxr_error_t error = q_spec? q_spec->e : 0; if (!error) { soxr_quality_spec_t q_spec1; if (!q_spec) q_spec1 = soxr_quality_spec(SOXR_LQ, 0), q_spec = &q_spec1; resampler = soxr_create(irate, orate, num_channels, &error, io_spec, q_spec, runtime_spec); } if (!error) { error = soxr_process(resampler, in, ~ilen, idone, out, olen, odone); soxr_delete(resampler); } return error; } soxr_error_t soxr_set_error(soxr_t p, soxr_error_t error) { if (!p) return "null pointer"; if (!p->error && p->error != error) return p->error; p->error = error; return 0; }