module Nes.APU.State.Envelope (
    -- * Type
    Envelope (..),
    newEnvelope,

    -- * Type class
    HasEnvelope (..),
    withEnvelope,

    -- * Clock
    tickEnvelope,

    -- * Output
    getEnvelopeOutput,
) where

data Envelope = MkE
    { Envelope -> Bool
startFlag :: {-# UNPACK #-} !Bool
    , Envelope -> Bool
useConstantVolume :: {-# UNPACK #-} !Bool
    , Envelope -> Int
constantVolume :: {-# UNPACK #-} !Int
    , Envelope -> Int
decayLevel :: {-# UNPACK #-} !Int
    , Envelope -> Int
divider :: {-# UNPACK #-} !Int
    , Envelope -> Bool
loopFlag :: {-# UNPACK #-} !Bool
    }

newEnvelope :: Envelope
newEnvelope :: Envelope
newEnvelope = Bool -> Bool -> Int -> Int -> Int -> Bool -> Envelope
MkE Bool
False Bool
False Int
0 Int
0 Int
0 Bool
False

class HasEnvelope a where
    getEnvelope :: a -> Envelope
    setEnvelope :: Envelope -> a -> a

{-# INLINE withEnvelope #-}
withEnvelope :: (HasEnvelope a) => (Envelope -> Envelope) -> a -> a
withEnvelope :: forall a. HasEnvelope a => (Envelope -> Envelope) -> a -> a
withEnvelope Envelope -> Envelope
f a
a = Envelope -> a -> a
forall a. HasEnvelope a => Envelope -> a -> a
setEnvelope (Envelope -> Envelope
f (Envelope -> Envelope) -> Envelope -> Envelope
forall a b. (a -> b) -> a -> b
$ a -> Envelope
forall a. HasEnvelope a => a -> Envelope
getEnvelope a
a) a
a

tickEnvelope :: Envelope -> Envelope
tickEnvelope :: Envelope -> Envelope
tickEnvelope Envelope
e =
    if Envelope -> Bool
startFlag Envelope
e
        then Envelope
e{startFlag = False, decayLevel = 15, divider = constantVolume e}
        else Envelope -> Envelope
tickDivider Envelope
e

tickDivider :: Envelope -> Envelope
tickDivider :: Envelope -> Envelope
tickDivider Envelope
e =
    if Envelope -> Int
divider Envelope
e Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0
        then Envelope
e{divider = constantVolume e, decayLevel = newDecay}
        else Envelope
e{divider = divider e - 1}
  where
    newDecay :: Int
newDecay =
        if Envelope -> Int
decayLevel Envelope
e Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0
            then if Envelope -> Bool
loopFlag Envelope
e then Int
15 else Int
0
            else Envelope -> Int
decayLevel Envelope
e Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1

getEnvelopeOutput :: Envelope -> Int
getEnvelopeOutput :: Envelope -> Int
getEnvelopeOutput Envelope
e =
    if Envelope -> Bool
useConstantVolume Envelope
e
        then Envelope -> Int
constantVolume Envelope
e
        else Envelope -> Int
decayLevel Envelope
e