public void setWindowType(WindowType windowType) { mWindowType = windowType; if (mSampleType == SampleType.COMPLEX) { mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2); } else { mWindow = Window.getWindow(mWindowType, mDFTSize.getSize()); } }
private void calculateConsumptionRate() { mNewFloatResidual = 0.0f; mNewFloatsPerFrame = ((float) mSampleRate / (float) mFrameRate) * (mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f); mFFTFloatsPerFrame = (mSampleType == SampleType.COMPLEX ? mDFTSize.getSize() * 2 : mDFTSize.getSize()); }
/** * Checks for a queued FFT width change request and applies it. This method will only be accessed * by the scheduled executor that gains access to run a calculate method, thus providing thread * safety. */ private void checkFFTSize() { if (mNewDFTSize.getSize() != mDFTSize.getSize()) { mDFTSize = mNewDFTSize; calculateConsumptionRate(); setWindowType(mWindowType); if (mSampleType == SampleType.COMPLEX) { mPreviousFrame = new float[mDFTSize.getSize() * 2]; } else { mPreviousFrame = new float[mDFTSize.getSize()]; } mFFT = new FloatFFT_1D(mDFTSize.getSize()); } }
/** * Processes both complex samples or float samples and dispatches a float array of DFT results, * using configurable fft size and output dispatch timelines. */ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeListener, IDFTWidthChangeProcessor { private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class); private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<DFTResultsConverter>(); private ArrayBlockingQueue<ComplexBuffer> mQueue = new ArrayBlockingQueue<ComplexBuffer>(200); private ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1, new NamingThreadFactory("spectrum dft")); public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate"; private DFTSize mDFTSize = DFTSize.FFT04096; private DFTSize mNewDFTSize = DFTSize.FFT04096; private double[] mWindow; /* The Cosine and Hanning windows seem to offer the best spectral display * with minimal bin leakage/smearing */ private WindowType mWindowType = Window.WindowType.HANNING; private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize()); private int mFrameRate; private int mSampleRate; private int mFFTFloatsPerFrame; private float mNewFloatsPerFrame; private float mNewFloatResidual; private float[] mPreviousFrame = new float[8192]; private float[] mCurrentBuffer; private int mCurrentBufferPointer = 0; private SampleType mSampleType; private AtomicBoolean mRunning = new AtomicBoolean(); public DFTProcessor(SampleType sampleType) { setSampleType(sampleType); loadSettings(); } private void loadSettings() { int frameRate = SystemProperties.getInstance().get(DFTProcessor.FRAME_RATE_PROPERTY, 20); setFrameRate(frameRate); } public void dispose() { stop(); mListeners.clear(); mQueue.clear(); mWindow = null; mCurrentBuffer = null; } public WindowType getWindowType() { return mWindowType; } public void setWindowType(WindowType windowType) { mWindowType = windowType; if (mSampleType == SampleType.COMPLEX) { mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2); } else { mWindow = Window.getWindow(mWindowType, mDFTSize.getSize()); } } /** * Sets the processor mode to Float or Complex, depending on the sample types that will be * delivered for processing */ public void setSampleType(SampleType type) { mSampleType = type; setWindowType(mWindowType); } public SampleType getSampleType() { return mSampleType; } /** * Queues an FFT size change request. The scheduled executor will apply the change when it runs. */ public void setDFTSize(DFTSize size) { mNewDFTSize = size; } public DFTSize getDFTSize() { return mDFTSize; } public int getFrameRate() { return mFrameRate; } public void setFrameRate(int framesPerSecond) { // TODO: make sure frame rate & sample rate sample requirement doesn't // expect overlap greater than the previous frame length if (framesPerSecond < 1 || framesPerSecond > 1000) { throw new IllegalArgumentException( "DFTProcessor cannot run " + "more than 1000 times per second -- requested setting:" + framesPerSecond); } mFrameRate = framesPerSecond; SystemProperties properties = SystemProperties.getInstance(); properties.set(FRAME_RATE_PROPERTY, String.valueOf(mFrameRate)); properties.save(); calculateConsumptionRate(); restart(); } public void start() { /** Reset the scheduler */ mScheduler = Executors.newScheduledThreadPool(1, new NamingThreadFactory("spectrum dft")); /** Schedule the DFT to run calculations at a fixed rate */ int initialDelay = 0; int period = (int) (1000 / mFrameRate); TimeUnit unit = TimeUnit.MILLISECONDS; mScheduler.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, unit); } public void stop() { /** Shutdown the scheduler and clear out any remaining tasks */ try { mScheduler.shutdown(); mScheduler.awaitTermination(100, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { /* Do nothing ... we're shutting down */ // mLog.error( "DFTProcessor - exception while awaiting shutdown of " // + "calculation scheduler for reset", e ); } } public void restart() { stop(); start(); } public int getCalculationsPerSecond() { return mFrameRate; } /** Places the sample into a transfer queue for future processing. */ @Override public void receive(ComplexBuffer sampleBuffer) { if (!mQueue.offer(sampleBuffer)) { mLog.error( "DFTProcessor - [" + mSampleType.toString() + "] queue is full, purging queue, " + "samples[" + sampleBuffer + "]"); mQueue.clear(); mQueue.offer(sampleBuffer); } } private void getNextBuffer() { mCurrentBuffer = null; try { Buffer buffer = mQueue.take(); mCurrentBuffer = buffer.getSamples(); } catch (InterruptedException e) { mCurrentBuffer = null; } mCurrentBufferPointer = 0; } private float[] getSamples() { int remaining = (int) mFFTFloatsPerFrame; float[] currentFrame = new float[remaining]; int currentFramePointer = 0; float integralFloatsToConsume = mNewFloatsPerFrame + mNewFloatResidual; int newFloatsToConsumeThisFrame = (int) integralFloatsToConsume; mNewFloatResidual = integralFloatsToConsume - newFloatsToConsumeThisFrame; /* If the number of required floats for the fft is greater than the * consumption rate per frame, we have to reach into the previous * frame to makeup the difference. */ if (newFloatsToConsumeThisFrame < remaining) { int previousFloatsRequired = remaining - newFloatsToConsumeThisFrame; System.arraycopy( mPreviousFrame, mPreviousFrame.length - previousFloatsRequired, currentFrame, currentFramePointer, previousFloatsRequired); remaining -= previousFloatsRequired; currentFramePointer += previousFloatsRequired; } /* Fill the rest of the buffer with new samples */ while (mRunning.get() && remaining > 0) { if (mCurrentBuffer == null || mCurrentBufferPointer >= mCurrentBuffer.length) { getNextBuffer(); } /* If we don't have new samples to use, send the current frame with * the remaining values as zero */ if (mCurrentBuffer == null) { remaining = 0; } else { int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer; while (remaining > 0 && samplesAvailable > 0) { currentFrame[currentFramePointer++] = (float) mCurrentBuffer[mCurrentBufferPointer++]; samplesAvailable--; remaining--; newFloatsToConsumeThisFrame--; } } } /* If the incoming float rate is greater than the fft consumption rate, * then we have to purge some floats, otherwise, store the previous * frame, because we have overlapping frames */ if (newFloatsToConsumeThisFrame > 0) { purge(newFloatsToConsumeThisFrame); } else { mPreviousFrame = Arrays.copyOf(currentFrame, currentFrame.length); } return currentFrame; } private void calculate() { float[] samples = getSamples(); Window.apply(mWindow, samples); if (mSampleType == SampleType.REAL) { mFFT.realForward(samples); } else { mFFT.complexForward(samples); } dispatch(samples); } private void purge(int samplesToPurge) { if (samplesToPurge <= 0) { throw new IllegalArgumentException("DFTProcessor - cannot purge " + "negative sample amount"); } while (mRunning.get() && samplesToPurge > 0) { if (mCurrentBuffer == null || mCurrentBufferPointer >= mCurrentBuffer.length) { getNextBuffer(); } if (mCurrentBuffer != null) { int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer; if (samplesAvailable >= samplesToPurge) { mCurrentBufferPointer += samplesToPurge; samplesToPurge = 0; } else { samplesToPurge -= samplesAvailable; mCurrentBufferPointer = mCurrentBuffer.length; } } else { samplesToPurge = 0; } } } /** * Takes a calculated DFT results set, reformats the data, and sends it out to all registered * listeners. */ private void dispatch(float[] results) { Iterator<DFTResultsConverter> it = mListeners.iterator(); while (it.hasNext()) { it.next().receive(results); } } public void addConverter(DFTResultsConverter listener) { mListeners.add(listener); } public void removeConverter(DFTResultsConverter listener) { mListeners.remove(listener); } private class DFTCalculationTask implements Runnable { @Override public void run() { try { /* Only run if we're not currently running */ if (mRunning.compareAndSet(false, true)) { checkFFTSize(); calculate(); mRunning.set(false); } } catch (Exception e) { mLog.error("error during dft processor calculation task", e); } } } /** * Checks for a queued FFT width change request and applies it. This method will only be accessed * by the scheduled executor that gains access to run a calculate method, thus providing thread * safety. */ private void checkFFTSize() { if (mNewDFTSize.getSize() != mDFTSize.getSize()) { mDFTSize = mNewDFTSize; calculateConsumptionRate(); setWindowType(mWindowType); if (mSampleType == SampleType.COMPLEX) { mPreviousFrame = new float[mDFTSize.getSize() * 2]; } else { mPreviousFrame = new float[mDFTSize.getSize()]; } mFFT = new FloatFFT_1D(mDFTSize.getSize()); } } public void clearBuffer() { mQueue.clear(); } @Override public void frequencyChanged(FrequencyChangeEvent event) { switch (event.getEvent()) { case SAMPLE_RATE_CHANGE_NOTIFICATION: mSampleRate = event.getValue().intValue(); calculateConsumptionRate(); break; default: break; } } /** */ private void calculateConsumptionRate() { mNewFloatResidual = 0.0f; mNewFloatsPerFrame = ((float) mSampleRate / (float) mFrameRate) * (mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f); mFFTFloatsPerFrame = (mSampleType == SampleType.COMPLEX ? mDFTSize.getSize() * 2 : mDFTSize.getSize()); } }