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.
Related
public class captureAudio{
IMediaWriter writerAudio=null;
long currentAudioTime;
void startAudio() throws Exception {
TargetDataLine line;
//gets a TargetDataLine in which gets data from the Microphone
line= getMic();
//creates an array that is half the data buffer so that we don't have to worry about sample discards
final byte[] audioBytes=new byte[line.getBufferSize()/2];
final String outputFilename = "c:/Users/NICK/Desktop/SaveJPEG/"+"saved"+".aac";
//creates a writer in which it's output is to outputFilename
writerAudio = ToolFactory.makeWriter(outputFilename);
//starts I/O process in which starts capturing
line.start();
//gets current system time in nanoseconds
currentAudioTime = System.nanoTime();
//sets audioRunning to true
setstopper(true);
writerAudio.addAudioStream(0, 0,ICodec.ID.AV_CODEC_ID_AAC, 1, 44100);
//create new thread in which audio will be captured and encoded
Thread thread = new Thread() {
#Override
public void run() {
while(audioRunning()) {
short[] audioSamples;
int numSamplesRead=0;
int numBytesRead=0;
//get the number of bytes read
numBytesRead=line.read(audioBytes,0,audioBytes.length);
numSamplesRead=numBytesRead/2;
//create an array with a size of numSamplesRead. It is smaller than numBytesRead since we
//have 16 bit of data in each index of this array
audioSamples = new short[numSamplesRead];
/*Since the IMediaWriter.encodeAudio method takes in a short array,
We need to convert byte array to a short*/
//checks to see if the format is bigEndian or littleEndian which is indeed littleEndian by the format
if(line.getFormat().isBigEndian()) {
for (int i = 0; i < numSamplesRead; i++) {
audioSamples[i] = (short)((audioBytes[2*i] << 8) | audioBytes[2*i + 1]);
}
}
else {
/*cast to short, get 2nd element create 8 zeros to the right(making it 16 bit) and include the first element (still 8 bit)*/
for (int i = 0; i < numSamplesRead; i++) {
audioSamples[i] = (short)((audioBytes[2*i + 1] << 8) | audioBytes[2*i]);
}
}
//Stream index is 0, pass in short[] audioSamples, get sample timestamp, units of timestamp
writerAudio.encodeAudio(0, audioSamples,System.nanoTime()-currentAudioTime,TimeUnit.NANOSECONDS);
}
}
};
//starts the thread above
thread.start();
}
TargetDataLine getMic() throws Exception {
//Audioformat object that takes a sample rate,bit size per sample,channel, and bool isBigEndian
AudioFormat format = new AudioFormat(44100F,16,1,true,false);
TargetDataLine line =null;
DataLine.Info info = new DataLine.Info(TargetDataLine.class,format); // format is an AudioFormat object
if (!AudioSystem.isLineSupported(info)) {
throw new Exception();
}
void stop() throws Exception {
TargetDataLine mic = getMic();
setstopper(false);
mic.stop();
mic.close();
writerAudio.flush();
writerAudio.close();
}
}
Summary
The main idea is that I am trying to retrieve data from the microphone through a TargetDataLine and then am creating a new thread to run the encoding process of the audio along with the main thread which my videoCapture class uses to encode the video. Inside of the new thread, I am turning the audio samples array (originally a byte array) into a short array to use in the IMediaWriter.encodeAudio(...) method described above which the writer will successfully create a .acc audio file in my output file path.
Question
When I run the code I do get the desired audio file, but the audio quality is terrible. When I play back the audio, it is real staticky and choppy. So how exactly can I make the quality of the audio better?
I have no idea how to do this. I have read the answers to several similar questions and some websites that probably had the answer somewhere, but either I could not understand them or they were not what I am trying to do. It is also possible that some did have the answer, but I could not focus well enough to interpret it. I want a method that converts the data from a WAV file signed 16-bit raw audio data and puts this into a short[]. I would prefer short minimalistic easy to understand answers because I would have less difficulty focusing on those.
Edit: Some have said this might be a duplicate of stackoverflow.com/questions/5210147/reading-wav-file-in-java. I do not understand that question or its answers well enough to even say whether it is different or why or how to change my question so it is not confused for that one.
Another edit: I have attempted using Phil Freihofner's answer, but when testing this by attempting to pay back the audio, I just heard a lot of clicks. I am not sure if I implemented it correctly. Here is the method that reads the file:
static void loadAudioDataTest(String filepath){
int totalFramesRead = 0;
File fileIn = new File(filepath);
try {
AudioInputStream audioInputStream =
AudioSystem.getAudioInputStream(fileIn);
int bytesPerFrame =
audioInputStream.getFormat().getFrameSize();
if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
bytesPerFrame = 1;
}
int numBytes = 1024 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
audioArray=new short[numBytes/2];
try{
int numBytesRead = 0;
int numFramesRead = 0;
while ((numBytesRead =
audioInputStream.read(audioBytes)) != -1) {
numFramesRead = numBytesRead / bytesPerFrame;
totalFramesRead += numFramesRead;
}for(int a=0;a<audioArray.length;a++){
audioArray[acc]=(short)((audioBytes[a*2]&0xff)|(audioBytes[acc*2+1]<<8));
}
} catch (Exception ex) {
// Handle the error...
}
} catch (Exception e) {
// Handle the error...
}
}
This bit plays the sound and is inside an actionPerformed(ActionEvent) void that is repeatedly activated by a timer, in case the issue is there
byte[]buf=new byte[2];
AudioFormat af=new AudioFormat(44100,16,1,true,false);
SourceDataLine sdl;
try{
sdl=AudioSystem.getSourceDataLine(af);
sdl.open();
sdl.start();
buf[1]=(byte) (audioArray[t%audioArray.length]&0xFF);
buf[0]=(byte) (audioArray[t%audioArray.length]>>8);
sdl.write(buf,0,2);
sdl.drain();
sdl.stop();
}catch(LineUnavailableException e1){
e1.printStackTrace();
}t++;
The current core java class commonly used for loading data into a byte array is AudioInputStream (javax.sound.sampled.AudioInputStream). An example of its use, with explanation, can be found in the Oracle tutorial Using Files and Format Converters. The sample code is in the section titled "Reading Sound Files". Note the point in the innermost while loop with the following line: // Here, do something useful with the audio data. At that point, you would load the data into your array.
Taking two bytes and converting them to a short has been answered several times but I don't have the links handy. It's easier to just post some code I have used.
audioArray[i] = ( buffer[bufferIdx] & 0xff )
| ( buffer[bufferIdx + 1] << 8 ) ;
... where audioArray could be a short[]. (In my code I use float[] and do another step to scale the values to range from -1 to 1.)
This is a slightly modified snipped from the library AudioCue on github, quoting from lines 391-393.
I have been trying to manually read a wav file in Java and read an array of bytes then write to an audio buffer for playback. I am receiving playback but it is heavily distorted. Java sound supports 16 bit sample rates but not 24-bit.
I went in to Logic 9 and exported a 24-bit audio file in to 16-bit and then used with my program. Originally, the 24-bit samples would produces white noise. Now I can hear my sample but very distorted and sounds like it has been bit crushed.
Can anyone help me to get a clean signal?
I am very new to audio programming but I am currently working on a basic Digital Audio Workstation.
import javax.sound.sampled.*;
import javax.sound.sampled.DataLine.Info;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.*;
public class AudioData {
private String filepath;
private String filepath1;
private File file;
private byte [] fileContent;
private Mixer mixer;
private Mixer.Info[] mixInfos;
private AudioInputStream input;
private ByteArrayOutputStream byteoutput;
public static void main (String [] args) {
AudioData audiodata = new AudioData();
}
public AudioData () {
filepath = "/Users/ivaannagen/Documents/Samples/Engineering Samples - Obscure Techno Vol 3 (WAV)/ES_OT3_Kit03_Gmin_130bpm/ES_OT3_Kit03_FX_Fast_Snare_Riser_Gmin_130bpm.wav";
filepath1 = "/Users/ivaannagen/Documents/Samples/dawsampletest.wav";
file = new File (filepath1);
readAudio();
}
public void readAudio () {
mixInfos = AudioSystem.getMixerInfo();
mixer = AudioSystem.getMixer(mixInfos[0]);
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
// set up an audio format.
try {
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); // creates data line with class type and audio format.
SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
System.out.println("Size of data line buffer: " + source.getBufferSize());
fileContent = new byte [source.getBufferSize() / 50];
byteoutput = new ByteArrayOutputStream();
input = AudioSystem.getAudioInputStream(file);
int readBytes = 0;
while ((readBytes = input.read(fileContent, 0, fileContent.length)) != -1) {
byteoutput.write(fileContent, 0, readBytes);
}
System.out.println("Size of audio buffer: " + fileContent.length);
//byteoutput.write(0);
// byteoutput.write(0);
System.out.println("Size of audio buffer: " + byteoutput.size());
source.open(format, source.getBufferSize()); // line must be open to be recognised by the mixer.
Line[] lines = mixer.getSourceLines();
System.out.println("mixer lines: " + lines.length);
// for(byte bytes: fileContent) {
// System.out.println(bytes);
// }
Thread playback = new Thread () {
public void run () {
// System.out.println((byteoutput.size() +2) % 4);
source.start(); // play (buffer originally empty)
source.write(byteoutput.toByteArray(), 0, byteoutput.size()); // write input bytes to output buffer
} // end run (to do).
}; // end thread action
playback.start(); // start thread
}
catch (LineUnavailableException lue) {
System.out.println(lue.getMessage());
}
catch (FileNotFoundException fnfe) {
System.out.println(fnfe.getMessage());
}
catch(IOException ioe) {
System.out.println(ioe.getMessage());
}
catch(UnsupportedAudioFileException uafe) {
System.out.println(uafe.getMessage());
}
}
}
Whether or not you can load and play a 24-bit file is system dependent, afaik.
I use Audacity for conversions. You should be able import your file into Audacity and export it as 16-bit, stereo, little-endian, 44100 fps, and then load that export with Java's AudioInputStream.
What you hear when playing from Audacity or from Java should be pretty much identical (adjusting for volume). If not, the most likely reason probably pertains to a mistake or overlook in the code, which is very easy to do.
The use of a ByteOutputStream in your code is superfluous. Read from the AudioInputStream into a fixed-size byte array (size being the buffer length, I recommend trying 8 or 16 * 1024 bytes as a first try) and then use the SourceDataLine write method to ship that array.
Following is code that works on my system for loading a playing a "CD Quality" wav called "a3.wav" that I have that is in the same directory as the Java class. You should be able to swap in your own 44100, 16-bit, stereo, little-endian wav file.
I've commented out an attempt to load and play a 24-bit wav file called "spoken8000_24.wav". That attempt gave me an IllegalArgumentException: No line matching interface SourceDataLine supporting format PCM_SIGNED 8000.0 Hz, 24 bit, stereo, 6 bytes/frame, little-endian is supported.
I have to admit, I'm unclear if my system doesn't provide the needed line or if I might have coded the format incorrectly! My OS can certainly play the file. So I'm thinking there is a distinction between what an OS can do and what a "Mixer" on a given system provides to Java.
As a get-around, I just always convert everything to "CD Quality" format, as that seems to be the most widely supported.
public class TriggerSound_SDL extends JFrame
{
public TriggerSound_SDL()
{
JButton button = new JButton("Play Sound");
button.addActionListener(e -> new Thread(() -> playBuzzer()).start());
getContentPane().add(button);
}
private void playBuzzer()
{
try
{
URL url;
url = getClass().getResource("a3.wav");
// url = getClass().getResource("spoken8000_24.wav");
AudioInputStream ais = AudioSystem.getAudioInputStream(url);
System.out.println(ais.getFormat());
AudioFormat audioFmt;
// "CD Quality" 44100 fps, 16-bit, stereo, little endian
audioFmt = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
44100, 16, 2, 4, 44100, false);
// 8000 fps, 32-bit, stereo
// audioFmt = new AudioFormat(
// AudioFormat.Encoding.PCM_SIGNED,
// 8000, 24, 2, 6, 8000, false);
Info info = new DataLine.Info(SourceDataLine.class,
audioFmt);
SourceDataLine sdl = (SourceDataLine)AudioSystem.getLine(info);
int bufferSize = 16 * 1024;
byte[] buffer = new byte[bufferSize];
sdl.open(audioFmt, bufferSize);
sdl.start();
int numBytesRead = 0;
while((numBytesRead = ais.read(buffer)) != -1)
{
sdl.write(buffer, 0, numBytesRead);
}
}
catch (IOException | UnsupportedAudioFileException
| LineUnavailableException ex)
{
ex.printStackTrace();
}
}
private static void createAndShowGUI()
{
JFrame frame = new TriggerSound_SDL();
frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
This code, with some small tweaks should let you at least test the different formats.
EDIT:
I'm seeing where your goal is to make a DAW!
In that case, you will want to convert the bytes to PCM data. Can I suggest you borrow some code from AudioCue? I basically wrote it to be a Clip-substitute, and part of that involved making the PCM data available for manipulation. Some techniques for mixing, playing back at different frequencies, multithreading can be found in it.
Thanks for all the advice guys. I will be getting rid of the ByteOutputStream and just use the AudioInputStream, I now understand what I was doing was unnecessary!! Thanks for the advice all! I have indeed tried using AudioCue but it is not low level enough for what I want to do!
One more thing guys. Previously, I created a multitrack media player which is using the Clip class. To play all the audio tracks together, I was looping through a list of Clips and playing them. However, this means that all tracks may be playing a tiny amount after each other due to the processing of the loop. Also, Clip class created a new thread per audio. I do not wants 100 threads running on 100 tracks, I want one thread for my audio output. I am still trying to work out how to start all tracks at the same time without a loop....(im guessing AudioCue have nailed the concurrent cues).
Does anyone know the best way to play multiple audio tracks in to one output? Do I need to route/bus all my audio tracks in to one output and somehow write all data from audio files in to one output buffer then play this output in a thread?
Thanks!!
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.
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!