3

I am a newbie in Java Sounds. I want to play 2 different frequencies alternatively for 1 second each in a loop for some specified time. Like, if I have 2 frequencies 440hz and 16000hz and the time period is 10 seconds then for every 'even' second 440hz gets played and for every 'odd' second 16000hz, i.e. 5 seconds each alternatively.

I have learned a few things through some examples and I have also made a program that runs for a single user specified frequency for a time also given by the user with the help of those examples.

I will really appreciate if someone can help me out on this. Thanks.

I am also attaching that single frequency code for reference.

  import java.nio.ByteBuffer;
  import java.util.Scanner;
  import javax.sound.sampled.*;

  public class Audio {

   public static void main(String[] args) throws InterruptedException, LineUnavailableException {
    final int SAMPLING_RATE = 44100;            // Audio sampling rate
    final int SAMPLE_SIZE = 2;                  // Audio sample size in bytes

    Scanner in = new Scanner(System.in);
    int time = in.nextInt();                      //Time specified by user in seconds
    SourceDataLine line;
    double fFreq = in.nextInt();                         // Frequency of sine wave in hz

    //Position through the sine wave as a percentage (i.e. 0 to 1 is 0 to 2*PI)
    double fCyclePosition = 0;

    //Open up audio output, using 44100hz sampling rate, 16 bit samples, mono, and big 
    // endian byte ordering
    AudioFormat format = new AudioFormat(SAMPLING_RATE, 16, 1, true, true);
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

    if (!AudioSystem.isLineSupported(info)) {
        System.out.println("Line matching " + info + " is not supported.");
        throw new LineUnavailableException();
    }

    line = (SourceDataLine) AudioSystem.getLine(info);
    line.open(format);
    line.start();

    // Make our buffer size match audio system's buffer
    ByteBuffer cBuf = ByteBuffer.allocate(line.getBufferSize());

    int ctSamplesTotal = SAMPLING_RATE * time;         // Output for roughly user specified time in seconds

    //On each pass main loop fills the available free space in the audio buffer
    //Main loop creates audio samples for sine wave, runs until we tell the thread to exit
    //Each sample is spaced 1/SAMPLING_RATE apart in time
    while (ctSamplesTotal > 0) {
        double fCycleInc = fFreq / SAMPLING_RATE;  // Fraction of cycle between samples

        cBuf.clear();                            // Discard samples from previous pass

        // Figure out how many samples we can add
        int ctSamplesThisPass = line.available() / SAMPLE_SIZE;
        for (int i = 0; i < ctSamplesThisPass; i++) {
            cBuf.putShort((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * fCyclePosition)));

            fCyclePosition += fCycleInc;
            if (fCyclePosition > 1) {
                fCyclePosition -= 1;
            }
        }

        //Write sine samples to the line buffer.  If the audio buffer is full, this will 
        // block until there is room (we never write more samples than buffer will hold)
        line.write(cBuf.array(), 0, cBuf.position());
        ctSamplesTotal -= ctSamplesThisPass;     // Update total number of samples written 

        //Wait until the buffer is at least half empty  before we add more
        while (line.getBufferSize() / 2 < line.available()) {
            Thread.sleep(1);
        }
    }

    //Done playing the whole waveform, now wait until the queued samples finish 
    //playing, then clean up and exit
    line.drain();
    line.close();
}

}

Vibhav Chaddha
  • 431
  • 7
  • 15
  • *"I will really appreciate if someone can help me out on this."* I will really appreciate if you ask a question. BTW 1) I think sound in the MHz range would be inaudible to humans, and not be able to be handled by any sound format Java supports. 2) See [Detection/fix for the hanging close bracket of a code block](http://meta.stackexchange.com/q/251795/155831) for a problem I could no longer be bothered fixing. – Andrew Thompson Nov 14 '16 at 13:57
  • @AndrewThompson Sorry for the mix-up. Actually, yesterday I was also looking for a RAM for my laptop, a 1600Mhz one. So that was the reason for such a silly mistake. – Vibhav Chaddha Nov 15 '16 at 05:52

2 Answers2

2

Your best bet is probably creating Clips as shown in the sample code below. That said, the MHz range is typically not audible—looks like you have a typo in your question. If it's no typo, you will run into issues with Mr. Nyquist.

Another hint: Nobody uses Hungarian Notation in Java.

import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;

public class AlternatingTones {

    public static void main(final String[] args) throws LineUnavailableException, InterruptedException {

        final Clip clip0 = createOneSecondClip(440f);
        final Clip clip1 = createOneSecondClip(16000f);

        clip0.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip1.setFramePosition(0);
                clip1.start();
            }
        });
        clip1.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip0.setFramePosition(0);
                clip0.start();
            }
        });
        clip0.start();

        // prevent JVM from exiting
        Thread.sleep(10000000);
    }

    private static Clip createOneSecondClip(final float frequency) throws LineUnavailableException {
        final Clip clip = AudioSystem.getClip();
        final AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100f, 16, 1, 2, 44100, true);
        final ByteBuffer buffer = ByteBuffer.allocate(44100 * format.getFrameSize());
        final ShortBuffer shortBuffer = buffer.asShortBuffer();
        final float cycleInc = frequency / format.getFrameRate();
        float cyclePosition = 0f;
        while (shortBuffer.hasRemaining()) {
            shortBuffer.put((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * cyclePosition)));
            cyclePosition += cycleInc;
            if (cyclePosition > 1) {
                cyclePosition -= 1;
            }
        }
        clip.open(format, buffer.array(), 0, buffer.capacity());
        return clip;
    }
}    
Hendrik
  • 5,085
  • 24
  • 56
  • I disagree with the overstatement that "nobody uses Hungarian Notation in Java". I recall reading articles on how this can be helpful, and in fact, use it myself as a way to conceptually show that GUI elements are related (e.g., label, slider and control variable). A simple search shows that many such articles exist. For example: http://www.developer.com/java/ent/article.php/615891/Applying-Hungarian-Notation-to-Java-programs-Part-1.htm Also, labeling variables clip1, clip2, is a rather weak alternative, and rather confused when one is also contending with a "clip" variable. – Phil Freihofner Nov 15 '16 at 04:51
  • 1
    @Hendrik Thank a lot. Your answer was really helpful. – Vibhav Chaddha Nov 15 '16 at 05:55
  • @Phil Hungarian or not—like all variable naming is a question of taste, of course. That said, Java is strongly typed. The compiler enforces type rules and any modern API color-codes the difference between local vars and members. Anyhow. This is not something one should fight about. There are different opinions. I guess the are all valid. I suggest reading the code of some popular open source projects to come up with an opinion that's aligned with the mainstream, because it helps in everyday work to use the same conventions as most other people. – Hendrik Nov 15 '16 at 09:43
  • @hendrik Thank you for helping me out. I really appreciate it. And if I need some further assistance, I will surely ask you. Thanks once again. – Vibhav Chaddha Nov 15 '16 at 11:58
  • @hendrik If I want user to specify the time in between each frequency in your above code then how should I do that? For example: if I run the above code it switches between frequencies after each second. What if I want user to specify the time, like 2 seconds or 3 seconds etc, in which the frequencies switch. – Vibhav Chaddha Nov 21 '16 at 07:33
  • Have you tried calling `loop(numberOfSeconds)` instead of `start()`? Does that work? Haven't tried it myself. – Hendrik Nov 21 '16 at 08:38
  • @hendrik I had tried it earlier but was doing some silly mistake and that is why it was not working. But now it works. Thanks a lot. One more thing, I want to write it into a .wav format. Can you also help me out on that. – Vibhav Chaddha Nov 21 '16 at 10:47
  • You might want to look at http://stackoverflow.com/questions/3297749/java-reading-manipulating-and-writing-wav-files or something comparable. – Hendrik Nov 21 '16 at 11:22
  • @hendrik Actually I have already seen the link you have mentioned above and a few others too, but I am finding it a bit tricky to understand. – Vibhav Chaddha Nov 21 '16 at 12:12
  • Then you should ask a new Stackoverflow question asking the stuff you don't get. – Hendrik Nov 22 '16 at 08:43
  • @hendrik http://stackoverflow.com/questions/40722139/how-to-write-a-generated-audio-into-a-wav-format-in-java – Vibhav Chaddha Nov 22 '16 at 09:28
2

The method I would use would be to count frames while outputting to a SourceDataLine. When you have written one second's worth of frames, switch frequencies. This will give much better timing accuracy than attempting to fiddle with Clips.

I'm unclear if the code you are showing is something you wrote or copied-and-pasted. If you have a question about how it doesn't work, I'm happy to help if you show what you tried and what errors or exceptions were generated.

When outputting to a SourceDataLine, there will have to be a step where you convert the short value (-32768..+32767) to two bytes as per the 16-bit encoding specified in the audio format you have. I don't see where this is being done in your code. [EDIT: can see where the putShort() method does this, though it only works for BigEndian, not the more common LittleEndian.]

Have you looked over the Java Tutorial Sound Trail?

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • Actually, that code is the single frequency one. Although you are right, I have not totally understood the code. There are a few things that I have never used in my life before this. So if you can take the pain to explain me the whole thing, then that would be of a great help. Thanks. – Vibhav Chaddha Nov 15 '16 at 05:42
  • 1
    What I recommend for you is to give the "Sound Trail" I linked a good try. It is okay to skip the parts on MIDI and Service Provider Interfaces for now. Do your best reading forward to the 3rd tutorial on Playing Back Audio. It is tough, but go slow and make test programs along the way. With this vocabulary of concepts as a reference, it will be easier to ask more specific questions and for others to answer them. Learning audio is perhaps as complex as learning advanced graphics in its own way, and takes some commitment and foundational knowledge. – Phil Freihofner Nov 15 '16 at 06:18
  • "This will give much better timing accuracy than attempting to fiddle with Clips." <- That's probably true and worth considering. – Hendrik Nov 15 '16 at 09:45
  • @PhilFreihofner Thank you for helping me out. I really appreciate it. And if I need some further assistance, I will surely ask you. Thanks once again. – Vibhav Chaddha Nov 15 '16 at 11:57