具有高速的Java节拍器

时间:2013-01-04 21:02:16

标签: java audio jmf

作为练习我正在尝试用Java创建一个节拍器,使用Thread.sleep作为计时器,使用JMF作为声音。它运作得很好,但由于某些原因,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的实施有关。我很确定有一个简单的解决方案,任何人都可以帮助我吗?

非常感谢你的帮助! :)

5 个答案:

答案 0 :(得分: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或短音或其他任何东西获得的声音来代替刺耳的咔嗒声。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

真正值得回答的人是Max Beikrich,Midi是你能够产生你想要的输出的唯一方式。

答案 4 :(得分:0)

作为一名音乐家,我有更多的经验,而不是程序员,但我刚刚完成了一段时间的节拍器应用,我把项目搁置一段时间,因为我无法弄清楚为什么我有同样的你是个问题。是的Thread.sleep()可能不可靠,但我设法使用该方法制作一个好的节拍器。

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

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

我不想在这里发布我的节拍器的代码,以防你想要自己想出来,但如果你想看到它只是给我发电子邮件kevin.bigler3@gmail.com我很乐意把它寄给你。祝你好运。