【发布时间】:2014-04-15 09:08:36
【问题描述】:
这是一个简短的 Haskell 程序,它可以生成 440 Hz 的声音。它使用 pulseaudio 作为音频后端。
import GHC.Float
import Control.Arrow
import Sound.Pulse.Simple
import qualified Data.List.Stream as S
import Data.List
type Time = Double
type Frequency = Double
type Sample = Double
type CV = Double
chunksize = 441 * 2
sampleRate :: (Fractional a) => a
sampleRate = 44100
integral :: [Double] -> [Double]
integral = scanl1 (\acc x -> acc + x / sampleRate)
chunks :: Int -> [a] -> [[a]]
chunks n = S.takeWhile (not . S.null) . S.unfoldr (Just . S.splitAt n)
pulseaudioOutput :: [Sample] -> IO ()
pulseaudioOutput sx = do
pa <- simpleNew Nothing "Synths" Play Nothing "Synths PCM output"
(SampleSpec (F32 LittleEndian) 44100 1) Nothing Nothing
mapM_ (simpleWrite pa . S.map double2Float) $ chunks 1000 sx
simpleDrain pa
simpleFree pa
oscSine :: Frequency -> [CV] -> [Sample]
oscSine f = S.map sin <<< integral <<< S.map ((2 * pi * f *) . (2**))
music ::[Sample]
music = oscSine 440 (S.repeat 0)
main = do
pulseaudioOutput music
如果我编译并运行它,我会看到 CPU 消耗不断增长。
如果我在“chunks”的定义中将“S.splitAt”改为“splitAt”,一切都很好。
谁能猜出为什么会这样?
谢谢。
更新
在以下代码中,所有三个版本的块都可以产生上述行为:
import GHC.Float
import Control.Arrow
import Sound.Pulse.Simple
import Data.List.Stream
import Prelude hiding ( unfoldr
, map
, null
, scanl1
, takeWhile
, repeat
, splitAt
, drop
, take
)
type Time = Double
type Frequency = Double
type Sample = Double
type CV = Double
chunksize = 441 * 2
sampleRate :: (Fractional a) => a
sampleRate = 44100
integral :: [Double] -> [Double]
integral = scanl1 (\acc x -> acc + x / sampleRate)
chunks :: Int -> [a] -> [[a]]
--chunks n = takeWhile (not . null) . unfoldr (Just . splitAt n)
--chunks n xs = take n xs : chunks n (drop n xs)
chunks n xs = h : chunks n t
where
(h, t) = splitAt n xs
pulseaudioOutput :: [Sample] -> IO ()
pulseaudioOutput sx = do
pa <- simpleNew Nothing "Synths" Play Nothing "Synths PCM output"
(SampleSpec (F32 LittleEndian) 44100 1) Nothing Nothing
mapM_ (simpleWrite pa . map double2Float) $ chunks 1000 sx
simpleDrain pa
simpleFree pa
oscSine :: Frequency -> [CV] -> [Sample]
oscSine f = map sin <<< integral <<< map ((2 * pi * f *) . (2**))
music ::[Sample]
music = oscSine 440 (repeat 0)
main = do
pulseaudioOutput music
我清理了代码以避免混合普通的旧列表和流融合列表。内存/cpu 泄漏仍然存在。要查看代码是否适用于旧列表,只需删除“Data.List”之后的 Prelude 导入和“.Stream”即可。
【问题讨论】:
-
基本上,列表无论如何都不适合音频样本。它有点工作,但永远不要指望性能足以应付任何不平凡的事情。 (虽然the trivial stuff can already be quite exciting...)
-
确实,但是当我到达那座桥时,我想越过它。如果我使用普通的旧列表,则性能实际上就其在硬件级别上的实际表现而言相当不错:) 在实际设置中,单个振荡器的 1-2% CPU 是可以的,因为昂贵的合成器程序可能会吃掉一两个一个简单的合成器补丁的核心。 (是的,它们写得不好。无论如何。)我实际上可以只使用列表来运行 128 阶 FIR(好吧,也许是系数的序列),所以只要这些东西是实验性的,性能对我来说还可以。感谢您的链接,非常好! :)
-
真的,128 阶 FIR?所以你直接在列表上运行 FFT,比如user3407776 does?我的意思是,很酷,GHC 可以如此快速地制作列表,但我可以理解为什么!如果使用紧密的数组块,您将获得更好的性能,而且 Haskell 的类型系统非常能够将它们抽象出来,因此它看起来和使用列表一样好。
-
正如我所说的,我会越过那座桥。这与性能无关,这只是一个实验。这个问题特别是关于我的代码或流融合库中可能存在的错误。目前我对其他方面不感兴趣。
标签: haskell memory-leaks cpu-usage stream-fusion