5

作为一项练习,我正在尝试使用 Thread.sleep 作为计时器和 JMF 来创建一个带有 Java 的节拍器。它工作得很好,但出于某种原因,JMF 似乎只以每分钟最多 207 拍的速度播放声音。

来自我的节拍器课:

public void play() {
    soundPlayer.play();
    waitPulse();        
    play();
}

从我的 SoundPlayer 类:

public void play() {
    new Thread(new ThreadPlayer()).start();
}

private class ThreadPlayer implements Runnable {
    public void run() {
        System.out.println("Click");
        player.setMediaTime(new Time(0));
        player.start();
    }   
}

我已经让 SoundPlayer.play() 作为一个线程来测试它是否会有所作为,但事实并非如此。我可以轻松地将速度更改为大约 207bpm,但即使我将计时器设置为 1000bpm,声音的播放速度也不会比大约 207bpm 快

我已经将System.out.println("Click");ThreadPlayer.run() 放在里面,以检查我的循环是否正常工作——它是。

问题似乎与我对 JMF 的实现有关。我很确定有一个简单的解决方案,有人可以帮助我吗?

非常感谢你的帮助!:)

4

5 回答 5

9

关于 Thread.sleep() 不可靠的答案是正确的:你不能指望它在你指定的时间内返回。事实上,我很惊讶您的节拍器完全可用,尤其是当您的系统处于负载状态时。阅读 Thread.sleep() 的文档以获取更多详细信息。Max Beikirch 关于 MIDI 的回答是一个很好的建议:MIDI 处理时序非常好。

但是你问如何用音频做到这一点。诀窍是打开一个音频流并在节拍器咔嗒声之间填充静音,然后将节拍器咔嗒声插入您想要的位置。当您这样做时,您的声卡会以恒定速率播放样本(无论它们是否包含咔嗒声或静音)。这里的关键是保持音频流打开并且永远不要关闭它。那么,时钟是音频硬件,而不是您的系统时钟——这是一个微妙但重要的区别。

因此,假设您正在生成 44100 Hz 的 16 位单声道样本。这是一个以请求的速率创建咔哒声的函数。请记住,这种咔哒声对扬声器(和您的耳朵)不利,因此如果您实际使用它,请以低音量播放。(此外,此代码未经测试——它只是为了演示这个概念)

int interval = 44100; // 1 beat per second, by default
int count = 0;
void setBPM( float bpm ) {
    interval = ( bpm / 60 ) * 44100 ;
}
void generateMetronomeSamples( short[] s ) {
    for( int i=0; i<s.length; ++i ) {
       s = 0;
       ++count;
       if( count == 0 ) {
          s = Short.MAX_VALUE;
       }
       if( count == interval ) {
          count = 0;
       }
    }
}

使用 setBPM 设置速度后,您可以播放通过重复调用 generateMetronomeSamples() 函数生成的样本,并使用 JavaSound 将输出流式传输到扬声器。(请参阅 JSResources.org 以获得良好的教程)

一旦你有了这个工作,你可以用你从 WAV 或 AIFF 或短音或其他任何东西中获得的声音来代替刺耳的咔嗒声。

于 2013-01-04T22:02:16.357 回答
1

就像 Jamie Duby 说的那样,仅仅因为你告诉线程休眠 1 毫秒,并不意味着它会在恰好 1 毫秒时被回调。唯一的保证是自从您调用 Thread.sleep(); 以来至少已经过了一毫秒。实际上,处理器无法以足够快的速度处理代码以每毫秒播放一次哔声,这就是您看到延迟的原因。如果你想要一个戏剧性的例子,制作一个自制的计时器类并尝试让它以一毫秒为一整分钟,你会看到计时器关闭了很多。

在这里真正值得回答的人是 Max Beikrich,Midi 是您能够产生所需输出的唯一方法。

于 2013-01-04T21:27:37.383 回答
1

我的假设是,也许其他人可以跳到这里,线程运行时间取决于线程调度程序的突发奇想。您无法保证 JVM 需要多长时间才能返回该线程。此外,看到 JVM 作为机器上的一个进程运行,并且受操作系统的进程调度程序的影响,您至少看到了两个级别的不可预测性。

于 2013-01-04T21:11:19.700 回答
1

慢慢来,看看 MIDI!- http://www.ibm.com/developerworks/library/it/it-0801art38/http://docs.oracle.com/javase/tutorial/sound/TOC.html。这是与计算机发出声音相关的所有问题的最佳解决方案。

于 2013-01-04T21:08:31.693 回答
0

我作为音乐家的经验比程序员多得多,但我刚刚完成了一个我不久前开始的节拍器应用程序,我把项目搁置了一段时间,因为我不明白为什么我遇到了和你一样的问题. 是的 Thread.sleep() 可能不可靠,但我设法使用该方法制作了一个好的节拍器。

我看到你提到尝试ExecutorService我不认为使用并发类会解决你的问题。我的猜测是它的系统资源问题,我很确定 MIDI 是节拍器的方式。我强迫我的学生用节拍器练习,我用过很多,我从来没有太在意节拍的音频质量,时间更重要,MIDI 将比任何其他音频文件快得多. 我使用javax.sound.midi了 Sound API 中的库。我怀疑这会解决你的问题。

当它正常工作时,您可能会注意到您的滴答声不均匀,这是由于 Thread.sleep() 方法不是很可靠。我如何解决这个问题是通过使用System.nanoTime()方法而不是方法在纳秒内完成所有计算System.currentTimeMillis(),只是不要忘记在将睡眠时间传递给 Thread.sleep() 方法之前转换回毫秒。

我不想在这里发布我的节拍器的代码,以防你想自己弄清楚,但如果你想看到它,只需给我发电子邮件 kevin.bigler3@gmail.com 和我很乐意将其发送给您。祝你好运。

于 2013-03-13T17:00:04.177 回答