All Downloads are FREE. Search and download functionalities are using the official Maven repository.

examples.be.tarsos.dsp.example.PitchShiftingExample Maven / Gradle / Ivy

The newest version!
/*
*      _______                       _____   _____ _____  
*     |__   __|                     |  __ \ / ____|  __ \ 
*        | | __ _ _ __ ___  ___  ___| |  | | (___ | |__) |
*        | |/ _` | '__/ __|/ _ \/ __| |  | |\___ \|  ___/ 
*        | | (_| | |  \__ \ (_) \__ \ |__| |____) | |     
*        |_|\__,_|_|  |___/\___/|___/_____/|_____/|_|     
*                                                         
* -------------------------------------------------------------
*
* 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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.BoxLayout;
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.JSlider;
import javax.swing.JSpinner;
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.AudioEvent;
import be.tarsos.dsp.AudioProcessor;
import be.tarsos.dsp.GainProcessor;
import be.tarsos.dsp.MultichannelToMono;
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 PitchShiftingExample extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3830419374132803358L;

	private JFileChooser fileChooser;

	private AudioDispatcher dispatcher;
	private WaveformSimilarityBasedOverlapAdd wsola;
	private GainProcessor gain;
	private AudioPlayer audioPlayer;
	private RateTransposer rateTransposer;
	private double currentFactor;// pitch shift factor
	private double sampleRate;
	private boolean loop;
	
	private final JSlider factorSlider;
	private final JLabel factorLabel;
	private final JSlider gainSlider;
	private final JCheckBox originalTempoCheckBox;
	private final JSpinner centsSpinner;
	
	
	
	private ChangeListener parameterSettingChangedListener = new ChangeListener(){
@Override
		public void stateChanged(ChangeEvent arg0) {
			if (arg0.getSource() instanceof JSpinner) {
				int centValue = Integer.valueOf(((JSpinner) arg0.getSource())
						.getValue().toString());
				currentFactor = centToFactor(centValue);
				factorSlider.removeChangeListener(this);
				factorSlider.setValue((int) Math.round(currentFactor * 100));
				factorSlider.addChangeListener(this);
			} else if (arg0.getSource() instanceof JSlider) {
				currentFactor = factorSlider.getValue() / 100.0;
				centsSpinner.removeChangeListener(this);
				centsSpinner.setValue(factorToCents(currentFactor));
				centsSpinner.addChangeListener(this);
			}
			factorLabel.setText("Factor " + Math.round(currentFactor * 100) + "%");
			if (PitchShiftingExample.this.dispatcher != null) {				 
				 if(originalTempoCheckBox.getModel().isSelected()){
					 wsola.setParameters(WaveformSimilarityBasedOverlapAdd.Parameters.musicDefaults(currentFactor, sampleRate));
				 } else {
					 wsola.setParameters(WaveformSimilarityBasedOverlapAdd.Parameters.musicDefaults(1, sampleRate));
				 }
				 rateTransposer.setFactor(currentFactor);
			 }
		}}; 
		
	public PitchShiftingExample(){
		this.setLayout(new BorderLayout());
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Pitch shifting: change the pitch of your audio.");
		
		originalTempoCheckBox = new JCheckBox("Keep original tempo?", true);
		originalTempoCheckBox.addChangeListener(parameterSettingChangedListener);
		
		this.loop = false;
		
		currentFactor = 1;
		
		factorSlider = new JSlider(20, 250);
		factorSlider.setValue(100);
		factorSlider.setPaintLabels(true);
		factorSlider.addChangeListener(parameterSettingChangedListener);
		
		JPanel fileChooserPanel = new JPanel(new BorderLayout());
		fileChooserPanel.setBorder(new TitledBorder("1... Or choose your audio (wav mono)"));
		
		fileChooser = new JFileChooser();
		
		JButton chooseFileButton = new JButton("Choose a file...");
		chooseFileButton.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0) {
				int returnVal = fileChooser.showOpenDialog(PitchShiftingExample.this);
	            if (returnVal == JFileChooser.APPROVE_OPTION) {
	                File file = fileChooser.getSelectedFile();
	                startFile(file,null);
	            } else {
	                //canceled
	            }
			}			
		});
		fileChooserPanel.add(chooseFileButton);
		fileChooser.setLayout(new BoxLayout(fileChooser, BoxLayout.PAGE_AXIS));
		

		JPanel inputSubPanel = new JPanel(new BorderLayout());
		JPanel inputPanel = new InputPanel();
		inputPanel.addPropertyChangeListener("mixer",
				new PropertyChangeListener() {
					@Override
					public void propertyChange(PropertyChangeEvent arg0) {
						startFile(null,(Mixer) arg0.getNewValue());
					}
				});
		inputSubPanel.add(inputPanel,BorderLayout.NORTH);
		inputSubPanel.add(fileChooserPanel,BorderLayout.SOUTH);
		
		gainSlider = new JSlider(0,200);
		gainSlider.setValue(100);
		gainSlider.setPaintLabels(true);
		gainSlider.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent arg0) {
				if (PitchShiftingExample.this.dispatcher != null) {
					double gainValue = gainSlider.getValue() / 100.0;
					gain.setGain(gainValue);
				}
			}
		});
		
		
		JPanel params = new JPanel(new BorderLayout());
		params.setBorder(new TitledBorder("2. Set the algorithm parameters"));
		
		JCheckBox loopCheckbox = new JCheckBox(("Loop sample?"));
		loopCheckbox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				loop = ((JCheckBox)e.getSource()).isSelected();
			}
		});
		
		JLabel label = new JLabel("Factor 100%");
		label.setToolTipText("The pitch shift factor in % (100 is no change, 50 is double pitch, 200 half).");
		factorLabel = label;
		params.add(label,BorderLayout.NORTH);
		params.add(factorSlider,BorderLayout.CENTER);
		
		
		JPanel subPanel = new JPanel(new GridLayout(0, 2));
			
		centsSpinner = new JSpinner();
		centsSpinner.addChangeListener(parameterSettingChangedListener);
		label = new JLabel("Pitch shift in cents");
		label.setToolTipText("Pitch shift in cents.");
		subPanel.add(label);
		subPanel.add(centsSpinner);

		label = new JLabel("Keep original tempo");
		label.setToolTipText("Pitch shift in cents.");
		subPanel.add(label);
		subPanel.add(originalTempoCheckBox);
		
		label = new JLabel("Loop sample?");
		subPanel.add(label);
		subPanel.add(loopCheckbox);
		
		params.add(subPanel,BorderLayout.SOUTH);
		
		JPanel gainPanel = new JPanel(new BorderLayout());
		label = new JLabel("Gain (in %)");
		label.setToolTipText("Volume in % (100 is no change).");
		gainPanel.add(label,BorderLayout.NORTH);
		gainPanel.add(gainSlider,BorderLayout.CENTER);
		gainPanel.setBorder(new TitledBorder("3. Optionally change the volume"));
		
		this.add(inputSubPanel,BorderLayout.NORTH);
		this.add(params,BorderLayout.CENTER);
		this.add(gainPanel,BorderLayout.SOUTH);
		
	}
	
	public static double centToFactor(double cents){
		return 1 / Math.pow(Math.E,cents*Math.log(2)/1200/Math.log(Math.E)); 
	}
	private static double factorToCents(double factor){
		return 1200 * Math.log(1/factor) / Math.log(2); 
	}
	
	private void startFile(final File inputFile,Mixer mixer){
		if(dispatcher != null){
			dispatcher.stop();
		}
		AudioFormat format;
		try {
			if(inputFile != null){
				format = AudioSystem.getAudioFileFormat(inputFile).getFormat();
			}else{
				format = new AudioFormat(44100, 16, 1, true,true);
			}
			rateTransposer = new RateTransposer(currentFactor);
			gain = new GainProcessor(1.0);
			audioPlayer = new AudioPlayer(format);
			sampleRate = format.getSampleRate();
			
			//can not time travel, unfortunately. It would be nice to go back and kill Hitler or something...
			 if(originalTempoCheckBox.getModel().isSelected() && inputFile != null){
				 wsola = new WaveformSimilarityBasedOverlapAdd(Parameters.musicDefaults(currentFactor, sampleRate));
			 } else {
				 wsola = new WaveformSimilarityBasedOverlapAdd(Parameters.musicDefaults(1, sampleRate));
			 }
			 if(inputFile == null){
				 DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, format);
					TargetDataLine line;
					line = (TargetDataLine) mixer.getLine(dataLineInfo);
					line.open(format, wsola.getInputBufferSize());
					line.start();
					final AudioInputStream stream = new AudioInputStream(line);
					JVMAudioInputStream audioStream = new JVMAudioInputStream(stream);
					// create a new dispatcher
					dispatcher = new AudioDispatcher(audioStream, wsola.getInputBufferSize(),wsola.getOverlap()); 
			 }else{
					if(format.getChannels() != 1){
						dispatcher = AudioDispatcherFactory.fromFile(inputFile,wsola.getInputBufferSize() * format.getChannels(),wsola.getOverlap() * format.getChannels());
						dispatcher.addAudioProcessor(new MultichannelToMono(format.getChannels(),true));
					}else{
						dispatcher = AudioDispatcherFactory.fromFile(inputFile,wsola.getInputBufferSize(),wsola.getOverlap());
					}
				 //dispatcher = AudioDispatcher.fromFile(inputFile,wsola.getInputBufferSize(),wsola.getOverlap());
			 }
			wsola.setDispatcher(dispatcher);
			dispatcher.addAudioProcessor(wsola);
			dispatcher.addAudioProcessor(rateTransposer);
			dispatcher.addAudioProcessor(gain);
			dispatcher.addAudioProcessor(audioPlayer);
			dispatcher.addAudioProcessor(new AudioProcessor() {
				
				@Override
				public void processingFinished() {
					if(loop){
					dispatcher =null;
					startFile(inputFile,null);
					}
					
				}
				
				@Override
				public boolean process(AudioEvent audioEvent) {
					return true;
				}
			});

			Thread t = new Thread(dispatcher);
			t.start();
		} catch (UnsupportedAudioFileException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (LineUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	
	public static void main(String[] argv) {
		if (argv.length == 3) {
			try {
				startCli(argv[0],argv[1],Double.parseDouble(argv[2]));
			} catch (NumberFormatException e) {
				printHelp("Please provide a well formatted number for the time stretching factor. See Synopsis.");
			} catch (UnsupportedAudioFileException e) {
				printHelp("Unsupported audio file, please check if the input is 16bit 44.1kHz MONO PCM wav.");
			} catch (IOException e) {
				printHelp("IO error, could not read from, or write to the audio file, does it exist?");
			}
		} else if(argv.length!=0){
			printHelp("Please provide exactly 3 arguments, see Synopsis.");
		}else{
			try {
				startGui();
			} catch (InterruptedException e) {
				e.printStackTrace();
				throw new Error(e);
			} catch (InvocationTargetException e) {
				e.printStackTrace();
				throw new Error(e);
			}
		}
	}
	
	private static void startGui() 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 en feel;
				}
				JFrame frame = new PitchShiftingExample();
				frame.pack();
				frame.setSize(400,450);
				frame.setVisible(true);
			}
		});
	}
	
	private static void startCli(String source,String target,double cents) throws UnsupportedAudioFileException, IOException{
		File inputFile = new File(source);
		AudioFormat format = AudioSystem.getAudioFileFormat(inputFile).getFormat();	
		double sampleRate = format.getSampleRate();
		double factor = PitchShiftingExample.centToFactor(cents);
		RateTransposer rateTransposer = new RateTransposer(factor);
		WaveformSimilarityBasedOverlapAdd wsola = new WaveformSimilarityBasedOverlapAdd(Parameters.musicDefaults(factor, sampleRate));
		WaveformWriter writer = new WaveformWriter(format,target);
		AudioDispatcher dispatcher;
		if(format.getChannels() != 1){
			dispatcher = AudioDispatcherFactory.fromFile(inputFile,wsola.getInputBufferSize() * format.getChannels(),wsola.getOverlap() * format.getChannels());
			dispatcher.addAudioProcessor(new MultichannelToMono(format.getChannels(),true));
		}else{
			dispatcher = AudioDispatcherFactory.fromFile(inputFile,wsola.getInputBufferSize(),wsola.getOverlap());
		}
		wsola.setDispatcher(dispatcher);
		dispatcher.addAudioProcessor(wsola);
		dispatcher.addAudioProcessor(rateTransposer);
		dispatcher.addAudioProcessor(writer);
		dispatcher.run();
	}

	
	private static final void printHelp(String error){
		SharedCommandLineUtilities.printPrefix();
		System.err.println("Name:");
		System.err.println("\tTarsosDSP Pitch shifting utility.");
		SharedCommandLineUtilities.printLine();
		System.err.println("Synopsis:");
		System.err.println("\tjava -jar PitchShift.jar source.wav target.wav cents");
		SharedCommandLineUtilities.printLine();
		System.err.println("Description:");
		System.err.println("\tChange the play back speed of audio without changing the pitch.\n");
		System.err.println("\t\tsource.wav\tA readable, mono wav file.");
		System.err.println("\t\ttarget.wav\tTarget location for the pitch shifted file.");
		System.err.println("\t\tcents\t\tPitch shifting in cents: 100 means one semitone up, -100 one down, 0 is no change. 1200 is one octave up.");
		if(!error.isEmpty()){
			SharedCommandLineUtilities.printLine();
			System.err.println("Error:");
			System.err.println("\t" + error);
		}
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy