Saturday, 23 December 2017

The FFT Code From Sonic Field

/* For Copyright and License see LICENSE.txt and COPYING.txt in the root directory */
 package com.nerdscentral.audio.pitch.algorithm;

import java.lang.ref.SoftReference;

public class OnHeapFFTBase
{
    private static final ThreadLocal<SoftReference<OnHeapFFT>> cachedF = new ThreadLocal<SoftReference<OnHeapFFT>>()
                                                                       {                                            /**/
                                                                       };
    private static final ThreadLocal<SoftReference<OnHeapFFT>> cachedR = new ThreadLocal<SoftReference<OnHeapFFT>>()
                                                                       {                                            /**/
                                                                       };

    @SuppressWarnings("resource")
    public static void fft(final double[] inputReal, double[] inputImag, double[] newArray, boolean forward)
    {
        int n = inputReal.length;
        SoftReference<OnHeapFFT> sref = forward ? cachedF.get() : cachedR.get();
        OnHeapFFT transform = null;
        if (sref != null)
        {
            transform = sref.get();
        }
        if (transform != null)
        {
            if (transform.size() != n)
            {
                transform.close();
                transform = null;
            }
        }
        if (transform == null)
        {
            transform = new OnHeapFFT(n, forward);
            sref = new SoftReference<>(transform);
            if (forward)
            {
                cachedF.set(sref);
            }
            else
            {
                cachedR.set(sref);
            }
        }
        transform.fft(inputReal, inputImag);

        long length = inputReal.length << 1;
        double radice = 1.0 / Math.sqrt(n);
        for (int i = 0; i < length; i += 2)
        {
            int i2 = i >> 1;
            newArray[i] = inputReal[i2] * radice;
            newArray[i + 1] = inputImag[i2] * radice;
        }
    }
}

...

/* For Copyright and License see LICENSE.txt and COPYING.txt in the root directory */
package com.nerdscentral.audio.pitch.algorithm;

public class OnHeapFFT implements AutoCloseable
{

    private final int        n, m;

    // Lookup tables. Only need to recompute when size of FFT changes.
    private final double[]   cos;
    private final double[]   sin;
    private volatile boolean closed = false;

    public long size()
    {
        return n;
    }

    public OnHeapFFT(int n1, boolean isForward)
    {
        this.n = n1;
        this.m = (int) (Math.log(n1) / Math.log(2));

        // Make sure n is a power of 2
        if (n1 != (1 << m)) throw new RuntimeException(Messages.getString("CacheableFFT.0")); //$NON-NLS-1$

        cos = new double[n1 >> 1];
        sin = new double[n1 >> 1];
        double dir = isForward ? -2 * Math.PI : 2 * Math.PI;

        for (int i = 0; i < n1 >> 1; ++i)
        {
            cos[i] = Math.cos(dir * i / n1);
            sin[i] = Math.sin(dir * i / n1);
        }

    }

    public void fft(double[] x, double[] y)
    {
        if (closed) throw new RuntimeException(Messages.getString("OffHeapFFT.0")); //$NON-NLS-1$
        int i, j, k, n1, n2, a;
        double c, s, t1, t2;

        // Bit-reverse
        j = 0;
        n2 = n >> 1;
        for (i = 1; i < n - 1; ++i)
        {
            n1 = n2;
            while (j >= n1)
            {
                j = j - n1;
                n1 >>= 1;
            }
            j = j + n1;

            if (i < j)
            {
                t1 = x[i];
                x[i] = x[j];
                x[j] = t1;
                t1 = y[i];
                y[i] = y[j];
                y[j] = t1;
            }
        }

        n1 = 0;
        n2 = 1;

        for (i = 0; i < m; ++i)
        {
            n1 = n2;
            n2 <<= 1;
            a = 0;

            for (j = 0; j < n1; j++)
            {
                c = cos[a];
                s = sin[a];
                a += 1 << (m - i - 1);
                for (k = j; k < n; k += n2)
                {
                    int kn1 = k + n1;
                    t1 = c * x[kn1] - s * y[kn1];
                    t2 = s * x[kn1] + c * y[kn1];
                    x[kn1] = x[k] - t1;
                    y[kn1] = y[k] - t2;
                    x[k] = x[k] + t1;
                    y[k] = y[k] + t2;
                }
            }
        }
    }

    @Override
    public void close() throws RuntimeException
    {
        if (closed) throw new RuntimeException(Messages.getString("OffHeapFFT.1")); //$NON-NLS-1$
        closed = true;
    }

    @Override
    public void finalize() throws RuntimeException
    {
        close();
    }

}

The Code For Biquad IIR Filters (RBJ) In Java

package com.nerdscentral.audio.pitch.algorithm;

import com.nerdscentral.audio.core.SFConstants;

/*
 * RBJ Filters from C++ version by arguru[AT]smartelectronix[DOT]com based on eq filter cookbook by Robert Bristow-Johnson
 * <rbj@audioimagination.com> The C++ code is believed to be public domain and license free after best efforts to establish its licensing. As this is a Java translation of that code I have placed this in the public domain as well.
 */
public class SFRBJFilter
{

    // filter coeffs
    double b0a0, b1a0, b2a0, a1a0, a2a0;

    // in/out history
    double ou1, ou2, in1, in2;

    public SFRBJFilter()
    {
        // reset filter coeffs
        b0a0 = b1a0 = b2a0 = a1a0 = a2a0 = 0.0;

        // reset in/out history
        ou1 = ou2 = in1 = in2 = 0.0f;
    }

    public double filter(double in0)
    {
        // filter
        final double yn = b0a0 * in0 + b1a0 * in1 + b2a0 * in2 - a1a0 * ou1 - a2a0 * ou2;

        // push in/out buffers
        in2 = in1;
        in1 = in0;
        ou2 = ou1;
        ou1 = yn;

        // return output
        return yn;
    }

    public double limitedFilter(double in0)
    {
        // filter
        double yn = b0a0 * in0 + b1a0 * in1 + b2a0 * in2 - a1a0 * ou1 - a2a0 * ou2;
    double ay = yn < 0 ? -yn : yn;
    if(ay > 1.0)
    {
        yn = yn < 0 ? -1.0 : 1.0;
    }
   
        // push in/out buffers
        in2 = in1;
        in1 = in0;
        ou2 = ou1;
        ou1 = yn;

        // return output
        return yn;
    }

    public enum FilterType
    {
        LOWPASS, HIGHPASS, BANDPASS_SKIRT, BANDPASS_PEAK, NOTCH, ALLPASS, PEAK, LOWSHELF, HIGHSHELF
    }

    public void calc_filter_coeffs(final FilterType type, final double frequency, final double q, final double db_gain)
    {
        boolean q_is_bandwidth;
        final double sample_rate = SFConstants.SAMPLE_RATE;
        switch (type)
        {
        case ALLPASS:
        case HIGHPASS:
        case LOWPASS:
        case LOWSHELF:
        case HIGHSHELF:
            q_is_bandwidth = false;
            break;
        default:
            q_is_bandwidth = true;
            break;
        }
        // System.out.println("Q Is Bandwidth " + q_is_bandwidth);
        // temp pi
        final double temp_pi = 3.1415926535897932384626433832795;

        // temp coef vars
        double alpha, a0 = 0, a1 = 0, a2 = 0, b0 = 0, b1 = 0, b2 = 0;

        // peaking, lowshelf and hishelf
        if (type == FilterType.PEAK || type == FilterType.HIGHSHELF || type == FilterType.LOWSHELF)
        {
            final double A = Math.pow(10.0, (db_gain / 40.0));
            final double omega = 2.0 * temp_pi * frequency / sample_rate;
            final double tsin = Math.sin(omega);
            final double tcos = Math.cos(omega);
            if (type == FilterType.PEAK) alpha = tsin * Math.sinh(Math.log(2.0) / 2.0 * q * omega / tsin);
            else
                alpha = tsin / 2.0 * Math.sqrt((A + 1 / A) * (1 / q - 1) + 2);

            final double beta = Math.sqrt(A) / q;

            // peaking
            if (type == FilterType.PEAK)
            {
                b0 = (1.0 + alpha * A);
                b1 = (-2.0 * tcos);
                b2 = (1.0 - alpha * A);
                a0 = (1.0 + alpha / A);
                a1 = (-2.0 * tcos);
                a2 = (1.0 - alpha / A);
            }

            // lowshelf
            if (type == FilterType.LOWSHELF)
            {
                b0 = (A * ((A + 1.0) - (A - 1.0) * tcos + beta * tsin));
                b1 = (2.0 * A * ((A - 1.0) - (A + 1.0) * tcos));
                b2 = (A * ((A + 1.0) - (A - 1.0) * tcos - beta * tsin));
                a0 = ((A + 1.0) + (A - 1.0) * tcos + beta * tsin);
                a1 = (-2.0 * ((A - 1.0) + (A + 1.0) * tcos));
                a2 = ((A + 1.0) + (A - 1.0) * tcos - beta * tsin);
            }

            // hishelf
            if (type == FilterType.HIGHSHELF)
            {
                b0 = (A * ((A + 1.0) + (A - 1.0) * tcos + beta * tsin));
                b1 = (-2.0 * A * ((A - 1.0) + (A + 1.0) * tcos));
                b2 = (A * ((A + 1.0) + (A - 1.0) * tcos - beta * tsin));
                a0 = ((A + 1.0) - (A - 1.0) * tcos + beta * tsin);
                a1 = (2.0 * ((A - 1.0) - (A + 1.0) * tcos));
                a2 = ((A + 1.0) - (A - 1.0) * tcos - beta * tsin);
            }
        }
        else
        {
            // other filters
            final double omega = 2.0 * temp_pi * frequency / sample_rate;
            final double tsin = Math.sin(omega);
            final double tcos = Math.cos(omega);

            if (q_is_bandwidth) alpha = tsin * Math.sinh(Math.log(2.0) / 2.0 * q * omega / tsin);
            else
                alpha = tsin / (2.0 * q);

            // lowpass
            if (type == FilterType.LOWPASS)
            {
                b0 = (1.0 - tcos) / 2.0;
                b1 = 1.0 - tcos;
                b2 = (1.0 - tcos) / 2.0;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }

            // hipass
            if (type == FilterType.HIGHPASS)
            {
                b0 = (1.0 + tcos) / 2.0;
                b1 = -(1.0 + tcos);
                b2 = (1.0 + tcos) / 2.0;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }

            // bandpass csg
            if (type == FilterType.BANDPASS_SKIRT)
            {
                b0 = tsin / 2.0;
                b1 = 0.0;
                b2 = -tsin / 2;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }

            // bandpass czpg
            if (type == FilterType.BANDPASS_PEAK)
            {
                b0 = alpha;
                b1 = 0.0;
                b2 = -alpha;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }

            // notch
            if (type == FilterType.NOTCH)
            {
                b0 = 1.0;
                b1 = -2.0 * tcos;
                b2 = 1.0;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }

            // allpass
            if (type == FilterType.ALLPASS)
            {
                b0 = 1.0 - alpha;
                b1 = -2.0 * tcos;
                b2 = 1.0 + alpha;
                a0 = 1.0 + alpha;
                a1 = -2.0 * tcos;
                a2 = 1.0 - alpha;
            }
        }

        // set filter coeffs
        b0a0 = (b0 / a0);
        b1a0 = (b1 / a0);
        b2a0 = (b2 / a0);
        a1a0 = (a1 / a0);
        a2a0 = (a2 / a0);
    }
}

Arbitrarily Large Off Heap File Back Block Storage - The Code

/* For Copyright and License see LICENSE.txt and COPYING.txt in the root directory */

package com.nerdscentral.audio.core;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongFunction;

import com.nerdscentral.audio.Messages;
import com.nerdscentral.data.OffHeapArray;
import com.nerdscentral.data.UnsafeProvider;
import com.nerdscentral.sython.SFPL_RuntimeException;

import sun.misc.Cleaner;
import sun.misc.Unsafe;

/**
 * @author a1t
 *
 */
public class SFData extends SFSignal implements Serializable
{
    // JIT time alias in effect, maybe remove in the future
    static final Unsafe unsafe = UnsafeProvider.unsafe;

    private static class MemoryZoneStack extends ThreadLocal<Stack<SFMemoryZone>>
    {
        @Override
        protected Stack<SFMemoryZone> initialValue()
        {
            return new Stack<>();
        }
    }

    static class ByteBufferWrapper
    {
        private long              address = 0;
        private final FileChannel underLyingFile;
        private final long        fileOffset;
        // buffer is used to hold a reference to the buffer
        // but its data is read via unsafe
        private MappedByteBuffer  buffer;
        private static long       _adOff  = 0;
        static
        {
            try
            {
                _adOff = unsafe.objectFieldOffset(ByteBufferWrapper.class.getDeclaredField("address")); //$NON-NLS-1$
            }
            catch (NoSuchFieldException | SecurityException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        private final static long adOff = _adOff;

        private final static boolean isUnmapped(ByteBufferWrapper obj)
        {
            return unsafe.getLongVolatile(obj, adOff) == 0;
        }

        private final static long setAddress(ByteBufferWrapper obj, long value)
        {
            return unsafe.getAndSetLong(obj, adOff, value);
        }

        public long getAddress()
        {
            if (isUnmapped(this)) remap();
            return address;
        }

        static Method getAddressMethod;

        synchronized void remap()
        {
            // Already done by another thread;
            if (!isUnmapped(this)) return;
            address = 0; // Stops warnings this should be final etc.
            try
            {
                buffer = underLyingFile.map(MapMode.READ_WRITE, fileOffset, CHUNK_LEN << 3l);
                if (getAddressMethod == null)
                {
                    getAddressMethod = buffer.getClass().getMethod("address"); //$NON-NLS-1$
                    getAddressMethod.setAccessible(true);
                }
                buffer.order(ByteOrder.nativeOrder());
                setAddress(this, (long) getAddressMethod.invoke(buffer));
            }
            catch (IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                            | NoSuchMethodException | SecurityException e)
            {
                throw new RuntimeException(e);
            }
        }

        public ByteBufferWrapper(FileChannel file, long offsetInFile) throws SecurityException, IllegalArgumentException
        {
            fileOffset = offsetInFile;
            underLyingFile = file;
            // Map the data so the underlying channel will create the disk storage for it and
            // so the file size etc continues to make sense.
            remap();
        }

        static Field cleanerField = null;

        public synchronized void release()
        {
            setAddress(this, 0);
            if (isUnmapped(this)) return;
            try
            {
                if (cleanerField == null)
                {
                    cleanerField = buffer.getClass().getDeclaredField("cleaner"); //$NON-NLS-1$
                    cleanerField.setAccessible(true);
                }
                Cleaner cleaner = (Cleaner) cleanerField.get(buffer);
                cleaner.clean();
            }
            catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e)
            {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

    }

    public static class NotCollectedException extends Exception
    {
        private static final long serialVersionUID = 1L;
    }

    // $NON-NLS-1$
    static final long                                       CHUNK_SHIFT          = 13;
    static final long                                       CHUNK_LEN            = (long) Math.pow(2, CHUNK_SHIFT);
    static final long                                       CHUNK_MASK           = CHUNK_LEN - 1;

    // How frequently to print of memory state once this many samples has been allocated and/or freed.
    private static final long                               MEM_REPORT_THRESHOLD = (long) SFConstants.SAMPLE_RATE
                    * SFConstants.HEART_BEAT;

    private static final long                               serialVersionUID     = 1L;
    private final int                                       length;
    ByteBufferWrapper[]                                     chunks;
    private final long                                      chunkIndex;
    // shadows chunkIndex for memory management as chunkIndex must
    // be final to enable optimisation
    private long                                            allocked;
    private static final AtomicLong                         totalCount           = new AtomicLong();
    private static final AtomicLong                         freeCount            = new AtomicLong();
    private static final AtomicLong                         reportTicker         = new AtomicLong();
    private static ConcurrentLinkedDeque<ByteBufferWrapper> freeChunks           = new ConcurrentLinkedDeque<>();
    private static ConcurrentLinkedDeque<ByteBufferWrapper> allChunks            = new ConcurrentLinkedDeque<>();
    @SuppressWarnings("synthetic-access")
    private static final MemoryZoneStack                    memoryZoneStack      = new MemoryZoneStack();
    private final AtomicBoolean                             kept                 = new AtomicBoolean(false);
    private final AtomicBoolean                             dead                 = new AtomicBoolean(false);
    private final AtomicBoolean                             pinned               = new AtomicBoolean(false);

    // TODO: The file channels are thread local but the chunks are not. Thus over time chunks will disperse
    // across threads and the locality will be broken. If we can make some mechanism to allow this were needed
    // but migrate chunks back to being thread local where possible to improve disk data locallity this would
    // be good.

    private static ConcurrentHashMap<File, FileChannel>     fileMap              = new ConcurrentHashMap<>();

    protected static FileChannel getRotatingChannel()
    {
        String pid = ManagementFactory.getRuntimeMXBean().getName();
        try
        {
            File dir = SFConstants.getRotatingTempDir();
            FileChannel channel = fileMap.get(dir);
            if (channel != null) return channel;

            // OK, we do not have an existing swap file in this dir to make a new one.
            File coreFile = File.createTempFile("SonicFieldSwap" + pid, ".mem", dir);  //$NON-NLS-1$//$NON-NLS-2$
            coreFile.deleteOnExit();
            // Now create the actual file
            @SuppressWarnings("resource")
            RandomAccessFile rfile = new RandomAccessFile(coreFile, "rw"); //$NON-NLS-1$
            channel = rfile.getChannel();
            fileMap.put(dir, channel);
            return channel;
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    private static void dataHeartBeat(int count)
    {
        /* Periodically perform actions based on when a particular amount of data has been allocated to SFData objects
         * or freed from them.
         */
        if (reportTicker.addAndGet(count) > MEM_REPORT_THRESHOLD)
        {
            reportTicker.set(0);
            final LongFunction<Long> toGigs = chunks -> chunks * CHUNK_LEN * 8 / SFConstants.ONE_MEG;
            System.out.println(Messages.getString("SFData.20") + toGigs.apply(totalCount.get()) //$NON-NLS-1$
                            + Messages.getString("SFData.21") + toGigs.apply((freeCount.get())));  //$NON-NLS-1$
            System.gc();
        }
    }

    private void makeMap(long size) throws SecurityException, IllegalArgumentException, IOException
    {
        long countDown = size;
        int chunkCount = 0;
        while (countDown > 0)
        {
            ++chunkCount;
            countDown -= CHUNK_LEN;
        }

        countDown = size;
        chunks = new ByteBufferWrapper[chunkCount];
        chunkCount = 0;

        while (countDown > 0)
        {
            ByteBufferWrapper chunk = freeChunks.pollFirst();
            if (chunk == null) break;
            chunks[chunkCount] = chunk;
            freeCount.decrementAndGet();
            ++chunkCount;
            countDown -= CHUNK_LEN;
        }

        if (countDown > 0)
        {
            // If the GC has found something, make sure the finalizers are run here so we get
            // the chunks freed.
            System.runFinalization();
            mapMoreData(countDown, chunkCount);
        }
        getChunkAddresses();
    }

    private void getChunkAddresses()
    {
        // now we have the chunks we get the address of the underlying memory
        // of each and place that in the off heap lookup so we no longer reference
        // them via objects but purely as raw memory
        long offSet = 0;
        for (ByteBufferWrapper chunk : chunks)
        {
            unsafe.putAddress(chunkIndex + offSet, chunk.getAddress());
            offSet += 8;
        }
    }

    private void mapMoreData(long countDown, int chunkCount) throws IOException
    {
        synchronized (fileMap)
        {
            // System.out.println("Adding " + (countDown / CHUNK_LEN) + " chunks");
            while (countDown > 0)
            {
                @SuppressWarnings("resource")
                FileChannel channel = getRotatingChannel();
                long from = channel.size();
                ByteBufferWrapper chunk = chunks[chunkCount] = new ByteBufferWrapper(channel, from);
                allChunks.add(chunk);
                ++chunkCount;
                totalCount.addAndGet(1);
                countDown -= CHUNK_LEN;
            }
        }
    }

    @Override
    public void release()
    {
        checkLive();
        if (pinned.get()) throw new RuntimeException("Trying to release pinned memory."); //$NON-NLS-1$
        dataHeartBeat(getLength());
        dead.set(true);
        synchronized (fileMap)
        {
            // If we are already released, do nothing;
            if (chunks == null) return;

            for (ByteBufferWrapper chunk : chunks)
            {
                chunk.release();
                freeChunks.push(chunk);
                freeCount.incrementAndGet();
            }
            chunks = null;
            freeUnsafeMemory();
        }
    }

    @Override
    public boolean isReleased()
    {
        return dead.get();
    }

    private void checkLive()
    {
        if (dead.get())
        {
            throw new RuntimeException(Messages.getString("SFData.22")); //$NON-NLS-1$
        }
    }

    private void freeUnsafeMemory()
    {
        synchronized (unsafe)
        {
            if (allocked != 0) unsafe.freeMemory(chunkIndex);
            allocked = 0;
        }
    }

    private SFData(int l)
    {
        allocked = 0;
        try
        {
            try
            {
                // Allocate the memory for the index - final so do it here
                long size = (1 + (l >> CHUNK_SHIFT)) << 3;
                allocked = chunkIndex = unsafe.allocateMemory(size);
                if (allocked == 0)
                {
                    throw new RuntimeException("Out of memory allocating " + size); //$NON-NLS-1$
                }
                makeMap(l);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
            this.length = l;
        }
        catch (Throwable t)
        {
            if (allocked != 0)
            {
                unsafe.freeMemory(allocked);
                allocked = 0;
            }
            throw t;
        }
        Stack<SFMemoryZone> stack = memoryZoneStack.get();
        if (stack.size() > 0)
        {
            SFMemoryZone zone = stack.peek();
            synchronized (zone.localData)
            {
                zone.localData.add(this);
            }
        }
        dataHeartBeat(l);
    }

    public final static SFData build(int l, boolean clear)
    {
        SFData ret = new SFData(l);
        if (clear)
        {
            for (long i = 0; i < ret.chunks.length; ++i)
            {
                unsafe.setMemory(unsafe.getAddress(ret.chunkIndex + (i << 3l)), CHUNK_LEN << 3l, (byte) 0);
            }
        }
        return ret;
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#replicate()
     */
    @Override
    public final SFSignal replicate()
    {
        checkLive();
        SFSignal data1 = new SFData(this.getLength());
        int l = this.getLength();
        for (int i = 0; i < l; ++i)
        {
            data1.setSample(i, this.getSample(i));
        }
        return data1;
    }

    private long getAddress(long index)
    {
        if (SFConstants.CHECK_MEMORY)
        {
            if (index < 0 || index > length) throw new RuntimeException("Access out of bounds."); //$NON-NLS-1$
        }
        long pos = index & CHUNK_MASK;
        long bufPos = index >> CHUNK_SHIFT;
        long address = chunkIndex + (bufPos << 3);
        long addr = unsafe.getAddress(address) + (pos << 3l);
        if (SFConstants.CHECK_MEMORY)
        {
            if (addr == 0) throw new RuntimeException("Accessing released memory."); //$NON-NLS-1$
        }
        return addr;
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#getSample(int)
     */
    @Override
    public final double getSample(int index)
    {
        return unsafe.getDouble(getAddress(index));
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#setSample(int, double)
     */
    @Override
    public final double setSample(int index, double value)
    {
        unsafe.putDouble(getAddress(index), value);
        return value;
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#getLength()
     */
    @Override
    public final int getLength()
    {
        return this.length;
    }

    public static final SFSignal build(float[] input)
    {
        // No nead to clear as we set the entire buffer.
        SFSignal data = new SFData(input.length);
        for (int i = 0; i < input.length; ++i)
        {
            data.setSample(i, input[i]);
        }
        return data;
    }

    public static SFSignal build(double[] input)
    {
        SFSignal data = new SFData(input.length);
        for (int i = 0; i < input.length; ++i)
        {
            data.setSample(i, input[i]);
        }
        return data;
    }

    public static final SFSignal build(double[] input, int j)
    {
        SFSignal data = SFData.build(j, false);
        for (int i = 0; i < j; ++i)
        {
            data.setSample(i, input[i]);
        }
        return data;
    }

    public static final SFSignal build(OffHeapArray input, int j)
    {
        SFSignal data = SFData.build(j, false);
        input.checkBoundsDouble(0, j);
        for (int i = 0; i < j; ++i)
        {
            data.setSample(i, input.getDouble(i));
        }
        return data;
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#setAt(int, com.nerdscentral.audio.SFData)
     */
    @Override
    public void setAt(int pos, SFSignal data2) throws SFPL_RuntimeException
    {
        checkLive();
        int pos2 = pos;
        if (pos2 + data2.getLength() > length)
        {
            System.out.println(Messages.getString("SFData.9") + pos2 + Messages.getString("SFData.10") + data2.getLength() //$NON-NLS-1$ //$NON-NLS-2$
                            + Messages.getString("SFData.11") + length);  //$NON-NLS-1$
            throw new SFPL_RuntimeException(Messages.getString("SFData.0")); //$NON-NLS-1$
        }
        long end = pos2 + data2.getLength();
        for (int index = pos2; index < end; ++index)
        {
            setSample(index, data2.getSample(index - pos2));
        }
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#setFrom(int, com.nerdscentral.audio.SFData)
     */
    @Override
    public void setFrom(int pos, SFSignal data2) throws SFPL_RuntimeException
    {
        checkLive();
        int pos2 = pos;
        if (pos2 + length > data2.getLength()) throw new SFPL_RuntimeException(Messages.getString("SFData.1")); //$NON-NLS-1$
        for (int index = 0; index < length; ++index)
        {
            setSample(index, data2.getSample(index - pos2));
        }
    }

    /* (non-Javadoc)
     * @see com.nerdscentral.audio.SFSignal#toString()
     */

    @Override
    public String toString()
    {
        checkLive();
        return Messages.getString("SFData.2") + length + Messages.getString("SFData.3"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    public static SFSignal realise(SFSignal in)
    {
        if (in instanceof SFData) return in;
        int len = in.getLength();
        SFSignal output = new SFData(len);
        for (int i = 0; i < len; ++i)
        {
            output.setSample(i, in.getSample(i));
        }
        return output;
    }

    @Override
    public double[] getDataInternalOnly()
    {
        double[] ret = new double[length];
        for (int index = 0; index < length; ++index)
        {
            ret[index] = getSample(index);
        }
        return ret;
    }

    @Override
    public boolean isRealised()
    {
        checkLive();
        return true;
    }

    private static long getAbsLen(SFSignal a, long aOffset, SFSignal b, long bOffset)
    {
        // Is there enough room irrespective of chunks

        // How much space left in b
        long absLen = b.getLength() - bOffset;

        // How much we can copy from a
        long wantLen = a.getLength() - aOffset;

        // abs len is the amount we could copy ignoring chunks
        if (wantLen < absLen) absLen = wantLen;

        // Find the end of the next chunks
        long aEnd = CHUNK_LEN + (aOffset & ~CHUNK_MASK);
        long bEnd = CHUNK_LEN + (bOffset & ~CHUNK_MASK);
        long aDif = aEnd - aOffset;
        long bDif = bEnd - bOffset;

        long maxChunkLen = aDif > bDif ? bDif : aDif;

        if (absLen > maxChunkLen)
        {
            absLen = maxChunkLen;
        }
        return absLen;
    }

    // Mixes a onto b for as long as that can be done without flipping chuncks
    // returns how many doubles it managed to mix, this can be call repeatedly
    // to then mix two entire buffers. If return is zero then the end of the
    // buffer has been reached.
    private static long addChunks(SFData a, long aOffset, SFData b, long bOffset)
    {
        long absLen = getAbsLen(a, aOffset, b, bOffset);

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long aAddr = a.getAddress(aOffset);
        long bAddr = b.getAddress(bOffset);
        long end = absLen << 3l;
        for (long pos = 0; pos < end; pos += 8)
        {
            double aValue = unsafe.getDouble(aAddr + pos);
            long putAddr = bAddr + pos;
            double bValue = unsafe.getDouble(putAddr);
            // Due to no compile time polymorphism we have to duplicate
            // this method for each operator in the next line or suffer
            // virtual dispatch :|
            unsafe.putDouble(putAddr, aValue + bValue);
        }
        return absLen;
    }

    private static long copyChunks(SFData a, long aOffset, SFData b, long bOffset)
    {
        long absLen = getAbsLen(a, aOffset, b, bOffset);

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long aAddr = a.getAddress(aOffset);
        long bAddr = b.getAddress(bOffset);
        long end = absLen << 3l;
        for (long pos = 0; pos < end; pos += 8)
        {
            double aValue = unsafe.getDouble(aAddr + pos);
            long putAddr = bAddr + pos;
            // Due to no compile time polymorphism we have to duplicate
            // this method for each operator in the next line or suffer
            // virtual dispatch :|
            unsafe.putDouble(putAddr, aValue);
        }
        return absLen;
    }

    private static long multiplyChunks(SFData a, long aOffset, SFData b, long bOffset)
    {
        long absLen = getAbsLen(a, aOffset, b, bOffset);

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long aAddr = a.getAddress(aOffset);
        long bAddr = b.getAddress(bOffset);
        long end = absLen << 3l;
        for (long pos = 0; pos < end; pos += 8)
        {
            double aValue = unsafe.getDouble(aAddr + pos);
            long putAddr = bAddr + pos;
            double bValue = unsafe.getDouble(putAddr);
            // Due to no compile time polymorphism we have to duplicate
            // this method for each operator in the next line or suffer
            // virtual dispatch :|
            unsafe.putDouble(putAddr, aValue * bValue);
        }
        return absLen;
    }

    private static long divideChunks(SFData a, long aOffset, SFData b, long bOffset)
    {
        long absLen = getAbsLen(a, aOffset, b, bOffset);

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long aAddr = a.getAddress(aOffset);
        long bAddr = b.getAddress(bOffset);
        long end = absLen << 3l;
        for (long pos = 0; pos < end; pos += 8)
        {
            double aValue = unsafe.getDouble(aAddr + pos);
            long putAddr = bAddr + pos;
            double bValue = unsafe.getDouble(putAddr);
            // Due to no compile time polymorphism we have to duplicate
            // this method for each operator in the next line or suffer
            // virtual dispatch :|
            unsafe.putDouble(putAddr, aValue / bValue);
        }
        return absLen;
    }

    private static long subtractChunks(SFData a, long aOffset, SFData b, long bOffset)
    {
        long absLen = getAbsLen(a, aOffset, b, bOffset);

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long aAddr = a.getAddress(aOffset);
        long bAddr = b.getAddress(bOffset);
        long end = absLen << 3l;
        for (long pos = 0; pos < end; pos += 8)
        {
            double aValue = unsafe.getDouble(aAddr + pos);
            long putAddr = bAddr + pos;
            double bValue = unsafe.getDouble(putAddr);
            // Due to no compile time polymorphism we have to duplicate
            // this method for each operator in the next line or suffer
            // virtual dispatch :|
            unsafe.putDouble(putAddr, aValue - bValue);
        }
        return absLen;
    }

    /**
     * Stores useful information about a SFData signal so it can be returned as a single return from an optimised analysis run.
     *
     */
    public static class Stats
    {
        public double maxValue   = 0;
        public double minValue   = 0;
        public double totalValue = 0;

    }

    private static long scanChunks(SFData b, long bOffset, Stats stats)
    {
        long absLen = b.getLength() - bOffset;

        // Not absLen is the longest continuous mix we can make from
        // a to b.
        long bAddr = b.getAddress(bOffset);
        for (long pos = 0; pos < absLen; pos += 8)
        {
            long putAddr = bAddr + pos;
            double bValue = unsafe.getDouble(putAddr);
            if (bValue > stats.maxValue) stats.maxValue = bValue;
            if (bValue < stats.minValue) stats.minValue = bValue;
            stats.totalValue += bValue;
        }
        return absLen;
    }

    public Stats getStats()
    {
        checkLive();
        Stats stats = new Stats();
        long done = 0;
        long thisAt = 0;
        while ((done = scanChunks(this, thisAt, stats)) != 0)
        {
            thisAt += done;
        }
        return stats;
    }

    public enum OPERATION
    {
        ADD, COPY, MULTIPLY, DIVIDE, SUBTRACT
    }

    // Note that this is laboriously laid out to give
    // separate call sites for each operation to help give
    // static dispatch after optimisation
    public void operateOnto(int at, SFSignal in, OPERATION operation)
    {
        checkLive();
        if (in.isRealised())
        {
            // TODO this is a bit ambiguous around the meaning of isRealise() but
            // in the current implementation where only SFData returns true for
            // isRealise() we are OK.
            long thisAt = at;
            long otherAt = 0;
            long done = 0;
            SFData data = (SFData) in;
            switch (operation)
            {
            case ADD:
                while ((done = addChunks(data, otherAt, this, thisAt)) != 0)
                {
                    thisAt += done;
                    otherAt += done;
                }
                break;
            case COPY:
                while ((done = copyChunks(data, otherAt, this, thisAt)) != 0)
                {
                    thisAt += done;
                    otherAt += done;
                }
                break;
            case MULTIPLY:
                while ((done = multiplyChunks(data, otherAt, this, thisAt)) != 0)
                {
                    thisAt += done;
                    otherAt += done;
                }
                break;
            case DIVIDE:
                while ((done = divideChunks(data, otherAt, this, thisAt)) != 0)
                {
                    thisAt += done;
                    otherAt += done;
                }
                break;
            case SUBTRACT:
                while ((done = subtractChunks(data, otherAt, this, thisAt)) != 0)
                {
                    thisAt += done;
                    otherAt += done;
                }
                break;
            }
        }
        else
        {
            int len = in.getLength();
            if (len > getLength()) len = getLength();
            switch (operation)
            {
            case ADD:
                for (int index = 0; index < len; ++index)
                {
                    long address = getAddress(index + at);
                    double value = unsafe.getDouble(address);
                    unsafe.putDouble(address, in.getSample(index) + value);
                }
                break;
            case COPY:
                for (int index = 0; index < len; ++index)
                {
                    long address = getAddress(index + at);
                    unsafe.putDouble(address, in.getSample(index));
                }
                break;
            case MULTIPLY:
                for (int index = 0; index < len; ++index)
                {
                    long address = getAddress(index + at);
                    double value = unsafe.getDouble(address);
                    unsafe.putDouble(address, in.getSample(index) * value);
                }
                break;
            case DIVIDE:
                for (int index = 0; index < len; ++index)
                {
                    long address = getAddress(index + at);
                    double value = unsafe.getDouble(address);
                    unsafe.putDouble(address, in.getSample(index) / value);
                }
                break;
            case SUBTRACT:
                for (int index = 0; index < len; ++index)
                {
                    long address = getAddress(index + at);
                    double value = unsafe.getDouble(address);
                    unsafe.putDouble(address, in.getSample(index) - value);
                }
                break;
            }
        }
    }

    public static SFMemoryZone popZone()
    {
        // Release everything in the head of the zone thread local stack.
        SFMemoryZone zone = memoryZoneStack.get().pop();
        // System.out.println("Popped: " + zone);
        synchronized (zone.localData)
        {
            for (SFData data : zone.localData)
            {
                // Pass
                ByteBufferWrapper[] someChunks = data.chunks;
                if (someChunks != null)
                {
                    // Not already released.
                    if (data.kept.get())
                    {
                        data.kept.set(false);
                    }
                    else
                    {
                        if (!data.pinned.get())
                        {
                            data.release();
                        }
                    }
                }
            }
            zone.localData.clear();
        }
        return zone;
    }

    public static SFMemoryZone unstackZone()
    {
        return memoryZoneStack.get().pop();
    }

    public static void stackZone(SFMemoryZone sfMemoryZone)
    {
        pushZone(sfMemoryZone);
    }

    public static SFMemoryZone peekZone()
    {
        Stack<SFMemoryZone> zone = memoryZoneStack.get();
        if (zone.size() == 0) return null;
        return zone.peek();
    }

    public static void pushZone(SFMemoryZone sfMemoryZone)
    {
        // Push to a thread local stack a collection which holds all new SFData objects.
        // So that they can be released explicitly.
        // System.out.println("Pushing: " + sfMemoryZone);
        memoryZoneStack.get().push(sfMemoryZone);
    }

    @Override
    public SFSignal pin()
    {
        checkLive();
        pinned.set(true);
        return this;
    }

    @Override
    public SFSignal keep()
    {
        checkLive();
        kept.set(true);
        return this;
    }
}