packages feed

wavy-0.1.0.0: src/Generate.hs

module Main where

import Sound.Wav
import Sound.Wav.ChannelData

import Data.List (find)
import System.Environment (getArgs)
import System.Console.GetOpt

import qualified Data.Vector as V
import qualified Data.ByteString.Lazy as BL

import SineGenerator

data Flag 
   = Version 
   | Help 
   | Frequency Double
   | Duration Integer
   deriving(Eq, Show)

options :: [OptDescr Flag]
options = 
   [ Option "h" ["help"] (NoArg Help) "show this help message"
   , Option "f" ["frequency"] (OptArg (Frequency . parseFrequency) "1000") "the frequency of the wave that will be generated"
   , Option "d" ["duration"] (OptArg (Duration . parseDuration) "5") "the duration the sine wave will play for"
   ]

parseFrequency :: Maybe String -> Double
parseFrequency Nothing      = 1000.0
parseFrequency (Just value) = read value

parseDuration :: Maybe String -> Integer
parseDuration Nothing      = 5
parseDuration (Just value) = read value

chosenSampleRate :: Integral a => a
chosenSampleRate = 8000

-- TODO I should provide a format generation function
format :: WaveFormat
format = WaveFormat
   { waveAudioFormat = MicrosoftPCM
   , waveNumChannels = 1
   , waveSampleRate = chosenSampleRate
   , waveByteRate = chosenSampleRate * 2
   , waveBlockAlignment = 2
   , waveBitsPerSample = 16
   }

-- TODO generating an empty wave file is easy but maybe we still require a template for it
waveFileTemplate :: WaveFile
waveFileTemplate = WaveFile
   { waveFormat = format
   , waveData = BL.empty
   , waveFact = Nothing
   , waveInfo = Just $ waveInfoDefault { creationSoftware = Just "wavy (Sine Generate)" }
   }

-- If people want to do this then they can. It is quite simple to do. But maybe it would
-- be a nice convinience method. Maybe I could put it in a module called convenience that
-- is not included by default.
toFloatingWaveData :: [Double] -> FloatingWaveData
toFloatingWaveData rawData = FloatingWaveData $ [V.fromList rawData]

finalWaveFile :: GenerateInfo -> WaveFile
finalWaveFile genInfo = encodeFloatingWaveData waveFileTemplate $ toFloatingWaveData (generateWave genInfo)

header = "Usage: wave-generate-sine <filename>"
usageMessage = usageInfo header options

main = do
   args <- getArgs
   case getOpt Permute options args of
      (flags, [filename], []) -> handleFlags flags filename
      (_, _, msgs@(x:_)) -> error $ concat msgs ++ usageMessage
      (_, [], _) -> do
         putStrLn "No output filename provided. Please provide one."
         putStrLn usageMessage
      (_, filenames@(x:_), _) -> do
         putStr "Too many filenames provided: "
         print filenames
         putStrLn "Just provide one output file name."
         putStrLn usageMessage

handleFlags :: [Flag] -> FilePath -> IO ()
handleFlags flags filename
   | Help `elem` flags = putStrLn usageMessage
   | otherwise = do
      putStr $ "Generating sine wave in file '" ++ filename ++ "'..."
      encodeWaveFile filename $ finalWaveFile genInfo
      putStrLn "[Done]"
      where
         genInfo = GenerateInfo
            { duration = selectedDuration
            , frequency = selectedFrequency
            , sampleRate = chosenSampleRate
            }

         selectedDuration = maybe 5 fromDuration $ find isDuration flags
         selectedFrequency = maybe 1000.0 fromFrequency $ find isFrequency flags

fromDuration (Duration x) = x
fromFrequency (Frequency x) = x

isDuration (Duration _) = True
isDuration _            = False

isFrequency (Frequency _) = True
isFrequency _             = False