-- | This module defines the **Alaw** audio sample type, as well as compading
-- conversion functions from/to 'S16' values.
module Data.MediaBus.Media.Audio.Raw.Alaw
  ( ALaw()
  , encodeALawSample
  , decodeALawSample
  , alawSample
  , alawValue
  ) where

import Control.Lens
import Control.DeepSeq (NFData)
import Data.Bits
import Data.Default
import Data.Function (on)
import Data.Int
import Data.MediaBus.Media.Audio.Raw
import Data.MediaBus.Media.Audio.Raw.Signed16bit
import Data.MediaBus.Media.Blank
import Data.Typeable
import Data.Word
import Foreign.Storable
import GHC.Generics (Generic)
import Test.QuickCheck (Arbitrary(..))

-- | A PCM audio sample represented by a single byte, that can be converted to a
-- signed 13bit audio sample.
newtype ALaw = MkALaw
  { _alawValue :: Word8
  } deriving ( Show
             , Storable
             , Num
             , Eq
             , Bits
             , Arbitrary
             , Generic
             , NFData
             , Default
             , Typeable
             )

-- | An 'Iso' for 'ALaw' sample values.
alawValue :: Iso' ALaw Word8
alawValue = iso _alawValue MkALaw

-- | An 'Iso' between 'ALaw' and 'S16' using 'encodeALawSample' and
-- 'decodeALawSample'.
alawSample :: Iso' ALaw S16
alawSample = iso decodeALawSample encodeALawSample

instance CanBeBlank ALaw where
  blank = 0xD5

instance IsPcmValue ALaw where
  pcmAverage !x !y = encodeALawSample $ (pcmAverage `on` decodeALawSample) x y

-- | Uncompress an alaw sample into a linear 16 signed value, see
-- 'encodeALawSample' for more information.
decodeALawSample :: ALaw -> S16
decodeALawSample (MkALaw !a') =
  let !a = a' `xor` 85
      !quant_mask = 15
      !quant_shift = 4
      !seg_mask = 112
      !seg_shift = 4
      tBase, tAbs, seg :: Int16
      !seg = (fromIntegral a .&. seg_mask) `shiftR` seg_shift
      !tBase = (fromIntegral a .&. quant_mask) `shiftL` quant_shift
      !tAbs =
        case seg of
          0 -> tBase + 8
          1 -> tBase + 264
          _ -> (tBase + 264) `shiftL` fromIntegral (seg - 1)
      !isPos = testBit a 7
  in MkS16 $
     if isPos
       then tAbs
       else tAbs * (-1)

-- | See http://opensource.apple.com//source/tcl/tcl-20/tcl_ext/snack/snack/generic/g711.c
--
-- >	Linear Input Code	 Compressed Code
-- >	-----------------  ---------------
-- >	  0000000wxyza         000wxyz
-- >	  0000001wxyza         001wxyz
-- >	  000001wxyzab         010wxyz
-- >	  00001wxyzabc         011wxyz
-- >	  0001wxyzabcd         100wxyz
-- >	  001wxyzabcde         101wxyz
-- >	  01wxyzabcdef         110wxyz
-- >	  1wxyzabcdefg         111wxyz
--
-- For further information see John C. Bellamy's Digital Telephony, 1982, John
-- Wiley & Sons, pps 98-111 and 472-476.
encodeALawSample :: S16 -> ALaw
encodeALawSample (MkS16 !pcmVal') =
  let !pcmVal = pcmVal' `shiftR` 3 -- to 13 bit
      (!mask, !pcmValAbs) =
        if pcmVal >= 0
          then ( 0xD5 -- sign bit (bit 7) = 1
               , pcmVal)
          else ( 0x55 -- sign bit = 0
               , (-1) * pcmVal - 1)
      !segment
        | pcmValAbs <= 31 = 0
        | pcmValAbs <= 63 = 1
        | pcmValAbs <= 127 = 2
        | pcmValAbs <= 255 = 3
        | pcmValAbs <= 511 = 4
        | pcmValAbs <= 1023 = 5
        | pcmValAbs <= 2047 = 6
        | pcmValAbs <= 4095 = 7
        | otherwise = 8
      !res =
        if segment == 8
          then 0x7F
          else let !segShift =
                     if segment < 2
                       then 1
                       else fromIntegral segment
               in shiftL segment 4 .|. (shiftR pcmValAbs segShift .&. 0xF)
  in MkALaw (fromIntegral res `xor` mask)