这就是我所拥有的。它会产生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
到处都是。我可以避免吗?答案 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
来电:
header
main
定义:t header
查看GHC为header
的类型签名提出的内容。这样做会产生:
*Main> :t header
header
:: (Integral a1, Integral a2, RealFrac a) =>
a -> a2 -> a1 -> PutM ()
这表明我们可以删除fromIntegral
和rate
参数上的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 ()
。
重点是使用类型检查器来帮助您了解函数可能具有的最常见类型签名。