/* * _______ _____ _____ _____ * |__ __| | __ \ / ____| __ \ * | | __ _ _ __ ___ ___ ___| | | | (___ | |__) | * | |/ _` | '__/ __|/ _ \/ __| | | |\___ \| ___/ * | | (_| | | \__ \ (_) \__ \ |__| |____) | | * |_|\__,_|_| |___/\___/|___/_____/|_____/|_| * * ------------------------------------------------------------- * * TarsosDSP is developed by Joren Six at IPEM, University Ghent * * ------------------------------------------------------------- * * Info: http://0110.be/tag/TarsosDSP * Github: https://github.com/JorenSix/TarsosDSP * Releases: http://0110.be/releases/TarsosDSP/ * * TarsosDSP includes modified source code by various authors, * for credits and info, see README. * */ /****************************************************************************** * * libresample4j * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved. * * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3, * which is in turn based on Julius Smith's Resample 1.7 library. * http://www-ccrma.stanford.edu/~jos/resample/ * * License: LGPL -- see the file LICENSE.txt for more information * *****************************************************************************/ package be.tarsos.dsp.resample; import java.nio.FloatBuffer; public class Resampler { public static class Result { public final int inputSamplesConsumed; public final int outputSamplesGenerated; public Result(int inputSamplesConsumed, int outputSamplesGenerated) { this.inputSamplesConsumed = inputSamplesConsumed; this.outputSamplesGenerated = outputSamplesGenerated; } } // number of values per 1/delta in impulse response protected static final int Npc = 4096; private final float[] Imp; private final float[] ImpD; private final float LpScl; private final int Nmult; private final int Nwing; private final double minFactor; private final double maxFactor; private final int XSize; private final float[] X; private int Xp; // Current "now"-sample pointer for input private int Xread; // Position to put new samples private final int Xoff; private final float[] Y; private int Yp; private double Time; /** * Clone an existing resampling session. Faster than creating one from scratch. * * @param other */ public Resampler(Resampler other) { this.Imp = other.Imp.clone(); this.ImpD = other.ImpD.clone(); this.LpScl = other.LpScl; this.Nmult = other.Nmult; this.Nwing = other.Nwing; this.minFactor = other.minFactor; this.maxFactor = other.maxFactor; this.XSize = other.XSize; this.X = other.X.clone(); this.Xp = other.Xp; this.Xread = other.Xread; this.Xoff = other.Xoff; this.Y = other.Y.clone(); this.Yp = other.Yp; this.Time = other.Time; } /** * Create a new resampling session. * * @param highQuality true for better quality, slower processing time * @param minFactor lower bound on resampling factor for this session * @param maxFactor upper bound on resampling factor for this session * @throws IllegalArgumentException if minFactor or maxFactor is not * positive, or if maxFactor is less than minFactor */ public Resampler(boolean highQuality, double minFactor, double maxFactor) { if (minFactor <= 0.0 || maxFactor <= 0.0) { throw new IllegalArgumentException("minFactor and maxFactor must be positive"); } if (maxFactor < minFactor) { throw new IllegalArgumentException("minFactor must be <= maxFactor"); } this.minFactor = minFactor; this.maxFactor = maxFactor; this.Nmult = highQuality ? 35 : 11; this.LpScl = 1.0f; this.Nwing = Npc * (this.Nmult - 1) / 2; // # of filter coeffs in right wing double Rolloff = 0.90; double Beta = 6; double[] Imp64 = new double[this.Nwing]; FilterKit.lrsLpFilter(Imp64, this.Nwing, 0.5 * Rolloff, Beta, Npc); this.Imp = new float[this.Nwing]; this.ImpD = new float[this.Nwing]; for (int i = 0; i < this.Nwing; i++) { this.Imp[i] = (float) Imp64[i]; } // Storing deltas in ImpD makes linear interpolation // of the filter coefficients faster for (int i = 0; i < this.Nwing - 1; i++) { this.ImpD[i] = this.Imp[i + 1] - this.Imp[i]; } // Last coeff. not interpolated this.ImpD[this.Nwing - 1] = -this.Imp[this.Nwing - 1]; // Calc reach of LP filter wing (plus some creeping room) int Xoff_min = (int) (((this.Nmult + 1) / 2.0) * Math.max(1.0, 1.0 / minFactor) + 10); int Xoff_max = (int) (((this.Nmult + 1) / 2.0) * Math.max(1.0, 1.0 / maxFactor) + 10); this.Xoff = Math.max(Xoff_min, Xoff_max); // Make the inBuffer size at least 4096, but larger if necessary // in order to store the minimum reach of the LP filter and then some. // Then allocate the buffer an extra Xoff larger so that // we can zero-pad up to Xoff zeros at the end when we reach the // end of the input samples. this.XSize = Math.max(2 * this.Xoff + 10, 4096); this.X = new float[this.XSize + this.Xoff]; this.Xp = this.Xoff; this.Xread = this.Xoff; // Make the outBuffer long enough to hold the entire processed // output of one inBuffer int YSize = (int) (((double) this.XSize) * maxFactor + 2.0); this.Y = new float[YSize]; this.Yp = 0; this.Time = (double) this.Xoff; // Current-time pointer for converter } public int getFilterWidth() { return this.Xoff; } /** * Process a batch of samples. There is no guarantee that the input buffer will be drained. * * @param factor factor at which to resample this batch * @param buffers sample buffer for producing input and consuming output * @param lastBatch true if this is known to be the last batch of samples * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced) */ public boolean process(double factor, SampleBuffers buffers, boolean lastBatch) { if (factor < this.minFactor || factor > this.maxFactor) { throw new IllegalArgumentException("factor " + factor + " is not between minFactor=" + minFactor + " and maxFactor=" + maxFactor); } int outBufferLen = buffers.getOutputBufferLength(); int inBufferLen = buffers.getInputBufferLength(); float[] Imp = this.Imp; float[] ImpD = this.ImpD; float LpScl = this.LpScl; int Nwing = this.Nwing; boolean interpFilt = false; // TRUE means interpolate filter coeffs int inBufferUsed = 0; int outSampleCount = 0; // Start by copying any samples still in the Y buffer to the output // buffer if ((this.Yp != 0) && (outBufferLen - outSampleCount) > 0) { int len = Math.min(outBufferLen - outSampleCount, this.Yp); buffers.consumeOutput(this.Y, 0, len); //for (int i = 0; i < len; i++) { // outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i]; //} outSampleCount += len; for (int i = 0; i < this.Yp - len; i++) { this.Y[i] = this.Y[i + len]; } this.Yp -= len; } // If there are still output samples left, return now - we need // the full output buffer available to us... if (this.Yp != 0) { return inBufferUsed == 0 && outSampleCount == 0; } // Account for increased filter gain when using factors less than 1 if (factor < 1) { LpScl = (float) (LpScl * factor); } while (true) { // This is the maximum number of samples we can process // per loop iteration /* * #ifdef DEBUG * printf("XSize: %d Xoff: %d Xread: %d Xp: %d lastFlag: %d\n", * this.XSize, this.Xoff, this.Xread, this.Xp, lastFlag); #endif */ // Copy as many samples as we can from the input buffer into X int len = this.XSize - this.Xread; if (len >= inBufferLen - inBufferUsed) { len = inBufferLen - inBufferUsed; } buffers.produceInput(this.X, this.Xread, len); //for (int i = 0; i < len; i++) { // this.X[this.Xread + i] = inBuffer[inBufferOffset + inBufferUsed + i]; //} inBufferUsed += len; this.Xread += len; int Nx; if (lastBatch && (inBufferUsed == inBufferLen)) { // If these are the last samples, zero-pad the // end of the input buffer and make sure we process // all the way to the end Nx = this.Xread - this.Xoff; for (int i = 0; i < this.Xoff; i++) { this.X[this.Xread + i] = 0; } } else { Nx = this.Xread - 2 * this.Xoff; } /* * #ifdef DEBUG fprintf(stderr, "new len=%d Nx=%d\n", len, Nx); * #endif */ if (Nx <= 0) { break; } // Resample stuff in input buffer int Nout; if (factor >= 1) { // SrcUp() is faster if we can use it */ Nout = lrsSrcUp(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt); } else { Nout = lrsSrcUD(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt); } /* * #ifdef DEBUG * printf("Nout: %d\n", Nout); * #endif */ this.Time -= Nx; // Move converter Nx samples back in time this.Xp += Nx; // Advance by number of samples processed // Calc time accumulation in Time int Ncreep = (int) (this.Time) - this.Xoff; if (Ncreep != 0) { this.Time -= Ncreep; // Remove time accumulation this.Xp += Ncreep; // and add it to read pointer } // Copy part of input signal that must be re-used int Nreuse = this.Xread - (this.Xp - this.Xoff); for (int i = 0; i < Nreuse; i++) { this.X[i] = this.X[i + (this.Xp - this.Xoff)]; } /* #ifdef DEBUG printf("New Xread=%d\n", Nreuse); #endif */ this.Xread = Nreuse; // Pos in input buff to read new data into this.Xp = this.Xoff; this.Yp = Nout; // Copy as many samples as possible to the output buffer if (this.Yp != 0 && (outBufferLen - outSampleCount) > 0) { len = Math.min(outBufferLen - outSampleCount, this.Yp); buffers.consumeOutput(this.Y, 0, len); //for (int i = 0; i < len; i++) { // outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i]; //} outSampleCount += len; for (int i = 0; i < this.Yp - len; i++) { this.Y[i] = this.Y[i + len]; } this.Yp -= len; } // If there are still output samples left, return now, // since we need the full output buffer available if (this.Yp != 0) { break; } } return inBufferUsed == 0 && outSampleCount == 0; } /** * Process a batch of samples. Convenience method for when the input and output are both floats. * * @param factor factor at which to resample this batch * @param inputBuffer contains input samples in the range -1.0 to 1.0 * @param outputBuffer output samples will be deposited here * @param lastBatch true if this is known to be the last batch of samples * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced) */ public boolean process(double factor, final FloatBuffer inputBuffer, boolean lastBatch, final FloatBuffer outputBuffer) { SampleBuffers sampleBuffers = new SampleBuffers() { public int getInputBufferLength() { return inputBuffer.remaining(); } public int getOutputBufferLength() { return outputBuffer.remaining(); } public void produceInput(float[] array, int offset, int length) { inputBuffer.get(array, offset, length); } public void consumeOutput(float[] array, int offset, int length) { outputBuffer.put(array, offset, length); } }; return process(factor, sampleBuffers, lastBatch); } /** * Process a batch of samples. Alternative interface if you prefer to work with arrays. * * @param factor resampling rate for this batch * @param inBuffer array containing input samples in the range -1.0 to 1.0 * @param inBufferOffset offset into inBuffer at which to start processing * @param inBufferLen number of valid elements in the inputBuffer * @param lastBatch pass true if this is the last batch of samples * @param outBuffer array to hold the resampled data * @param outBufferOffset Offset in the output buffer. * @param outBufferLen Output buffer length. * @return the number of samples consumed and generated */ public Result process(double factor, float[] inBuffer, int inBufferOffset, int inBufferLen, boolean lastBatch, float[] outBuffer, int outBufferOffset, int outBufferLen) { FloatBuffer inputBuffer = FloatBuffer.wrap(inBuffer, inBufferOffset, inBufferLen); FloatBuffer outputBuffer = FloatBuffer.wrap(outBuffer, outBufferOffset, outBufferLen); process(factor, inputBuffer, lastBatch, outputBuffer); return new Result(inputBuffer.position() - inBufferOffset, outputBuffer.position() - outBufferOffset); } /* * Sampling rate up-conversion only subroutine; Slightly faster than * down-conversion; */ private int lrsSrcUp(float X[], float Y[], double factor, int Nx, int Nwing, float LpScl, float Imp[], float ImpD[], boolean Interp) { float[] Xp_array = X; int Xp_index; float[] Yp_array = Y; int Yp_index = 0; float v; double CurrentTime = this.Time; double dt; // Step through input signal double endTime; // When Time reaches EndTime, return to user dt = 1.0 / factor; // Output sampling period endTime = CurrentTime + Nx; while (CurrentTime < endTime) { double LeftPhase = CurrentTime - Math.floor(CurrentTime); double RightPhase = 1.0 - LeftPhase; Xp_index = (int) CurrentTime; // Ptr to current input sample // Perform left-wing inner product v = FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1); // Perform right-wing inner product v += FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1); v *= LpScl; // Normalize for unity filter gain Yp_array[Yp_index++] = v; // Deposit output CurrentTime += dt; // Move to next sample by time increment } this.Time = CurrentTime; return Yp_index; // Return the number of output samples } private int lrsSrcUD(float X[], float Y[], double factor, int Nx, int Nwing, float LpScl, float Imp[], float ImpD[], boolean Interp) { float[] Xp_array = X; int Xp_index; float[] Yp_array = Y; int Yp_index = 0; float v; double CurrentTime = this.Time; double dh; // Step through filter impulse response double dt; // Step through input signal double endTime; // When Time reaches EndTime, return to user dt = 1.0 / factor; // Output sampling period dh = Math.min(Npc, factor * Npc); // Filter sampling period endTime = CurrentTime + Nx; while (CurrentTime < endTime) { double LeftPhase = CurrentTime - Math.floor(CurrentTime); double RightPhase = 1.0 - LeftPhase; Xp_index = (int) CurrentTime; // Ptr to current input sample // Perform left-wing inner product v = FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1, dh); // Perform right-wing inner product v += FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1, dh); v *= LpScl; // Normalize for unity filter gain Yp_array[Yp_index++] = v; // Deposit output CurrentTime += dt; // Move to next sample by time increment } this.Time = CurrentTime; return Yp_index; // Return the number of output samples } }