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

时间:2012-11-18 08:38:16

标签: haskell audio

这就是我所拥有的。它会产生5秒Au file,带有440 Hz正弦波,灵感来自this question

-- 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

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

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

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

2 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:2)

您可以使用类型检查器来帮助您移除fromIntegral来电:

  1. 注释掉header
  2. 的类型签名
  3. 同时注释掉您的main定义
  4. 将代码加载到ghci
  5. 使用:t header查看GHC为header的类型签名提出的内容。
  6. 这样做会产生:

    *Main> :t header
    header
      :: (Integral a1, Integral a2, RealFrac a) =>
         a -> a2 -> a1 -> PutM ()
    

    这表明我们可以删除fromIntegralrate参数上的bps,以及header类型检查的定义:

    header dur rate bps = do
      putLazyByteString $ BLC.pack ".snd"
      putWord32be 24
      putWord32be $ floor $ bps * dur * rate
      putWord32be 3
      putWord32be $ fromIntegral rate
      putWord32be 1
    

    现在的类型是:

    *Main> :t header
    header :: (Integral a, RealFrac a) => a -> a -> a -> PutM ()
    

    请注意,我们在rate上仍然有一个fromIntegral,我们可以使用floor消除这种情况,例如:

      putWord32be $ floor rate
    

    header的类型更改为RealFrac a => a -> a -> a -> PutM ()

    重点是使用类型检查器来帮助您了解函数可能具有的最常见类型签名。