作为练习,我试图使用Java创建一个节拍器。sleep是定时器,JMF是声音。它工作得很好,但由于某种原因,JMF似乎只能以每分钟最多207拍的速度播放声音。
From my Metronome-class:
public void play() {
soundPlayer.play();
waitPulse();
play();
}
From my SoundPlayer-class:
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的实现。我很确定有一个简单的解决办法,有人能帮我吗?
非常感谢你的帮助!:)
关于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或短音调或任何东西得到的声音代替刺耳的点击。
慢慢来,看看MIDI吧!- http://www.ibm.com/developerworks/library/it/it-0801art38/或http://docs.oracle.com/javase/tutorial/sound/TOC.html。这是最好的解决方案,一切有关电脑发出的声音。
我的假设是,线程运行时间取决于线程调度器的突发事件。您无法保证JVM需要多长时间才能返回到该线程。此外,由于JVM作为一个进程在机器上运行,并且受操作系统的进程调度程序的影响,因此您将看到至少两个级别的不可预测性。
就像Jamie Duby说的,仅仅因为你让Thread睡1毫秒,并不意味着它会在1毫秒被回调。唯一的保证是,自从你调用Thread.sleep();以来,至少有一毫秒已经过去了。实际上,处理器处理代码的速度不够快,无法每毫秒播放一次哔哔声,这就是为什么您会看到延迟。如果你想要一个戏剧性的例子,创建一个自制的计时器类,并尝试让它在一分钟内计数一毫秒,你会看到计时器偏离了相当多。
真正值得表扬的人是Max Beikrich, Midi是你能够产生你想要的输出的唯一方法。
作为一名音乐家,我的经验远比程序员丰富,但我刚刚完成了一个节拍器应用程序,我开始了一段时间,我把这个项目搁置了一段时间,因为我不明白为什么我有同样的问题。是的,Thread.sleep()可能不可靠,但我设法使用该方法制作一个好的节拍器。
我看到你提到尝试ExecutorService
,我不认为使用并发类会解决你的问题。我猜这是系统资源问题,我很确定midi是节拍器的选择。我强迫我的学生用节拍器练习,我已经用过很多了,我从来没有太过关心节拍的音频质量,计时更重要,midi比任何其他音频文件都要快得多。我使用的javax.sound.midi
库从声音API。我想这会解决你的问题。
当它正常工作时,您可能会注意到tick是不均匀的,这是由于Thread.sleep()方法不是很可靠。我如何解决这个问题是通过使用System.nanoTime()
方法而不是System.currentTimeMillis()
方法在纳秒内完成所有计算,只是不要忘记在将睡眠时间传递给Thread.sleep()方法之前转换回毫秒。
我不想在这里发布我的节拍器的代码,以防你想自己弄清楚,但如果你想看它,只要给我发电子邮件kevin.bigler3@gmail.com,我很乐意把它发给你。好运。