/*******************************************************************************
* Thierry.Vieville@sophia.inria.fr, Copyright (C) 2009.  All rights reserved. *
*******************************************************************************/

package org.javascool.proglets.syntheSons;

// Used to define an audio stream
import javax.sound.sampled.AudioInputStream;
import java.io.IOException;

// Used to show a curve
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;

import org.javascool.tools.sound.SoundBit;

/** Defines the drawing of a SoundBit spectrum and trace's start.
 <div>Opens a frame, drawing: <ul>
 *  <li>drawing the frequencies amplitudes (in red, in normalized dB (log coordinates)), frequencies being drawn between A0 (27.5Hz) and A9 (6400Hz) around A3 (440Hz);</li>
 *  <li>drawing the 1st samples of the signal (in yellow, the 1st <tt>11</tt>ms).</li>
 </ul> while the sound name main frequency and spectral magnitude is printed.
 *
 @see <a href="SoundBitPanel.java.html">code source</a>
 @serial exclude
 */
public class SoundBitPanel extends JPanel {
  private static final long serialVersionUID = 1L;

  /** Defines the spectrum and trace's start of the sound.
   @param sound The sound to visualize.
   @param channel Left 'l' or right 'r' channel.
   */
  public void reset(SoundBit sound, char channel) {
	// Calculates the spectrum magnitude and phase
	data = getData(sound, channel);
	complex fft[] = getFFT(data);
	mag = new double[hsize + 1];
	for(int i = 0; i <= hsize; i++) {
	  double f = f0 * Math.pow(f1 / f0, i / (doublehsize);
	  int k = (intMath.rint(fft.length * f / SoundBit.SAMPLING);
	  mag[i= Math.sqrt(fft[k].x * fft[k].x + fft[k].y * fft[k].y);
	}
	// Smooths the spectrum magnitude and calculates the maximum
	double f_max = 0;
	mag_max = 0;
	smag = new double[hsize + 1];
	for(int i = 0, w = 5; i <= hsize; i++) {
	  double n = 0, v = 0;
	  for(int j = Math.max(0, i - w); j <= Math.min(hsize, i + w); j++) {
		n++;
		v += mag[j];
	  }
	  smag[i= v / n;
	  double f = f0 * Math.pow(f1 / f0, i / (doublehsize);
	  if(mag_max < smag[i]) {
		f_max = f;
		mag_max = smag[i];
	  }
	}
	label = sound.getName() " (|fft| < " ((intmag_max", f_max = " ((intf_max"Hz) ";
	repaint();
  }
  int hsize = 16 31, vsize = 160, b = 0, m = 20, width = * m + hsize, height = b + * m + * vsize, height2 = b + * m + * vsize;
  double f0 = 440.0 16, f1 = 440.0 16;

  /** Routine interne de tracé, ne pas utiliser.
   *
   */
  @Override
  public void paint(Graphics g) {
	super.paint(g);
	// Backgrounds and axis
	g.setColor(Color.GRAY);
	g.fillRect(m, b + m, hsize, * vsize);
	g.fillRect(m, height2 + m, hsize, vsize);
	g.setColor(Color.BLACK);
	g.drawLine(m, height2, width - m, height2);
	g.drawLine(m / 2, height2 + m + vsize / 2, m, height2 + m + vsize / 2);
	g.drawLine(width - m / 2, height2 + m + vsize / 2, width - m, height2 + m + vsize / 2);
	g.setFont(new Font("Times"010));
	for(int i = m; i <= width - m; i += hsize / 16) {
	  g.drawLine(i, height2 - (i == width / ? m : m / 2), i, height2 + (i == width / ? m - : m / 2));
	  double f = f0 * Math.pow(f1 / f0, (i - m(doublehsize);
	  if(i < width - m) {
		g.drawString(Integer.toString((intf), i + 1, height2 - 1);
	  }
	}
	// Amplitude and phase curves
	for(int i = m, a1 = 0, d1 = 0; i <= width - m; i++) {
	  double a = Math.log(* mag[i - m/ mag_max/ Math.log(10);
	  int a0 = height2 - m - (intMath.rint(* vsize * a);
	  if(a0 < b + m) {
		a0 = b + m;
	  }
	  g.setColor(Color.RED);
	  if(i > m) {
		g.drawLine(i - 1, a1, i, a0);
	  }
	  a1 = a0;
	  // Drawing the 1st data samples
	  int d0 = height - m - (intMath.rint(vsize * (0.5 ((i - m < data.length ? data[i - m0))));
	  g.setColor(Color.YELLOW);
	  if(i > m) {
		g.drawLine(i - 1, d1, i, d0);
	  }
	  d1 = d0;
	}
	g.setColor(Color.BLACK);
	g.setFont(new Font("Times", Font.BOLD, 16));
	g.drawString("Amplitudes du spectre", m + m / 2, b + * m);
	g.drawString("Début du signal", m + m / 2, height2 + * m);
  }
  private double data[], mag[], mag_max, smag[];
  private String label;

  /** Converts a mono/stereo 16bit stream to a data buffer.
   @param sound The audio stream to convert.
   @param channel Left 'l' or right 'r' channel.
   */
  private static double[] getData(SoundBit sound, char channel) {
	AudioInputStream stream = sound.getStream();
	SoundBit.checkFormat(stream);
	if(stream.getFrameLength() (longInteger.MAX_VALUE) { throw new IllegalArgumentException("Cannot convert huge audio stream to buffer");
	}
	int length = (intstream.getFrameLength();
	double data[] new double[length];
	int n = stream.getFormat().getChannels() 2, o = stream.getFormat().getChannels() == || channel == 'l' 2;
	byte read[] new byte[n];
	try {
	  for(int i = 0; i < length; i++) {
		stream.read(read);
		int h = read[o + 1], l = read[o], v = ((128 + h<< 8(128 + l);
		data[i(v / 32767.0 1);
	  }
	catch(IOException e) { throw new RuntimeException(e + " when reading the audio stream " + sound.getName());
	}
	return data;
  }
  // Computes the FFT of a stream after http://www.cs.princeton.edu/introcs/97data/FFT.java.html
  private static complex[] getFFT(double data[]) {
	// Calculates the largest power of two not greater than data length
	int length = (intMath.pow(2, Math.ceil(Math.log(data.length/ Math.log(2)));
	if(length == 0) {
	  length = 1;
	}
	// Builds the complex buffer and computes fft, appkying a Hann's windowing
	complex cdata[] new complex[length];
	for(int i = 0; i < length; i++)
	  cdata[inew complex(i < data.length ? 0.5 (- Math.cos(2.0 * Math.PI * i / data.length)) * data[i00);
	return fft(cdata);
  }
  // Defines a complex number and its multiplication
  private static class complex complex(double x, double y) {
								   this.x = x;
								   this.y = y;
								 }
								 double x, y;
								 complex mul(complex w) {
								   return new complex(x * w.x - y * w.y, x * w.y + y * w.x);
								 }
  }
  // Implements the Cooley-Tukey FFT algorithm assuming x.length is a power of two
  private static complex[] fft(complex[] x) {
	int n = x.length;
	if(n == 1) {
	  return new complex[] { x[0] };
	}
	// Applies FFT on event and odd parts of the signal
	complex e[] new complex[n / 2], o[] new complex[n / 2];
	for(int i = 0; i < n / 2; i++) {
	  e[i= x[* i];
	  o[i= x[* i + 1];
	}
	e = fft(e);
	o = fft(o);
	// Combines the resukt
	complex s[] new complex[n];
	for(int i = 0; i < n / 2; i++) {
	  double k = -* i * Math.PI / n;
	  complex w = new complex(Math.cos(k), Math.sin(k));
	  w = w.mul(o[i]);
	  s[inew complex(e[i].x + w.x, e[i].y + w.y);
	  s[i + n / 2new complex(e[i].x - w.x, e[i].y - w.y);
	}
	return s;
  }
  // Returns the inverse FFT of spectrum which length is a power of two
  private double[] getInvFFT(complex[] spectrum) {
	for(int i = 0; i < spectrum.length; i++)
	  spectrum[i].y = -spectrum[i].y;
	complex[] sp = fft(spectrum);
	double s[] new double[spectrum.length];
	for(int i = 0; i < spectrum.length; i++) {
	  s[i= sp[i].x = sp[i].x / spectrum.length;
	  sp[i].y = -sp[i].y / spectrum.length;
	  spectrum[i].y = -spectrum[i].y;
	}
	return s;
  }
}