4

这就是我所拥有的。受此问题的启发,它会生成一个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

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

我怎样才能使这段代码更地道?例如:

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

2 回答 2

5

这里没有什么不好的。foldl1 (>>) [put i | i <- [0..numSamples-1]]相当于mapM_ put [0 .. numSamples-1]。Rate 应该只是 a Double,这样你就可以摆脱fromIntegrals 了。

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

于 2012-11-18T11:27:32.807 回答
2

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

  1. 注释掉你的类型签名header
  2. 还要注释掉你的main定义
  3. 将代码加载到 ghci
  4. 用于:t header查看 GHC 为header.

这样做会产生:

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

这表明我们可以删除fromIntegralonratebps参数,实际上,这个类型检查的定义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 ()

请注意,我们仍然有一个 fromIntegral rate,我们可以通过使用来消除它floor,例如:

  putWord32be $ floor rate

这将类型更改headerRealFrac a => a -> a -> a -> PutM ()

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

于 2012-11-18T17:18:21.460 回答