Java 上的多种声音

发布于 2024-12-12 01:23:25 字数 6432 浏览 0 评论 0原文

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

我设法使用 StdAudio 播放一种声音。 后来我把它改为非静态的,并删除了一些与我无关的方法。

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

下面是我修改后的 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;
    }

}

提前致谢, 谢伊·本·摩西

编辑: 解决方案是编写此方法:

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

I am trying to play 2 sounds (e.g. 220Hz and 440Hz) at the same time in Java.

I managed to play one sound using StdAudio.
Later on, I made it not static, and removed some methods that are irrelevant for me.

What I don't know is how to play 2 sounds at the same time.
I tried to do that with thread but they aren't always synchronized.

Below is my modified version of StdAudio, and here is the example of how I tried to use threads.

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;
    }

}

Thanks in advance,
Shay Ben Moshe

EDIT:
The solution was writing this method:

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:
Even better solution for me (O(1) memory):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

马蹄踏│碎落叶 2024-12-19 01:23:25

稍微扩展一下我关于简单地将两种声音合并为一个的评论...

您展示了这一点:

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,...,...);

Expanding a bit on my comment about simply combining the two sounds into one...

You showed this:

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;
}

So what about mixing yourself the two sounds into one and playing that one unique sound? For example you could do something like this:

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;
}

And you'd simply call it like that:

final double[] sound = notes(220,400,...,...);
套路撩心 2024-12-19 01:23:25

iOS 中的 OpenAL 框架中提供的开源 OpenAL 音频 API 提供了一个优化的接口,用于在播放期间将声音定位在立体声场中。播放、定位和移动声音的方式与在其他平台上一样。 OpenAL 还可以让您混合声音。有关更多信息,请访问:http://developer. apple.com/library/IOS/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html

The open-sourced OpenAL audio API, available in iOS in the OpenAL framework, provides an interface optimized for positioning sounds in a stereo field during playback. Playing, positioning, and moving sounds works just as it does on other platforms. OpenAL also lets you mix sounds. Here for more: http://developer.apple.com/library/IOS/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html

你好,陌生人 2024-12-19 01:23:25

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

它可以满足您现在想要的功能,如果您想做更复杂的事情,您可能需要稍后再使用它。

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

作为一个例子,我还设法弄清楚这里发出一些稍微复杂的声音:

JSyn,使用振荡器馈送/控制的警报器声音/ inputInto/daisy-chainedTo 由另一个振荡器和一个常数...并生成多个声音

此代码将完成一次生成 220 Hz 和 440 Hz 的音调。

com.jsyn.Synthesizer 合成 = 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);
合成.add(sine2);
合成器.add(lineOut);

正弦1.频率.set(220);
sine2.频率.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();

You can play multiple specific-frequency sounds using the JSyn library.

It will work for what you want right now, and you may want to move to it later if you want to do anything more complicated.

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

As an example, I also managed to figure out some slightly more complex sounds here:

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

This code will accomplish generating tones at 220 Hz and 440 Hz at once.

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(lineOut);

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

sine1.output.connect(0, lineOut.input, 0); //left and right channels
sine1.output.connect(0, lineOut.input, 1);
sine2.output.connect(0, lineOut.input, 0); //left and right channels
sine2.output.connect(0, lineOut.input, 1);

lineOut.start();

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文