Java上的多个声音

时间:2011-10-24 12:47:51

标签: java audio

我正试图在Java中同时播放2种声音(例如220Hz和440Hz)。

我设法使用StdAudio播放一首声音。 后来,我让它不是静态的,并删除了一些与我无关的方法。

我不知道的是如何同时播放2种声音。 我尝试用线程做到这一点,但它们并不总是同步。

下面是我修改过的StdAudio版本,下面是我尝试使用线程的示例。

program.java

public class program {

    public static void main(String[] args) {
        Thread t1 = new Thread(new soundThread(220));
        t1.start();
        Thread t2 = new Thread(new soundThread(440));
        t2.start();

        t1.notify();
        t2.notify();
    }

}

soundThread.java

public class soundThread implements Runnable {
    private int fq;

    public soundThread(int fq) {
        this.fq = fq;
    }

    public void run() {
        StdAudio s = new StdAudio();
        double[] note = s.note(fq, 2, 1);
        try {
            this.wait();
        } catch (Exception e) {
        }

        s.play(note);

        s.close();
    }

}

StdAudio.java

/*************************************************************************
 *  Compilation:  javac this.java
 *  Execution:    java StdAudio
 *  
 *  Simple library for reading, writing, and manipulating .wav files.

 *
 *  Limitations
 *  -----------
 *    - Does not seem to work properly when reading .wav files from a .jar file.
 *    - Assumes the audio is monaural, with sampling rate of 44,100.
 *
 *************************************************************************/

import javax.sound.sampled.*;

/**
 * <i>Standard audio</i>. This class provides a basic capability for creating,
 * reading, and saving audio.
 * <p>
 * The audio format uses a sampling rate of 44,100 (CD quality audio), 16-bit,
 * monaural.
 * 
 * <p>
 * For additional documentation, see <a
 * href="http://introcs.cs.princeton.edu/15inout">Section 1.5</a> of
 * <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by
 * Robert Sedgewick and Kevin Wayne.
 */
public final class StdAudio {

    /**
     * The sample rate - 44,100 Hz for CD quality audio.
     */
    public final int SAMPLE_RATE = 44100;

    private final int BYTES_PER_SAMPLE = 2; // 16-bit audio
    private final int BITS_PER_SAMPLE = 16; // 16-bit audio
    private final double MAX_16_BIT = Short.MAX_VALUE; // 32,767
    private final int SAMPLE_BUFFER_SIZE = 4096;

    private SourceDataLine line; // to play the sound
    private byte[] buffer; // our internal buffer
    private int bufferSize = 0; // number of samples currently in internal
                                // buffer

    // initializer
    {
        init();
    }

    // open up an audio stream
    private void init() {
        try {
            // 44,100 samples per second, 16-bit audio, mono, signed PCM, little
            // Endian
            AudioFormat format = new AudioFormat((float) SAMPLE_RATE,
                    BITS_PER_SAMPLE, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);

            // the internal buffer is a fraction of the actual buffer size, this
            // choice is arbitrary
            // it gets divided because we can't expect the buffered data to line
            // up exactly with when
            // the sound card decides to push out its samples.
            buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE / 3];
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.exit(1);
        }

        // no sound gets made before this call
        line.start();
    }

    /**
     * Close standard audio.
     */
    public void close() {
        line.drain();
        line.stop();
    }

    /**
     * Write one sample (between -1.0 and +1.0) to standard audio. If the sample
     * is outside the range, it will be clipped.
     */
    public void play(double in) {

        // clip if outside [-1, +1]
        if (in < -1.0)
            in = -1.0;
        if (in > +1.0)
            in = +1.0;

        // convert to bytes
        short s = (short) (MAX_16_BIT * in);
        buffer[bufferSize++] = (byte) s;
        buffer[bufferSize++] = (byte) (s >> 8); // little Endian

        // send to sound card if buffer is full
        if (bufferSize >= buffer.length) {
            line.write(buffer, 0, buffer.length);
            bufferSize = 0;
        }
    }

    /**
     * Write an array of samples (between -1.0 and +1.0) to standard audio. If a
     * sample is outside the range, it will be clipped.
     */
    public void play(double[] input) {
        for (int i = 0; i < input.length; i++) {
            play(input[i]);
        }
    }

    /**
     * Create a note (sine wave) of the given frequency (Hz), for the given
     * duration (seconds) scaled to the given volume (amplitude).
     */
    public double[] note(double hz, double duration, double amplitude) {
        int N = (int) (this.SAMPLE_RATE * duration);
        double[] a = new double[N + 1];
        for (int i = 0; i <= N; i++)
            a[i] = amplitude
                    * Math.sin(2 * Math.PI * i * hz / this.SAMPLE_RATE);
        return a;
    }

}

提前致谢, Shay Ben Moshe

修改 解决方案是写这个方法:

public double[] multipleNotes(double[] hzs, double duration,
        double amplitude) {
    amplitude = amplitude / hzs.length;
    int N = (int) (SAMPLE_RATE * duration);
    double[] a = new double[N + 1];
    for (int i = 0; i <= N; i++) {
        a[i] = 0;
        for (int j = 0; j < hzs.length; j++)
            a[i] += amplitude
                    * Math.sin(2 * Math.PI * i * hzs[j] / SAMPLE_RATE);
    }
    return a;
}

EDIT2: 对我来说更好的解决方案(O(1)内存):

public void multiplePlay(double[] hzs, double duration, double amplitude) {
    amplitude = amplitude / hzs.length;
    int N = (int) (SAMPLE_RATE * duration);
    double sum;
    for (int i = 0; i <= N; i++) {
        sum = 0;
        for (int j = 0; j < hzs.length; j++)
            sum += amplitude
                    * Math.sin(2 * Math.PI * i * hzs[j] / SAMPLE_RATE);
        this.play(sum);
    }
}

4 个答案:

答案 0 :(得分:6)

对我简单地将两个声音合二为一的评论进行了扩展...

你证明了这一点:

public double[] note(double hz, double duration, double amplitude) {
    int N = (int) (this.SAMPLE_RATE * duration);
    double[] a = new double[N + 1];
    for (int i = 0; i <= N; i++)
        a[i] = amplitude
                * Math.sin(2 * Math.PI * i * hz / this.SAMPLE_RATE);
    return a;
}

那么将两个声音混合成一个并播放那个独特的声音呢?例如,您可以这样做:

public double[] notes(double hz1, double hz2, double duration, double amplitude) {
    final double[] a1 = note( hz1, duration, amplitude );
    final double[] a2 = note( hz2, duration, amplitude );
    final double[] a3 = new double[a2.length];
    for ( int i = 0; i < a1.length; i++ ) {
        a3[i] = (a1[i] + a2[i]) / 2;       
    }
    return a3;
}

你只需要这样称呼它:

final double[] sound = notes(220,400,...,...);

答案 1 :(得分:0)

尝试Pulpcore

答案 2 :(得分:0)

OpenAL框架中的iOS中提供的开源OpenAL音频API提供了一个优化的界面,用于在播放期间定位立体声场中的声音。播放,定位和移动声音就像在其他平台上一样。 OpenAL还可以让你混合声音。更多信息:http://developer.apple.com/library/IOS/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html

答案 3 :(得分:0)

您可以使用JSyn库播放多个特定频率的声音。

它现在可以满足您的需求,如果您想做更复杂的事情,您可能希望以后再转移它。

http://www.softsynth.com/jsyn/

作为一个例子,我还设法在这里找出一些稍微复杂的声音:

JSyn, siren sound using oscillator fed/controlled/inputInto/daisy-chainedTo by another oscillator and a constant...and generating more than one sound

此代码将同时生成220 Hz和440 Hz的音调。

  

com.jsyn.Synthesizer synth = JSyn.createSynthesizer();
  com.jsyn.unitgen.SineOscillator sine1 = new SineOscillator();
  com.jsyn.unitgen.SineOscillator sine2 = new SineOscillator();
  com.jsyn.unitgen.LineOut lineOut = new LineOut();

     

synth.add(sine1);
  synth.add(sine2);
  synth.add(线路输出);

     

sine1.frequency.set(220);
  sine2.frequency.set(440);

     

sine1.output.connect(0,lineOut.input,0); //左右声道
  sine1.output.connect(0,lineOut.input,1);
  sine2.output.connect(0,lineOut.input,0); //左右声道
  sine2.output.connect(0,lineOut.input,1);

     

lineOut.start();