package org.herac.tuxguitar.gui.tools.custom.tuner;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import org.apache.log4j.Logger;
import craigl.spectrumanalyzer.FFT;
/**
* @author Nikola Kolarovic <nikola.kolarovic at gmail.com>
*
*
* Having great help analizyng project:
*
* GuitarTuner developed by OpenStudio www.openstudio.fr Copyright
* (2005) Arnault Pachot. Project URL:
* http://sourceforge.net/projects/guitartuner
*
*/
public class TGTuner extends Thread {
static class TGTunerException extends Exception {
private static final long serialVersionUID = -1860514279319403845L;
public TGTunerException(String string) {
super(string);
}
}
public static final transient Logger LOG = Logger.getLogger(TGTuner.class);
public static void computeFFT(int sign, int n, double[] ar, double[] ai) {
double scale = 2.0 / n;
int i, j;
for (i = j = 0; i < n; ++i) {
if (j >= i) {
double tempr = ar[j] * scale;
double tempi = ai[j] * scale;
ar[j] = ar[i] * scale;
ai[j] = ai[i] * scale;
ar[i] = tempr;
ai[i] = tempi;
}
int m = n / 2;
while ((m >= 1) && (j >= m)) {
j -= m;
m /= 2;
}
j += m;
}
int mmax, istep;
for (mmax = 1, istep = 2 * mmax; mmax < n; mmax = istep, istep = 2 * mmax) {
double delta = sign * Math.PI / mmax;
for (int m = 0; m < mmax; ++m) {
double w = m * delta;
double wr = Math.cos(w);
double wi = Math.sin(w);
for (i = m; i < n; i += istep) {
j = i + mmax;
double tr = wr * ar[j] - wi * ai[j];
double ti = wr * ai[j] + wi * ar[j];
ar[j] = ar[i] - tr;
ai[j] = ai[i] - ti;
ar[i] += tr;
ai[i] += ti;
}
}
mmax = istep;
}
}
protected double[] ai;
protected double[] ar;
protected boolean canceled;
protected byte[] data;
protected TargetDataLine dataLine;
// static final int LOG2_FFTSIZE = 14;
// protected int FFT_SIZE;
protected FFT fft;
protected TGTunerListener mainWindow = null;
protected double maximumFrequency;
protected double minimumFrequency;
protected boolean paused;
protected TGTunerQueue queue;
protected double rate;
protected TGTunerSettings settings;
TGTuner(TGTunerListener mainWindow) {
this.mainWindow = mainWindow;
this.canceled = false;
this.paused = false;
this.queue = new TGTunerQueue();
try {
this.settings = TGTunerSettings.loadTuxGuitarSettings();
this.dataLine = TGTunerSettings.getDataLine(this.settings);
} catch (Exception ex) {
this.mainWindow.fireException(ex);
}
this.setWantedRange();
}
public void closeDataLine() {
synchronized (this) {
if (this.dataLine != null) {
this.dataLine.stop();
this.dataLine.close();
}
}
}
protected void computeFFTParams() {
if (this.settings != null) {
int number = this.settings.getFFTSize(), log = 0;
for (int i = 0; i < 20; i++) { // count log2
number /= 2;
if (number < 1) {
this.fft = new FFT(log); // instantiate proper FFT alrgorithm
break;
}
log++;
}
this.data = new byte[this.settings.getBufferSize()]; // data buffer
this.ar = new double[this.settings.getFFTSize()]; // create real array for
// FFT
this.ai = new double[this.settings.getFFTSize()]; // create imaginary
// array for FFT
// TODO: rate is maybe not the best /2 ?
this.rate = ((double) this.settings.getSampleRate())
/ this.settings.getFFTSize();
}
}
public double getNoteFrequency(int midiNote) {
// A = 110Hz = MidiNote(45)
return 110 * Math.pow(2.0, (midiNote - 45) / 12.0);
}
public TGTunerSettings getSettings() {
return this.settings;
}
public void openDataLine() {
if (this.dataLine != null) {
synchronized (this) {
this.computeFFTParams();
try {
this.dataLine.open();
this.dataLine.start();
// this.dataLine.open(this.settings.getAudioFormat(),
// this.settings.getBufferSize());
} catch (LineUnavailableException e) {
LOG.error("------- TGTuner: openDataLine() exception -------", e);
}
}
}
}
public void pause() {
this.paused = true;
}
public void resumeFromPause() {
this.paused = false;
this.queue.clear();
}
public void run() {
// TODO: remove this benchmark
int cycles = 0;
long timePassed = 0;
if (this.dataLine != null) {
this.openDataLine();
}
while (!this.canceled) {
if (this.dataLine != null)
if (!this.paused && this.dataLine.isOpen()) {
synchronized (this) {
// read from the input
this.dataLine.read(this.data, 0, this.settings.getBufferSize());
// preapare the arrays for FFT
for (int i = 0; i < this.settings.getBufferSize(); i++) {
this.ar[i] = this.data[i]; // implicit cast to double
this.ai[i] = 0.0f;
}
// when buffer size is smaller than FFT_SIZE
for (int i = this.settings.getBufferSize(); i < this.settings
.getFFTSize(); i++) {
this.ar[i] = 0.0f;
this.ai[i] = 0.0f;
}
// ///////////////////////////////////////////////////////////
// TODO: look at the benchmark, uncoment the other one and comment
// the first one
cycles++;
long startTime = System.currentTimeMillis();
if (this.fft != null)
this.fft.doFFT(this.ar, this.ai, false);
// TGTuner.computeFFT(1, this.settings.getFFTSize(), this.ar,
// this.ai);
timePassed += System.currentTimeMillis() - startTime;
// ///////////////////////////////////////////////////////////
// ------ determine the dominant frequency -------
double frequency = -1;
double maxAmplitude = this.settings.getTreshold(); // noise gate
// TODO: maybe to analyze only the data around the area of interest
// start has to be at least 1, because we want to skip the DC
// component!
// for (int i=0; i<this.settings.getFFTSize(); i++) {
for (int i = (int) Math.round(this.minimumFrequency / this.rate); i < Math
.round(this.maximumFrequency / this.rate); i++) {
double curFreq = i * this.rate;
// z = x*i + y*j // power = |z| = sqrt(x^2 + y^2)
double power = Math.sqrt(Math.pow(this.ar[i], 2)
+ Math.pow(this.ai[i], 2));
if (power > maxAmplitude) {
maxAmplitude = power;
frequency = curFreq;
}
}
// LOG.debug("Max Amplitude: "+maxAmplitude);
// ** buffer the frequency
this.queue.add(frequency);
// fire the frequency event on GUI
this.mainWindow.fireFrequency(this.queue.getFreqApproximation());
// flush the dataline, so the new data is received
this.dataLine.flush();
} // end of synchronized block
}
// sleep thread to get better app response
try {
Thread.sleep(this.settings.getWaitPeriod());
} catch (InterruptedException e) {
// do nothing?
}
}
this.closeDataLine();
LOG.debug(" Average FFT time: " + (double) timePassed / (double) cycles);
}
public void setCanceled(boolean canceled) {
this.canceled = canceled;
}
public void setSettings(TGTunerSettings settings) {
this.closeDataLine();
synchronized (this) {
this.settings = settings;
try {
this.dataLine = TGTunerSettings.getDataLine(settings);
} catch (Exception ex) {
this.mainWindow.fireException(ex);
}
}
this.openDataLine();
}
public void setWantedNote(int note) {
// TODO: maybe too restrictive? But eliminates fifths harmonics...
this.minimumFrequency = getNoteFrequency(note - 3);
this.maximumFrequency = getNoteFrequency(note + 3);
}
public void setWantedRange() {
// TODO: maybe to restrict only to 1 or 2 octaves!!!!!!!!!
int[] tuning = this.mainWindow.getTuning();
this.maximumFrequency = getNoteFrequency(tuning[0] + 3); // 3 frets higher
// that the
// thiniest string
this.minimumFrequency = getNoteFrequency(tuning[tuning.length - 1] - 3); // 3
// frets
// lower
// that
// the
// thickest
// string
}
}