-- | Read and write NeXT/Sun format sound files.
module Sound.File.NeXT ( Header(..)
                       , header
                       , read
                       , write
                       , module Sound.File.Encoding ) where

import Prelude hiding (read)
import qualified Data.ByteString.Lazy as B
import Data.Int
import Data.Maybe
import Sound.OpenSoundControl.Byte
import Sound.File.Decode
import Sound.File.Encode
import Sound.File.Encoding

type Offset = Int64
type SampleRate = Int
type FrameCount = Int
type ChannelCount = Int

-- | Data type encapsulating sound file meta data.
data Header = Header { frameCount :: FrameCount
                     , encoding :: Encoding
                     , sampleRate :: SampleRate
                     , channelCount :: ChannelCount }
              deriving (Eq, Show)

toEncoding :: Int -> Encoding
toEncoding 2 = Linear8
toEncoding 3 = Linear16
toEncoding 5 = Linear32
toEncoding 6 = Float
toEncoding 7 = Double
toEncoding _ = undefined

fromEncoding :: Encoding -> Int
fromEncoding Linear8 = 2
fromEncoding Linear16 = 3
fromEncoding Linear32 = 5
fromEncoding Float = 6
fromEncoding Double = 7

-- | The NeXT header magic number.
magic :: Int
magic = 0x2e736e64

-- | Byte-encode a NeXT header.
encodeHeader :: Header -> B.ByteString
encodeHeader (Header nf enc sr nc) = B.concat (map encode_i32 h)
    where nb = nf * nc * sizeOf enc
          h = [magic, 28, nb, (fromEncoding enc), sr, nc, 0]

-- | Byte-decode a NeXT header.
decodeHeader :: B.ByteString -> Maybe (Offset, Header)
decodeHeader u = if m == magic then Just (off, Header nf enc sr nc) else Nothing
    where f n = decode_i32 (B.drop n u)
          m = f 0
          off = fromIntegral (f 4)
          nb = f 8
          enc = toEncoding (f 12)
          sr = f 16
          nc = f 20
          nf = nb `div` (nc * sizeOf enc)

writeB :: FilePath -> Header -> B.ByteString -> IO ()
writeB fn (Header nf enc sr nc) d = B.writeFile fn b
    where h = encodeHeader (Header nf enc sr nc)
          b = B.append h d

-- | Write sound file, data is non-interleaved.
write :: FilePath -> Header -> [[Double]] -> IO ()
write fn (Header nf enc sr nc) d = writeB fn (Header nf enc sr nc) b
    where b = encode enc d

readB :: FilePath -> IO (Header, B.ByteString)
readB fn = do b <- B.readFile fn
              let (off, h) = fromMaybe undefined (decodeHeader b)
              return (h, B.drop off b)

-- | Read sound file meta data.
header :: FilePath -> IO Header
header fn = do (h, _) <- read fn
               return h

-- | Read sound file, data is interleaved.
read :: FilePath -> IO (Header, [[Double]])
read fn = do (Header nf enc sr nc, b) <- readB fn
             let d = decode enc nc b
             return (Header nf enc sr nc, d)