I'd like to develop a simple java music player to accelerate and play music using this Sonic Algorithm github/Sonic.java. And here's the main class: github/Main.java. The Main.java simply calls Sonic.java and then it can play the music. Even though it works well when running a WAV file, but what I want is to write a new WAV file from the accelerated input stream.
I've tried to write bytes to a ByteArrayOutputStream in the do-while loop of Main.java, and transformed them into a local WAV file, while the generated music gets cut off and obviously there have some lost data during this process.
public class App {
private static void runSonic(
AudioInputStream audioStream,
SourceDataLine line,
float speed,
float pitch,
float rate,
float volume,
boolean emulateChordPitch,
int quality,
int sampleRate,
int numChannels) throws IOException
{
Sonic sonic = new Sonic(sampleRate, numChannels);
int bufferSize = line.getBufferSize();
byte inBuffer[] = new byte[bufferSize];
byte outBuffer[] = new byte[bufferSize];
int numRead,numWritten;
AudioFormat af = audioStream.getFormat();
ByteArrayOutputStream output = new ByteArrayOutputStream();
sonic.setSpeed(speed);
sonic.setPitch(pitch);
sonic.setRate(rate);
sonic.setVolume(volume);
sonic.setChordPitch(emulateChordPitch);
sonic.setQuality(quality);
int count = 0;
do {
numRead = audioStream.read(inBuffer, 0, bufferSize);
if(numRead <= 0) {
sonic.flushStream();
} else {
sonic.writeBytesToStream(inBuffer, numRead);
}
do {
numWritten = sonic.readBytesFromStream(outBuffer, bufferSize);
if(numWritten > 0) {
line.write(outBuffer, 0, numWritten);
output.write(outBuffer);
}
} while(numWritten > 0);
} while(numRead > 0);
byte fileBuffer[] = output.toByteArray();
ByteArrayInputStream bais1 = new ByteArrayInputStream(fileBuffer);
AudioInputStream aisAccelerated1 =
new AudioInputStream(bais1, af, fileBuffer.length);
try {
AudioSystem.write(aisAccelerated1, AudioFileFormat.Type.WAVE, new
File("newFile.wav")
);
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(
String[] argv) throws UnsupportedAudioFileException, IOException, LineUnavailableException
{
float speed = 1.5f;
float pitch = 1.5f;
float rate = 1.0f;
float volume = 1.0f;
boolean emulateChordPitch = false;
int quality = 0;
String fileName = "file.wav";
AudioInputStream stream = AudioSystem.getAudioInputStream(new File(fileName));
AudioFormat format = stream.getFormat();
int sampleRate = (int)format.getSampleRate();
int numChannels = format.getChannels();
SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, format,
((int)stream.getFrameLength()*format.getFrameSize()));
SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
line.open(stream.getFormat());
line.start();
runSonic(stream, line, speed, pitch, rate, volume, emulateChordPitch, quality,
sampleRate, numChannels);
line.drain();
line.stop();
}
}
Who can tell me what's going on here? I think all bytes stored in outBuffer has been writted into the output stream in this way.
You can find the whole class using the links above.
output.write(outBuffer);
The problem is here. It should be
output.write(outBuffer, 0, numWritten);
You are writing garbage to the output.
Related
Edit: I am using a .wav file
I'm trying to figure out how to start audio at a certain position (for example: 10 seconds into audio file rather than at the start). Reading the documentation for SourceDataLine had me believe this may be achieved using the offset during:
line.write(byte[] b, int offset, int length)
but every time I've tried any value other than 0 (the default I believe), I get java.lang.IndexOutOfBoundsException, which maybe it hasn't read x byte position yet so cannot write x byte position? I'm unsure and left scratching my head.
I figured this would be a common enough request but can't seem to find anything online related to this, only pausing and resuming audio. I'm probably not searching properly.
In case it matters, here is how I'm currently doing my audio:
AudioInputStream stream = AudioSystem.getAudioInputStream("...file...");
AudioFormat format = stream.getFormat();
SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, format,((int)stream.getFrameLength()*format.getFrameSize()));
SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
int bufferSize = line.getBufferSize();
byte inBuffer[] = new byte[bufferSize];
byte outBuffer[] = new byte[bufferSize];
int numRead, numWritten;
do {
numRead = audioStream.read(inBuffer, 0, bufferSize);
if(numRead <= 0) {
myAudio.flushStream();
} else {
myAudio.writeBytesToStream(inBuffer, numRead);
}
do {
numWritten = myAudio.readBytesFromStream(outBuffer, bufferSize);
if(numWritten > 0) {
line.write(outBuffer, 0, numWritten);
}
} while(numWritten > 0);
} while(numRead > 0);
The problem you are having probably stems from the fact that you are adjusting the offset without adjusting the length. If your array is 10 bytes long and you are starting reading 10 bytes from offset 5 instead of 0, you are reading 5 bytes past its end.
I'd recommend to first skip the appropriate number of bytes using skip(long) on the AudioInputStream and then write to the line.
AudioInputStream stream = AudioSystem.getAudioInputStream("...file...");
AudioFormat format = stream.getFormat();
// find out how many bytes you have to skip, this depends on bytes per frame (a.k.a. frameSize)
int secondsToSkip = 10;
long bytesToSkip = format.getFrameSize() * ((int)format.getFrameRate()) * secondsToSkip;
// now skip until the correct number of bytes have been skipped
int justSkipped = 0;
while (bytesToSkip > 0 && (justSkipped = stream.skip(bytesToSkip)) > 0) {
bytesToSkip -= justSkipped;
}
// then proceed with writing to your line like you have done before
[...]
Note that this only works, if the audio file is uncompressed. If you are dealing with something like .mp3, you first have to convert the stream to PCM (see https://stackoverflow.com/a/41850901/942774)
I've created an example which compiles and works. You can play a .wav file from any time point. It should also work for an mp3 file, but I haven't tested that. Invoke mp3ToWav() for that.
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
public class PlayWavAtTimePoint {
public static void main(String[] args) throws Exception {
String fileName = args[0];
int secondsToSkip = (Integer.parseInt(args[1]));
PlayWavAtTimePoint program = new PlayWavAtTimePoint();
AudioInputStream is = program.getAudioInputStream(fileName);
program.skipFromBeginning(is, secondsToSkip);
program.playSound(is);
}
private static void skipFromBeginning(AudioInputStream audioStream, int secondsToSkip) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioFormat format = audioStream.getFormat();
// find out how many bytes you have to skip, this depends on bytes per frame (a.k.a. frameSize)
long bytesToSkip = format.getFrameSize() * ((int)format.getFrameRate()) * secondsToSkip;
// now skip until the correct number of bytes have been skipped
long justSkipped = 0;
while (bytesToSkip > 0 && (justSkipped = audioStream.skip(bytesToSkip)) > 0) {
bytesToSkip -= justSkipped;
}
}
private static final int BUFFER_SIZE = 128000;
/**
* #param filename the name of the file that is going to be played
*/
public void playSound(String filename) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
AudioInputStream audioStream = getAudioInputStream(filename);
playSound(audioStream);
}
private AudioInputStream getAudioInputStream(String filename) throws UnsupportedAudioFileException, IOException {
return AudioSystem.getAudioInputStream(new File(filename));
}
public void playSound(AudioInputStream audioStream) throws LineUnavailableException, IOException {
AudioFormat audioFormat = audioStream.getFormat();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine audioOutput = (SourceDataLine) AudioSystem.getLine(info);
audioOutput.open(audioFormat);
audioOutput.start();
//This seems to be reading the whole file into a buffer before playing ... not efficient.
//Why not stream it?
int nBytesRead = 0;
byte[] abData = new byte[BUFFER_SIZE];
while (nBytesRead != -1) {
nBytesRead = audioStream.read(abData, 0, abData.length);
if (nBytesRead >= 0) {
audioOutput.write(abData, 0, nBytesRead);
}
}
audioOutput.drain();
audioOutput.close();
}
/**
* Invoke this function to convert to a playable file.
*/
public static void mp3ToWav(File mp3Data) throws UnsupportedAudioFileException, IOException {
// open stream
AudioInputStream mp3Stream = AudioSystem.getAudioInputStream(mp3Data);
AudioFormat sourceFormat = mp3Stream.getFormat();
// create audio format object for the desired stream/audio format
// this is *not* the same as the file format (wav)
AudioFormat convertFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
sourceFormat.getSampleRate(), 16,
sourceFormat.getChannels(),
sourceFormat.getChannels() * 2,
sourceFormat.getSampleRate(),
false);
// create stream that delivers the desired format
AudioInputStream converted = AudioSystem.getAudioInputStream(convertFormat, mp3Stream);
// write stream into a file with file format wav
AudioSystem.write(converted, AudioFileFormat.Type.WAVE, new File("/tmp/out.wav"));
}
}
I've been trying to make a loading program for my friend. He said that it would be better if it played music, so I tried to add music and it's saying it doesn't work. It keeps on giving me an error saying The system cannot find the file specified but the file is in the same package as the class.
static File sound;
static boolean muted = false;
static float volume = 100.0f;
static float pan = 0.0f;
static double seconds = 0.0d;
static boolean loopedForever = false;
static int loopTimes = 0;
static int loopsDone = 0;
public static void main(String[] args){
sound = new File("src/196006__corsica-s__patriceo.wav");
new Thread(play).start();
}
final static Runnable play = new Runnable() // This Thread/Runnabe is for playing the sound
{
public void run()
{
try
{
// Check if the audio file is a .wav file
if (sound.getName().toLowerCase().contains(".wav"))
{
AudioInputStream stream = AudioSystem.getAudioInputStream(sound);
AudioFormat format = stream.getFormat();
if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED)
{
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
format.getSampleRate(),
format.getSampleSizeInBits() * 2,
format.getChannels(),
format.getFrameSize() * 2,
format.getFrameRate(),
true);
stream = AudioSystem.getAudioInputStream(format, stream);
}
SourceDataLine.Info info = new DataLine.Info(
SourceDataLine.class,
stream.getFormat(),
(int) (stream.getFrameLength() * format.getFrameSize()));
SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
line.open(stream.getFormat());
line.start();
// Set Volume
FloatControl volume_control = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
volume_control.setValue((float) (Math.log(volume / 100.0f) / Math.log(10.0f) * 20.0f));
// Mute
BooleanControl mute_control = (BooleanControl) line.getControl(BooleanControl.Type.MUTE);
mute_control.setValue(muted);
FloatControl pan_control = (FloatControl) line.getControl(FloatControl.Type.PAN);
pan_control.setValue(pan);
long last_update = System.currentTimeMillis();
double since_last_update = (System.currentTimeMillis() - last_update) / 1000.0d;
// Wait the amount of seconds set before continuing
while (since_last_update < seconds)
{
since_last_update = (System.currentTimeMillis() - last_update) / 1000.0d;
}
System.out.println("Playing!");
int num_read = 0;
byte[] buf = new byte[line.getBufferSize()];
while ((num_read = stream.read(buf, 0, buf.length)) >= 0)
{
int offset = 0;
while (offset < num_read)
{
offset += line.write(buf, offset, num_read - offset);
}
}
line.drain();
line.stop();
if (loopedForever)
{
new Thread(play).start();
}
else if (loopsDone < loopTimes)
{
loopsDone++;
new Thread(play).start();
}
}
}
catch (Exception ex) {ex.printStackTrace();}
}
};
I tested your code, it works fine. You problem is with the location of the file in the folder/package hierarchy.
This is how I access the file based on your code:
public class Example {
public static void main(String[] args) {
sound = new File("harpsi-cs.wav");
new Thread(play).start();
}
}
and this is my hierarchy:
Often projects create a resources folder to put their files there. In that case, don't forget to make it a source folder and change your path accordingly:
sound = new File("resources/harpsi-cs.wav");
I am using Java sound to read in sound from the mic and transform it using fft. The issue I am having is that when I examine the byte array after the call to soundline.read() returns, it only contains 0,1, and -1. I am thoroughly confused. Below is my code:
private void setupSound() {
Vector<AudioFormat> formats = getSupportedFormats(TargetDataLine.class);
try {
float sampleRate = 44100;
int sampleSizeInBits = 16;
int channels = 1;
int frameSize = 2;
boolean signed = false;
boolean bigEndian = false;
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, sampleSizeInBits, channels, frameSize, sampleRate, bigEndian);
soundLine = (TargetDataLine) AudioSystem.getTargetDataLine(format);
soundLine.open(format);
soundLine.start();
} catch (LineUnavailableException ex) {
System.out.println(ex);
}
}
private byte[] readSoundSample() {
byte[] buffer = new byte[44100];
int len = soundLine.read(buffer, 0, buffer.length);
return buffer;
}
I am a newbie in programming and I want to learn more.
I want to record sound from my microphone in real-time. Below is my code for recording.
while (true) {
int numBytesRead = line.read(data, 0, data.length);
out.write(data, 0, numBytesRead);
}
I tried to enter some codes there and I got to play some chunks of data but after a few seconds, the recording delays for about 3 seconds. Also, when I tried to talk, it plays backs in loop what I tried to say
while (true) {
int numBytesRead = line.read(data, 0, data.length);
out.write(data, 0, numBytesRead);
try {
byte audio[] = out.toByteArray();
InputStream input = new ByteArrayInputStream(audio);
final SourceDataLine line1 = (SourceDataLine) AudioSystem.getLine(info1);
final AudioInputStream ais = new AudioInputStream(input, format, audio.length / format.getFrameSize());
int bufferSize = (int) format.getSampleRate() * format.getFrameSize();
line1.open(format);
line1.start();
byte buffer[] = new byte[bufferSize];
try {
while (true) {
numBytesRead = ais.read(buffer, 0, buffer.length);
if (numBytesRead == -1) break;
line1.write(buffer, 0, numBytesRead);
}
} catch (IOException e) {
System.err.println("I/O problems: " + e);
System.exit(-3);
}
}
Could somebody help me with my project.
thank you for your answer sir. but i tried adding this line to my code and the playback worked
while (numBytesRemaining>0){
numBytesRemaining-=line1.write(data,0,numBytesRemaining);
}
thank you for your help sir
why don't you use Clip for reading the recorded sound.
wavdata = out.toByteArray();
AudioInputStream ais = new AudioInputStream(new ByteArrayInputStream(wavdata), WAVFormat(), wavdata.length / WAVFormat().getFrameSize());
format = ais.getFormat();
info = new DataLine.Info(Clip.class, format);
Clip clip = (Clip) AudioSystem.getLine(info);
clip.open(ais);
//this is for playing
clip.start();
//this is for stopping or pause use it on the pause or stop button.
//clip.stop();
and this is the WAVFormat()
private AudioFormat WAVFormat() {
int channels = 2;
Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
float sampleRate = rateconstant;
int frameSize = framesize;
float frameRate = rateconstant;
int sampleSizeInBits = 16;
boolean bigEndian = false;
return new AudioFormat(encoding, sampleRate, sampleSizeInBits, channels, frameSize, frameRate, bigEndian);
}
hope it helps
I'm trying to create code to get wav files into an array list and then for the arraylist to play the files. Why does this code not work? Thanks.
public class trackList {
public void tracklist(){
ArrayList<String> songs = new ArrayList();
songs.add("c:\\01.wav");
songs.add("c:\\02.wav");
byte[] buffer = new byte[4096];
for(int x = 0; x < songs.size(); x++){
try{
AudioInputStream ais = AudioSystem.getAudioInputStream(songs(x));
AudioFormat format = ais.getFormat();
SourceDataLine line = AudioSystem.getSourceDataLine(format);
line.open(format);
line.start();
while (ais.available() > 0) {
int len = ais.read(buffer);
line.write(buffer, 0, len);
}
line.drain();
line.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
Not a Java expert, but probably the line:
AudioInputStream ais = AudioSystem.getAudioInputStream(songs(x));
Must be
AudioInputStream ais = AudioSystem.getAudioInputStream(songs.get(x));