Making Haskell code more idiomatic (440 Hz)

Here is what I have. It generates 5 seconds 440Hz sine wave Au file , 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 are using Linux, you can listen with runghc tone.hs | aplay runghc tone.hs | aplay . For other operating systems, you can redirect the output to a .au file and play it in an audio player.

How can I make this code more idiomatic? For instance:

  • I wrote fromIntegral everywhere. Can i avoid this?
  • Should / should I use another package to output binary data?
  • Am I using reasonable types?
+4
source share
2 answers

There is nothing wrong with that. foldl1 (>>) [put i | i <- [0..numSamples-1]] foldl1 (>>) [put i | i <- [0..numSamples-1]] equivalent to mapM_ put [0 .. numSamples-1] . Rate should be just Double , which will save you from fromIntegral s.

Data.Binary.Put great for binary output. One may ask whether it is good to write samples to the monad right away (it might be more flexible to store them as available floating point values ​​in some suitable container (for example, pieces of Data.Vector.Storable ) and only put them from some common function in the end) but in terms of effectiveness, your approach is actually very effective. And since you are not using IO , you can always return data in a safe, clean way.

+5
source

You can use a type checker to help you remove fromIntegral calls:

  • Comment out your type signature for header
  • Also comment out the definition of main
  • Download the code in ghci
  • Use :t header to find out if GHC is suitable for a signature like header .

Doing this gives:

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

This suggests that we can remove fromIntegral by the rate and bps parameters, and indeed, this is the definition of the 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 

and type now:

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

Note that we still have fromIntegral at rate , which we could eliminate using floor , for example:

  putWord32be $ floor rate 

which changes the header type to RealFrac a => a -> a -> a -> PutM () .

The main thing is to use a type checker to help you understand what the most common type signature can have a function.

+2
source

Source: https://habr.com/ru/post/1446739/


All Articles