[Java Sound API] Changing pitch of a sound, sampling rate above 48kHz idea?
This week I was trying to figure out how to play sounds with Java built-in sound API. After 2 days of work I got "something" working - I can easily play/loop/pause/stop MIDIs, simple sounds and streamed audio.
But now I need to change pitch of a sound for my game project. Basically, I need to use 1x-2x pitch. I made the pitch working by changing the sampling rate of a Line. But because of the Java's sampling rate 48kHz limit... I can't set the pitch for my 44.1kHz sounds to a higher value than about 1.08x.
And I thought about something. What if I would just skip some bytes, and don't change the sampling rate any higher than 48kHz (so, if I want to set it higher, I would just set it to 48kHz and skip some bytes) ? The problem is - I can't think of a way to do that properly.
My current code:
Code Java:
package pl.shockah.audio;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Mixer;
public abstract class Audio {
protected static final Mixer mixer;
static {
mixer = AudioSystem.getMixer(null);
}
public abstract void play();
public abstract void loop();
public abstract void pause();
public abstract void stop();
public abstract boolean isPlaying();
public abstract boolean isPaused();
public abstract boolean isStopped();
public abstract boolean isLooping();
}
Code Java:
package pl.shockah.audio;
public abstract class AudioWave extends Audio {
public abstract float getPitch();
public abstract void setPitch(float pitch);
}
Code Java:
package pl.shockah.audio;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import javax.sound.midi.ControllerEventListener;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
public class MIDI extends Audio implements ControllerEventListener {
protected static int[] controllers;
static {
controllers = new int[128];
for (int i = 0; i < controllers.length; i++) controllers[i] = i;
}
protected Sequencer sequencer;
protected boolean paused = false, looping = false;
protected float factor = 1;
public MIDI(File file) {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(MidiSystem.getSequence(file));
sequencer.addControllerEventListener(this,controllers);
} catch (Exception e) {e.printStackTrace();}
}
public MIDI(URL url) {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(MidiSystem.getSequence(url));
} catch (Exception e) {e.printStackTrace();}
}
public MIDI(InputStream is) {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(MidiSystem.getSequence(is));
} catch (Exception e) {e.printStackTrace();}
}
public void play() {play(false);}
public void loop() {play(true);}
public void play(boolean loop) {
if (!isPaused()) stop();
looping = loop;
paused = false;
sequencer.setLoopCount(looping ? Sequencer.LOOP_CONTINUOUSLY : 0);
sequencer.start();
}
public void pause() {
paused = true;
sequencer.stop();
}
public void stop() {
paused = false;
looping = false;
sequencer.stop();
sequencer.setTickPosition(0);
}
public boolean isPlaying() {
return sequencer.isRunning();
}
public boolean isPaused() {
return paused;
}
public boolean isStopped() {
return !isPaused() && !isPlaying();
}
public boolean isLooping() {
return looping;
}
public float getTempoFactor() {
return factor;
}
public void setTempoFactor(float factor) {
this.factor = factor;
sequencer.setTempoFactor(factor);
}
public void controlChange(ShortMessage shortMsg) {
if (shortMsg.getCommand() == ShortMessage.START) paused = false;
}
}
Code Java:
package pl.shockah.audio;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
public class Sample extends AudioWave implements LineListener {
protected Clip clip;
protected boolean paused = false, looping = false;
protected float frequency, pitch = 1;
public Sample(File file) {
try {
clip = AudioSystem.getClip(mixer.getMixerInfo());
clip.open(AudioSystem.getAudioInputStream(file));
frequency = clip.getFormat().getSampleRate();
clip.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public Sample(URL url) {
try {
clip = AudioSystem.getClip(mixer.getMixerInfo());
clip.open(AudioSystem.getAudioInputStream(url));
frequency = clip.getFormat().getSampleRate();
clip.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public Sample(InputStream is) {
try {
clip = AudioSystem.getClip(mixer.getMixerInfo());
clip.open(AudioSystem.getAudioInputStream(is));
frequency = clip.getFormat().getSampleRate();
clip.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public Sample(AudioInputStream ais) {
try {
clip = AudioSystem.getClip(mixer.getMixerInfo());
clip.open(ais);
frequency = clip.getFormat().getSampleRate();
clip.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public void play() {play(false);}
public void loop() {play(true);}
public void play(boolean loop) {
looping = loop;
if (!isPaused()) stop();
paused = false;
if (loop) clip.loop(Clip.LOOP_CONTINUOUSLY);
else clip.start();
}
public void pause() {
paused = true;
clip.stop();
}
public void stop() {
paused = false;
clip.stop();
clip.setFramePosition(0);
}
public boolean isPlaying() {
return clip.isRunning();
}
public boolean isPaused() {
return paused;
}
public boolean isStopped() {
return !isPaused() && !isPlaying();
}
public boolean isLooping() {
return looping;
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
((FloatControl)clip.getControl(FloatControl.Type.SAMPLE_RATE)).setValue(frequency*pitch);
}
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.START) paused = false;
}
}
Code Java:
package pl.shockah.audio;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
public class SampleMulti extends AudioWave implements LineListener {
protected ArrayList<Sample> samples = new ArrayList<Sample>();
protected AudioInputStream ais;
public SampleMulti(File file) {
try {
ais = AudioSystem.getAudioInputStream(file);
} catch (Exception e) {e.printStackTrace();}
}
public SampleMulti(URL url) {
try {
ais = AudioSystem.getAudioInputStream(url);
} catch (Exception e) {e.printStackTrace();}
}
public ArrayList<Sample> getSamples() {
return new ArrayList<Sample>(samples);
}
public void play() {
Sample sample = new Sample(ais);
sample.clip.addLineListener(this);
samples.add(sample);
sample.play();
}
public void loop() {
Sample sample = new Sample(ais);
sample.clip.addLineListener(this);
samples.add(sample);
sample.loop();
}
public void pause() {
for (Sample sample : samples) sample.pause();
}
public void stop() {
for (Sample sample : samples) {
sample.stop();
sample.clip.removeLineListener(this);
}
samples.clear();
}
public boolean isPlaying() {
for (Sample sample : samples) if (sample.isPlaying()) return true;
return false;
}
public boolean isPaused() {
for (Sample sample : samples) if (!sample.isPaused()) return false;
return true;
}
public boolean isStopped() {
for (Sample sample : samples) if (!sample.isStopped()) return false;
return true;
}
public boolean isLooping() {
for (Sample sample : samples) if (sample.isLooping()) return true;
return false;
}
public float getPitch() {
float p = 0; int n = 0;
for (Sample sample : samples) {p += sample.getPitch(); n++;}
if (n == 0) return 1;
return p/((float)n);
}
public void setPitch(float pitch) {
for (Sample sample : samples) sample.setPitch(pitch);
}
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.STOP) {
for (int i = 0; i < samples.size(); i++) if (event.getSource() == samples.get(i)) {
if (!samples.get(i).isPaused()) {
samples.get(i).clip.removeLineListener(this);
samples.remove(i);
return;
}
}
}
}
}
Code Java:
package pl.shockah.audio;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.SourceDataLine;
public class Stream extends AudioWave implements LineListener {
protected SourceDataLine sdl;
protected AudioInputStream ais;
protected boolean paused = false;
protected float frequency, pitch = 1;
protected ThreadStream thread = null;
protected File ctrFile = null;
protected URL ctrURL = null;
public Stream(File file) {
try {
ctrFile = file;
} catch (Exception e) {e.printStackTrace();}
}
public Stream(URL url) {
try {
ctrURL = url;
} catch (Exception e) {e.printStackTrace();}
}
public Stream(InputStream is) {
try {
ais = AudioSystem.getAudioInputStream(is);
sdl = AudioSystem.getSourceDataLine(ais.getFormat(),mixer.getMixerInfo());
sdl.open(ais.getFormat());
sdl.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public Stream(AudioInputStream ais) {
try {
this.ais = ais;
sdl = AudioSystem.getSourceDataLine(ais.getFormat(),mixer.getMixerInfo());
sdl.open(ais.getFormat());
sdl.addLineListener(this);
} catch (Exception e) {e.printStackTrace();}
}
public void play() {play(false);}
public void loop() {play(true);}
public void play(boolean loop) {
if (!isPaused()) stop();
(thread = new ThreadStream(loop){
public void run() {
try {
do {
if (!isPaused()) restartStream();
paused = false;
sdl.start();
int numRead = 0;
byte[] buf = new byte[sdl.getBufferSize()];
while ((numRead = ais.read(buf,0,buf.length)) >= 0 && !stopThread) {
int offset = 0;
while (offset < numRead) {
offset += sdl.write(buf,offset,numRead-offset);
if (stopThread) return;
}
if (stopThread) return;
}
if (stopThread) return;
sdl.drain();
if (stopThread) return;
sdl.stop();
sdl.flush();
} while (isLooping() && !stopThread);
} catch (Exception e) {e.printStackTrace();}
}
}).start();
}
public void pause() {
if (thread == null) return;
thread.stopThread = true;
sdl.stop();
paused = true;
}
public void stop() {
if (thread == null) return;
thread.stopThread = true;
thread.looping = false;
paused = false;
sdl.stop();
sdl.flush();
thread = null;
}
public boolean isPlaying() {
if (sdl == null) return false;
return sdl.isRunning();
}
public boolean isPaused() {
return paused;
}
public boolean isStopped() {
return !isPaused() && !isPlaying();
}
public boolean isLooping() {
if (thread == null) return false;
return thread.looping;
}
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.START) paused = false;
if (event.getType() == LineEvent.Type.STOP && !isLooping() && !isPaused()) stop();
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
((FloatControl)sdl.getControl(FloatControl.Type.SAMPLE_RATE)).setValue(frequency*pitch);
}
protected void restartStream() {
try {
if (ctrFile == null && ctrURL == null) return;
if (sdl != null && sdl.isOpen()) {
sdl.stop();
sdl.flush();
sdl.removeLineListener(this);
sdl.close();
}
if (ctrFile != null) ais = AudioSystem.getAudioInputStream(ctrFile);
else if (ctrURL != null) ais = AudioSystem.getAudioInputStream(ctrURL);
sdl = AudioSystem.getSourceDataLine(ais.getFormat(),mixer.getMixerInfo());
sdl.open(ais.getFormat());
sdl.addLineListener(this);
frequency = sdl.getFormat().getSampleRate();
setPitch(getPitch());
} catch (Exception e) {e.printStackTrace();}
}
}
Code Java:
package pl.shockah.audio;
public class ThreadStream extends Thread {
protected boolean looping;
protected boolean stopThread = false;
public ThreadStream(boolean looping) {
this.looping = looping;
}
}