packages feed

dahdit-audio-0.8.0: src/Dahdit/Audio/Riff.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}

module Dahdit.Audio.Riff
  ( labelRiff
  , labelList
  , listChunkHeaderSize
  , Chunk (..)
  , KnownChunk (..)
  , ListChunkBody (..)
  , ListChunk (..)
  , KnownListChunk (..)
  , KnownOptChunk (..)
  , ChunkLabel (..)
  , peekChunkLabel
  )
where

import Control.Monad (unless)
import Dahdit
  ( Binary (..)
  , ByteCount
  , Get
  , StaticByteSized (..)
  , byteSizeFoldable
  , getExact
  , getLookAhead
  , getRemainingSeq
  , getSkip
  , putSeq
  , putWord8
  )
import Dahdit.Audio.Common
  ( ChunkHeaderSize
  , KnownLabel (..)
  , Label
  , LabelSize
  , PadCount
  , chunkHeaderSize
  , countSize
  , getChunkSizeLE
  , getExpectLabel
  , labelSize
  , padCount
  , putChunkSizeLE
  )
import Data.Default (Default)
import Data.Proxy (Proxy (..))
import Data.Sequence (Seq)
import GHC.TypeLits (Nat, type (+))

labelRiff, labelList :: Label
labelRiff = "RIFF"
labelList = "LIST"

type ListChunkHeaderSize = (ChunkHeaderSize + LabelSize) :: Nat

listChunkHeaderSize :: ByteCount
listChunkHeaderSize = chunkHeaderSize + labelSize

data Chunk a = Chunk
  { chunkLabel :: !Label
  , chunkBody :: !a
  }
  deriving stock (Eq, Show)

chunkUnpaddedByteSize :: (Binary a) => Chunk a -> ByteCount
chunkUnpaddedByteSize (Chunk _ body) = byteSize body

instance (StaticByteSized a) => StaticByteSized (Chunk a) where
  type StaticSize (Chunk a) = PadCount (ChunkHeaderSize + StaticSize a)

instance (Binary a) => Binary (Chunk a) where
  byteSize c = padCount (chunkHeaderSize + chunkUnpaddedByteSize c)
  get = do
    lab <- get
    usz <- getChunkSizeLE
    body <- getExact usz get
    unless (even usz) (getSkip 1)
    pure $! Chunk lab body
  put c@(Chunk lab body) = do
    put lab
    let !usz = chunkUnpaddedByteSize c
    putChunkSizeLE usz
    put body
    unless (even usz) (putWord8 0)

newtype KnownChunk a = KnownChunk
  { knownChunkBody :: a
  }
  deriving stock (Show)
  deriving newtype (Eq, Default)

knownChunkUnpaddedByteSize :: (Binary a) => KnownChunk a -> ByteCount
knownChunkUnpaddedByteSize (KnownChunk body) = byteSize body

instance (StaticByteSized a) => StaticByteSized (KnownChunk a) where
  type StaticSize (KnownChunk a) = PadCount (ChunkHeaderSize + StaticSize a)

instance (Binary a, KnownLabel a) => Binary (KnownChunk a) where
  byteSize c = padCount (chunkHeaderSize + knownChunkUnpaddedByteSize c)
  get = do
    getExpectLabel (knownLabel (Proxy :: Proxy a))
    usz <- getChunkSizeLE
    body <- getExact usz get
    unless (even usz) (getSkip 1)
    pure $! KnownChunk body
  put kc@(KnownChunk body) = do
    put (knownLabel (Proxy :: Proxy a))
    let !usz = knownChunkUnpaddedByteSize kc
    putChunkSizeLE usz
    put body
    unless (even usz) (putWord8 0)

data ListChunkBody a = ListChunkBody
  { lcbLabel :: !Label
  , lcbItems :: !(Seq a)
  }
  deriving stock (Eq, Show)

instance (Binary a) => Binary (ListChunkBody a) where
  byteSize (ListChunkBody _ items) = labelSize + byteSizeFoldable items
  get = do
    label <- get
    items <- getRemainingSeq get
    pure $! ListChunkBody label items
  put (ListChunkBody label items) = do
    put label
    putSeq put items

instance KnownLabel (ListChunkBody a) where
  knownLabel _ = labelList

newtype ListChunk a = ListChunk {unListChunk :: KnownChunk (ListChunkBody a)}
  deriving stock (Show)
  deriving newtype (Eq, Binary)

newtype KnownListChunk a = KnownListChunk
  { klcItems :: Seq a
  }
  deriving stock (Show)
  deriving newtype (Eq, Default)

instance (Binary a, KnownLabel a) => Binary (KnownListChunk a) where
  byteSize (KnownListChunk body) = padCount (listChunkHeaderSize + byteSizeFoldable body)
  get = do
    getExpectLabel labelList
    usz <- getChunkSizeLE
    items <- getExact usz $ do
      getExpectLabel (knownLabel (Proxy :: Proxy a))
      getRemainingSeq get
    unless (even usz) (getSkip 1)
    pure $! KnownListChunk items
  put (KnownListChunk items) = do
    put labelList
    let !usz = labelSize + byteSizeFoldable items
    putChunkSizeLE usz
    put (knownLabel (Proxy :: Proxy a))
    putSeq put items
    unless (even usz) (putWord8 0)

newtype KnownOptChunk a = KnownOptChunk
  { kocItem :: Maybe a
  }
  deriving stock (Show)
  deriving newtype (Eq, Default)

instance (Binary a, KnownLabel a) => Binary (KnownOptChunk a) where
  byteSize (KnownOptChunk item) = padCount (listChunkHeaderSize + byteSizeFoldable item)
  get = do
    getExpectLabel labelList
    usz <- getChunkSizeLE
    item <- getExact usz $ do
      let !label = knownLabel (Proxy :: Proxy a)
      getExpectLabel label
      if usz == labelSize
        then pure Nothing
        else fmap Just get
    unless (even usz) (getSkip 1)
    pure $! KnownOptChunk item
  put (KnownOptChunk item) = do
    put labelList
    let !usz = labelSize + byteSizeFoldable item
    putChunkSizeLE usz
    put (knownLabel (Proxy :: Proxy a))
    maybe (pure ()) put item
    unless (even usz) (putWord8 0)

data ChunkLabel
  = ChunkLabelSingle !Label
  | ChunkLabelList !Label
  deriving stock (Eq, Show)

peekChunkLabel :: Get ChunkLabel
peekChunkLabel = getLookAhead $ do
  label <- get
  if label == labelList
    then getSkip countSize *> fmap ChunkLabelList get
    else pure (ChunkLabelSingle label)