在Java中交替播放2个不同频率

3
我是Java Sounds的新手,我想循环播放2个不同的频率,每次播放1秒钟,在特定时间内。 例如,如果我有2个频率440hz和16000hz,时间为10秒,则每个“偶数”秒播放440hz,而每个“奇数”秒播放16000hz,即交替5秒钟。

我通过一些示例学习了一些东西,并且还编写了一个程序,借助这些示例,该程序可以在用户指定的时间内以单一频率运行。

如果有人能够帮助我解决这个问题,我将不胜感激。 谢谢。

我也附上了单一频率代码以供参考。

  import java.nio.ByteBuffer;
  import java.util.Scanner;
  import javax.sound.sampled.*;

  public class Audio {

   public static void main(String[] args) throws InterruptedException, LineUnavailableException {
    final int SAMPLING_RATE = 44100;            // Audio sampling rate
    final int SAMPLE_SIZE = 2;                  // Audio sample size in bytes

    Scanner in = new Scanner(System.in);
    int time = in.nextInt();                      //Time specified by user in seconds
    SourceDataLine line;
    double fFreq = in.nextInt();                         // Frequency of sine wave in hz

    //Position through the sine wave as a percentage (i.e. 0 to 1 is 0 to 2*PI)
    double fCyclePosition = 0;

    //Open up audio output, using 44100hz sampling rate, 16 bit samples, mono, and big 
    // endian byte ordering
    AudioFormat format = new AudioFormat(SAMPLING_RATE, 16, 1, true, true);
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

    if (!AudioSystem.isLineSupported(info)) {
        System.out.println("Line matching " + info + " is not supported.");
        throw new LineUnavailableException();
    }

    line = (SourceDataLine) AudioSystem.getLine(info);
    line.open(format);
    line.start();

    // Make our buffer size match audio system's buffer
    ByteBuffer cBuf = ByteBuffer.allocate(line.getBufferSize());

    int ctSamplesTotal = SAMPLING_RATE * time;         // Output for roughly user specified time in seconds

    //On each pass main loop fills the available free space in the audio buffer
    //Main loop creates audio samples for sine wave, runs until we tell the thread to exit
    //Each sample is spaced 1/SAMPLING_RATE apart in time
    while (ctSamplesTotal > 0) {
        double fCycleInc = fFreq / SAMPLING_RATE;  // Fraction of cycle between samples

        cBuf.clear();                            // Discard samples from previous pass

        // Figure out how many samples we can add
        int ctSamplesThisPass = line.available() / SAMPLE_SIZE;
        for (int i = 0; i < ctSamplesThisPass; i++) {
            cBuf.putShort((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * fCyclePosition)));

            fCyclePosition += fCycleInc;
            if (fCyclePosition > 1) {
                fCyclePosition -= 1;
            }
        }

        //Write sine samples to the line buffer.  If the audio buffer is full, this will 
        // block until there is room (we never write more samples than buffer will hold)
        line.write(cBuf.array(), 0, cBuf.position());
        ctSamplesTotal -= ctSamplesThisPass;     // Update total number of samples written 

        //Wait until the buffer is at least half empty  before we add more
        while (line.getBufferSize() / 2 < line.available()) {
            Thread.sleep(1);
        }
    }

    //Done playing the whole waveform, now wait until the queued samples finish 
    //playing, then clean up and exit
    line.drain();
    line.close();
}

}


如果有人能帮我解决这个问题,我会非常感激。顺便说一下:1)我认为在 MHz 范围内的声音对人类来说是听不到的,并且无法被 Java 支持的任何声音格式处理。2)请参阅 Detection/fix for the hanging close bracket of a code block 以了解一个我已经不再费心修复的问题。 - Andrew Thompson
@AndrewThompson 对混淆感到抱歉。实际上,昨天我也在为我的笔记本电脑寻找一条1600Mhz的RAM。所以这就是这样一个愚蠢错误的原因。 - Vibhav Chaddha
2个回答

2
我会使用在输出到SourceDataLine时计算帧数的方法。当你写了一秒钟的帧时,切换频率。这将比尝试调整剪辑得到更好的时间精度。
我不清楚你展示的代码是你自己编写的还是复制粘贴的。如果你对它不起作用有疑问,如果你展示你尝试过什么以及生成了什么错误或异常,我很乐意帮助你。
在输出到SourceDataLine时,必须有一个步骤将短值(-32768..+32767)转换为两个字节,根据你所拥有的音频格式中指定的16位编码。我没有看到在你的代码中进行这个步骤。[编辑: 可以看到putShort()方法是这样做的,但它只适用于BigEndian,而不是更常见的LittleEndian。]
你是否查看了Java教程中的声音Trail

实际上,那段代码是单频的。虽然你说得对,但我并没有完全理解这段代码。有一些东西在我之前的生活中从未使用过。所以如果你能花点时间向我解释整个过程,那将是非常有帮助的。谢谢。 - Vibhav Chaddha
1
我建议你尝试一下我提供的“Sound Trail”。现在可以跳过关于MIDI和服务提供接口的部分。尽力阅读到第三个有关播放音频的教程。这很困难,但是要慢慢来,并沿途制作测试程序。有了这些概念词汇作为参考,提出更具体的问题并得到答案将会更容易。学习音频可能与学习高级图形一样复杂,并需要一些承诺和基础知识。 - Phil Freihofner
“这将比试图调整剪辑更能提供更好的时间精度。”<- 这很可能是真实的,值得考虑。 - Hendrik
@PhilFreihofner 谢谢你的帮助,我非常感激。如果我需要进一步的帮助,我一定会向你求助。再次感谢。 - Vibhav Chaddha

2
您的最佳选择可能是创建如下示例代码中所示的剪辑。 话虽如此,MHz范围通常是听不到的——看起来您在问题中有一个打字错误。 如果没有打字错误,您将遇到Nyquist先生的问题。
另一个提示:在Java中没有人使用匈牙利命名法
import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;

public class AlternatingTones {

    public static void main(final String[] args) throws LineUnavailableException, InterruptedException {

        final Clip clip0 = createOneSecondClip(440f);
        final Clip clip1 = createOneSecondClip(16000f);

        clip0.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip1.setFramePosition(0);
                clip1.start();
            }
        });
        clip1.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip0.setFramePosition(0);
                clip0.start();
            }
        });
        clip0.start();

        // prevent JVM from exiting
        Thread.sleep(10000000);
    }

    private static Clip createOneSecondClip(final float frequency) throws LineUnavailableException {
        final Clip clip = AudioSystem.getClip();
        final AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100f, 16, 1, 2, 44100, true);
        final ByteBuffer buffer = ByteBuffer.allocate(44100 * format.getFrameSize());
        final ShortBuffer shortBuffer = buffer.asShortBuffer();
        final float cycleInc = frequency / format.getFrameRate();
        float cyclePosition = 0f;
        while (shortBuffer.hasRemaining()) {
            shortBuffer.put((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * cyclePosition)));
            cyclePosition += cycleInc;
            if (cyclePosition > 1) {
                cyclePosition -= 1;
            }
        }
        clip.open(format, buffer.array(), 0, buffer.capacity());
        return clip;
    }
}    

1
@Hendrik 非常感谢。你的回答真的很有帮助。 - Vibhav Chaddha
@hendrik 感谢您的帮助,我非常感激。如果我需要进一步的帮助,我一定会向您求助。再次感谢您。 - Vibhav Chaddha
@hendrik 如果我想让用户在您上面的代码中指定每个频率之间的时间,我该怎么做呢? 例如:如果我运行上面的代码,它会在每秒钟之后切换频率。如果我想让用户指定时间,比如2秒或3秒等,在这些时间内切换频率,该怎么办? - Vibhav Chaddha
@hendrik 我之前试过了,但是犯了一些愚蠢的错误,所以没能成功。不过现在它可以工作了。 非常感谢。 还有一件事,我想将它写入.wav格式。你能帮助我吗? - Vibhav Chaddha
你可能想看一下 https://dev59.com/dXA75IYBdhLWcg3wbojM 或类似的内容。 - Hendrik
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接