2

I am using Sonic Algorithm developed by waywardgeek to speed and slow down my audio files. It's working fine. But what I need is I need to save that playing audio to wav file. I have tried to save using ByteArrayInputStream and TargetDataLink. But I am failed to do that. So anybody can help me to save that playing audio to wav file? And also I am new to Java Audio.

Here is the code,

Main.java

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Main {

    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;

        sonic.setSpeed(speed);
        sonic.setPitch(pitch);
        sonic.setRate(rate);
        sonic.setVolume(volume);
        sonic.setChordPitch(emulateChordPitch);
        sonic.setQuality(quality);
        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);
                }
            } while (numWritten > 0);
        } while (numRead > 0);
    }

    public static void main(String[] argv) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        float speed = 1.0f;
        float pitch = 1.0f;
        float rate = 1.5f;
        float volume = 1.0f;
        boolean emulateChordPitch = false;
        int quality = 0;

        AudioInputStream stream = AudioSystem.getAudioInputStream(new File("C:\\audio1.wav"));
        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();
    }
}

I have tried with the following code and it saved to wav file but when playing it has one-word audio. And also with the TargetDataLink, nothing works.

byteArrayOutputStream = new ByteArrayInputStream(outBuffer);
audioStreams = new AudioInputStream(byteArrayOutputStream, line.getFormat(), bufferSize);
AudioSystem.write(audioStreams, AudioFileFormat.Type.WAVE, new File("C:\\audio2.wav"));

Sonic.java

Can't post the full code here because of the limited characters allowed. Code is here.

PS: The code is developed by Bill Cox. Thank you Bill Cox for this great code.

piet.t
  • 11,718
  • 21
  • 43
  • 52
Hasitha Jayawardana
  • 2,326
  • 4
  • 18
  • 36
  • @AndrewThompson Sorry, I didn't understand. What should I change in this? – Hasitha Jayawardana Jun 10 '19 at 03:34
  • When I go to compile that, I get two errors. 1) `Sonic sonic = new Sonic(sampleRate, numChannels);` What is a `Sonic`? 2) `AudioInputStream stream = AudioSystem.getAudioInputStream(new File("C:\\audio1.wav));` .. 'unclosed literal string'. - While the 2nd is easily fixed this end (it's even easier to fix your end) the lack of a `Sonic` class means other people cannot run it, therefore it is not reproducible for anyone else. – Andrew Thompson Jun 10 '19 at 04:48
  • @AndrewThompson I have updated the Question and posted the code URL. Thank you for your effort to help me. – Hasitha Jayawardana Jun 10 '19 at 05:02
  • How is the `Sonic` code even relevant to *"How to Save Playing Audio to WAV Format in Java"*? Can you save audio sans the `Sonic` code? – Andrew Thompson Jun 10 '19 at 05:06
  • No, the sonic is for speeding up the audio. I have given the audio file and give parameters and it will speed the audio. Here it is only playing not saving. When I run `Main.java` class it plays the audio file speedily. I need that to be saved to a `.wav` file. (That speedy audio) So I don't need to run the program again and again because I am having the generated audio file. – Hasitha Jayawardana Jun 10 '19 at 05:09
  • If you **can** save audio sans the sonic code, it suggests the problem is with the sonic code. Any which way, you won't get further help from me, as I'm not stuffing around with building non-Java code. – Andrew Thompson Jun 10 '19 at 05:37
  • @AndrewThompson It is not saving and I also don't see there is any code to save it is only speeding up the given audio. I need to save that playing audio. I tried with `ByteArrayInputStream` but didn't work well. – Hasitha Jayawardana Jun 10 '19 at 05:41

2 Answers2

1

I have tried and now I came up with a solution using ByteArrayInputStream and ByteArrayOutputStream. First I write outBuffer using ByteArrayOutputStream as same as SourceDataLine line.write(). And then convert it to byteArray and saved it using AudioInputStream. This tutorial from java2s helped me to do this. It's working but I don't know if this is the best solution. So any suggestions are welcome. Thank you.

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Main {

    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);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int bufferSize = line.getBufferSize();
        byte inBuffer[] = new byte[bufferSize];
        byte outBuffer[] = new byte[bufferSize];
        int numRead, numWritten;

        sonic.setSpeed(speed);
        sonic.setPitch(pitch);
        sonic.setRate(rate);
        sonic.setVolume(volume);
        sonic.setChordPitch(emulateChordPitch);
        sonic.setQuality(quality);
        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);
                    byteArrayOutputStream.write(outBuffer, 0, numWritten);
                }
            } while (numWritten > 0);
        } while (numRead > 0);
        byte audioBytes[] = byteArrayOutputStream.toByteArray();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(audioBytes);
        AudioFormat format = line.getFormat();

        AudioInputStream audioInputStream = new AudioInputStream(byteArrayInputStream, format, audioBytes.length / format.getFrameSize());
        AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, new File("C:\\audio2.wav"));

    }

    public static void main(String[] argv) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        float speed = 1.0f;
        float pitch = 1.0f;
        float rate = 1.5f;
        float volume = 1.0f;
        boolean emulateChordPitch = false;
        int quality = 0;

        AudioInputStream stream = AudioSystem.getAudioInputStream(new File("C:\\audio1.wav"));
        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();
    }
}
Hasitha Jayawardana
  • 2,326
  • 4
  • 18
  • 36
  • It seems you are processing the whole audio stream before you start to forward it to the AudioSystem and the file. Consider sending the data inside the loop as bytes become available; this avoids delays when the input is large, or unbounded as when retrieving the audio from an audio input instead of a file, and spares you from having to keep the whole audio output in memory at the same time. – JimmyB Jun 11 '19 at 11:12
  • An even more generic solution would be to implement a custom `AudioOutputStream` that forwards data to an actual `AudioOutputStream` and writes the data to a file at the same time. – JimmyB Jun 11 '19 at 11:27
  • @JimmyB I am sorry. But I didn't understand that. Can you give me a code snippet showing that? I really appreciate that. – Hasitha Jayawardana Jun 11 '19 at 15:56
1

I'm pretty sure that both questions, how to change the playback rate, and how to save wavs have been covered in past questions. For example Java - Adjust playback speed of a WAV file See the answer on linear interpolation. This is not that hard to implement. The chosen answer is fine if you only wish to double the speed, but the interpolation algorithm allows for many other speeds to be designated.

As for saving to wav, I recommend starting with the Oracle Sound tutorial. https://docs.oracle.com/javase/tutorial/sound/converters.html

As far as doing this as a stream rather than holding the complete files in memory, it takes a bit of management, but this is a pretty common task and should be in any programmer's bag of tricks. While inputting and processing data, you save it to an output buffer array, and track the progress on the output buffer. Only write to the output stream when this buffer is ready.

Overview of steps: read bytes, convert to PCM, do the pitch shift, if a full buffer is ready for output: convert PCM back to bytes, write bytes.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • Thank you very much for your answer. What about my solution? Did you check that? I am writing `ByteArrayInputStream ` to the `AudioInputStream ` after the `while` loop. – Hasitha Jayawardana Jun 12 '19 at 05:44
  • I took a look, but my coding reading is not the greatest. If it works, I won't knock it! It shouldn't be a big leap to go from that to a solution that streams, but if you have plenty RAM and the time to process the entire file before running it, then the simple answer (doing the entire file in each stage) should be perfectly fine afaik. – Phil Freihofner Jun 12 '19 at 17:54
  • Thank you very much. I am currently using that code. – Hasitha Jayawardana Jul 12 '19 at 09:03