All Downloads are FREE. Search and download functionalities are using the official Maven repository.

core.be.tarsos.dsp.resample.Resampler Maven / Gradle / Ivy

The newest version!
/*
*      _______                       _____   _____ _____  
*     |__   __|                     |  __ \ / ____|  __ \ 
*        | | __ _ _ __ ___  ___  ___| |  | | (___ | |__) |
*        | |/ _` | '__/ __|/ _ \/ __| |  | |\___ \|  ___/ 
*        | | (_| | |  \__ \ (_) \__ \ |__| |____) | |     
*        |_|\__,_|_|  |___/\___/|___/_____/|_____/|_|     
*                                                         
* -------------------------------------------------------------
*
* 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
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy