Coderino Javarino Coderino Javarino - 6 months ago 12
Java Question

Java sound generation produces noisy sound

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: http://stackoverflow.com/a/1932537/3787777

Answer

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 :)

Comments