Java sound generation creates noisy sound

I am using javax.sound to create sounds, however when you play it they have some kind of noise in the background that even overcomes the sound if you play multiple 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: StackOverflow question.

+3


source to share


2 answers


In this answer, I will be referring to 1) your code, 2) a better approach (IMHO :) and 3) playing two notes at the same time.

Your code

First, the sampling rate should be independent of the note frequency. So try:

AudioFormat(44100,...

      

Then use 16

bit sampling (sounds better!). Here is your code that reproduces a simple tone without noise - but I would use it a bit differently (see below). Please find 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();

      

Suggestion for better code

Here is a simplified version of your code that plays some note (frequency) without noise. I like it when we first create an array of double

s that are generic values. These values ​​can be merged together or stored or further modified. Then we convert them to values ​​(8 bits or 16 bits).



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 good;)

Play two notes at one 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 a double array, you can combine and control your tones, i.e. compose the timbre sounds that are played at the same time. Of course, if you add 3 tones, you need to normalize the value by dividing it by 3, etc.

Ding Dong :)

+7


source


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

Why 44100?

44.1 kHz audio is widely used, which is why it is the sampling rate used on CDs. Analog audio is recorded by sampling 44,100 times per second (1 cycle per second = 1 Hz), and then these samples are used to reconstruct the audio signal during playback. The reason for choosing this frequency is complex; and is irrelevant to this explanation. However, the suggestion to use 22000 is not very good because this frequency is too close to the human hearing range (20 Hz - 20 kHz). For good sound quality, you would like to use a sampling rate over 40 kHz. I think mp4 uses 96kHz.

Why 16-bit?



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

Why is this explanation important?

Remember that you are trying to create digital sound (not analog). This means that these bits and bytes must be processed with CODEC audio . In hardware, an audio codec is a device that encodes analog audio as digital signals and decodes digital back to analog . For audio outputs, the digitized audio must pass through a digital-to-analog converter (DAC) for proper sound to come out of the speakers. Two of the most important characteristics of a DAC are its bandwidth and signal-to-noise ratio, and the actual bandwidth of a DAC is characterized primarily by its sampling rate .

Basically, you cannot use an arbitrary sample rate, because the sound will not play well with your audio device for the reasons stated above. When in doubt, check your computer hardware and see what your CODEC supports.

+4


source







All Articles