Making Haskell code more idiomatic (440 Hz tone)
audio, haskell
Solution
Nothing really bad here. `foldl1 (>>) [put i | i <- [0..numSamples-1]]` is equivalent to `mapM_ put [0 .. numSamples-1]`. Rate should just be a `Double`, that gets you rid of the `fromIntegral`s.
`Data.Binary.Put` is really fine for binary output. One might question whether it's good to write the samples to the monad right away (it might be more flexible to keep them as directly-accessible floating-point values in some suitable container (like chunks of `Data.Vector.Storable`) and only `put` them from some generic function right in the end), but performance-wise your approach is actually very efficient. And since it's not `IO` your using, you can always get the data back in a safe, pure way.
Problem
Here's what I have. It generates a 5 second Au file with a 440 Hz sine wave, inspired by 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 ``` If you're running Linux, you can listen with `runghc tone.hs | aplay`. For other operating systems, you can probably redirect output to a `.au` file and play it in an audio player. How can I make this code more idiomatic? For example: - I wrote `fromIntegral` all over the place. Could I have avoided that? - Should/could I be using a different package for outputting the binary data? - Am I using reasonable types?