/*
*
      
_______
                       
_____
   
_____ _____
  

*
     
|__
   
__|
                     
|
  
__ \ / ____|__ \
 

*
        
| | __ _ _ __ ___
  
______| || | (___ | |__) |
*
        
| |/ _` | '__/ __|/ _ \/ __| |
  
| |\___ \|___/
 

*
        
| | (_| | |
  
\__ \ (_) \__ \ |__| |____) | |
     

*
        
|_|\__,_|_|
  
|___/\___/|___/_____/|_____/|_|
     

*
                                                         

* -------------------------------------------------------------
*
* TarsosDSP is developed by Joren Six at IPEM, University Ghent
*
  

* -------------------------------------------------------------
*
*
  
Info: http://0110.be/tag/TarsosDSP
*
  
Github: https://github.com/JorenSix/TarsosDSP
*
  
Releases: http://0110.be/releases/TarsosDSP/
*
  

*
  
TarsosDSP includes modified source code by various authors,
*
  
for credits and info, see README.
*
 

*/

package be.tarsos.dsp.example;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import be.tarsos.dsp.AudioDispatcher;
import be.tarsos.dsp.StopAudioProcessor;
import be.tarsos.dsp.WaveformSimilarityBasedOverlapAdd;
import be.tarsos.dsp.WaveformSimilarityBasedOverlapAdd.Parameters;
import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
import be.tarsos.dsp.io.jvm.AudioPlayer;
import be.tarsos.dsp.io.jvm.JVMAudioInputStream;
import be.tarsos.dsp.io.jvm.WaveformWriter;
import be.tarsos.dsp.resample.RateTransposer;

public class SampleExtractor
  
extends JFrame {
	

	
/**
	 
*
 

	 
*/
	
private static final long serialVersionUID = -8749523013657700525L;
	
private final JFileChooser fileChooser;
	
private final JSpinner startSelectionSpinner;
	
private final JSpinner endSelectionSpinner;
	
private final JSpinner[] centsSpinner;
	
private final JSpinner[] durationSpinner;
	
private final JLabel[] sampleLabel;
	
private final JButton playSelection;
	
private final JCheckBox[] saveSampleCheckboxes;
	
private File file;
	
private final char[] codes = {'e','r','t','y','u','i','o','p'};
	

	
private double currentDuration = 0;
	
private ChangeListener selectionChangedListener = new ChangeListener() {
		
@Override
		
public void stateChanged(ChangeEvent event) {
			
double endValue = (Double) endSelectionSpinner.getValue();
			
double startValue = (Double) startSelectionSpinner.getValue();
			
if(endValue < startValue){
				
startSelectionSpinner.removeChangeListener(this);
				
startSelectionSpinner.setValue(endValue);
				
startSelectionSpinner.addChangeListener(this);
			
}
			
for(int i = 0 ; i < durationSpinner.length;i++){
				
double duration = (Double) durationSpinner[i].getValue();
				
if(duration == 0 || duration == currentDuration){
					
durationSpinner[i].setValue(selectionDuration());
				
}
			
}
			
currentDuration = selectionDuration();
		
}
	
};
	
public SampleExtractor(){
		
this.setLayout(new BorderLayout());
		
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
this.setTitle("Sample Exctractor: Extract & Modify Samples");
		

		

		
fileChooser = new JFileChooser();
		
startSelectionSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 0.1));
		
startSelectionSpinner.setEnabled(false);
		
startSelectionSpinner.addChangeListener(selectionChangedListener);
		
endSelectionSpinner = new JSpinner(new SpinnerNumberModel(0,0,10000,0.1));
		
endSelectionSpinner.setEnabled(false);
		
endSelectionSpinner.addChangeListener(selectionChangedListener);
		
centsSpinner = new JSpinner[codes.length];
		
durationSpinner = new JSpinner[codes.length];
		
sampleLabel = new JLabel[codes.length];
		
saveSampleCheckboxes = new JCheckBox[codes.length];
		

		
playSelection = new JButton("Play");
		
playSelection.setEnabled(false);
		
playSelection.addActionListener(new ActionListener() {
			
AudioDispatcher dispatcher = null;
			
@Override
			
public void actionPerformed(ActionEvent arg0) {
				
if(((Double)startSelectionSpinner.getValue())<((Double)endSelectionSpinner.getValue())){
					
try {
						
dispatcher = AudioDispatcherFactory.fromFile(file, 1024, 0);
						
dispatcher.skip((Double)startSelectionSpinner.getValue());
						
dispatcher.addAudioProcessor(new StopAudioProcessor((Double)endSelectionSpinner.getValue()));
						
dispatcher.addAudioProcessor(new AudioPlayer(JVMAudioInputStream.toAudioFormat(dispatcher.getFormat())));
					
} catch (UnsupportedAudioFileException e1) {
					

					
} catch (IOException e1) {

					
} catch (LineUnavailableException e) {
					
}
					
new Thread(dispatcher).start();
				
}
			
}
		
});
		

		
JPanel fileChooserPanel = new JPanel(new GridLayout(2,4));
		
fileChooserPanel.setBorder(new TitledBorder("1. Choose your audio to extract samples from (wav mono)"));
		

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

	            
} else {
	                
//canceled
	            
}
			
}
		

		
});
		
fileChooserPanel.add(new JLabel("Load a file"));
		
fileChooserPanel.add(new JLabel("Start (s)"));
		
fileChooserPanel.add(new JLabel("End (s)"));
		
fileChooserPanel.add(new JLabel("Play Selection"));
		
fileChooserPanel.add(chooseFileButton);
		
fileChooserPanel.add(startSelectionSpinner);
		
fileChooserPanel.add(endSelectionSpinner);
		
fileChooserPanel.add(playSelection);
		

		

		
this.add(fileChooserPanel,BorderLayout.NORTH);
		

		
JPanel samplePanel = new JPanel(new GridLayout(0,1));
		
JPanel sampleSubPanel = new JPanel(new GridLayout(1,0));
		
sampleSubPanel.add(new JLabel("Key"));
		
sampleSubPanel.add(new JLabel("Cents"));
		
sampleSubPanel.add(new JLabel("Duration"));
		
samplePanel.add(sampleSubPanel);
		
samplePanel.add(sampleSubPanel);
		

		
final KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		
for(int i = 0;i<centsSpinner.length;i++){
			
sampleSubPanel = new JPanel(new GridLayout(1,0));
			
centsSpinner[i] = new JSpinner(new SpinnerNumberModel(i*100,-1200,1200,1));
			
centsSpinner[i].setEnabled(false);
			
durationSpinner[i] = new JSpinner(new SpinnerNumberModel(0,0,1200,0.1));
			
durationSpinner[i].setEnabled(false);
			
sampleLabel[i] = new JLabel("Press " + codes[i] + " to play.");
			
saveSampleCheckboxes[i] = new JCheckBox("Save?");
			
saveSampleCheckboxes[i].setEnabled(false);
			
sampleSubPanel.add(sampleLabel[i]);
			
sampleSubPanel.add(centsSpinner[i]);
			
sampleSubPanel.add(durationSpinner[i]);
			
sampleSubPanel.add(durationSpinner[i]);
			
sampleSubPanel.add(saveSampleCheckboxes[i]);
			
samplePanel.add(sampleSubPanel);
			
final Integer index = i;
		    
manager.addKeyEventPostProcessor(new KeyEventPostProcessor() {
				
@Override
				
public boolean postProcessKeyEvent(KeyEvent e) {
					
boolean consumed = false;
					
if(e.getKeyChar()== codes[index] && e.getID() == KeyEvent.KEY_TYPED && file != null){
						
try{
							
int cents = (Integer) centsSpinner[index].getValue();
							
double duration = (Double) durationSpinner[index].getValue();
							
double originalDuration = selectionDuration();
							
double pitchFactor = PitchShiftingExample.centToFactor(cents);
							
double durationFactor = originalDuration/duration * pitchFactor;
							
boolean saveWav = saveSampleCheckboxes[index].isSelected();
							
AudioFormat format = AudioSystem.getAudioFileFormat(file).getFormat();
							
double sampleRate = format.getSampleRate();
							
double endValue = (Double) endSelectionSpinner.getValue();
							
double startValue = (Double) startSelectionSpinner.getValue();
							
WaveformSimilarityBasedOverlapAdd wsola;
							
AudioPlayer audioPlayer;
							
RateTransposer rateTransposer;
							
rateTransposer = new RateTransposer(pitchFactor);
							
wsola = new WaveformSimilarityBasedOverlapAdd(Parameters.musicDefaults(durationFactor,sampleRate));
							
AudioDispatcher dispatcher = AudioDispatcherFactory.fromFile(file,wsola.getInputBufferSize(), wsola.getOverlap());
							
audioPlayer = new AudioPlayer(format);
							
wsola.setDispatcher(dispatcher);
							
dispatcher.skip(startValue);
							
dispatcher.addAudioProcessor(new StopAudioProcessor(endValue));
							
dispatcher.addAudioProcessor(wsola);
							
dispatcher.addAudioProcessor(rateTransposer);
							
if(saveWav){
								
String filename = String.format("%s_%.2fs-%.2fs_modified_%dcents_%.2fs.wav", file.getName(),startValue,endValue,cents,duration);
								
WaveformWriter wfw = new WaveformWriter(format,filename);
								
dispatcher.addAudioProcessor(wfw);
								
System.out.println("Saving to " + filename);
							
}else{
								
dispatcher.addAudioProcessor(audioPlayer);
							
}
							

							
Thread t = new Thread(dispatcher);
							
t.start();
							
consumed = true;
						
} catch (Exception ex) {
							
ex.printStackTrace();
						
}
					
}
					
return consumed;
				
}
			
});
		    

		
}
		
samplePanel.setBorder(new TitledBorder("2. Adapt and play the samples."));
		
samplePanel.setFocusable(true);
		
this.add(samplePanel,BorderLayout.CENTER);
		

		
try{
			
final String tempDir = System.getProperty("java.io.tmpdir");
			
String path = new File(tempDir,"flute_sample.wav").getAbsolutePath();
			
String resource = "/be/tarsos/dsp/example/resources/flute_sample.wav";
			
copyFileFromJar(resource,path);
			
setFile(new File(path));
		
}catch (Exception e){
			
e.printStackTrace();
		
}
	
}
	

	
/**
	 
* Copy a file from a jar.
	 
*
 

	 
* @param source
	 
*
            
The path to read e.g. /package/name/here/help.html
	 
* @param target
	 
*
            
The target to save the file to.
	 
*/

	
private void copyFileFromJar(final String source, final String target) {
		
try {
			
final InputStream inputStream = this.getClass().getResourceAsStream(source);
			
OutputStream out;
			
out = new FileOutputStream(target);
			
final byte[] buffer = new byte[4096];
			
int len = inputStream.read(buffer);
			
while (len != -1) {
				
out.write(buffer, 0, len);
				
len = inputStream.read(buffer);
			
}
			
out.close();
			
inputStream.close();
		
} catch (final FileNotFoundException e) {
			

		
} catch (final IOException e) {
			

		
}
	
}
	

	
private double selectionDuration(){
		
return (Double) endSelectionSpinner.getValue() - (Double) startSelectionSpinner.getValue();
	
}
	

	
private void setFile(File file) {
		
this.file=file;
		
endSelectionSpinner.setEnabled(true);
        
startSelectionSpinner.setEnabled(true);
        
playSelection.setEnabled(true);
        
for(int i = 0;i<centsSpinner.length;i++){
			
centsSpinner[i].setEnabled(true);
			
durationSpinner[i].setEnabled(true);
			
saveSampleCheckboxes[i].setEnabled(true);
		
}
        

        
try {
        	
AudioDispatcher dispatcher = AudioDispatcherFactory.fromFile(file, 1024, 0);
        	
dispatcher.run();
        	

        	
endSelectionSpinner.setValue((double) dispatcher.secondsProcessed());
		
} catch (UnsupportedAudioFileException e) {
		
} catch (IOException e) {
		
}
        

	
}
	

	

	
public static void main(String...strings) throws InterruptedException, InvocationTargetException{
		
SwingUtilities.invokeAndWait(new Runnable() {
			
@Override
			
public void run() {
				
try {
					
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
				
} catch (Exception e) {
					
//ignore failure to set default look and feel;
				
}
				
JFrame frame = new SampleExtractor();
				
frame.pack();
				
frame.setSize(450,400);
				
frame.setVisible(true);
			
}
		
});
	
}
}