{-# OPTIONS -fno-implicit-prelude #-}
module Sox.File where

import qualified BinarySample as BinSmp
import qualified Sox          as Sox

import System.Cmd(rawSystem)
import System.Exit(ExitCode)
import Data.List(isSuffixOf)

import qualified Algebra.RealField          as RealField
import qualified Algebra.Field              as Field

import PreludeBase
import NumericPrelude


render :: (RealField.C a, BinSmp.C v) =>
   FilePath -> a -> (a -> [v]) -> IO ExitCode
render fileName sampleRate renderer =
   write fileName sampleRate (renderer sampleRate)

renderMono :: (RealField.C a) =>
   FilePath -> a -> (a -> [a]) -> IO ExitCode
renderMono fileName sampleRate renderer =
   writeMono fileName sampleRate (renderer sampleRate)

renderStereo :: (RealField.C a) =>
   FilePath -> a -> (a -> [(a,a)]) -> IO ExitCode
renderStereo fileName sampleRate renderer =
   writeStereo fileName sampleRate (renderer sampleRate)


write :: (RealField.C a, BinSmp.C v) =>
   FilePath -> a -> [v] -> IO ExitCode
write fileName sampleRate signal =
   writeSignalRaw fileName [] sampleRate
      (BinSmp.numChannels (head signal))
      (BinSmp.signalToBinary signal)

writeMono :: (RealField.C a) =>
   FilePath -> a -> [a] -> IO ExitCode
writeMono fileName sampleRate signal =
   writeSignalRaw fileName []
      sampleRate 1 (BinSmp.signalToBinaryMono signal)

writeStereo :: (RealField.C a) =>
   FilePath -> a -> [(a,a)] -> IO ExitCode
writeStereo fileName sampleRate signal =
   writeSignalRaw fileName []
      sampleRate 2 (BinSmp.signalToBinaryStereo signal)


writeSignalRaw :: (RealField.C a) =>
   FilePath -> [String] -> a -> Int -> [Int] -> IO ExitCode
writeSignalRaw fileName soxOptions sampleRate numChannels stream =
   let fileNameRaw  = fileName ++ ".sw"
   in  do BinSmp.writeInt16Stream fileNameRaw stream
          rawToAIFF fileName soxOptions sampleRate numChannels
          encode fileName

rawToAIFF :: (RealField.C a) =>
   FilePath -> [String] -> a -> Int -> IO ExitCode
rawToAIFF fileName soxOptions sampleRate numChannels =
   let fileNameRaw  = fileName ++ ".sw"
       fileNameAIFF = fileName ++ ".aiff"
   in  rawSystem "sox"
          (soxOptions ++
           Sox.sampleRateOption sampleRate ++
           Sox.channelOption numChannels ++
           [fileNameRaw, fileNameAIFF])

encode :: FilePath -> IO ExitCode
encode fileName =
     let fileNameAIFF = fileName ++ ".aiff"
         --fileNameOGG  = fileName ++ ".ogg"
         fileNameMP3  = fileName ++ ".mp3"
     in do rawSystem "oggenc" ["--quality", "5", fileNameAIFF]
           rawSystem "lame"   ["-h", fileNameAIFF, fileNameMP3]


{- This implementation doesn't work properly.
   It seems like readFile is run
   after all system calls to Sox are performed.
   Aren't the calls serialized?
readAIFFMono :: (RealField.C a, Floating a) => FilePath -> IO [a]
readAIFFMono file =
   do putStrLn ("sox "++file++" /tmp/sample.sw")
      system ("sox "++file++" /tmp/sample.sw")
      str <- readFile "/tmp/sample.sw"
      return (binaryToSignalMono str)
-}
readAIFFMono :: (Field.C a) => FilePath -> IO [a]
readAIFFMono file =
   let stem = if isSuffixOf ".aiff" file
                then take (length file - 5) file
                else file
       tmp  = stem ++ ".sw"
   in  do --putStrLn ("sox "++file++" "++tmp)
          rawSystem "sox" [file, tmp]
          fmap (map BinSmp.int16ToNum) (BinSmp.readInt16Stream tmp)