package be.tarsos.dsp.example.spectrum;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import be.tarsos.dsp.AudioEvent;
import be.tarsos.dsp.AudioProcessor;
import be.tarsos.dsp.example.constantq.Player;
import be.tarsos.dsp.example.constantq.Player.PlayerState;
import be.tarsos.dsp.ui.Axis;
import be.tarsos.dsp.ui.AxisUnit;
import be.tarsos.dsp.ui.CoordinateSystem;
import be.tarsos.dsp.ui.LinkedPanel;
import be.tarsos.dsp.ui.ViewPort;
import be.tarsos.dsp.ui.ViewPort.ViewPortChangedListener;
import be.tarsos.dsp.ui.layers.AmplitudeAxisLayer;
import be.tarsos.dsp.ui.layers.BackgroundLayer;
import be.tarsos.dsp.ui.layers.DragMouseListenerLayer;
import be.tarsos.dsp.ui.layers.LegendLayer;
import be.tarsos.dsp.ui.layers.MouseCursorLayer;
import be.tarsos.dsp.ui.layers.PitchContourLayer;
import be.tarsos.dsp.ui.layers.Scalogram;
import be.tarsos.dsp.ui.layers.SelectionLayer;
import be.tarsos.dsp.ui.layers.TimeAxisLayer;
import be.tarsos.dsp.ui.layers.TooltipLayer;
import be.tarsos.dsp.ui.layers.VerticalFrequencyAxisLayer;
import be.tarsos.dsp.ui.layers.WaveFormLayer;
import be.tarsos.dsp.ui.layers.ZoomMouseListenerLayer;

public class FFTZoomGeneralizedGoertzel extends JPanel{
	
/**
	 
*
 

	 
*/
	
private static final long serialVersionUID = 5689356875546643126L;
	

	
JLabel progressLabel;
	
JLabel totalLabel;
	

	
//position value in the slider
	
private int newPositionValue;
	

	
JSlider positionSlider;
	

	
final Player player;
	

	
private LinkedPanel waveForm;
	
private LinkedPanel timeFrequencyPane;
	
private CoordinateSystem waveFormCS;
	
private CoordinateSystem timeFrequencyPaneCS;
	


	
final AudioProcessor processor = new AudioProcessor() {
		

		
@Override
		
public boolean process(AudioEvent audioEvent) {
			
double timeStamp =
  
audioEvent.getTimeStamp();
			
if(!positionSlider.getValueIsAdjusting()){
				
newPositionValue = (int) (audioEvent.getProgress() * 1000);
				
positionSlider.setValue(newPositionValue);
				
setProgressLabelText(timeStamp,player.getDurationInSeconds());
			
}
			
return true;
		
}
		

		
@Override
		
public void processingFinished() {
			

		
}
	
};

	

	
public FFTZoomGeneralizedGoertzel(){
		
this.setLayout(new BorderLayout());
		

	

		
player = new Player(processor,1024,0);
		

		
JPanel subPanel = new JPanel(new GridLayout(0,1));
		
subPanel.add(createButtonPanel());
		
subPanel.add(createProgressPanel());
		
subPanel.add(createGainPanel());
		

		
JComponent featurePanel =
  
createFeaturePanel();
		

		
JComponent splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, subPanel,featurePanel);
		

		

		
player.addPropertyChangeListener(new PropertyChangeListener() {
			
@Override
			
public void propertyChange(PropertyChangeEvent arg0) {
				
if(arg0.getPropertyName()=="state"){
					
PlayerState newState = (PlayerState) arg0.getNewValue();
					
reactToPlayerState(newState);
				
}
			
}
		
});
		
reactToPlayerState(player.getState());
		

		
this.add(splitPane);
	
}
	

	
private void reactToPlayerState(PlayerState newState){
		
positionSlider.setEnabled(newState != PlayerState.NO_FILE_LOADED);
		
if(newState == PlayerState.STOPPED || newState == PlayerState.FILE_LOADED){
			
newPositionValue = 0;
			
positionSlider.setValue(0);
			
setProgressLabelText(0, player.getDurationInSeconds());
		
}
		
if(newState == PlayerState.FILE_LOADED){
	

			

			
waveForm.removeLayers();
			
waveForm.addLayer(new BackgroundLayer(waveFormCS));
			
waveForm.addLayer(new AmplitudeAxisLayer(waveFormCS));
			
waveForm.addLayer(new TimeAxisLayer(waveFormCS));
			
waveForm.addLayer(new WaveFormLayer(waveFormCS, player.getLoadedFile()));
			
waveForm.addLayer(new ZoomMouseListenerLayer());
			
waveForm.addLayer(new DragMouseListenerLayer(waveFormCS));
			

			

			
final MouseCursorLayer waveFormCursor = new MouseCursorLayer(waveFormCS);
			
waveForm.addLayer(waveFormCursor);
			

			
LegendLayer legend = new LegendLayer(waveFormCS,50);
			
waveForm.addLayer(legend);
			
legend.addEntry("Wave",Color.BLACK);
			

			
timeFrequencyPane.removeLayers();
			
timeFrequencyPane.addLayer(new BackgroundLayer(timeFrequencyPaneCS));
		
// timeFrequencyPane.addLayer(new GeneralizedGoertzelLayer(timeFrequencyPaneCS,player.getLoadedFile(),20));
			

			
Scalogram fftLayer = new Scalogram(timeFrequencyPaneCS,player.getLoadedFile().getAbsolutePath());
			
timeFrequencyPane.addLayer(fftLayer);
			
MouseCursorLayer cl = new MouseCursorLayer(timeFrequencyPaneCS);
			
timeFrequencyPane.addLayer(cl);
			

			

			
timeFrequencyPane.addLayer(new PitchContourLayer(timeFrequencyPaneCS,player.getLoadedFile(),Color.red,2048,0));
			
timeFrequencyPane.addLayer(new VerticalFrequencyAxisLayer(timeFrequencyPaneCS));
			
timeFrequencyPane.addLayer(new ZoomMouseListenerLayer());
			
timeFrequencyPane.addLayer(new DragMouseListenerLayer(timeFrequencyPaneCS));
			
timeFrequencyPane.addLayer(new SelectionLayer(timeFrequencyPaneCS));
			
timeFrequencyPane.addLayer(new TimeAxisLayer(timeFrequencyPaneCS));
			
timeFrequencyPane.addLayer(new TooltipLayer(timeFrequencyPaneCS,fftLayer));
			

			
legend = new LegendLayer(timeFrequencyPaneCS,110);
			
timeFrequencyPane.addLayer(legend);
			
legend.addEntry("ConstantQ",Color.BLACK);
			
legend.addEntry("Pitch estimations",Color.RED);
			

			
cl.addPropertyChangeListener(new PropertyChangeListener() {
				

				
@Override
				
public void propertyChange(PropertyChangeEvent evt) {
					
if(evt.getPropertyName()=="cursor"){
						
Point newPoint = (Point) evt.getNewValue();
						
waveFormCursor.setPoint(newPoint);
					
}
					

				
}
			
});
		
}
	
}
	

	
private CoordinateSystem getCoordinateSystem(AxisUnit yUnits) {
		
float minValue = -1000;
		
float maxValue = 1000;
		
if(yUnits == AxisUnit.FREQUENCY){
			
minValue = 200;
			
maxValue = 8000;
		
}
		
return new CoordinateSystem(yUnits, minValue, maxValue);
	
}
	

	

	
private JComponent createFeaturePanel() {
		
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		

		
waveFormCS = getCoordinateSystem(AxisUnit.AMPLITUDE);
		
waveForm = new LinkedPanel(waveFormCS);
		
waveForm.addLayer(new BackgroundLayer(waveFormCS));
		
waveForm.addLayer(new AmplitudeAxisLayer(waveFormCS));
		
waveForm.addLayer(new TimeAxisLayer(waveFormCS));
		
waveForm.addLayer(new ZoomMouseListenerLayer());
		
waveForm.addLayer(new DragMouseListenerLayer(waveFormCS));
		
LegendLayer legend = new LegendLayer(waveFormCS,50);
		
waveForm.addLayer(legend);
		
legend.addEntry("Wave",Color.BLACK);
		

		
splitPane.add(waveForm, JSplitPane.TOP);
		

		
timeFrequencyPaneCS = getCoordinateSystem(AxisUnit.FREQUENCY);
		
timeFrequencyPane = new LinkedPanel(timeFrequencyPaneCS);
		
timeFrequencyPane.addLayer(new BackgroundLayer(timeFrequencyPaneCS));
		
timeFrequencyPane.addLayer(new VerticalFrequencyAxisLayer(timeFrequencyPaneCS));
		
timeFrequencyPane.addLayer(new TimeAxisLayer(timeFrequencyPaneCS));
		
timeFrequencyPane.addLayer(new ZoomMouseListenerLayer());
		
timeFrequencyPane.addLayer(new DragMouseListenerLayer(timeFrequencyPaneCS));
		
timeFrequencyPane.addLayer(new SelectionLayer(timeFrequencyPaneCS));
		

		
legend = new LegendLayer(timeFrequencyPaneCS,110);
		
timeFrequencyPane.addLayer(legend);
		

		
legend.addEntry("Spectrogram",Color.BLACK);
		
legend.addEntry("Pitch estimations",Color.RED);
		

		
splitPane.add(timeFrequencyPane, JSplitPane.BOTTOM);
		
splitPane.setDividerLocation(150);
		

		
ViewPortChangedListener listener = new ViewPortChangedListener() {
			
@Override
			
public void viewPortChanged(ViewPort newViewPort) {
				
waveForm.repaint();
				
timeFrequencyPane.repaint();
			
}
		
};
		
waveForm.getViewPort().addViewPortChangedListener(new ViewPortChangedListener() {
			
@Override
			
public void viewPortChanged(ViewPort newViewPort) {
				
timeFrequencyPaneCS.setMin(Axis.X,waveFormCS.getMin(Axis.X));
				
timeFrequencyPaneCS.setMax(Axis.X,waveFormCS.getMax(Axis.X));
			
}
		
});
		

		

		

		
waveForm.getViewPort().addViewPortChangedListener(listener);
		

		
timeFrequencyPane.getViewPort().addViewPortChangedListener(new ViewPortChangedListener() {
			
@Override
			
public void viewPortChanged(ViewPort newViewPort) {
				
waveFormCS.setMin(Axis.X,timeFrequencyPaneCS.getMin(Axis.X));
				
waveFormCS.setMax(Axis.X,timeFrequencyPaneCS.getMax(Axis.X));
			
}
		
});
		
timeFrequencyPane.getViewPort().addViewPortChangedListener(listener);
		
return splitPane;
	
}

	
private JComponent createProgressPanel(){
	    
positionSlider = new JSlider(0,1000);
		
positionSlider.setValue(0);
		
positionSlider.setPaintLabels(false);
		
positionSlider.setPaintTicks(false);
		
positionSlider.setEnabled(false);
		
positionSlider.addChangeListener(new ChangeListener() {
			
@Override
			
public void stateChanged(ChangeEvent arg0) {
				
if (newPositionValue != positionSlider.getValue()) {
					
double promille = positionSlider.getValue() / 1000.0;
					
double currentPosition = player.getDurationInSeconds() * promille;
					
if (positionSlider.getValueIsAdjusting()) {
						
setProgressLabelText(currentPosition, player.getDurationInSeconds());
					
} else {
						
double secondsToSkip = currentPosition;
						
PlayerState currentState = player.getState();
						

						
player.pauze(secondsToSkip);
						
if(currentState == PlayerState.PLAYING){
							
player.play();
							

						
}
					
}
				
}
			
}
		
});
		

		
progressLabel = new JLabel();
		
totalLabel = new JLabel();
		
setProgressLabelText(0, 0);
		

		
JPanel subPanel = new JPanel(new BorderLayout());
		
subPanel.add(progressLabel,BorderLayout.WEST);
		
subPanel.add(positionSlider,BorderLayout.CENTER);
		
subPanel.add(totalLabel,BorderLayout.EAST);
		

		
JPanel panel = new JPanel(new BorderLayout());
		
JLabel label = new JLabel("Progress (in %°)");
		
label.setToolTipText("Progress in promille.");
		
panel.add(label,BorderLayout.NORTH);
		
panel.add(subPanel,BorderLayout.CENTER);
		
panel.setBorder(new TitledBorder("Progress control"));

		
return panel;
	
}
	

	
private void setProgressLabelText(double current, double max){
		
progressLabel.setText(formattedToString(current));
		
totalLabel.setText(formattedToString(max));
	
}
	

	
public String formattedToString(double seconds) {
		
int minutes = (int) (seconds / 60);
		
int completeSeconds = (int) seconds - (minutes * 60);
		
int hundred =
  
(int) ((seconds - (int) seconds) * 100);
		
return String.format(Locale.US, "%02d:%02d:%02d", minutes , completeSeconds, hundred);
	
}
	

	
private JComponent createButtonPanel(){
		
JPanel fileChooserPanel = new JPanel(new GridLayout(1,0));
		
fileChooserPanel.setBorder(new TitledBorder("Actions"));
		

		
final JFileChooser fileChooser = new JFileChooser();
		

		
final JButton chooseFileButton = new JButton("Open...");
		
chooseFileButton.addActionListener(new ActionListener(){
			
@Override
			
public void actionPerformed(ActionEvent arg0) {
				
int returnVal = fileChooser.showOpenDialog(FFTZoomGeneralizedGoertzel.this);
	            
if (returnVal == JFileChooser.APPROVE_OPTION) {
	                
File file = fileChooser.getSelectedFile();
	                

	                
PlayerState currentState = player.getState();
	                
player.load(file);
	                
if(currentState == PlayerState.NO_FILE_LOADED || currentState == PlayerState.PLAYING){
	                	
player.play();
	                
}
	            
} else {
	                
//canceled
	            
}
			
}
			

		
});
		
fileChooserPanel.add(chooseFileButton);
		

		
final JButton stopButton = new JButton("Stop");
		
stopButton.setEnabled(false);
		
fileChooserPanel.add(stopButton);
		
stopButton.addActionListener(new ActionListener() {
			
@Override
			
public void actionPerformed(ActionEvent arg0) {
				
player.stop();
			
}
		
});
		

		

		
final JButton playButton = new JButton("Play");
		
playButton.setEnabled(false);
		
playButton.addActionListener(new ActionListener() {
			
@Override
			
public void actionPerformed(ActionEvent arg0) {
				
player.play();
			
}
		
});
		
fileChooserPanel.add(playButton);
		

		

		
final JButton pauzeButton = new JButton("Pauze");
		
pauzeButton.setEnabled(false);
		
pauzeButton.addActionListener(new ActionListener() {
			
@Override
			
public void actionPerformed(ActionEvent arg0) {
				
player.pauze();
			
}
		
});
		
fileChooserPanel.add(pauzeButton);
		

		
player.addPropertyChangeListener(new PropertyChangeListener() {
			
@Override
			
public void propertyChange(PropertyChangeEvent evt) {
				
if(evt.getPropertyName()=="state"){
					
PlayerState newState = (PlayerState) evt.getNewValue();
					
playButton.setEnabled(newState != PlayerState.PLAYING && newState != PlayerState.NO_FILE_LOADED);
					
pauzeButton.setEnabled(newState == PlayerState.PLAYING && newState != PlayerState.NO_FILE_LOADED );
					
stopButton.setEnabled((newState == PlayerState.PLAYING || newState == PlayerState.PAUZED) && newState != PlayerState.NO_FILE_LOADED);
				
}
			
}
		
});
		

		
return fileChooserPanel;
	
}
	

	

	
private JComponent createGainPanel(){
		
JSlider gainSlider;
		
gainSlider = new JSlider(0,200);
		
gainSlider.setValue(100);
		
gainSlider.setPaintLabels(true);
		
gainSlider.setPaintTicks(true);
		
final JLabel label = new JLabel("Gain: 100%");
		
gainSlider.addChangeListener(new ChangeListener() {
			
@Override
			
public void stateChanged(ChangeEvent arg0) {
				
JSlider gainSlider = ((JSlider)arg0.getSource());
				
double gainValue = gainSlider.getValue() / 100.0;
				
label.setText(String.format("Gain: %3d", gainSlider.getValue())+"%");
				
player.setGain(gainValue);
			
}
		
});
		

		
JPanel gainPanel = new JPanel(new BorderLayout());
		
label.setToolTipText("Volume in % (100 is no change).");
		
gainPanel.add(label,BorderLayout.NORTH);
		
gainPanel.add(gainSlider,BorderLayout.CENTER);
		
gainPanel.setBorder(new TitledBorder("Volume control"));
		
return gainPanel;
	
}
	

	
public static void main(String... args) throws InterruptedException, InvocationTargetException {
		
SwingUtilities.invokeAndWait(new Runnable() {
			
@Override
			
public void run() {
				
JFrame frame = new JFrame();
				
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				
frame.setTitle("FFT Zoom with Generalized Goertzel");
				
frame.add(new FFTZoomGeneralizedGoertzel());
				
frame.pack();
				
frame.setSize(450,650);
				
frame.setVisible(true);
			
}
		
});
	
}
}