3

I am using javax.sound to make sounds, however when you play it they have some sort of noise in background, which even overcomes the sound if you play few notes at once. Here is the code:

public final static double notes[] = new double[] {130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185,
        196, 207.65, 220, 233.08, 246.94, 261.63, 277.18, 293.66,
        311.13, 329.63, 349.23, 369.99, 392, 415.3, 440, 466.16,
        493.88, 523.25, 554.37};



public static void playSound(int note, int type) throws LineUnavailableException {          //type 0 = sin, type 1 = square

    Thread t = new Thread() {
        public void run() {
            try {

                int sound = (int) (notes[note] * 100);


                byte[] buf = new byte[1];
                AudioFormat af = new AudioFormat((float) sound, 8, 1, true,
                        false);
                SourceDataLine sdl;

                sdl = AudioSystem.getSourceDataLine(af);

                sdl = AudioSystem.getSourceDataLine(af);
                sdl.open(af);
                sdl.start();
                int maxi = (int) (1000 * (float) sound / 1000);
                for (int i = 0; i < maxi; i++) {
                    double angle = i / ((float) 44100 / 440) * 2.0
                            * Math.PI;
                    double val = 0;
                    if (type == 0) val = Math.sin(angle)*100;
                    if (type == 1) val = square(angle)*50;

                    buf[0] = (byte) (val * (maxi - i) / maxi);
                    sdl.write(buf, 0, 1);
                }
                sdl.drain();
                sdl.stop();
                sdl.close();
            } catch (LineUnavailableException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        };
    };

    t.start();
}

public static double square (double angle){
    angle = angle % (Math.PI*2);
    if (angle > Math.PI) return 1;
    else return 0;
}

This code is from here: https://stackoverflow.com/a/1932537/3787777

Community
  • 1
  • 1
Coderino Javarino
  • 2,819
  • 4
  • 21
  • 43
  • Maybe the data does not have same rate as you use while decoding. Try 22000 or something like that. – win_wave Oct 22 '14 at 13:01
  • Actually you code works for one note, if you play several notes, then you need to have one stream, not a thread per note – win_wave Oct 22 '14 at 13:09
  • I wrote [multi-tone generator during hackathon](https://github.com/wojta/gimmeatone/blob/master/src/cz/gug/hackathon/glass/gimmeatone/audio/WaveGenerator.java). It was for Android, but the principles are the same. and you might find some useful stuff there. What I suggest is to pre-generate master sin wave and then just iterate through it at different speeds. With this approach you will reduce unwanted audio glitches to minimum. – Pavel Horal Oct 22 '14 at 13:50

2 Answers2

9

In this answer I will refer to 1) your code, 2) better approach (IMHO:) and 3) playing of two notes in the same time.

Your code

First, the sample rate should not depend on note frequency. Therefore try:

AudioFormat(44100,...

Next, use 16 bit sampling (sounds better!). Here is your code that plays simple tone without noise - but I would use it bit differently (see later). Please look for the comments:

Thread t = new Thread() {
    public void run() {
        try {

            int sound = (440 * 100);        // play A
            AudioFormat af = new AudioFormat(44100, 16, 1, true, false);
            SourceDataLine sdl;
            sdl = AudioSystem.getSourceDataLine(af);
            sdl.open(af, 4096 * 2);
            sdl.start();

            int maxi = (int) (1000 * (float) sound / 1000); // should not depend on notes frequency!
            byte[] buf = new byte[maxi * 2];   // try to find better len!
            int i = 0;
            while (i < maxi * 2) {
                // formula is changed to be simple sine!!
                double val = Math.sin(Math.PI * i * 440 / 44100);
                short s = (short) (Short.MAX_VALUE * val);
                buf[i++] = (byte) s;
                buf[i++] = (byte) (s >> 8);   // little endian
            }
            sdl.write(buf, 0, maxi);
            sdl.drain();
            sdl.stop();
            sdl.close();
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }
};

t.start();

Proposal for better code

Here is a simplified version of your code that plays some note (frequency) without noise. I like it better as we first create array of doubles, which are universal values. These values can be combined together, or stored or further modified. Then we convert them to (8bit or 16bit) samples values.

private static byte[] buffer = new byte[4096 * 2 / 3];
private static int bufferSize = 0;

// plays a sample in range (-1, +1).
public static void play(SourceDataLine line, double in) {
    if (in < -1.0) in = -1.0;  // just sanity checks
    if (in > +1.0) in = +1.0;

    // convert to bytes - need 2 bytes for 16 bit sample
    short s = (short) (Short.MAX_VALUE * in);
    buffer[bufferSize++] = (byte) s;
    buffer[bufferSize++] = (byte) (s >> 8);   // little Endian

    // send to line when buffer is full
    if (bufferSize >= buffer.length) {
        line.write(buffer, 0, buffer.length);
        bufferSize = 0;
    }
    // todo: be sure that whole buffer is sent to line!
}

// prepares array of doubles, not related with the sampling value!
private static double[] tone(double hz, double duration) {
    double amplitude = 1.0;
    int N = (int) (44100 * duration);
    double[] a = new double[N + 1];
    for (int i = 0; i <= N; i++) {
        a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / 44100);
    }
    return a;
}

// finally:
public static void main(String[] args) throws LineUnavailableException {
    AudioFormat af = new AudioFormat(44100, 16, 1, true, false);
    SourceDataLine sdl = AudioSystem.getSourceDataLine(af);

    sdl.open(af, 4096 * 2);
    sdl.start();

    double[] tones = tone(440, 2.0);    // play A for 2 seconds
    for (double t : tones) {
        play(sdl, t);
    }

    sdl.drain();
    sdl.stop();
    sdl.close();
}

Sounds nice ;)

Play two notes in the same time

Just combine two notes:

double[] a = tone(440, 1.0);        // note A
double[] b = tone(523.25, 1.0);     // note C (i hope:)
for (int i = 0; i < a.length; i++) {
    a[i] = (a[i] + b[i]) / 2;
}
for (double t : a) {
    play(sdl, t);
}

Remember that with double array you can combine and manipulate your tones - i.e. to make composition of tone sounds that are being played in the same time. Of course, if you add 3 tones, you need to normalize the value by dividing with 3 and so on.

Ding Dong :)

igr
  • 10,199
  • 13
  • 65
  • 111
  • That does sound nice, however how do I play ie 2 sounds at once? I tried replacing `double[] tones = tone(440, 2.0); // play A for 2 seconds for (double t : tones) { play(sdl, t); }` with `double[] tones = tone(440, 2.0); // play A for 2 seconds double[] tones2 = tone (500, 2.0); for (int t = 0; t < tones.length; t++) { play(sdl, tones[t]); play(sdl, tones2[t]); }` but it didn't work properly. – Arvy Oct 22 '14 at 13:29
  • One way for doing that is to sum two sines and divide by two (ive updated the example). Of course, for more notes you need to divide with number of notes played in the same time. Finally, there should be a way to play by writing to the source line, but something is not working for me at the moment :( Anyway I would not go with the Threads. – igr Oct 22 '14 at 13:46
  • this is awsome guys :D i just try this an its fun, Can yo give me a tone list and period time each of them for a simply song ? – SüniÚr Oct 22 '14 at 13:47
4

The answer has already been provided, but I want to provide some information that might help understanding the solution.

Why 44100?

44.1 kHz audio is widely used, due to this being the sampling rate used in CDs. Analog audio is recorded by sampling it 44,100 times per second (1 cycle per second = 1 Hz), and then these samples are used to reconstruct the audio signal when playing it back. The reason behind the selection of this frequency is rather complex; and unimportant for this explanation. That said, the suggestion of using 22000 is not very good because that frequency is too close to the human hearing range (20Hz - 20kHz). You would want to use a sampling rate higher than 40kHz for good sound quality. I think mp4 uses 96kHz.

Why 16-bit?

The standard used for CDs is 44.1kHz/16-bit. MP4 uses 96kHz/24-bit. The sample rate refers to how many X-bit samples are recorded every second. CD-quality sampling uses 44,100 16-bit samples to reproduce sound.

Why is this explanation important?

The thing to remember is that you are trying to produce digital sound (not analog). This means that these bits and bytes have to be processed by an audio CODEC. In hardware, an audio CODEC is a device that encodes analog audio as digital signals and decodes digital back into analog. For audio outputs, the digitized sound must go through a Digital-to-Analog Converter (DAC) in order for proper sound to come out of the speakers. Two of the most important characteristics of a DAC are its bandwidth and its signal-to-noise ratio and the actual bandwidth of a DAC is characterized primarily by its sampling rate.

Basically, you can't use an arbitrary sampling rate because the audio will not be reproduced well by your audio device for the reasons stated above. When in doubt, check your computer hardware and find out what your CODEC supports.

Community
  • 1
  • 1
hfontanez
  • 5,774
  • 2
  • 25
  • 37