module Nes.APU.State.Triangle (
    -- * Definition
    Triangle (..),
    newTriangle,

    -- * Output
    getTriangleOutput,

    -- * Clock
    tickTriangle,
    tickTriangleLinearCounter,
) where

import Nes.APU.State.LengthCounter

data Triangle = MkT
    { Triangle -> Bool
controlFlag :: {-# UNPACK #-} !Bool
    , Triangle -> Bool
reloadFlag :: {-# UNPACK #-} !Bool
    , Triangle -> Int
reloadValue :: {-# UNPACK #-} !Int
    , Triangle -> LengthCounter
lengthCounter :: !LengthCounter
    , Triangle -> Int
linearCounter :: {-# UNPACK #-} !Int
    , Triangle -> Int
period :: {-# UNPACK #-} !Int
    , Triangle -> Int
timer :: {-# UNPACK #-} !Int
    , Triangle -> Int
sequenceStep :: {-# UNPACK #-} !Int
    }

newTriangle :: Triangle
newTriangle :: Triangle
newTriangle = MkT{Bool
Int
LengthCounter
controlFlag :: Bool
reloadFlag :: Bool
reloadValue :: Int
lengthCounter :: LengthCounter
linearCounter :: Int
period :: Int
timer :: Int
sequenceStep :: Int
controlFlag :: Bool
reloadFlag :: Bool
reloadValue :: Int
lengthCounter :: LengthCounter
period :: Int
linearCounter :: Int
sequenceStep :: Int
timer :: Int
..}
  where
    controlFlag :: Bool
controlFlag = Bool
False
    reloadFlag :: Bool
reloadFlag = Bool
False
    reloadValue :: Int
reloadValue = Int
0
    lengthCounter :: LengthCounter
lengthCounter = LengthCounter
newLengthCounter
    period :: Int
period = Int
0
    linearCounter :: Int
linearCounter = Int
0
    sequenceStep :: Int
sequenceStep = Int
0
    timer :: Int
timer = Int
0

{-# INLINE getSequenceValue #-}
getSequenceValue :: Triangle -> Int
getSequenceValue :: Triangle -> Int
getSequenceValue Triangle
t = if Int
step Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
15 then Int
15 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
step else Int
step Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
16
  where
    step :: Int
step = Triangle -> Int
sequenceStep Triangle
t

{-# INLINE getTriangleOutput #-}
getTriangleOutput :: Triangle -> Int
getTriangleOutput :: Triangle -> Int
getTriangleOutput Triangle
t = if LengthCounter -> Int
remainingLength (Triangle -> LengthCounter
lengthCounter Triangle
t) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
0 then Triangle -> Int
getSequenceValue Triangle
t else Int
0

instance HasLengthCounter Triangle where
    getLengthCounter :: Triangle -> LengthCounter
getLengthCounter = Triangle -> LengthCounter
lengthCounter
    setLengthCounter :: LengthCounter -> Triangle -> Triangle
setLengthCounter LengthCounter
lc Triangle
t = Triangle
t{lengthCounter = lc}

tickTriangle :: Triangle -> Triangle
tickTriangle :: Triangle -> Triangle
tickTriangle Triangle
t = Triangle
t{timer = newTimer, sequenceStep = newSequenceStep}
  where
    newTimer :: Int
newTimer = if Triangle -> Int
timer Triangle
t Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 then Triangle -> Int
period Triangle
t else Triangle -> Int
timer Triangle
t Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
    tickSequence :: Bool
tickSequence = Triangle -> Int
timer Triangle
t Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& Triangle -> Int
linearCounter Triangle
t Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
0 Bool -> Bool -> Bool
&& LengthCounter -> Int
remainingLength (Triangle -> LengthCounter
lengthCounter Triangle
t) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
0
    newSequenceStep :: Int
newSequenceStep = if Bool
tickSequence then (Triangle -> Int
sequenceStep Triangle
t Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
32 else Triangle -> Int
sequenceStep Triangle
t

tickTriangleLinearCounter :: Triangle -> Triangle
tickTriangleLinearCounter :: Triangle -> Triangle
tickTriangleLinearCounter Triangle
t = Triangle
t2
  where
    t1 :: Triangle
t1 =
        if Triangle -> Bool
reloadFlag Triangle
t
            then Triangle
t{linearCounter = reloadValue t}
            else Triangle
t{linearCounter = max 0 (linearCounter t - 1)}
    t2 :: Triangle
t2 =
        if Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Triangle -> Bool
controlFlag Triangle
t1
            then Triangle
t1{reloadFlag = False}
            else Triangle
t1