16

I was experimenting with producing beep using Java. I found this answer on SO.

I am using the code from that answer to produce beeps. The code is:

import javax.sound.sampled.*;
public class Sound
{
    public static float SAMPLE_RATE = 8000f;
    public static void tone(int hz, int msecs) 
    throws LineUnavailableException 
    {
        tone(hz, msecs, 1.0);
    }

    public static void tone(int hz, int msecs, double vol)
    throws LineUnavailableException 
    {
        byte[] buf = new byte[1];
        AudioFormat af = new AudioFormat(SAMPLE_RATE,8,1,true,false);     
        SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
        sdl.open(af);
        sdl.start();
        for (int i=0; i < msecs*8; i++) {
              double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI;
              buf[0] = (byte)(Math.sin(angle) * 127.0 * vol);
              sdl.write(buf,0,1);
        }
        sdl.drain();
        sdl.stop();
        sdl.close();
    }

    public static void main(String[] args) throws Exception {
        Sound.tone(15000,1000); 
    }
}

In the main method, I use Sound.tone(15000,1000); to produce a sound of frequency 15000 Hz to play for 1000 ms

But, I can hear the sound if I change it to :

  • Sound.tone(1,1000);, .
  • Sound.tone(19999,1000);

Scientifically, this is not possible.

  • In the first case, the sound should be infrasonic, and I should not be able to perceive it.
  • In the second case, I should still not be able to hear the sound, because as we age, the hearing ability tends to decrease, and a person of about my age should only be able to hear a sound of approximately 16000 Hz.

Furthermore, I cannot hear:

  • Sound.tone(0,1000); (somewhat as expected)
  • Sound.tone(20000,1000);

So, How can I produce sounds of some specific frequencies?
I searched on the internet, but could not find anything regarding it.

The answers given before this edit explain why it occurs, but do not give the answer I want.

Community
  • 1
  • 1
dryairship
  • 6,022
  • 4
  • 28
  • 54
  • You can still listen the sound but you can't differentiate the frequency with human eye when they are very minute :) – Suresh Atta Jan 05 '16 at 11:56
  • 2
    But I certainly can't hear infrasonic sound, which I apparently can according to this program. – dryairship Jan 05 '16 at 11:57
  • There's also the chance that computers simply aren't manufactured to produce infra-/ultrasound – Arc676 Jan 05 '16 at 11:58
  • 4
    @SureshAtta I suggest using an ear instead of an eye. – Marged Jan 05 '16 at 11:59
  • 1
    @Arc676 I guess you misunderstood my question. If computers aren't manufactured to produce infra-/ultrasound, then how am I able to hear it using this program. I mean to say that I **can** hear a sound if I write `Sound.tone(10,1000);`, which should have been infrasonic. – dryairship Jan 05 '16 at 12:01
  • Yes, so since computer aren't _meant_ to produce such a frequency maybe they just don't. I was suggesting that maybe the frequency _isn't_ always accurate. It just uses the nearest valid frequency. – Arc676 Jan 05 '16 at 12:02
  • It'd be helpful to know what device you are using to play the sound (and the rated frequency). Also you cannot really take a decrease in your hearing for granted if it's only based on your age, there are vast differences between people of the same age when it comes to the variety of frequencies they're able to perceive. Also, take a look at this, maybe it helps: http://electronics.stackexchange.com/questions/156197/can-computer-speakers-emit-ultrasound – Seth Jan 05 '16 at 12:03
  • 2
    It is possible that your hardware can't produce frequencies so low or high and doesn't throw any exception, instead it try to play as much closer as possible to the values you are passing to your hardware. Those values could be human udible. – Davide Lorenzo MARINO Jan 05 '16 at 12:04
  • @Marged lol that's ear. :P – Suresh Atta Jan 05 '16 at 12:04
  • @DavideLorenzoMARINO I can hear `Sound.tone(1,1000);`, and `Sound.tone(19999,1000);`. I cannot hear `Sound.tone(0,1000);`(as expected) and also `Sound.tone(20000,1000);` – dryairship Jan 05 '16 at 12:06
  • So I imagine that your hardware is not fine enough and that the result tones are not right. Sound.tone(0, 1000) is reasonable that is not playing nothing and that you can hear something for Sound.tone(1, 1000), instead what is strange is that you can hear Sound.tone(19999, 1000) and not Sound.tone(20000, 1000). – Davide Lorenzo MARINO Jan 05 '16 at 12:08
  • 1
    @DavideLorenzoMARINO, actually I **shouldn't** be able to hear sounds of frequency below 20 Hz. So even `Sound.tone(1, 1000)` is abnormal. – dryairship Jan 05 '16 at 12:10
  • Has anybody tried it on his computer? – dryairship Jan 05 '16 at 12:11
  • 2
    How about using a microphone and doing a frequency analysis on the recorded sound. If you have a smartphone at hand, there should be an app available in your favorite appstore. – SpaceTrucker Jan 05 '16 at 12:14
  • 1
    @PriydarshiSingh Most audio devices have 20Hz set as their minimum frequency, so I'd guess that the computer accepts the lowest frequency possible if it'd be impossible otherwhise. – Seth Jan 05 '16 at 12:18
  • I'm wondering if you are getting some weirdness due to integer division. The first three terms are all integers and are processed before the doubles. Also, you should just process the invariants one time, outside of the loop, instead of recalculating each iteration. Make a constant = hz / (samplerate * 2 * PI) and multiply that against i. The comment about Nyquist and sampling rate tell why you can't expect to generate sounds above half the sample rate. If you can hear something with a frequency setting below 20, the code is definitely buggy. – Phil Freihofner Jan 06 '16 at 04:27
  • @PriydarshiSingh why did you unaccept? – Jan Jan 06 '16 at 12:19
  • Mainly because that did not actually solve my problem. – dryairship Jan 06 '16 at 12:26

2 Answers2

10

What you are experiencing is a phenomenon known as aliasing. You may have witnessed examples of this in video where a spinning wheel appears to rotate slowly or even rotate in reverse. This is because the wheel has rotated only slightly more or less than a multiple of a complete number of turns per video frame. Likewise if the wheel rotates an exact number of turns per frame it will appear stationary. The reason this is called aliasing is that there is no way to know how many turns it actually rotated.

Audio sampling has the same artifact and is referred to as the Nyquist sampling theorem which basically indicates that you can only represent frequencies up to half of the sampling rate (nyquist frequency). Going beyond this frequency the tones begin to fold back down (e.g. turn backwards).

At your 8kHz sample rate, frequencies from 0Hz to 4kHz will play back fine (a 4 kHz sine wave will have 2 samples per period). Beyond 4kHz the frequencies will begin folding back such that 4001Hz is heard as 3999, 5000Hz as 3000Hz and ultimately 8000Hz as 0Hz (hence the silence). Beyond 8kHz it will once again begin folding up so that 8001Hz is 1Hz and so on.

The key take away is that you need to choose a sample rate that is at least twice the highest frequency you plan to playback.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • 6
    If I stay any longer in this thread, I might become a sound engineer! – hamena314 Jan 05 '16 at 14:39
  • But making the sample rate **40000** still doesn't solve the problem. I can still hear below 20 Hz, and upto 19999 Hz. – dryairship Jan 06 '16 at 12:17
  • Your sine generation is a bit suspect since 8-bit audio is typically unsigned (0-255). You should listen to a tone at 1kHz and compare with https://www.youtube.com/watch?v=3yQcXlvccCI. If yours does not sound the same then you need to change this bit `(byte)(Math.sin(angle) * 127.0 * vol);` to `(byte)((Math.sin(angle) * 127.0 + 127) * vol);` – jaket Jan 06 '16 at 20:02
  • @jaket changing it to `(byte)((Math.sin(angle) * 127.0 + 127) * vol);` doesn't work. – dryairship Jan 27 '16 at 12:17
6

Just rechecked with a guitar tuner in front of my speaker - the method you provided is accurate for low frequencies.

However, if you get too low you might hear overtones or "stuttering" sound due to the speakers not working too great in infrasound (at least I got that)

On high frequencies, your code does not compute too good - with a sample rate of 8000 samples per second you'll quickly create "invalid" tones for higher frequencies - the sine wave oscillation will just accidentally align to your sample rate. So you'll get some high frequencies you can hear (basically because your samples always hit some non zero value) or not hear (all your samples occur at times when the wave goes through null.

Check this loop (with your speaker on):

 public static void main(String[] args) throws Exception {
    for(int freq = 400; freq < 20000; freq *= 2) {
        Sound.tone(freq,500);
    }
 }

You'll hear low-high-low-high at the end for the reasons just described.

Another test:

  Sound.tone(8000,500);   

is absolutely silent, and

  Sound.tone(8001,500);

produces a very funky sound.

Jan
  • 13,738
  • 3
  • 30
  • 55
  • 1
    That depends on what you want to do with this. For alerting users the range 20-3000 Hz is probably sufficient. For higher sample rates though the angle calculation seems invalid as well. would have to think this trough a bit more. – Jan Jan 05 '16 at 12:34
  • How does one get overtones off of a sin wave, Jan? I don't see anything in the calculation that should cause the playback to start/stop (stutter). Even with the integer division, the progression of the angle looks like it will be smooth. – Phil Freihofner Jan 06 '16 at 04:30