cutting a wave file - java

How can i cut a .wave file using java ?
What i want is :
when the user presses the button labeled cut it should cut the audio from the previous mark (in nanoseconds) to the current position in nanoseconds. (mark is positioned to the current position in nanoseconds after the sound is cut) After i get that piece of audio,i want to save that piece of audio file.
// obtain an audio stream
long mark = 0; // initially set to zero
//get the current position in nanoseconds
// after that how to proceed ?
// another method ?
How can i do that ?

This has originally been answered by Martin Dow
import java.io.*;
import javax.sound.sampled.*;
class AudioFileProcessor {
public static void main(String[] args) {
copyAudio("/tmp/uke.wav", "/tmp/uke-shortened.wav", 2, 1);
}
public static void copyAudio(String sourceFileName, String destinationFileName, int startSecond, int secondsToCopy) {
AudioInputStream inputStream = null;
AudioInputStream shortenedStream = null;
try {
File file = new File(sourceFileName);
AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(file);
AudioFormat format = fileFormat.getFormat();
inputStream = AudioSystem.getAudioInputStream(file);
int bytesPerSecond = format.getFrameSize() * (int)format.getFrameRate();
inputStream.skip(startSecond * bytesPerSecond);
long framesOfAudioToCopy = secondsToCopy * (int)format.getFrameRate();
shortenedStream = new AudioInputStream(inputStream, format, framesOfAudioToCopy);
File destinationFile = new File(destinationFileName);
AudioSystem.write(shortenedStream, fileFormat.getType(), destinationFile);
} catch (Exception e) {
println(e);
} finally {
if (inputStream != null) try { inputStream.close(); } catch (Exception e) { println(e); }
if (shortenedStream != null) try { shortenedStream.close(); } catch (Exception e) { println(e); }
}
}
}
Originally answered HERE

Create an AudioInputStream from the file source (you can use AudioSystem.getAudioInputStream(File) for this).
Use AudioFormat from the stream's getFormat() to determine the number of bytes you need to read from the stream and the positions.
File position (bytes) = time(seconds) / sample rate * sample size (bits) * 8 * channels for wave files
Create a new AudioInputStream based on the original that only reads the data you want from the original. You can do this by skipping the bytes you want in the original stream, create a wrapper that fixes the length for the endpoint and then using AudioSystem.getAudioInputStream(AudioFormat, AudioInputStream). There are other ways to do this as well which might be better.
Use AudioSystem.write() method to write out the new file.
You might also want to look at Tritonus and its AudioOutputStream, it might make things easier.

there's an api which may helps you to achieve your goal http://code.google.com/p/musicg-sound-api/

Related

Java audio - trim an audio file down to a specified length

I am trying to create a small java program to cut an audio file down to a specified length. Currently I have the following code:-
import java.util.*;
import java.io.*;
import javax.sound.sampled.*;
public class cuttest_3{
public static void main(String[]args)
{
int totalFramesRead = 0;
File fileIn = new File("output1.wav");
// somePathName is a pre-existing string whose value was
// based on a user selection.
try {
AudioInputStream audioInputStream =
AudioSystem.getAudioInputStream(fileIn);
int bytesPerFrame =
audioInputStream.getFormat().getFrameSize();
if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
// some audio formats may have unspecified frame size
// in that case we may read any amount of bytes
bytesPerFrame = 1;
}
// Set a buffer size of 5512 frames - semiquavers at 120bpm
int numBytes = 5512 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
try {
int numBytesRead = 0;
int numFramesRead = 0;
// Try to read numBytes bytes from the file.
while ((numBytesRead =
audioInputStream.read(audioBytes)) != -1) {
// Calculate the number of frames actually read.
numFramesRead = numBytesRead / bytesPerFrame;
totalFramesRead += numFramesRead;
// Here, - output a trimmed audio file
AudioInputStream cutFile =
new AudioInputStream(audioBytes);
AudioSystem.write(cutFile,
AudioFileFormat.Type.WAVE,
new File("cut_output1.wav"));
}
} catch (Exception ex) {
// Handle the error...
}
} catch (Exception e) {
// Handle the error...
}
}
}
On attempting compilation, the following error is returned:-
cuttest_3.java:50: error: incompatible types: byte[] cannot be converted to TargetDataLine
new AudioInputStream(audioBytes);
I am not very familiar with AudioInputStream handling in Java, so can anyone suggest a way I can conform the data to achieve output? Many thanks
You have to tell the AudioInputStream how to decipher the bytes you pass in as is specified by Matt in the answer here. This documentation indicates what each of the parameters mean.
A stream of bytes does not mean anything until you indicate to the system playing the sound how many channels there are, the bit resolution per sample, samples per second, etc.
Since .wav files are an understood protocol and I think they have data at the front of the file defining various parameters of the audio track, the AudioInputStream can correctly decipher the 1st file you pass in.

Play Base64 encoded audio file in java [duplicate]

This question already has answers here:
How to play .wav files with java
(10 answers)
Closed 8 years ago.
----Solution--------
public class SimpleWavPlayer {
public final static String encoded = "base64 encoded binary that
I previously parsed and outputted then copied here";
public static void main(String[] args)
throws IOException,
InterruptedException,
LineUnavailableException,
UnsupportedAudioFileException {
byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);
AudioInputStream audioIn = AudioSystem.getAudioInputStream(
new ByteArrayInputStream(decoded));
Clip song = AudioSystem.getClip();
song.open(audioIn);
song.start();
// Wait for clip to finish.
final CountDownLatch latch = new CountDownLatch(1);
song.addLineListener(new LineListener() {
#Override
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP)) {
event.getLine().close();
latch.countDown();
}
}
});
latch.await();
}
}
----Original question--------
I have a string that contains a base64 encoded mp3 file. I want to decode that file then play it.
File file = new File("song.wav");
byte[] bytes = FileUtils.readFileToByteArray(file);
String encoded = Base64.encodeToString(bytes, 0);
byte[] decoded = Base64.decode(encoded, 0);
AudioInputStream audioIn = AudioSystem.getAudioInputStream(/*what do i do here?*/);
Clip song = /*what do i do here?*/;
song.start;
I now i have the byte array ready. How can I use this decoded byte array to play music using clip or audioinputstr
--------EDIT 1------------
I've updated the code with two different ways of doing things. Both compile and run, and I can view the encoded string, but there is no sound. Instead of using FileUtils.readFileToByteArray(); I used Path and Paths.get in conjunction with File.readAllBytes(). I could not get FileUtils working because it wanted me to use the apacha library and I do not want to use 3rd party libraries. Also I don't know if this is important information, but I am on an archlinux installation that is currently using pulseaudio. Here is the code. Thanks for all the help thus far. Excuse my lazy exception handling.
import java.io.OutputStream;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
//import java.io.*;
//import java.util.Base64.*;
import javax.xml.bind.DatatypeConverter;
import javax.sound.sampled.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import sun.audio.*;
import java.io.*;
public class wavestring {
public static void main(String[] args){
Path path = Paths.get("/home/me/Documents/audiototext/yo.wav");
byte[] bytes = null;
try{
bytes = Files.readAllBytes(path);
}catch(IOException e){
System.out.print("Idk man, something happened man");
}
String encoded = DatatypeConverter.printBase64Binary(bytes);
System.out.println(encoded);
byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);
// Convert byte array to inputStream
InputStream is = new ByteArrayInputStream(decoded);
// // Get AudioInputStream from InputStream
AudioInputStream audioIn = null;
try{
audioIn = AudioSystem.getAudioInputStream(is);
}catch(UnsupportedAudioFileException u){
System.out.println("Well bruh...something happened");
}catch(IOException e){
System.out.println("brooooo");
}
// // Acquire audio format and create a DataLine.Infoobject:
AudioFormat format = audioIn.getFormat();
/*
//METHOD 3
AudioInputStream audioIn = null;
try{
audioIn = AudioSystem.getAudioInputStream(is);
}catch(UnsupportedAudioFileException u){
System.out.println("Well bruh...something happened");
}catch(IOException e){
System.out.println("brooooo");
}
// // Acquire audio format and create a DataLine.Infoobject:
AudioFormat format = audioIn.getFormat();
Clip song = null;
try{
song = AudioSystem.getClip();
song.open(audioIn);
}catch(LineUnavailableException l){
}catch(IOException e){
}
song.start();
*/
//METHOD 2
SourceDataLine source_line = null;
try{
source_line = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, format));
source_line.open(format);
}catch(LineUnavailableException l){
System.out.println("yooooooo");
}
byte[] buffer = new byte[524288];
int bytes_read = 0;
while(true){
try{
bytes_read = audioIn.read(buffer);
}catch(IOException e){
System.out.println("idk man");
}
if(bytes_read < 0)
break;
source_line.write(buffer, 0, bytes_read);
}
try{
audioIn.close();
}catch(IOException e){
System.out.println("yooooooooooo man");
}
source_line.drain();
source_line.close();
//METHOD 1
/*DataLine.Info info = new DataLine.Info(Clip.class, format);
Clip song = null;
try{
song = (Clip) AudioSystem.getLine(info);
}catch(LineUnavailableException l){
System.out.println("We were so close but something happened man");
}
song.start();*/
}
}
The answers to all of your questions are in the documentation.
First, let's look at the documentation for AudioSystem. There are five getAudioInputStream methods. Two take explicit AudioFormat arguments, which don't apply to playing a .wav file. The remaining three methods take a File, InputStream and URL, respectively.
Since you already have a byte array, the best choice is to wrap the bytes in a ByteArrayInputStream. Now we have an InputStream we can pass to a getAudioInputStream method.
If you're wondering how to obtain a Clip object, the documentation is, again, your best friend. If you go to the documentation for Clip, and look at the very top of the page, you'll see a navigation row with several links, including a "USE" link. Follow that link, and you will get a list of all methods in the Java SE API which return a Clip or take a Clip as an argument.
It just so happens that this is a short list: As of Java 8, there are only two methods, both static, which can return a Clip. One takes zero arguments, while the other takes an explicit Mixer.Info. Normally, you just want to play sounds through the default Mixer, so just use the zero-argument getClip() method to obtain a new Clip.
So now you have a Clip, but it isn't associated with your AudioInputStream yet. Once again, the documentation comes to our rescue. The documentation for AudioSystem.getClip() states:
The returned clip must be opened with the open(AudioFormat) or open(AudioInputStream) method.
So if we return to the Clip documentation again, we see two open methods (as of Java 8). One of them takes an AudioInputStream as its only argument. That's the one you want to use.
Finally, as you seem to already know, you must start the clip by calling its inherited start() method.
Now we have enough information to write the code:
AudioInputStream audioIn = AudioSystem.getAudioInputStream(
new ByteArrayInputStream(decoded));
Clip song = AudioSystem.getClip();
song.open(audioIn);
song.start();
Update: The above should be able to play sound for you. Here is the complete program I wrote to test it:
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.util.concurrent.CountDownLatch;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
public class SimpleWavPlayer {
public static void main(String[] args)
throws IOException,
InterruptedException,
LineUnavailableException,
UnsupportedAudioFileException {
for (String arg : args) {
Path file = Paths.get(arg);
byte[] decoded = Files.readAllBytes(file);
AudioInputStream audioIn = AudioSystem.getAudioInputStream(
new ByteArrayInputStream(decoded));
Clip song = AudioSystem.getClip();
song.open(audioIn);
song.start();
// Wait for clip to finish.
final CountDownLatch latch = new CountDownLatch(1);
song.addLineListener(new LineListener() {
#Override
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP)) {
event.getLine().close();
latch.countDown();
}
}
});
latch.await();
}
}
}
Untested but you can use this as a guide:
File file = new File("song.wav");
byte[] bytes = FileUtils.readFileToByteArray(file);
String encoded = Base64.encodeToString(bytes, 0);
byte[] decoded = Base64.decode(encoded, 0);
// Convert byte array to inputStream
InputStream is = new ByteArrayInputStream(decoded);
// Get AudioInputStream from InputStream
AudioInputStream audioIn = AudioSystem.getAudioInputStream(is);
// Acquire audio format and create a DataLine.Infoobject:
AudioFormat format = audioIn.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
Clip song = (Clip) AudioSystem.getLine(info);
song.start();
Partially based on this: link
(code mostly taken from this answer)
To get an AudioInputStream after reading the file into memory, you could use:
AudioInputStream audio_in = AudioSystem.getAudioInputStream(new ByteArrayInputStream(bytes));
Alternatively, you could read from the file directly. If the file is large, this saves memory (by not storing all of it in memory at once). AudioSystem has a convenient method for this:
AudioInputStream audio_in = AudioSystem.getAudioInputStream(file);
Once you have an AudioInputStream which lets you read audio from the file, you need a SourceDataLine to let you play audio through the speakers.
AudioFormat audio_format = audio_in.getFormat();
SourceDataLine source_line = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, audio_format));
source_line.open(audio_format);
and then you can read audio from the file, and send it to the speakers, until you get to the end of the file:
byte[] buffer = new byte[65536]; // the exact size doesn't matter too much; I chose 64 KiB
while(true) {
int bytes_read = audio_in.read(buffer);
if(bytes_read < 0)
break; // end of file reached
source_line.write(buffer, 0, bytes_read);
}
Note that the SourceDataLine will only buffer a certain amount of data; once the buffer is full, trying to write more data will block (i.e. make your program wait) until the already-written data has been played. That means the above loop will only finish once most of the file has been played.
Once it does, all that's left is to clean up:
audio_in.close();
source_line.drain();
source_line.close();
SourceDataLine.drain waits for any data the line has buffered to finish playing. If you don't call it, then the file will stop playing immediately when you close the SourceDataLine, which might cause the last few seconds to be cut off.

Program runs completely in Eclipse but does not run when exported as a Runnable JAR file

I have a main class that looks like this:
package complete;
import java.io.File;
import java.io.*;
import javax.sound.sampled.*;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Presentation");
frame.setSize(806, 506);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new GameFrame());
frame.setVisible(true);
sound = new File("Assets/BackgroundSound.wav"); // Write you own file location here and be aware that it need to be an .wav file
new Thread(play).start();
}
static File sound;
static boolean muted = false; // This should explain itself
static float volume = 100.0f; // This is the volume that goes from 0 to 100
static float pan = 0.0f; // The balance between the speakers 0 is both sides and it goes from -1 to 1
static double seconds = 0.0d; // The amount of seconds to wait before the sound starts playing
static boolean looped_forever = true; // It will keep looping forever if this is true
static int loop_times = 0; // Set the amount of extra times you want the sound to loop (you don't need to have looped_forever set to true)
static int loops_done = 0; // When the program is running this is counting the times the sound has looped so it knows when to stop
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 (looped_forever)
{
new Thread(play).start();
}
else if (loops_done < loop_times)
{
loops_done++;
new Thread(play).start();
}
}
}
catch (Exception ex) { ex.printStackTrace(); }
}
};
}
When I run the runnable JAR, the frame opens with the correct size and title but with a blank screen.
When I run from the command line I get this error:
java.io.FileNotFoundException: Assets\BackgroundSound.wav <The system cannot find the path specified>
at java.io.FileInputStream.open<Native Method>
at java.io.FileInputStream.<init><Unknown Source>
at com.sun.media.sound.WaveFloatFileReader.getAudioInputStream<Unknown Source>
at javax.sound.sampled.AudioSystem.getAudioInputStream<Unknown Source>
at complete.Main$1.run<Main.Java:50>
at java.lang.Thread.run<Unknown Source>
I have extracted the files from the JAR and all the classes, images and the WAV file are there.
When I remove the sound section from the Main class and run in Eclipse, the program runs completely and without sound as expected.
When I export this version as a Runnable JAR, the same thing happens as before when I attempted to run it, except this time there are no command line errors.
To load your file from a jar you need to use getResources or getResourceAsStream.
Use:
sound = new File(Main.class.getResource("Assets/BackgroundSound.wav").getPath());
The data for sound is not contained in a file on your disk directly; it is inside the jar file for your project. So, using File to access it is incorrect. Instead, use the overloaded AudioStream method:
public static AudioInputStream getAudioInputStream(InputStream stream)
throws UnsupportedAudioFileException,
IOException
Obtains an audio input stream from the provided input stream. The stream must point to valid audio file data. The implementation of this method may require multiple parsers to examine the stream to determine whether they support it. These parsers must be able to mark the stream, read enough data to determine whether they support the stream, and, if not, reset the stream's read pointer to its original position. If the input stream does not support these operation, this method may fail with an IOException.
You get the InputStream by using Class.getResourceAsStream(String name). That will look on the class path, and so can be set to work in both Eclipse and from a jar file. Easiest is to move the sound file to be alongside your class file, so you can just use:
InputStream soundResource = Main.class.getResourceAsStream("BackgroundSound.wav");
AudioInputStream ais = AudioSystem.getAudioInputStream(soundResource);
Sorry--I needed to edit the last part--I forgot that this was all in main. Try not to do that for any but the most trivial programs. Instead, instantiate the class and have the instance do things. And rename the class from Main to something like SoundDemo. Otherwise in three months you'll say to yourself, "Where did I do that exercise on learning how to have Java play sounds? What file was it in? It was in Main? Really?"

Java SFXR Port - Trouble writing byte[] to WAV file

I'm using a Java port of the sound effect generator SFXR, which involves lots of arcane music code that I don't understand, being something of a novice when it comes to anything to do with audio. What I do know is that the code can reliably generate and play sounds within Java, using a SourceDataLine object.
The data that the SDL object uses is stored in a byte[]. However, simply writing this out to a file doesn't work (presumably because of the lack of a WAV header, or so I thought).
However, I downloaded this WAV read/write class: http://computermusicblog.com/blog/2008/08/29/reading-and-writing-wav-files-in-java/ which adds in header information when it writes a WAV file. Giving it the byte[] data from SFXR still produces files that can't be played by any music player I have.
I figure I must be missing something. Here's the relevant code when it plays the sound data:
public void play(int millis) throws Exception {
AudioFormat stereoFormat = getStereoAudioFormat();
SourceDataLine stereoSdl = AudioSystem.getSourceDataLine(stereoFormat);
if (!stereoSdl.isOpen()) {
try {
stereoSdl.open();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
if (!stereoSdl.isRunning()) {
stereoSdl.start();
}
double seconds = millis / 1000.0;
int bufferSize = (int) (4 * 41000 * seconds);
byte[] target = new byte[bufferSize];
writeBytes(target);
stereoSdl.write(target, 0, target.length);
}
That's from the SFXR port. Here's the save() file from the WavIO class (there's a lot of other code in that class of course, I figured this might be worth posting in case someone wants to see exactly how the buffer data is being handled:
public boolean save()
{
try
{
DataOutputStream outFile = new DataOutputStream(new FileOutputStream(myPath));
// write the wav file per the wav file format
outFile.writeBytes("RIFF"); // 00 - RIFF
outFile.write(intToByteArray((int)myChunkSize), 0, 4); // 04 - how big is the rest of this file?
outFile.writeBytes("WAVE"); // 08 - WAVE
outFile.writeBytes("fmt "); // 12 - fmt
outFile.write(intToByteArray((int)mySubChunk1Size), 0, 4); // 16 - size of this chunk
outFile.write(shortToByteArray((short)myFormat), 0, 2); // 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
outFile.write(shortToByteArray((short)myChannels), 0, 2); // 22 - mono or stereo? 1 or 2? (or 5 or ???)
outFile.write(intToByteArray((int)mySampleRate), 0, 4); // 24 - samples per second (numbers per second)
outFile.write(intToByteArray((int)myByteRate), 0, 4); // 28 - bytes per second
outFile.write(shortToByteArray((short)myBlockAlign), 0, 2); // 32 - # of bytes in one sample, for all channels
outFile.write(shortToByteArray((short)myBitsPerSample), 0, 2); // 34 - how many bits in a sample(number)? usually 16 or 24
outFile.writeBytes("data"); // 36 - data
outFile.write(intToByteArray((int)myDataSize), 0, 4); // 40 - how big is this data chunk
outFile.write(myData); // 44 - the actual data itself - just a long string of numbers
}
catch(Exception e)
{
System.out.println(e.getMessage());
return false;
}
return true;
}
All I know is, I've got a bunch of data, and I want it to end up in a playable audio file of some kind (at this point I'd take ANY format!). What's the best way for me to get this byte buffer into a playable file? Or is this byte[] not what I think it is?
I do not get much chance to play with the sound capabilities of Java so I'm using your question as a learning exercise (I hope you don't mind). The article that you referenced about Reading and Writing WAV Files in Java is very old in relation to Java history (1998). Also something about constructing the WAV header by hand didn't sit quite right with me (it seemed a little error prone). As Java is quite a mature language now I would expect library support for this kind of thing.
I was able to construct a WAV file from a byte array by hunting around the internet for sample code snippets. This is the code that I came up with (I expect it is sub-optimal but it seems to work):
// Generate bang noise data
// Sourced from http://www.rgagnon.com/javadetails/java-0632.html
public static byte[] bang() {
byte[] buf = new byte[8050];
Random r = new Random();
boolean silence = true;
for (int i = 0; i < 8000; i++) {
while (r.nextInt() % 10 != 0) {
buf[i] =
silence ? 0
: (byte) Math.abs(r.nextInt()
% (int) (1. + 63. * (1. + Math.cos(((double) i)
* Math.PI / 8000.))));
i++;
}
silence = !silence;
}
return buf;
}
private static void save(byte[] data, String filename) throws IOException, LineUnavailableException, UnsupportedAudioFileException {
InputStream byteArray = new ByteArrayInputStream(data);
AudioInputStream ais = new AudioInputStream(byteArray, getAudioFormat(), (long) data.length);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename));
}
private static AudioFormat getAudioFormat() {
return new AudioFormat(
8000f, // sampleRate
8, // sampleSizeInBits
1, // channels
true, // signed
false); // bigEndian
}
public static void main(String[] args) throws Exception {
byte[] data = bang();
save(data, "test.wav");
}
I hope it helps.

Java - reading, manipulating and writing WAV files

In a Java program, what is the best way to read an audio file (WAV file) to an array of numbers (float[], short[], ...), and to write a WAV file from an array of numbers?
I read WAV files via an AudioInputStream. The following snippet from the Java Sound Tutorials works well.
int totalFramesRead = 0;
File fileIn = new File(somePathName);
// somePathName is a pre-existing string whose value was
// based on a user selection.
try {
AudioInputStream audioInputStream =
AudioSystem.getAudioInputStream(fileIn);
int bytesPerFrame =
audioInputStream.getFormat().getFrameSize();
if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
// some audio formats may have unspecified frame size
// in that case we may read any amount of bytes
bytesPerFrame = 1;
}
// Set an arbitrary buffer size of 1024 frames.
int numBytes = 1024 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
try {
int numBytesRead = 0;
int numFramesRead = 0;
// Try to read numBytes bytes from the file.
while ((numBytesRead =
audioInputStream.read(audioBytes)) != -1) {
// Calculate the number of frames actually read.
numFramesRead = numBytesRead / bytesPerFrame;
totalFramesRead += numFramesRead;
// Here, do something useful with the audio data that's
// now in the audioBytes array...
}
} catch (Exception ex) {
// Handle the error...
}
} catch (Exception e) {
// Handle the error...
}
To write a WAV, I found that quite tricky. On the surface it seems like a circular problem, the command that writes relies on an AudioInputStream as a parameter.
But how do you write bytes to an AudioInputStream? Shouldn't there be an AudioOutputStream?
What I found was that one can define an object that has access to the raw audio byte data to implement TargetDataLine.
This requires a lot of methods be implemented, but most can stay in dummy form as they are not required for writing data to a file. The key method to implement is read(byte[] buffer, int bufferoffset, int numberofbytestoread).
As this method will probably be called multiple times, there should also be an instance variable that indicates how far through the data one has progressed, and update that as part of the above read method.
When you have implemented this method, then your object can be used in to create a new AudioInputStream which in turn can be used with:
AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination)
As a reminder, an AudioInputStream can be created with a TargetDataLine as a source.
As to the direct manipulating the data, I have had good success acting on the data in the buffer in the innermost loop of the snippet example above, audioBytes.
While you are in that inner loop, you can convert the bytes to integers or floats and multiply a volume value (ranging from 0.0 to 1.0) and then convert them back to little endian bytes.
I believe since you have access to a series of samples in that buffer you can also engage various forms of DSP filtering algorithms at that stage. In my experience I have found that it is better to do volume changes directly on data in this buffer because then you can make the smallest possible increment: one delta per sample, minimizing the chance of clicks due to volume-induced discontinuities.
I find the "control lines" for volume provided by Java tend to situations where the jumps in volume will cause clicks, and I believe this is because the deltas are only implemented at the granularity of a single buffer read (often in the range of one change per 1024 samples) rather than dividing the change into smaller pieces and adding them one per sample. But I'm not privy to how the Volume Controls were implemented, so please take that conjecture with a grain of salt.
All and all, Java.Sound has been a real headache to figure out. I fault the Tutorial for not including an explicit example of writing a file directly from bytes. I fault the Tutorial for burying the best example of Play a File coding in the "How to Convert..." section. However, there's a LOT of valuable FREE info in that tutorial.
EDIT: 12/13/17
I've since used the following code to write audio from a PCM file in my own projects. Instead of implementing TargetDataLine one can extend InputStream and use that as a parameter to the AudioSystem.write method.
public class StereoPcmInputStream extends InputStream
{
private float[] dataFrames;
private int framesCounter;
private int cursor;
private int[] pcmOut = new int[2];
private int[] frameBytes = new int[4];
private int idx;
private int framesToRead;
public void setDataFrames(float[] dataFrames)
{
this.dataFrames = dataFrames;
framesToRead = dataFrames.length / 2;
}
#Override
public int read() throws IOException
{
while(available() > 0)
{
idx &= 3;
if (idx == 0) // set up next frame's worth of data
{
framesCounter++; // count elapsing frames
// scale to 16 bits
pcmOut[0] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
pcmOut[1] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
// output as unsigned bytes, in range [0..255]
frameBytes[0] = (char)pcmOut[0];
frameBytes[1] = (char)(pcmOut[0] >> 8);
frameBytes[2] = (char)pcmOut[1];
frameBytes[3] = (char)(pcmOut[1] >> 8);
}
return frameBytes[idx++];
}
return -1;
}
#Override
public int available()
{
// NOTE: not concurrency safe.
// 1st half of sum: there are 4 reads available per frame to be read
// 2nd half of sum: the # of bytes of the current frame that remain to be read
return 4 * ((framesToRead - 1) - framesCounter)
+ (4 - (idx % 4));
}
#Override
public void reset()
{
cursor = 0;
framesCounter = 0;
idx = 0;
}
#Override
public void close()
{
System.out.println(
"StereoPcmInputStream stopped after reading frames:"
+ framesCounter);
}
}
The source data to be exported here is in the form of stereo floats ranging from -1 to 1. The format of the resulting stream is 16-bit, stereo, little-endian.
I omitted skip and markSupported methods for my particular application. But it shouldn't be difficult to add them if they are needed.
This is the source code to write directly to a wav file.
You just need to know the mathematics and sound engineering to produce the sound you want.
In this example the equation calculates a binaural beat.
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
public class Program {
public static void main(String[] args) throws IOException {
final double sampleRate = 44100.0;
final double frequency = 440;
final double frequency2 = 90;
final double amplitude = 1.0;
final double seconds = 2.0;
final double twoPiF = 2 * Math.PI * frequency;
final double piF = Math.PI * frequency2;
float[] buffer = new float[(int)(seconds * sampleRate)];
for (int sample = 0; sample < buffer.length; sample++) {
double time = sample / sampleRate;
buffer[sample] = (float)(amplitude * Math.cos(piF * time) * Math.sin(twoPiF * time));
}
final byte[] byteBuffer = new byte[buffer.length * 2];
int bufferIndex = 0;
for (int i = 0; i < byteBuffer.length; i++) {
final int x = (int)(buffer[bufferIndex++] * 32767.0);
byteBuffer[i++] = (byte)x;
byteBuffer[i] = (byte)(x >>> 8);
}
File out = new File("out10.wav");
final boolean bigEndian = false;
final boolean signed = true;
final int bits = 16;
final int channels = 1;
AudioFormat format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian);
ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer);
AudioInputStream audioInputStream = new AudioInputStream(bais, format, buffer.length);
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
audioInputStream.close();
}
}
Some more detail on what you'd like to achieve would be helpful. If raw WAV data is okay for you, simply use a FileInputStream and probably a Scanner to turn it into numbers. But let me try to give you some meaningful sample code to get you started:
There is a class called com.sun.media.sound.WaveFileWriter for this purpose.
InputStream in = ...;
OutputStream out = ...;
AudioInputStream in = AudioSystem.getAudioInputStream(in);
WaveFileWriter writer = new WaveFileWriter();
writer.write(in, AudioFileFormat.Type.WAVE, outStream);
You could implement your own AudioInputStream that does whatever voodoo to turn your number arrays into audio data.
writer.write(new VoodooAudioInputStream(numbers), AudioFileFormat.Type.WAVE, outStream);
As #stacker mentioned, you should get yourself familiar with the API of course.
The javax.sound.sample package is not suitable for processing WAV files if you need to have access to the actual sample values. The package lets you change volume, sample rate, etc., but if you want other effects (say, adding an echo), you are on your own. (The Java tutorial hints that it should be possible to process the sample values directly, but the tech writer overpromised.)
This site has a simple class for processing WAV files: http://www.labbookpages.co.uk/audio/javaWavFiles.html
WAV File Specification
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
There is an API for your purpose
http://code.google.com/p/musicg/
First of all, you may need to know the headers and data positions of a WAVE structure, you can find the spec here.
Be aware that the data are little endian.
There's an API which may helps you to achieve your goal.
Wave files are supported by the javax.sound.sample package
Since isn't a trivial API you should read an article / tutorial which introduces the API like
Java Sound, An Introduction
If anyone still can find it required, there is an audio framework I'm working on that aimed to solve that and similar issues. Though it's on Kotlin. You can find it on GitHub: https://github.com/WaveBeans/wavebeans
It would look like this:
wave("file:///path/to/file.wav")
.map { it.asInt() } // here it as Sample type, need to convert it to desired type
.asSequence(44100.0f) // framework processes everything as sequence/stream
.toList() // read fully
.toTypedArray() // convert to array
And it's not dependent on Java Audio.
I use FileInputStream with some magic:
byte[] byteInput = new byte[(int)file.length() - 44];
short[] input = new short[(int)(byteInput.length / 2f)];
try{
FileInputStream fis = new FileInputStream(file);
fis.read(byteInput, 44, byteInput.length - 45);
ByteBuffer.wrap(byteInput).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(input);
}catch(Exception e ){
e.printStackTrace();
}
Your sample values are in short[] input!

Categories