/* * Created on Mar 20, 2007 * * Copyright (c) 2006-2007 P.J.Leonard * * http://www.frinika.com * * This file is part of Frinika. * * Frinika is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * Frinika is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with Frinika; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.frinika.audio.analysis.constantq; import com.frinika.audio.io.LimitedAudioReader; import java.awt.Dimension; import java.io.IOException; import rasmus.interpreter.sampled.util.FFT; import com.frinika.audio.analysis.DataBuilder; import com.frinika.audio.analysis.SpectrogramDataListener; import com.frinika.audio.analysis.SpectrumDataBuilder; import com.frinika.audio.analysis.StaticSpectrogramSynth; import uk.org.toot.audio.core.AudioBuffer; /** * Creates a spectrogram from a DoubleDataSource * * Observers are notified when data changes (during build) * * SizeObserver are notify when the number of frequency bins is changed. * * @author pjl * */ public class ConstantQSpectrogramDataBuilder extends DataBuilder implements SpectrumDataBuilder { private LimitedAudioReader reader; private float[][] magnArray; private float[][] phaseArray; private float[][] dPhaseFreqHz; float freqArray[]; int chunkPtr = 0; private int sizeInChunks; private int nBin; double dt = .01; double Fs; int nFrame; double minF; double maxF; int binsPerOctave = 48; double thresh = 0.02; int chunksize; Dimension size; double spread; private int chunkStartInSamples; FFTConstantQ fftCQ; public ConstantQSpectrogramDataBuilder() { } // /** // * // * @param reader // * @param minF // * @param nOctave // * @param binsPerOctave // */ // ConstantQSpectrogramDataBuilder(AudioReader reader, double minF, double maxF, // int binsPerOctave, double thresh, double spread, double dt) { // // setParameters(reader, minF, maxF, binsPerOctave, thresh, spread, dt); // } public void setParameters(LimitedAudioReader reader, double minF, double maxF, int binsPerOctave, double thresh, double spread, double dt) { abortConstruction(); this.reader = reader; if (minF == this.minF && maxF == this.maxF && binsPerOctave == this.binsPerOctave && thresh == this.thresh && this.spread == spread && this.dt == dt) return; this.dt = dt;// = .01; Fs = reader.getSampleRate(); nFrame = (int) reader.getEnvelopedLengthInFrames(); this.minF = minF; // maxF = Math.pow(2, nOctave) * minF; this.maxF = maxF; this.binsPerOctave = binsPerOctave; this.thresh = thresh; this.spread = spread; chunksize = (int) (Fs * dt); sizeInChunks = nFrame / chunksize; startConstruction(); } public void addSizeObserver(SpectrogramDataListener o) { sizeObservers.add(o); } void notifySizeObservers() { for (SpectrogramDataListener o : sizeObservers) o.notifySizeChange(size); } void notifyMoreDataObservers() { for (SpectrogramDataListener o : sizeObservers) o.notifyMoreDataReady(); } public int getSizeInChunks() { return sizeInChunks; } public int getChunkRenderedCount() { return chunkPtr; } public int getBinCount() { return nBin; } public float[][] getMagnitude() { return magnArray; } protected void doWork() { chunkPtr=-1; // make invalid try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(" Interrupted before I even started !! "); e.printStackTrace(); return; } dt = chunksize / Fs; fftCQ = new FFTConstantQ(Fs, minF, maxF, binsPerOctave, thresh, spread); int fftsize = fftCQ.getFFTSize(); double freq[] = fftCQ.freqs; /* * Here the size of arrays changes any user should synchronize with me * here */ synchronized (this) { freqArray = new float[freq.length]; for (int i = 0; i < freq.length; i++) { freqArray[i] = (float) freq[i]; } System.out.println(" fftsize/chunkSIze = " + fftsize + "/" + chunksize); nBin = fftCQ.getNumberOfOutputBands(); size = new Dimension(sizeInChunks, nBin); dPhaseFreqHz = new float[sizeInChunks][nBin]; magnArray = new float[sizeInChunks][nBin]; phaseArray = new float[sizeInChunks][nBin]; } double twoPI = 2 * Math.PI; // Phase change (radians) of each bin due to chunksize translation double dPhaRef[] = new double[nBin]; for (int i = 0; i < nBin; i++) { dPhaRef[i] = (twoPI * freq[i] * dt); // >0 } double fftOut[] = new double[nBin * 2]; double fftIn[] = new double[fftsize]; double input[] = new double[fftsize]; try { reader.seekEnvelopeStart(false); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } double testF = minF * 2; // reader = new SInDoubleSource(testF,Fs); int ch = reader.getChannels(); int nRead=0; AudioBuffer buffer = new AudioBuffer("TEMP",ch,chunksize,44100); chunkPtr = -fftsize / chunksize / 2; int extraChunks= -chunkPtr; notifySizeObservers(); chunkStartInSamples=0; do { if (Thread.interrupted()) { return; } if (fftsize != chunksize) { for (int i = 0; i < fftsize - chunksize; i++) input[i] = input[i + chunksize]; } buffer.makeSilence(); reader.processAudio(buffer); nRead += chunksize; // System.out.println(reader.getCurrentFrame()); float left[]=buffer.getChannel(0); for (int i = fftsize - chunksize, j = 0; i < fftsize; i++, j++) { // if (ch == 2) // input[i] = buffer[2 * j + 1]; // else input[i] = left[j]; } if (chunkPtr < 0) { chunkPtr++; chunkStartInSamples += chunksize; continue; } for (int i = 0; i < fftsize; i++) { fftIn[i] = input[i]; } fftCQ.calc(fftIn, fftOut); for (int i = 0; i < nBin; i++) { double real = fftOut[2 * i]; double imag = fftOut[2 * i + 1]; magnArray[chunkPtr][i] = (float) Math.sqrt(real * real + imag * imag); phaseArray[chunkPtr][i] = (float) Math.atan2(imag, real); // -PI // PI double phaLast; if (chunkPtr > 0) { phaLast = phaseArray[chunkPtr - 1][i]; } else { phaLast = 0.0; } double dpha = phaseArray[chunkPtr][i] - phaLast; // make it in the range [-PI PI] dpha = -((dPhaRef[i] - dpha + Math.PI + twoPI) % twoPI - Math.PI); dPhaseFreqHz[chunkPtr][i] = (float) (freq[i] + dpha / twoPI / dt); } chunkPtr++; // System.out.println(vertPtr[0]); // bar.setValue(pix); if (chunkPtr % 50 == 0) { notifyMoreDataObservers(); } // } while (!reader.eof() && chunkPtr < sizeInChunks); }while (chunkPtr < sizeInChunks); System.out.println(" DATA BUILT "); notifyMoreDataObservers(); } public float[] getFreqArray() { return freqArray; } public float[] getMagnitudeAt(long chunkPtr) { if (magnArray == null) return null; // int pix = (int) (framePtr / chunksize); if (chunkPtr >= magnArray.length || chunkPtr < 0) return null; return magnArray[(int) chunkPtr]; } public float[] getPhaseAt(long chunkPtr) { if (phaseArray == null) return null; // int pix = (int) (framePtr / chunksize); if (chunkPtr >= phaseArray.length || chunkPtr < 0) return null; return phaseArray[(int) chunkPtr]; } public float[] getPhaseFreqAt(long chunkPtr) { if (dPhaseFreqHz == null) return null; // int pix = (int) (framePtr / chunksize); if (chunkPtr >= dPhaseFreqHz.length) return null; return dPhaseFreqHz[(int) chunkPtr]; } public long getLengthInFrames() { return reader.getEnvelopedLengthInFrames(); } public long chunkStartInSamples(long chunkPtr) { return chunkStartInSamples + chunkPtr*chunksize; } public int getChunkAtFrame(long framePtr) { int chunkPtr=(int) ((framePtr-chunkStartInSamples)/chunksize); return chunkPtr; } public boolean validAt(long chunkPtr2) { return chunkPtr2>=0 && chunkPtr2 < this.chunkPtr; } public StaticSpectrogramSynth getSynth() { return new StaticSpectrogramSynth(this); } public FFT getFFT() { return fftCQ.getFFT(); } // public float[] getSMagnitudeAt(long chunkPtr) { // // TODO Auto-generated method stub // return null; // } // public float[][] getSMagnitude() { // // TODO Auto-generated method stub // return getMagnitude(); // } }