使Haskell代码更加惯用(440 Hz音)

Sno*_*all 4 audio haskell

这就是我所拥有的.它受到这个问题的启发,产生一个带有440 Hz正弦波的5秒Au文件.

-- file: tone.hs

import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import Data.Binary.Put

-- au format header: https://en.wikipedia.org/wiki/Au_file_format
header :: Double -> Integer -> Integer -> Put
header dur rate bps = do
  putLazyByteString $ BLC.pack ".snd"
  putWord32be 24
  putWord32be $ fromIntegral $ floor $ fromIntegral bps * dur * fromIntegral rate
  putWord32be 3
  putWord32be $ fromIntegral rate
  putWord32be 1


-- audio sample data
samples :: Double -> Integer -> Integer -> Double -> Double -> Put
samples dur rate bps freq vol =
    foldl1 (>>) [put i | i <- [0..numSamples-1]]
  where
    numSamples = floor $ fromIntegral rate * dur
    scale i = 2 * pi * freq / fromIntegral rate * fromIntegral i
    sample i = vol * sin (scale i)
    coded samp = floor $ (2 ^ (8*bps-1) - 1) * samp
    put i = putWord16be $ coded $ sample i


freq = 440 :: Double    -- 440 Hz sine wave
dur = 5 :: Double       -- played for 5 seconds
rate = 44100 :: Integer -- at a 44.1 kHz sample rate
vol = 0.8 :: Double     -- with a peak amplitude of 0.8
bps = 2 :: Integer      -- at 16 bits (2 bytes) per sample

main =
    BL.putStr $ runPut au
  where
    au = do
      header dur rate bps
      samples dur rate bps freq vol
Run Code Online (Sandbox Code Playgroud)

如果你正在运行Linux,你可以听runghc tone.hs | aplay.对于其他操作系统,您可以将输出重定向到.au文件并在音频播放器中播放.

如何使这段代码更具惯用语?例如:

  • fromIntegral到处写了一遍.我可以避免吗?
  • 我应该/可以使用不同的包来输出二进制数据吗?
  • 我使用合理的类型吗?

lef*_*out 5

这里没什么不好的.foldl1 (>>) [put i | i <- [0..numSamples-1]]相当于mapM_ put [0 .. numSamples-1].速率应该是一个Double,让你摆脱fromIntegrals.

Data.Binary.Put二进制输出真的很好.有人可能会质疑将样本立即写入monad是否合适(将它们作为直接可访问的浮点值保存在某个合适的容器(如块Data.Vector.Storable)中可能会更灵活,并且只有put来自某些通用函数的最后),但在性能方面,你的方法实际上是非常有效的.由于它不是IO您的使用,您可以始终以安全,纯粹的方式获取数据.