-- | Read and write NeXT/Sun format sound files.
module Sound.File.NeXT ( FrameCount,SampleRate,ChannelCount,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.Coding.Byte
import Sound.File.Decode
import Sound.File.Encode
import Sound.File.Encoding

type Offset = Int64

-- | Sample rate at 'Header'.
type SampleRate = Int

-- | Number of frames at 'Header'.
type FrameCount = Int

-- | Number of channels at 'Header'.
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) =
    let nb = nf * nc * sizeOf enc
        h = [magic, 28, nb, (fromEncoding enc), sr, nc, 0]
    in B.concat (map encode_i32 h)

-- | Byte-decode a NeXT header.
decodeHeader :: B.ByteString -> Maybe (Offset, Header)
decodeHeader u =
    let 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)
    in if m == magic
       then Just (off, Header nf enc sr nc)
       else Nothing

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

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

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)