2012-11-18 49 views
4

這就是我所擁有的。它產生一個5秒鐘的Au file,帶有一個440赫茲的正弦波,受this question啓發。使Haskell代碼更習慣(440赫茲音)

-- 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到處都是。我可以避免這種情況嗎?
  • 應該/我可以使用不同的包輸出二進制數據?
  • 我是否使用合理的類型?

回答

5

這裏沒什麼不好。 foldl1 (>>) [put i | i <- [0..numSamples-1]]相當於mapM_ put [0 .. numSamples-1]。費率應該只是Double,這可以讓你擺脫fromIntegral s。

Data.Binary.Put對二進制輸出來說確實很好。有人可能會質疑是否將樣本立即寫入monad是很好的做法(將它們作爲直接可訪問的浮點值放入某些合適的容器(如塊大小爲Data.Vector.Storable),並且只有put來自某些泛型函數正確的結果),但在性能方面,您的方法實際上非常高效。而且,由於它不是您使用的IO,您始終可以以安全,純淨的方式獲取數據。

2

您可以使用類型檢查,以幫助您刪除fromIntegral電話:

  1. 評論了您的類型簽名header
  2. 也註釋掉你main定義
  3. 代碼加載到ghci的
  4. 使用:t header來查看的型號簽名有什麼GHC。

這樣做收益率:

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

這意味着我們可以消除對ratebps參數fromIntegral,而事實上,這個定義的header typechecks:

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()類型。

重點是使用類型檢查器來幫助您瞭解函數可能具有的最一般類型簽名。

+0

使簽名更一般化並不一定是一個好主意:這通常會使GHC難以生成高效的代碼,這對音頻應用程序來說確實很重要。 – leftaroundabout