Haskell threadDelay中的时序不准确

时间:2014-10-18 08:30:28

标签: haskell timing

我想用Haskell的printf制作节奏。以下应该产生重复节奏,其中一个音符是另外两个音符的两倍。 (该节奏由列表[1,1,2]编码。)

import Control.Concurrent
import Text.Printf
import Control.Monad

main = mapM_ note (cycle [1,1,2])

beat = round (10^6 / 4) -- measured in miliseconds

note :: Int -> IO ()
note n = do
    threadDelay $ beat * n
    printf "\BEL\n"

当我跑它时,长音符听起来大约是其他音符的​​三倍,而不是两倍。如果我加快速度,通过将数字4改为10,节奏就会完全被破坏:音符都具有相同的长度。

刷新率是否有变化?如果我想要精确计时,threadDelay不是要使用的服务吗?

3 个答案:

答案 0 :(得分:3)

  

如果我想要精确计时,threadDelay不是要使用的服务吗?

不,not at all

threadDelay :: Int -> IO () Source
     

暂停当前线程达到给定的微秒数(仅限GHC)。

     

无法保证在延迟过期时,线程会立即重新安排,,但线程将永远不会继续比指定时间更早运行。

然而,在我的机器上(Win 8.1 x64 i5-3570k@3.4GHz),节奏运行良好。话虽如此,\BEL并不是创造节拍的好方法:

  1. \BEL声音取决于操作系统(如果以该频率播放,Windows 8中的声音可怕),
  2. 不清楚\BEL是否阻止。
  3. 如果后者发生,则最终会得到大致相同的长度,因为每个\BEL都会阻止,而threadDelay会比实际的\BEL声音短。

答案 1 :(得分:2)

问题似乎是打印,而不是线程。 Haskell-Cafe的Rohan Drape向我展示了如何使用OSC而不是打印。以下测试的时间,使用OSC,是我的耳朵与完美无法区分。我让它向Max / MSP中的正弦波振荡器发送指令。

import Control.Concurrent
import Control.Monad
import System.IO
import Sound.OSC

main = do
    hSetBuffering stdout NoBuffering
    mapM_ note (cycle [1,1,2])

withMax = withTransport (openUDP "127.0.0.1" 9000)
beat = 60000 -- 60 ms, measured in µs

note :: Int -> IO ()
note n = do
    withMax (sendMessage (Message "sin0 frq 100" []))
        -- set sine wave 0 to frequency 100
    withMax (sendMessage (Message "sin0 amp 1" []))
        -- set sine wave 0 to amplitude 1
    threadDelay $ beat * n
    withMax (sendMessage (Message "sin0 amp 0" []))
        -- set sine wave 0 to amplitude 0
    threadDelay $ beat * n

谢谢大家!

答案 2 :(得分:0)

您很可能不得不依赖OS支持或GHC内部。例如,我在

中使用了GHC.Event
registerTimeout :: TimerManager -> Int -> TimeoutCallback -> IO TimeoutKey

funtion。但是它也会与回调异步。这也是GHC特有的。

其他选项是hackage上的计时器库,但不确定它们是否都是可移植的,或者是否可以在Windows上使用,但大多数都使用OS支持,因为精确计时需要硬件计时器。