/**
* Xtreme Media Player a cross-platform media player.
* Copyright (C) 2005-2011 Besmir Beqiri
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package xtrememp.visualization;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.Point;
import java.awt.Toolkit;
import java.nio.FloatBuffer;
import java.text.DecimalFormat;
import java.util.Map;
import xtrememp.player.dsp.DssContext;
import xtrememp.visualization.spectrum.Band;
import xtrememp.visualization.spectrum.BandDistribution;
import xtrememp.visualization.spectrum.BandGain;
import xtrememp.visualization.spectrum.FFT;
import xtrememp.visualization.spectrum.FlatBandGain;
import xtrememp.visualization.spectrum.FrequencyBandGain;
import xtrememp.visualization.spectrum.LinearBandDistribution;
import xtrememp.visualization.spectrum.LogBandDistribution;
import javax.sound.sampled.SourceDataLine;
/**
* Renders a spectrum analyzer.
*
* Based on KJ-DSS project by Kristofer Fudalewski (http://sirk.sytes.net).
*
* @author Besmir Beqiri
*/
public final class SpectrumBars extends Visualization {
public static final String NAME = "Spectrum Bars";
//
public static final BandDistribution BAND_DISTRIBUTION_LINEAR = new LinearBandDistribution();
public static final BandDistribution BAND_DISTRIBUTION_LOG = new LogBandDistribution(4, 20.0D);
public static final BandGain BAND_GAIN_FLAT = new FlatBandGain(4.0F);
public static final BandGain BAND_GAIN_FREQUENCY = new FrequencyBandGain(4.0F);
//
public static final BandDistribution DEFAULT_SPECTRUM_ANALYSER_BAND_DISTRIBUTION = BAND_DISTRIBUTION_LOG;
public static final BandGain DEFAULT_SPECTRUM_ANALYSER_BAND_GAIN = BAND_GAIN_FREQUENCY;
public static final int DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT = 20;
public static final int DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY = 25;
public static final float DEFAULT_SPECTRUM_ANALYSER_DECAY = 0.02F;
public static final float DEFAULT_SPECTRUM_ANALYSER_GAIN = 1.0F;
//
private BandDistribution bandDistribution;
private BandGain bandGain;
private Band[] bdTable;
private float[] bgTable;
private int bands;
private int fftSampleSize;
private float fftSampleRate;
private FFT fft;
private float decay;
private float gain;
private int[] peaks;
private int[] peaksDelay;
private int peakDelay;
private boolean peaksEnabled = true;
private float bandWidth;
private boolean showFrequencies = true;
private float[] old_FFT;
private LinearGradientPaint lgp;
private Map desktopHints;
private Font freqFont;
public SpectrumBars() {
this.bandDistribution = DEFAULT_SPECTRUM_ANALYSER_BAND_DISTRIBUTION;
this.bandGain = DEFAULT_SPECTRUM_ANALYSER_BAND_GAIN;
this.decay = DEFAULT_SPECTRUM_ANALYSER_DECAY;
this.gain = DEFAULT_SPECTRUM_ANALYSER_GAIN;
this.peaks = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT];
this.peaksDelay = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT];
this.peakDelay = DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY;
Toolkit tk = Toolkit.getDefaultToolkit();
this.desktopHints = (Map) (tk.getDesktopProperty("awt.font.desktophints"));
this.freqFont = new Font("Arial", Font.PLAIN, 10);
setBandCount(DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT);
}
/**
* Sets the numbers of bands rendered by the spectrum analyser.
*
* @param count Cannot be more than half the "FFT sample size".
*/
public synchronized void setBandCount(int count) {
bands = count;
computeBandTables();
}
private void computeBandTables() {
if (bands > 0 && fftSampleSize > 0 & fft != null) {
//Create band table.
bdTable = bandDistribution.create(bands, fft, fftSampleRate);
bands = bdTable.length;
//Resolve band descriptions.
resolveBandDescriptions(bdTable);
//Create gain table.
bgTable = bandGain.create(fft, fftSampleRate);
}
}
private void resolveBandDescriptions(Band[] bandTable) {
DecimalFormat df = new DecimalFormat("###.#");
for (Band band : bandTable) {
if (band.frequency >= 1000.0F) {
band.description = df.format(band.frequency / 1000.0F) + "k";
} else {
band.description = df.format(band.frequency);
}
}
}
@Override
public void init(int sampleSize, SourceDataLine sourceDataLine) {
this.fftSampleSize = sampleSize;
this.fftSampleRate = sourceDataLine.getFormat().getFrameRate();
this.fft = new FFT(fftSampleSize);
this.old_FFT = new float[bands];
computeBandTables();
}
@Override
public String getDisplayName() {
return NAME;
}
@Override
public synchronized void render(DssContext dssContext, Graphics2D g2d, int width, int height) {
float c = 0;
int b, bd, i, li = 0, mi;
float fs, m;
int bm = 1;
//Preparation used for rendering band frequencies.
if (showFrequencies) {
g2d.setRenderingHints(desktopHints);
g2d.setFont(freqFont);
bm = Math.round(32.0F / bandWidth);
if (bm == 0) {
bm = 1;
}
}
//FFT processing.
FloatBuffer[] channelsBuffer = dssContext.getDataNormalized();
float[] _fft = fft.calculate(channelsMerge(channelsBuffer));
bandWidth = (float) width / (float) bands;
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, width, height);
//Group up available bands using band distribution table.
for (bd = 0; bd < bands; bd++) {
//Get band distribution entry.
i = bdTable[bd].distribution;
m = 0;
mi = 0;
//Find loudest band in group. (Group is from 'li' to 'i').
for (b = li; b < i; b++) {
float lf = _fft[b];
if (lf > m) {
m = lf;
mi = b;
}
}
li = i;
//Calculate gain using log, then static gain.
fs = (m * bgTable[mi]) * gain;
//Limit over-saturation.
if (fs > 1.0F) {
fs = 1.0F;
}
//Compute decay.
if (fs >= (old_FFT[bd] - decay)) {
old_FFT[bd] = fs;
} else {
old_FFT[bd] -= decay;
if (old_FFT[bd] < 0) {
old_FFT[bd] = 0;
}
fs = old_FFT[bd];
}
if (lgp == null || lgp.getEndPoint().getY() != height) {
Point start = new Point(0, 0);
Point end = new Point(0, height);
float[] dist = {0.0F, 0.25F, 0.75F, 1.0F};
Color[] colors = {Color.red, Color.yellow, Color.green, Color.green.darker().darker()};
lgp = new LinearGradientPaint(start, end, dist, colors, CycleMethod.REPEAT);
}
g2d.setPaint(lgp);
renderSpectrumBar(g2d, Math.round(c), height - 18, Math.round(bandWidth) - 1,
Math.round(fs * (height - 20)), bd, bdTable[bd], showFrequencies && (bd % bm) == 0);
c += bandWidth;
}
}
private void renderSpectrumBar(Graphics2D g2d, int x, int y, int w, int h, int bd, Band band, boolean renderFrequency) {
//Render spectrum bar.
g2d.fillRect(x, y - h, w, h);
//Render peak.
if ((peaksEnabled == true)) {
g2d.setColor(foregroundColor);
if (h > peaks[bd]) {
peaks[bd] = h;
peaksDelay[bd] = peakDelay;
} else {
peaksDelay[bd]--;
if (peaksDelay[bd] < 0) {
peaks[bd]--;
}
if (peaks[bd] < 0) {
peaks[bd] = 0;
}
}
g2d.fillRect(x, y - peaks[bd], w, 1);
}
//Render frequency string.
if (renderFrequency) {
g2d.setColor(foregroundColor);
int sx = x + ((w - g2d.getFontMetrics().stringWidth(band.description)) >> 1);
// g2d.drawLine(x + (w >> 1), y, x + (w >> 1), y - (g2d.getFontMetrics().getHeight() - g2d.getFontMetrics().getAscent()));
g2d.drawString(band.description, sx, y + g2d.getFontMetrics().getHeight());
}
}
}