packages feed

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

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}

module Dahdit.Audio.Sfont
  ( Sfont (..)
  , InfoChunk (..)
  , PdtaChunk (..)
  , SdtaChunk (..)
  , Info (..)
  , Sdta (..)
  , PdtaCat (..)
  , PdtaBlock (..)
  , Pdta (..)
  , Phdr (..)
  , Bag (..)
  , Mod (..)
  , Gen (..)
  , GenPair (..)
  , GenTag (..)
  , defGenVal
  , lookupGenVal
  , Inst (..)
  , Shdr (..)
  , buildPdta
  , labelSfbk
  )
where

import Control.Monad (unless)
import Dahdit
  ( Binary (..)
  , ByteCount (..)
  , Get
  , Int16LE (..)
  , Put
  , ShortByteString
  , StaticByteSized (..)
  , StaticBytes
  , TermBytes16
  , ViaStaticGeneric (..)
  , Word16LE
  , Word32LE
  , byteSizeViaStatic
  , getExact
  , getPrimArray
  , getRemainingSeq
  , getRemainingSize
  , getRemainingStaticSeq
  , getRemainingString
  , getSkip
  , putByteString
  , putPrimArray
  , putSeq
  )
import Dahdit.Audio.Common
  ( KnownLabel (..)
  , Label
  , chunkHeaderSize
  , getChunkSizeLE
  , getExpectLabel
  , labelSize
  , putChunkSizeLE
  )
import Dahdit.Audio.Riff (KnownListChunk, KnownOptChunk, labelRiff)
import Data.ByteString.Short qualified as BSS
import Data.Foldable (foldl')
import Data.Int (Int8)
import Data.Primitive.ByteArray (ByteArray (..), sizeofByteArray)
import Data.Primitive.PrimArray (PrimArray (..))
import Data.Proxy (Proxy (..))
import Data.Sequence (Seq (..))
import Data.Sequence qualified as Seq
import Data.Type.Equality (testEquality)
import Data.Word (Word8)
import GHC.Generics (Generic)
import Type.Reflection (TypeRep, Typeable, typeRep, (:~:) (..))

labelSfbk
  , labelInfo
  , labelIfil
  , labelIver
  , labelIsng
  , labelInam
  , labelIrom
  , labelIcrd
  , labelIeng
  , labelIprd
  , labelIcop
  , labelIcmt
  , labelIsft
  , labelSdta
  , labelSmpl
  , labelSm24
  , labelPdta
  , labelPhdr
  , labelPbag
  , labelPmod
  , labelPgen
  , labelInst
  , labelIbag
  , labelImod
  , labelIgen
  , labelShdr
    :: Label
labelSfbk = "sfbk"
labelInfo = "INFO"
labelIfil = "ifil"
labelIver = "iver"
labelIsng = "isng"
labelInam = "INAM"
labelIrom = "irom"
labelIcrd = "ICRD"
labelIeng = "IENG"
labelIprd = "IPRD"
labelIcop = "ICOP"
labelIcmt = "ICMT"
labelIsft = "ISFT"
labelSdta = "sdta"
labelSmpl = "smpl"
labelSm24 = "sm24"
labelPdta = "pdta"
labelPhdr = "phdr"
labelPbag = "pbag"
labelPmod = "pmod"
labelPgen = "pgen"
labelInst = "inst"
labelIbag = "ibag"
labelImod = "imod"
labelIgen = "igen"
labelShdr = "shdr"

newtype SampleCount = SampleCount {unSampleCount :: Word32LE}
  deriving stock (Show)
  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, StaticByteSized, Binary)

type ShortText = StaticBytes 20

data Sfont = Sfont
  { sfontInfo :: !InfoChunk
  , sfontSdta :: !SdtaChunk
  , sfontPdta :: !PdtaChunk
  }
  deriving stock (Eq, Show)

instance Binary Sfont where
  byteSize (Sfont info sdta pdta) = chunkHeaderSize + labelSize + byteSize info + byteSize sdta + byteSize pdta
  get = do
    getExpectLabel labelRiff
    chunkSize <- getChunkSizeLE
    getExact chunkSize $ do
      getExpectLabel labelSfbk
      info <- get
      sdta <- get
      pdta <- get
      pure $! Sfont info sdta pdta
  put sfont@(Sfont info sdta pdta) = do
    put labelRiff
    let !chunkSize = byteSize sfont - chunkHeaderSize
    putChunkSizeLE chunkSize
    put labelSfbk
    put info
    put sdta
    put pdta

newtype InfoChunk = InfoChunk {unInfoChunk :: KnownListChunk Info}
  deriving stock (Show)
  deriving newtype (Eq, Binary)

newtype PdtaChunk = PdtaChunk {unPdtaChunk :: KnownListChunk PdtaBlock}
  deriving stock (Show)
  deriving newtype (Eq, Binary)

data Info
  = InfoVersion !Word16LE !Word16LE
  | InfoTargetSoundEngine !TermBytes16
  | InfoBankName !TermBytes16
  | InfoRomName !TermBytes16
  | InfoRomVersion !Word16LE !Word16LE
  | InfoCreationDate !TermBytes16
  | InfoAuthors !TermBytes16
  | InfoIntendedProduct !TermBytes16
  | InfoCopyrightMessage !TermBytes16
  | InfoComments !TermBytes16
  | InfoUsedTools !TermBytes16
  | InfoReserved !Label !ShortByteString
  deriving stock (Eq, Show)

instance KnownLabel Info where
  knownLabel _ = labelInfo

whichLabelInfo :: Info -> Label
whichLabelInfo = \case
  InfoVersion _ _ -> labelIfil
  InfoRomVersion _ _ -> labelIver
  InfoTargetSoundEngine _ -> labelIsng
  InfoBankName _ -> labelInam
  InfoRomName _ -> labelIrom
  InfoCreationDate _ -> labelIcrd
  InfoAuthors _ -> labelIeng
  InfoIntendedProduct _ -> labelIprd
  InfoCopyrightMessage _ -> labelIcop
  InfoComments _ -> labelIcmt
  InfoUsedTools _ -> labelIsft
  InfoReserved l _ -> l

instance Binary Info where
  byteSize info =
    chunkHeaderSize + case info of
      InfoVersion _ _ -> 4
      InfoTargetSoundEngine z -> byteSize z
      InfoBankName z -> byteSize z
      InfoRomName z -> byteSize z
      InfoRomVersion _ _ -> 4
      InfoCreationDate z -> byteSize z
      InfoAuthors z -> byteSize z
      InfoIntendedProduct z -> byteSize z
      InfoCopyrightMessage z -> byteSize z
      InfoComments z -> byteSize z
      InfoUsedTools z -> byteSize z
      InfoReserved _ bs -> ByteCount (BSS.length bs)
  get = do
    label <- get
    chunkSize <- getChunkSizeLE
    getExact chunkSize $
      if
        | label == labelIfil -> do
            unless (chunkSize == 4) (fail "Bad ifil chunk size")
            w1 <- get
            w2 <- get
            pure $! InfoVersion w1 w2
        | label == labelIver -> do
            unless (chunkSize == 4) (fail "Bad iver chunk size")
            w1 <- get
            w2 <- get
            pure $! InfoRomVersion w1 w2
        | label == labelIsng -> fmap InfoTargetSoundEngine get
        | label == labelInam -> fmap InfoBankName get
        | label == labelIrom -> fmap InfoRomName get
        | label == labelIcrd -> fmap InfoCreationDate get
        | label == labelIeng -> fmap InfoAuthors get
        | label == labelIprd -> fmap InfoIntendedProduct get
        | label == labelIcop -> fmap InfoCopyrightMessage get
        | label == labelIcmt -> fmap InfoComments get
        | label == labelIsft -> fmap InfoUsedTools get
        | otherwise -> do
            bs <- getRemainingString
            pure $! InfoReserved label bs
  put info = do
    let !label = whichLabelInfo info
    put label
    let !chunkSize = byteSize info - chunkHeaderSize
    putChunkSizeLE chunkSize
    case info of
      InfoVersion w1 w2 -> put w1 *> put w2
      InfoTargetSoundEngine z -> put z
      InfoBankName z -> put z
      InfoRomName z -> put z
      InfoRomVersion w1 w2 -> put w1 *> put w2
      InfoCreationDate z -> put z
      InfoAuthors z -> put z
      InfoIntendedProduct z -> put z
      InfoCopyrightMessage z -> put z
      InfoComments z -> put z
      InfoUsedTools z -> put z
      InfoReserved _ bs -> putByteString bs

data Sdta = Sdta
  { sdtaHighBits :: !(PrimArray Int16LE)
  , sdtaLowBits :: !(Maybe (PrimArray Word8))
  }
  deriving stock (Eq, Show)

instance KnownLabel Sdta where
  knownLabel _ = labelSdta

getHighBits :: SampleCount -> Get (PrimArray Int16LE)
getHighBits numSamples = getPrimArray (Proxy :: Proxy Int16LE) (fromIntegral numSamples)

getLowBits :: SampleCount -> Get (PrimArray Word8)
getLowBits numSamples = getPrimArray (Proxy :: Proxy Word8) (fromIntegral numSamples)

instance Binary Sdta where
  byteSize (Sdta (PrimArray hba) mlow) = sizeHigh + sizeLow
   where
    sizeHigh = chunkHeaderSize + ByteCount (sizeofByteArray (ByteArray hba))
    sizeLow = maybe 0 (\(PrimArray lba) -> chunkHeaderSize + ByteCount (sizeofByteArray (ByteArray lba))) mlow

  get = do
    chunkSize <- getRemainingSize
    getExpectLabel labelSmpl
    highSize <- getChunkSizeLE
    let !numSamples = div (fromIntegral highSize) 2
    highBits <- getHighBits numSamples
    let !numExtra = chunkSize - highSize - chunkHeaderSize
    if
      | numExtra > 0 -> do
          getExpectLabel labelSm24
          lowSize <- getChunkSizeLE
          let !expectedSize = if even numSamples then numSamples else numSamples + 1
          unless (fromIntegral lowSize == expectedSize) (fail "invalid low sample size")
          lowBits <- getLowBits numSamples
          unless (even numSamples) (getSkip 1)
          pure $! Sdta highBits (Just lowBits)
      | numExtra == 0 -> pure $! Sdta highBits Nothing
      | otherwise -> fail "invalid sdata chunk/sample sizes"
  put (Sdta high@(PrimArray hba) mlow) = do
    put labelSmpl
    putChunkSizeLE (ByteCount (sizeofByteArray (ByteArray hba)))
    putPrimArray high
    case mlow of
      Nothing -> pure ()
      Just low@(PrimArray lba) -> do
        put labelSm24
        putChunkSizeLE (ByteCount (sizeofByteArray (ByteArray lba)))
        putPrimArray low

newtype SdtaChunk = SdtaChunk {unSdtaChunk :: KnownOptChunk Sdta}
  deriving stock (Show)
  deriving newtype (Eq, Binary)

data PdtaCat
  = PdtaCatPreset
  | PdtaCatInst
  deriving stock (Eq, Show)

data PdtaBlock
  = PdtaBlockPhdr !(Seq Phdr)
  | PdtaBlockBag !PdtaCat !(Seq Bag)
  | PdtaBlockMod !PdtaCat !(Seq Mod)
  | PdtaBlockGen !PdtaCat !(Seq Gen)
  | PdtaBlockInst !(Seq Inst)
  | PdtaBlockShdr !(Seq Shdr)
  deriving stock (Eq, Show)

instance KnownLabel PdtaBlock where
  knownLabel _ = labelPdta

whichLabelPdtaBlock :: PdtaBlock -> Label
whichLabelPdtaBlock = \case
  PdtaBlockPhdr _ -> labelPhdr
  PdtaBlockBag pc _ -> case pc of
    PdtaCatPreset -> labelPbag
    PdtaCatInst -> labelIbag
  PdtaBlockMod pc _ -> case pc of
    PdtaCatPreset -> labelPmod
    PdtaCatInst -> labelImod
  PdtaBlockGen pc _ -> case pc of
    PdtaCatPreset -> labelPgen
    PdtaCatInst -> labelIgen
  PdtaBlockInst _ -> labelInst
  PdtaBlockShdr _ -> labelShdr

instance Binary PdtaBlock where
  byteSize block = res
   where
    res =
      chunkHeaderSize + case block of
        PdtaBlockPhdr phdrs -> fromIntegral (Seq.length phdrs) * sizePhdr
        PdtaBlockBag _ bags -> fromIntegral (Seq.length bags) * sizeBag
        PdtaBlockMod _ mods -> fromIntegral (Seq.length mods) * sizeMod
        PdtaBlockGen _ gens -> fromIntegral (Seq.length gens) * sizeGen
        PdtaBlockInst insts -> fromIntegral (Seq.length insts) * sizeInst
        PdtaBlockShdr shdrs -> fromIntegral (Seq.length shdrs) * sizeShdr
    sizePhdr = staticByteSize (Proxy :: Proxy Phdr)
    sizeBag = staticByteSize (Proxy :: Proxy Bag)
    sizeMod = staticByteSize (Proxy :: Proxy Mod)
    sizeGen = staticByteSize (Proxy :: Proxy Gen)
    sizeInst = staticByteSize (Proxy :: Proxy Inst)
    sizeShdr = staticByteSize (Proxy :: Proxy Shdr)

  get = do
    label <- get
    chunkSize <- getChunkSizeLE
    getExact chunkSize $
      if
        | label == labelPhdr ->
            fmap PdtaBlockPhdr (getRemainingStaticSeq get)
        | label == labelPbag ->
            fmap (PdtaBlockBag PdtaCatPreset) (getRemainingStaticSeq get)
        | label == labelPmod ->
            fmap (PdtaBlockMod PdtaCatPreset) (getRemainingStaticSeq get)
        | label == labelPgen ->
            fmap (PdtaBlockGen PdtaCatPreset) (getRemainingStaticSeq get)
        | label == labelInst ->
            fmap PdtaBlockInst (getRemainingSeq get)
        | label == labelIbag ->
            fmap (PdtaBlockBag PdtaCatInst) (getRemainingStaticSeq get)
        | label == labelImod ->
            fmap (PdtaBlockMod PdtaCatInst) (getRemainingStaticSeq get)
        | label == labelIgen ->
            fmap (PdtaBlockGen PdtaCatInst) (getRemainingStaticSeq get)
        | label == labelShdr ->
            fmap PdtaBlockShdr (getRemainingStaticSeq get)
        | otherwise ->
            fail ("unrecognized pdta elem: " ++ show label)
  put block = do
    let !label = whichLabelPdtaBlock block
    put label
    let !chunkSize = byteSize block - chunkHeaderSize
    putChunkSizeLE chunkSize
    case block of
      PdtaBlockPhdr phdrs -> putSeq put phdrs
      PdtaBlockBag _ bags -> putSeq put bags
      PdtaBlockMod _ mods -> putSeq put mods
      PdtaBlockGen _ gens -> putSeq put gens
      PdtaBlockInst insts -> putSeq put insts
      PdtaBlockShdr shdrs -> putSeq put shdrs

-- | Preset header
data Phdr = Phdr
  { phdrPresetName :: !ShortText
  , phdrPreset :: !Word16LE
  , phdrBank :: !Word16LE
  , phdrPresetBagIndex :: !Word16LE
  , phdrLibrary :: !Word32LE
  , phdrGenre :: !Word32LE
  , phdrMorphology :: !Word32LE
  }
  deriving stock (Eq, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Phdr)

data Bag = Bag
  { bagGenIndex :: !Word16LE
  , bagModIndex :: !Word16LE
  }
  deriving stock (Eq, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Bag)

-- | Modulator
data Mod = Mod
  { modSrcOper :: !Word16LE
  , modDestOper :: !Word16LE
  , modAmount :: !Int16LE
  , modAmtSrcOper :: !Word16LE
  , modTransOper :: !Word16LE
  }
  deriving stock (Eq, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Mod)

data SampleMode
  = SampleModeNoLoop !Int16LE
  | SampleModeContLoop
  | SampleModePressLoop
  deriving stock (Eq, Ord, Show)

instance StaticByteSized SampleMode where
  type StaticSize SampleMode = 2
  staticByteSize _ = 2

instance Binary SampleMode where
  byteSize = byteSizeViaStatic
  get = do
    c <- get
    pure $! case c of
      1 -> SampleModeContLoop
      3 -> SampleModePressLoop
      _ -> SampleModeNoLoop c
  put sm =
    put $! case sm of
      SampleModeContLoop -> 1
      SampleModePressLoop -> 3
      SampleModeNoLoop c -> c

data Range = Range
  { rangeLo :: !Word8
  , rangeHi :: !Word8
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Range)

data ReservedGen = ReservedGen
  { reservedGenTag :: !Word16LE
  , reservedGetVal :: !Int16LE
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric ReservedGen)

-- | Instrument
data Inst = Inst
  { instName :: !ShortText
  , instBagIndex :: !Word16LE
  }
  deriving stock (Eq, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Inst)

-- | Sample header
data Shdr = Shdr
  { shdrSampleName :: !ShortText
  , shdrStart :: !Word32LE
  , shdrEnd :: !Word32LE
  , shdrStartLoop :: !Word32LE
  , shdrEndLoop :: !Word32LE
  , shdrSampleRate :: !Word32LE
  , shdrOriginalPitch :: !Word8
  , shdrPitchCorrection :: !Int8
  , shdrSampleLink :: !Word16LE
  , shdrSampleType :: !Word16LE
  }
  deriving stock (Eq, Show, Generic)
  deriving (StaticByteSized, Binary) via (ViaStaticGeneric Shdr)

data Pdta = Pdta
  { pdtaPhdrs :: !(Seq Phdr)
  -- ^ Preset headers
  , pdtaPbags :: !(Seq Bag)
  -- ^ Preset bags
  , pdtaPmods :: !(Seq Mod)
  -- ^ Preset modulators
  , pdtaPgens :: !(Seq Gen)
  -- ^ Preset generators
  , pdtaInsts :: !(Seq Inst)
  -- ^ Instrument names
  , pdtaIbags :: !(Seq Bag)
  -- ^ Instrument bags
  , pdtaImods :: !(Seq Mod)
  -- ^ Instrument modulators
  , pdtaIgens :: !(Seq Gen)
  -- ^ Instrument generators
  , pdtaShdrs :: !(Seq Shdr)
  -- ^ Sample headers
  }
  deriving stock (Eq, Show)

emptyPdta :: Pdta
emptyPdta = Pdta Empty Empty Empty Empty Empty Empty Empty Empty Empty

buildPdta :: Seq PdtaBlock -> Pdta
buildPdta = foldl' go emptyPdta
 where
  go p = \case
    PdtaBlockPhdr phdrs -> p {pdtaPhdrs = pdtaPhdrs p <> phdrs}
    PdtaBlockBag cat bags -> case cat of
      PdtaCatPreset -> p {pdtaPbags = pdtaPbags p <> bags}
      PdtaCatInst -> p {pdtaIbags = pdtaIbags p <> bags}
    PdtaBlockMod cat mods -> case cat of
      PdtaCatPreset -> p {pdtaPmods = pdtaPmods p <> mods}
      PdtaCatInst -> p {pdtaImods = pdtaImods p <> mods}
    PdtaBlockGen cat gens -> case cat of
      PdtaCatPreset -> p {pdtaPgens = pdtaPgens p <> gens}
      PdtaCatInst -> p {pdtaIgens = pdtaIgens p <> gens}
    PdtaBlockInst insts -> p {pdtaInsts = pdtaInsts p <> insts}
    PdtaBlockShdr shdrs -> p {pdtaShdrs = pdtaShdrs p <> shdrs}

-- | Tags for generators
data GenTag a where
  GenTagStartAddressOffset :: GenTag Int16LE
  GenTagEndAddressOffset :: GenTag Int16LE
  GenTagLoopStartAddressOffset :: GenTag Int16LE
  GenTagLoopEndAddressOffset :: GenTag Int16LE
  GenTagStartAddressCoarseOffset :: GenTag Int16LE
  GenTagModLfoToPitch :: GenTag Int16LE
  GenTagVibLfoToPitch :: GenTag Int16LE
  GenTagModEnvToPitch :: GenTag Int16LE
  GenTagInitFc :: GenTag Int16LE
  GenTagInitQ :: GenTag Int16LE
  GenTagModLfoToFc :: GenTag Int16LE
  GenTagModEnvToFc :: GenTag Int16LE
  GenTagEndAddressCoarseOffset :: GenTag Int16LE
  GenTagModLfoToVol :: GenTag Int16LE
  GenTagChorus :: GenTag Int16LE
  GenTagReverb :: GenTag Int16LE
  GenTagPan :: GenTag Int16LE
  GenTagDelayModLfo :: GenTag Int16LE
  GenTagFreqModLfo :: GenTag Int16LE
  GenTagDelayVibLfo :: GenTag Int16LE
  GenTagFreqVibLfo :: GenTag Int16LE
  GenTagDelayModEnv :: GenTag Int16LE
  GenTagAttackModEnv :: GenTag Int16LE
  GenTagHoldModEnv :: GenTag Int16LE
  GenTagDecayModEnv :: GenTag Int16LE
  GenTagSustainModEnv :: GenTag Int16LE
  GenTagReleaseModEnv :: GenTag Int16LE
  GenTagKeyToModEnvHold :: GenTag Int16LE
  GenTagKeyToModEnvDecay :: GenTag Int16LE
  GenTagDelayVolEnv :: GenTag Int16LE
  GenTagAttackVolEnv :: GenTag Int16LE
  GenTagHoldVolEnv :: GenTag Int16LE
  GenTagDecayVolEnv :: GenTag Int16LE
  GenTagSustainVolEnv :: GenTag Int16LE
  GenTagReleaseVolEnv :: GenTag Int16LE
  GenTagKeyToVolEnvHold :: GenTag Int16LE
  GenTagKeyToVolEnvDecay :: GenTag Int16LE
  GenTagInstIndex :: GenTag Word16LE
  GenTagKeyRange :: GenTag Range
  GenTagVelRange :: GenTag Range
  GenTagLoopStartAddressCoarseOffset :: GenTag Int16LE
  GenTagKey :: GenTag Word16LE
  GenTagVel :: GenTag Word16LE
  GenTagInitAtten :: GenTag Int16LE
  GenTagLoopEndAddressCoarseOffset :: GenTag Int16LE
  GenTagCoarseTune :: GenTag Int16LE
  GenTagFineTune :: GenTag Int16LE
  GenTagSampleIndex :: GenTag Word16LE
  GenTagSampleMode :: GenTag SampleMode
  GenTagScaleTuning :: GenTag Int16LE
  GenTagExclusiveClass :: GenTag Int16LE
  GenTagRootKey :: GenTag Word16LE
  GenTagReserved :: Word16LE -> GenTag Int16LE

deriving instance Eq (GenTag a)

deriving instance Ord (GenTag a)

deriving instance Show (GenTag a)

genTagRep :: GenTag a -> Word16LE
genTagRep = \case
  GenTagStartAddressOffset -> 0
  GenTagEndAddressOffset -> 1
  GenTagLoopStartAddressOffset -> 2
  GenTagLoopEndAddressOffset -> 3
  GenTagStartAddressCoarseOffset -> 4
  GenTagModLfoToPitch -> 5
  GenTagVibLfoToPitch -> 6
  GenTagModEnvToPitch -> 7
  GenTagInitFc -> 8
  GenTagInitQ -> 9
  GenTagModLfoToFc -> 10
  GenTagModEnvToFc -> 11
  GenTagEndAddressCoarseOffset -> 12
  GenTagModLfoToVol -> 13
  GenTagChorus -> 15
  GenTagReverb -> 16
  GenTagPan -> 17
  GenTagDelayModLfo -> 21
  GenTagFreqModLfo -> 22
  GenTagDelayVibLfo -> 23
  GenTagFreqVibLfo -> 24
  GenTagDelayModEnv -> 25
  GenTagAttackModEnv -> 26
  GenTagHoldModEnv -> 27
  GenTagDecayModEnv -> 28
  GenTagSustainModEnv -> 29
  GenTagReleaseModEnv -> 30
  GenTagKeyToModEnvHold -> 31
  GenTagKeyToModEnvDecay -> 32
  GenTagDelayVolEnv -> 33
  GenTagAttackVolEnv -> 34
  GenTagHoldVolEnv -> 35
  GenTagDecayVolEnv -> 36
  GenTagSustainVolEnv -> 37
  GenTagReleaseVolEnv -> 38
  GenTagKeyToVolEnvHold -> 39
  GenTagKeyToVolEnvDecay -> 40
  GenTagInstIndex -> 41
  GenTagKeyRange -> 43
  GenTagVelRange -> 44
  GenTagLoopStartAddressCoarseOffset -> 45
  GenTagKey -> 46
  GenTagVel -> 47
  GenTagInitAtten -> 48
  GenTagLoopEndAddressCoarseOffset -> 50
  GenTagCoarseTune -> 51
  GenTagFineTune -> 52
  GenTagSampleIndex -> 53
  GenTagSampleMode -> 54
  GenTagScaleTuning -> 56
  GenTagExclusiveClass -> 57
  GenTagRootKey -> 58
  GenTagReserved t -> t

data GenPair a = GenPair
  { genPairTag :: !(GenTag a)
  , genPairVal :: !a
  }
  deriving stock (Eq, Ord, Show)

putGenPair :: (Binary a) => GenPair a -> Put
putGenPair (GenPair tag val) = put (genTagRep tag) *> put val

data Gen
  = GenInt !(GenPair Int16LE)
  | GenWord !(GenPair Word16LE)
  | GenRange !(GenPair Range)
  | GenSampleMode !(GenPair SampleMode)
  deriving stock (Eq, Ord, Show)

getGenInt :: GenTag Int16LE -> Get Gen
getGenInt tag = fmap (GenInt . GenPair tag) get

getGenWord :: GenTag Word16LE -> Get Gen
getGenWord tag = fmap (GenWord . GenPair tag) get

getGenRange :: GenTag Range -> Get Gen
getGenRange tag = fmap (GenRange . GenPair tag) get

getGenSampleMode :: GenTag SampleMode -> Get Gen
getGenSampleMode tag = fmap (GenSampleMode . GenPair tag) get

instance StaticByteSized Gen where
  type StaticSize Gen = 4
  staticByteSize _ = 4

instance Binary Gen where
  byteSize = byteSizeViaStatic
  get = do
    tag <- get
    case tag of
      0 -> getGenInt GenTagStartAddressOffset
      1 -> getGenInt GenTagEndAddressOffset
      2 -> getGenInt GenTagLoopStartAddressOffset
      3 -> getGenInt GenTagLoopEndAddressOffset
      4 -> getGenInt GenTagStartAddressCoarseOffset
      5 -> getGenInt GenTagModLfoToPitch
      6 -> getGenInt GenTagVibLfoToPitch
      7 -> getGenInt GenTagModEnvToPitch
      8 -> getGenInt GenTagInitFc
      9 -> getGenInt GenTagInitQ
      10 -> getGenInt GenTagModLfoToFc
      11 -> getGenInt GenTagModEnvToFc
      12 -> getGenInt GenTagEndAddressCoarseOffset
      13 -> getGenInt GenTagModLfoToVol
      15 -> getGenInt GenTagChorus
      16 -> getGenInt GenTagReverb
      17 -> getGenInt GenTagPan
      21 -> getGenInt GenTagDelayModLfo
      22 -> getGenInt GenTagFreqModLfo
      23 -> getGenInt GenTagDelayVibLfo
      24 -> getGenInt GenTagFreqVibLfo
      25 -> getGenInt GenTagDelayModEnv
      26 -> getGenInt GenTagAttackModEnv
      27 -> getGenInt GenTagHoldModEnv
      28 -> getGenInt GenTagDecayModEnv
      29 -> getGenInt GenTagSustainModEnv
      30 -> getGenInt GenTagReleaseModEnv
      31 -> getGenInt GenTagKeyToModEnvHold
      32 -> getGenInt GenTagKeyToModEnvDecay
      33 -> getGenInt GenTagDelayVolEnv
      34 -> getGenInt GenTagAttackVolEnv
      35 -> getGenInt GenTagHoldVolEnv
      36 -> getGenInt GenTagDecayVolEnv
      37 -> getGenInt GenTagSustainVolEnv
      38 -> getGenInt GenTagReleaseVolEnv
      39 -> getGenInt GenTagKeyToVolEnvHold
      40 -> getGenInt GenTagKeyToVolEnvDecay
      41 -> getGenWord GenTagInstIndex
      43 -> getGenRange GenTagKeyRange
      44 -> getGenRange GenTagVelRange
      45 -> getGenInt GenTagLoopStartAddressCoarseOffset
      46 -> getGenWord GenTagKey
      47 -> getGenWord GenTagVel
      48 -> getGenInt GenTagInitAtten
      50 -> getGenInt GenTagLoopEndAddressCoarseOffset
      51 -> getGenInt GenTagCoarseTune
      52 -> getGenInt GenTagFineTune
      53 -> getGenWord GenTagSampleIndex
      54 -> getGenSampleMode GenTagSampleMode
      56 -> getGenInt GenTagScaleTuning
      57 -> getGenInt GenTagExclusiveClass
      58 -> getGenWord GenTagRootKey
      _ -> getGenInt (GenTagReserved tag)
  put = \case
    GenInt gp -> putGenPair gp
    GenWord gp -> putGenPair gp
    GenRange gp -> putGenPair gp
    GenSampleMode gp -> putGenPair gp

defGenVal :: GenTag a -> a
defGenVal = \case
  GenTagStartAddressOffset -> 0
  GenTagEndAddressOffset -> 0
  GenTagLoopStartAddressOffset -> 0
  GenTagLoopEndAddressOffset -> 0
  GenTagStartAddressCoarseOffset -> 0
  GenTagModLfoToPitch -> 0
  GenTagVibLfoToPitch -> 0
  GenTagModEnvToPitch -> 0
  GenTagInitFc -> 13500
  GenTagInitQ -> 0
  GenTagModLfoToFc -> 0
  GenTagModEnvToFc -> 0
  GenTagEndAddressCoarseOffset -> 0
  GenTagModLfoToVol -> 0
  GenTagChorus -> 0
  GenTagReverb -> 0
  GenTagPan -> 0
  GenTagDelayModLfo -> -12000
  GenTagFreqModLfo -> 0
  GenTagDelayVibLfo -> -12000
  GenTagFreqVibLfo -> 0
  GenTagDelayModEnv -> -12000
  GenTagAttackModEnv -> -12000
  GenTagHoldModEnv -> -12000
  GenTagDecayModEnv -> -12000
  GenTagSustainModEnv -> 0
  GenTagReleaseModEnv -> -12000
  GenTagKeyToModEnvHold -> 0
  GenTagKeyToModEnvDecay -> 0
  GenTagDelayVolEnv -> -12000
  GenTagAttackVolEnv -> -12000
  GenTagHoldVolEnv -> -12000
  GenTagDecayVolEnv -> -12000
  GenTagSustainVolEnv -> 0
  GenTagReleaseVolEnv -> -12000
  GenTagKeyToVolEnvHold -> 0
  GenTagKeyToVolEnvDecay -> 0
  GenTagInstIndex -> 0
  GenTagKeyRange -> Range 0 127
  GenTagVelRange -> Range 0 127
  GenTagLoopStartAddressCoarseOffset -> 0
  GenTagKey -> -1
  GenTagVel -> -1
  GenTagInitAtten -> 0
  GenTagLoopEndAddressCoarseOffset -> 0
  GenTagCoarseTune -> 0
  GenTagFineTune -> 0
  GenTagSampleIndex -> 0
  GenTagSampleMode -> SampleModeNoLoop 0
  GenTagScaleTuning -> 100
  GenTagExclusiveClass -> 0
  GenTagRootKey -> -1
  GenTagReserved _ -> 0

genTagTyRep :: (Typeable a) => GenTag a -> TypeRep a
genTagTyRep _ = typeRep

genTagTest :: (Typeable a, Typeable b) => GenTag a -> GenTag b -> Maybe (a :~: b)
genTagTest t s = case testEquality (genTagTyRep t) (genTagTyRep s) of
  Nothing -> Nothing
  Just Refl -> if t == s then Just Refl else Nothing

lookupGenVal :: (Typeable a) => GenTag a -> Seq Gen -> Maybe a
lookupGenVal t = go
 where
  go = \case
    Empty -> Nothing
    gp :<| rest ->
      case gp of
        GenInt (GenPair s v) ->
          case genTagTest t s of
            Just Refl -> Just v
            Nothing -> go rest
        GenWord (GenPair s v) ->
          case genTagTest t s of
            Just Refl -> Just v
            Nothing -> go rest
        GenRange (GenPair s v) ->
          case genTagTest t s of
            Just Refl -> Just v
            Nothing -> go rest
        GenSampleMode (GenPair s v) ->
          case genTagTest t s of
            Just Refl -> Just v
            Nothing -> go rest