diff options
Diffstat (limited to 'nyqsrc/resamp.c')
-rw-r--r-- | nyqsrc/resamp.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/nyqsrc/resamp.c b/nyqsrc/resamp.c new file mode 100644 index 0000000..e02e344 --- /dev/null +++ b/nyqsrc/resamp.c @@ -0,0 +1,348 @@ +/* resamp.c -- resample signal using sinc interpolation */ + +/* CHANGE LOG + * -------------------------------------------------------------------- + * 28Apr03 dm min->MIN, max->MAX + */ + + + +#include "stdio.h" +#ifndef mips +#include "stdlib.h" +#endif +#include "xlisp.h" +#include "sound.h" +#include "assert.h" + +#include "falloc.h" +#include "cext.h" +#include "resamp.h" +#include "fresample.h" +#include "ffilterkit.h" +#include "fsmallfilter.h" + +/* Algorithm: + To resample, we convolve a sinc function with the input stream at +times corresponding to the output samples. This requires a sliding +window on the input samples. Since samples are accessed a block at a +time, the places where the sliding window would span two blocks are +too tricky for me. Instead of trying to manage the breaks across +blocks, I copy the blocks into another buffer (called X). When the +sliding window reaches the end of X, the samples at the end of X +are copied to the beginning of X, the remainder of X is filled with +new samples, and the computation continues. The trickiest part of +all this is keeping all the pointers and phase accumulators correct +when the sliding window is relocated from the end of X to the +beginning. + Although there are different ways to do this, I decided that the +output would always go directly to a Nyquist sample block, so the +resampling routines (SrcUp and SrcUD) are always called upon to +compute max_sample_block_len samples (except that a partial block +may be computed when the input sound terminates). + To compute max_sample_block_len samples, the input buffer needs: + + - max_sample_block_len/factor samples, where factor is the ratio of + the new sample rate to the old one. I.e. if upsampling by a factor + of 2, the input buffer needs half the samples of the output block + size. + + - additional samples the size of the sliding window. Since the + output is taken from the center of the window, we can't take + samples from the first or last windowsize/2 samples. + + - to simplify rounding, we throw in some extra samples. This costs + only a bit of space and an extra copy for each spare sample. + + The window size is determined by the particular filter used and +by factor (the sample rate ratio). The filter size is Nmult, the +number of filter coefficients. When upsampling, this is the window +size (the filter acts as a reconstruction filter for the additional +samples). When downsampling, the filter is stretched by 1/factor +(the filter acts as an anti-aliasing low-pass filter). + +*/ + +void resample_free(); + +typedef struct resample_susp_struct { + snd_susp_node susp; + long terminate_cnt; + boolean logically_stopped; + sound_type s; + long s_cnt; + sample_block_values_type s_ptr; + float *X; + long Xsize; + double Time; /* location (offset) in X of next output sample */ + double LpScl; + double factor; + sample_type *Imp; + sample_type *ImpD; + boolean interpFilt; + int Nmult; + int Nwing; + int Xp; /* first free location at end of X */ + int Xoff; /* number of extra samples at beginning and end of X */ +} resample_susp_node, *resample_susp_type; + +/* Sampling rate up-conversion only subroutine; + * Slightly faster than down-conversion; + */ +static int SrcUp(float X[], float Y[], double factor, double *Time, + int Nx, int Nwing, double LpScl, + float Imp[], float ImpD[], boolean Interp) +{ + mem_float *Xp, *Ystart; + fast_float v; + + double dt; /* Step through input signal */ + mem_float *Yend; /* When Y reaches Yend, return to user */ + +/* nyquist_printf("SrcUp: interpFilt %d\n", Interp);*/ + + dt = 1.0/factor; /* Output sampling period */ + + Ystart = Y; + Yend = Y + Nx; + while (Y < Yend) { + long iTime = (long) *Time; + Xp = &X[iTime]; /* Ptr to current input sample */ + /* Perform left-wing inner product */ + v = FilterUp(Imp, ImpD, Nwing, Interp, Xp, *Time - iTime, -1); + /* Perform right-wing inner product */ + v += FilterUp(Imp, ImpD, Nwing, Interp, Xp+1, + (1 + iTime) - *Time, 1); + v *= LpScl; /* Normalize for unity filter gain */ +/* nyquist_printf("SrcUp output sample %g\n", v); */ + *Y++ = (float) v; + *Time += dt; /* Move to next sample by time increment */ + } + return (Y - Ystart); /* Return the number of output samples */ +} + + +/* Sampling rate conversion subroutine */ + +static int SrcUD(float X[], float Y[], double factor, double *Time, + int Nx, int Nwing, double LpScl, + float Imp[], float ImpD[], boolean Interp) +{ + mem_float *Xp, *Ystart; + fast_float v; + + double dh; /* Step through filter impulse response */ + double dt; /* Step through input signal */ + mem_float *Yend; /* When Y reaches Yend, return to user */ + + dt = 1.0/factor; /* Output sampling period */ + + dh = MIN(Npc, factor*Npc); /* Filter sampling period */ + + Ystart = Y; + Yend = Y + Nx; + while (Y < Yend) { + long iTime = (long) *Time; + Xp = &X[iTime]; /* Ptr to current input sample */ + v = FilterUD(Imp, ImpD, Nwing, Interp, Xp, *Time - iTime, + -1, dh); /* Perform left-wing inner product */ + v += FilterUD(Imp, ImpD, Nwing, Interp, Xp+1, (1 + iTime) - *Time, + 1, dh); /* Perform right-wing inner product */ + v *= LpScl; /* Normalize for unity filter gain */ + *Y++ = (float) v; + *Time += dt; /* Move to next sample by time increment */ + } + return (Y - Ystart); /* Return the number of output samples */ +} + + +void resample__fetch(register resample_susp_type susp, snd_list_type snd_list) +{ + int togo; + int n; + int Nout; + sample_block_type out; + /* note that in this fetch routine, out_ptr is used to remember where + * to put the "real" output, while X_ptr_reg is used in the inner + * loop that copies input samples into X, a buffer + */ + register sample_block_values_type out_ptr; + register sample_block_values_type X_ptr_reg; + + register sample_type *s_ptr_reg; + falloc_sample_block(out, "resample__fetch"); + out_ptr = out->samples; + snd_list->block = out; + +/* Algorithm: + Fetch samples until X (the buffered input) is full. X stores enough +contiguous samples that a sliding window convolving with the filter +coefficients can output a full block without sliding beyond the range +of X. Every time we reenter resample__fetch, we take the remaining +samples at the end of X, shift them to the beginning, and refill. + After X is full, call on SrcUp or SrcUD to compute an output block. + The first time resample__fetch is called, the fill pointer Xp will +point near the beginning of X, indicating that no previously read +samples need to be shifted from the end of X to the beginning. +*/ + + /* first, shift samples from end of X to beginning if necessary */ + if (susp->Xp > 2 * susp->Xoff) { + int i; + int shiftcount = (long) (susp->Time) - susp->Xoff; + +/* nyquist_printf("shift %d from %d to %lx\n", susp->Xsize + susp->Xoff - susp->Xp, susp->Xp - susp->Xoff, susp->X); */ + for (i = 0; i < susp->Xp - shiftcount; i++) { + susp->X[i] = susp->X[i + shiftcount]; +/* if (susp->X[i] == 0.0) nyquist_printf("shifted zero to X[%d]\n", i);*/ + } + susp->Time -= shiftcount; + susp->Xp -= shiftcount; + } + + while (susp->Xp < susp->Xsize) { /* outer loop */ + /* read samples from susp->s into X */ + togo = susp->Xsize - susp->Xp; + /* don't run past the s input sample block. If termination or + * logical stop info become available, translate to susp->terminate_cnt + * and susp->log_stop_cnt. + */ + susp_check_term_log_samples(s, s_ptr, s_cnt); + togo = MIN(togo, susp->s_cnt); + + memcpy(susp->X + susp->Xp, susp->s_ptr, togo * sizeof(sample_type)); + susp->s_ptr += togo; + susp_took(s_cnt, togo); + susp->Xp += togo; + } /* outer loop */ + + /* second, compute samples to output, this is done in one pass because + * we have enough data in X + */ + + /* don't run past terminate time */ + togo = max_sample_block_len; + if (susp->terminate_cnt != UNKNOWN && + susp->terminate_cnt <= susp->susp.current + max_sample_block_len) { + togo = susp->terminate_cnt - susp->susp.current; + } + if (!susp->logically_stopped && + susp->susp.log_stop_cnt != UNKNOWN) { + int to_stop = susp->susp.log_stop_cnt - susp->susp.current; + assert(to_stop >= 0); + if (to_stop < togo) { + if (to_stop == 0) susp->logically_stopped = true; + else togo = to_stop; + } + } + if (togo == 0) { +/* stdputstr("resamp calling snd_list_terminate\n"); */ + snd_list_terminate(snd_list); + } else { + if (susp->factor >= 1) { /* SrcUp() is faster if we can use it */ + Nout = SrcUp(susp->X, out_ptr, susp->factor, &susp->Time, + togo, susp->Nwing, susp->LpScl, susp->Imp, + susp->ImpD, susp->interpFilt); + } else { + Nout = SrcUD(susp->X, out_ptr, susp->factor, &susp->Time, + togo, susp->Nwing, susp->LpScl, susp->Imp, + susp->ImpD, susp->interpFilt); + } + snd_list->block_len = togo; + susp->susp.current += togo; + } +#ifdef RESAMPTEST + for (n = 0; n < snd_list->block_len; n++) { + if (out->samples[n] == 0.0) { + nyquist_printf("resamp: zero at samples[%d]\n", n); + } + } +#endif +/* + if (susp->logically_stopped) { + snd_list->logically_stopped = true; + } else if (susp->susp.log_stop_cnt == susp->susp.current) { + susp->logically_stopped = true; + } + */ +} /* resample__fetch */ + + +void resample_mark(resample_susp_type susp) +{ + sound_xlmark(susp->s); +} + + +void resample_free(resample_susp_type susp) +{ + sound_unref(susp->s); + free(susp->X); + ffree_generic(susp, sizeof(resample_susp_node), "resample_free"); +} + + +void resample_print_tree(resample_susp_type susp, int n) +{ + indent(n); + stdputstr("s:"); + sound_print_tree_1(susp->s, n); +} + + +sound_type snd_make_resample(sound_type s, rate_type sr) +{ + register resample_susp_type susp; + int i; + + falloc_generic(susp, resample_susp_node, "snd_make_resample"); + susp->susp.fetch = resample__fetch; + + susp->Nmult = SMALL_FILTER_NMULT; + susp->Imp = SMALL_FILTER_IMP; + susp->ImpD = SMALL_FILTER_IMPD; + /* these scale factors are here because filter coefficients + are expressed as integers, and so is SMALL_FILTER_SCALE: */ + susp->LpScl = SMALL_FILTER_SCALE / 32768.0; + susp->LpScl /= 16384.0; + /* this is just a fudge factor, is SMALL_FILTER_SCALE wrong? */ + susp->LpScl /= 1.0011; + + susp->Nwing = SMALL_FILTER_NWING; + susp->factor = sr / s->sr; + if (susp->factor < 1) susp->LpScl *= susp->factor; + + /* factor in the scale factor of s, since resample is linear */ + susp->LpScl *= s->scale; + + susp->terminate_cnt = UNKNOWN; + /* initialize susp state */ + susp->susp.free = resample_free; + susp->susp.sr = sr; + susp->susp.t0 = s->t0; + susp->susp.mark = resample_mark; + susp->susp.print_tree = resample_print_tree; + susp->susp.name = "resample"; + susp->logically_stopped = false; + susp->susp.log_stop_cnt = logical_stop_cnt_cvt(s); + susp->susp.current = 0; + susp->s = s; + susp->s_cnt = 0; + susp->Xoff = (int) (((susp->Nmult + 1) / 2.0) * MAX(1.0, 1.0 / susp->factor) + 10); + susp->Xsize = (long) ((max_sample_block_len / susp->factor) + 2 * susp->Xoff); + susp->X = calloc(susp->Xsize, sizeof(sample_type)); + susp->Xp = susp->Xoff; + susp->Time = susp->Xoff; + susp->interpFilt = true; + for (i = 0; i < susp->Xoff; i++) susp->X[i] = 0.0F; + + return sound_create((snd_susp_type)susp, susp->susp.t0, + susp->susp.sr, 1.0 /* scale factor */); +} + + +sound_type snd_resample(sound_type s, rate_type sr) +{ + sound_type s_copy = sound_copy(s); + return snd_make_resample(s_copy, sr); +} |