/* For Copyright and License see LICENSE.txt and COPYING.txt in the root directory */ package com.nerdscentral.audio.core; import java.util.concurrent.atomic.AtomicLong; import com.nerdscentral.audio.core.SFData.ByteBufferWrapper; import com.nerdscentral.audio.pitch.CubicInterpolator; import com.nerdscentral.sython.SFMaths; import com.nerdscentral.sython.SFPL_RuntimeException; public abstract class SFSignal { private final static AtomicLong uniqueId = new AtomicLong(0); private final long myId; public abstract SFSignal replicate(); private static ThreadLocal<String> pythonStack = new ThreadLocal<>(); public final SFSignal replicateEmpty() { return SFData.build(this.getLength()); } public abstract double getSample(int index); protected SFSignal() { myId = uniqueId.incrementAndGet(); } public SFSignal __pos__() { return this; } public SFSignal __neg__() { return this; } public SFSignal pin() { return SFData.realise(this).pin(); } public SFSignal keep() { return SFData.realise(this).keep(); } /** * Returns a linearly interpolated sample based on the samples either side of the the passed index. This is used for super * sampling or pitch effects. * * @param index * @return */ public final double getSampleLinear(double index) { double s = SFMaths.floor(index); double e = SFMaths.ceil(index); if (s < 0 || e >= getLength()) { return 0; } if (s == e) return getSample((int) s); double a = getSample((int) s); double b = getSample((int) e); return ((index - s) * b + (e - index) * a); } /** * Returns a cubic interpolated sample based on the samples either side of the the passed index. This is used for super * sampling or pitch effects. Cubic interpolation uses two samples either side of the required point and so at the ends of * the sample this will fall back to linear interpolation. * * @param index * @return */ public final double getSampleCubic(double index) { int s = (int) SFMaths.floor(index); int e = (int) SFMaths.ceil(index); if (s < 0 || e >= getLength()) { return 0; } if (s > getLength() - 3 || index < 1) { if (s == e) return getSample(s); double a = getSample(s); double b = getSample(e); return ((index - s) * b + (e - index) * a); } return CubicInterpolator.getValue(getSample(s - 1), getSample(s), getSample(s + 1), getSample(s + 2), index - s); } /** * Get a sample using periodic boundary conditions. As an optimisation it will only work negative values up it -length. * * @param asked * @param length * @return the sample at the ask location assuming periodic boundaries */ private final double getPeriodicSample(int asked, int length) { int correctedAsked = asked; if (asked < 0) correctedAsked += length; return getSample(correctedAsked % length); } /** As per getSampleCubic but using periodic boundary conditions */ public final double getSampleCubicPeriodic(double index) { int s = (int) SFMaths.floor(index); int len = getLength(); return CubicInterpolator.getValue(getPeriodicSample(s - 1, len), getPeriodicSample(s, len), getPeriodicSample(s + 1, len), getPeriodicSample(s + 2, len), index - s); } public abstract double setSample(int index, double value); public abstract int getLength(); public abstract void setAt(int pos, SFSignal data) throws SFPL_RuntimeException; public abstract void setFrom(int pos, SFSignal dataIn) throws SFPL_RuntimeException; public abstract double[] getDataInternalOnly(); /** * Get a globally unique identifier for this SFSingal * * @return */ public final long getUniqueId() { return myId; } public abstract void release(); public static String getPythonStack() { return pythonStack.get(); } public static void setPythonStack(String ps) { SFSignal.pythonStack.set(ps); } public void clear() { // NOOP on root class } public SFSignal realise() { // FIXME: Parent has knowledge of child which is BAD. return SFData.realise(this); } @SuppressWarnings("static-method") public boolean isRealised() { return false; } @Override public void finalize() { release(); } public SFSignal flush() { // FIXME: Parent has knowledge of child which is BAD. SFData data = (SFData) SFData.realise(this); for (ByteBufferWrapper chunk : data.chunks) { chunk.force(); } return data.keep(); } }