如何使用 javax.sound.sampled 包同时播放一组频率(和弦)

Posted

技术标签:

【中文标题】如何使用 javax.sound.sampled 包同时播放一组频率(和弦)【英文标题】:How to play a set of frequencies (Chords) at the same time with javax.sound.sampled package 【发布时间】:2012-08-09 10:41:28 【问题描述】:

我正在尝试实现一个系统,在该系统中,我给出了一组同时播放的频率,目前可以单独播放每个频率。下面我有一个代码,一次播放一个给定的频率。

    import java.applet.*;
    import java.io.*;
    import java.net.*;
    import javax.sound.sampled.*;

    public final class StdAudio 

        public static final int SAMPLE_RATE = 44100;

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


        private static SourceDataLine line;   // to play the sound
        private static byte[] buffer;         // our internal buffer
        private static int bufferSize = 0;    

        // not-instantiable
        private StdAudio()  


        // static initializer
        static  init(); 

        // open up an audio stream
    private static 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);

        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 static 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 static 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 static void play(double[] input) 
    for (int i = 0; i < input.length; i++) 
        play(input[i]);
    


private static double[] tone(double hz, double duration) 
    int N = (int) (StdAudio.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 / StdAudio.SAMPLE_RATE);
    return a;


/**
 * Test client - play an A major scale to standard audio.
 */
public static void main(String[] args) 


    double hz = 440.4// 440.4 Hz for 1 sec
    double duration = 1.0;

    double[] a = tone(hz,duration);
    StdAudio.play(a);


【问题讨论】:

+1 sscce 嗨,你最终找到解决方案了吗? 【参考方案1】:

简单地将每个时间点的样本值相加,然后除以适当的比例因子以使您的值保持在范围内。例如同时播放 A 440 和 C 523.25:

double[] a = tone(440,1.0);
double[] b = tone(523.25,1.0);
for( int i=0; i<a.length; ++ i )
   a[i] = ( a[i] + b[i] ) / 2;
StdAudio.play(a);

【讨论】:

什么是StdAudiotone()? @trashgod:阅读问题:它们直接来自 abdal 的优秀代码。例如,参见他的主要功能。 啊,我现在明白了。 DYMa.length?我可以看到在这个较低级别混合的吸引力。 Abdal 将不得不权衡可扩展性与复杂性。 +1 用于利用 OP 的方法。【参考方案2】:

在此example 中,Tone 为每个 Note 打开一个 SourceDataLine,但您可以打开和弦中的音符一样多的行。然后只需将和弦的write() 的每个Note 转换为单独的一行。您可以使用Note.REST 创建琶音效果。

【讨论】:

感谢回复,我希望同时播放一组频率,而不是琶音效果。如果可能的话,你能给我一个两个不同频率的例子吗?非常感谢。 对于一个双音和弦,只需使用AudioSystem.getSourceDataLine()打开两行,然后使用Tone#play()在每行写一个和弦的音符。 这会带来很多开销,而且很难保持同步。【参考方案3】:

你可以为每个频率使用不同的线程:)

here you can find a small example of threads in java

希望对你有帮助:)

【讨论】:

我不明白这会有什么帮助。我会将这些行放在AudioSystem.getSourceDataLine() 内部使用的默认Mixer 中。

以上是关于如何使用 javax.sound.sampled 包同时播放一组频率(和弦)的主要内容,如果未能解决你的问题,请参考以下文章

Java getAudioInputStream 试图读取音频文件,得到 javax.sound.sampled.UnsupportedAudioFileException,

使用 javax.sound.sampled.Clip 在游戏中播放、循环和停止多个声音。意外错误

Java Wav 文件错误(javax.sound.sampled.UnsupportedAudioFileException:无法从输入文件获取音频输入流)

在java中怎么获取音频的总时长?

如何摆脱错误?

写了一个用java播放音频的代码,运行时总是出现错误!求指点!