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 は単に ,である必要があり、これにより sDoubleが取り除かれますfromIntegral

Data.Binary.Putバイナリ出力には本当に問題ありません。サンプルをすぐにモナドに書き込むのが良いかどうか疑問に思うかもしれません (直接アクセス可能な浮動小数点値として適切なコンテナー (のチャンクなど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 ()

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

のタイプを に変更headerRealFrac a => a -> a -> a -> PutM ()ます。

要点は、型チェッカーを使用して、関数が持つことができる最も一般的な型シグネチャを理解するのに役立つことです。

于 2012-11-18T17:18:21.460 に答える