module Sound.MED.Generic.Tempo (
  MEDTempo(..),
  Mode(..),
  song0Tempo,
  song2Tempo,

  update,
  toTime,
  ) where

import Sound.MED.Generic.Block(Cmd,Val)

import qualified Sound.MED.Raw.MMD0Song as MMD0Song
import qualified Sound.MED.Raw.MMD2Song as MMD2Song

import Sound.MED.Basic.Amiga

import Data.Bits (testBit, (.&.))
import Data.Bool.HT (if')

data MEDTempo = MEDTempo
  { MEDTempo -> Mode
mode :: Mode,
    MEDTempo -> Cmd
primary, MEDTempo -> Cmd
secondary :: Int
  }

data Mode = Speed | Octa | BPM {Mode -> Cmd
linesPerBeat :: Int}

tempoMode :: UBYTE -> UBYTE -> Mode
tempoMode :: UBYTE -> UBYTE -> Mode
tempoMode UBYTE
flags UBYTE
flags2 =
  forall a. Bool -> a -> a -> a
if' (forall a. Bits a => a -> Cmd -> Bool
testBit UBYTE
flags Cmd
6) Mode
Octa forall a b. (a -> b) -> a -> b
$
  forall a. Bool -> a -> a -> a
if' (forall a. Bits a => a -> Cmd -> Bool
testBit UBYTE
flags2 Cmd
5) (Cmd -> Mode
BPM forall a b. (a -> b) -> a -> b
$ forall a b. (Integral a, Num b) => a -> b
fromIntegral (UBYTE
flags2 forall a. Bits a => a -> a -> a
.&. UBYTE
0x1F) forall a. Num a => a -> a -> a
+ Cmd
1)
  Mode
Speed

song0Tempo :: MMD0Song.MMD0Song -> MEDTempo
song0Tempo :: MMD0Song -> MEDTempo
song0Tempo MMD0Song
song =
  MEDTempo
    { mode :: Mode
mode = UBYTE -> UBYTE -> Mode
tempoMode (MMD0Song -> UBYTE
MMD0Song.flags MMD0Song
song) (MMD0Song -> UBYTE
MMD0Song.flags2 MMD0Song
song)
    , primary :: Cmd
primary = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ MMD0Song -> UWORD
MMD0Song.deftempo MMD0Song
song
    , secondary :: Cmd
secondary = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ MMD0Song -> UBYTE
MMD0Song.tempo2 MMD0Song
song
    }

song2Tempo :: MMD2Song.MMD2Song -> MEDTempo
song2Tempo :: MMD2Song -> MEDTempo
song2Tempo MMD2Song
song =
  MEDTempo
    { mode :: Mode
mode = UBYTE -> UBYTE -> Mode
tempoMode (MMD2Song -> UBYTE
MMD2Song.flags MMD2Song
song) (MMD2Song -> UBYTE
MMD2Song.flags2 MMD2Song
song)
    , primary :: Cmd
primary = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ MMD2Song -> UWORD
MMD2Song.deftempo MMD2Song
song
    , secondary :: Cmd
secondary = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ MMD2Song -> UBYTE
MMD2Song.tempo2 MMD2Song
song
    }


{- |
Interpret tempo related commands.
-}
update :: MEDTempo -> (Cmd, Val) -> MEDTempo
update :: MEDTempo -> (Cmd, Cmd) -> MEDTempo
update MEDTempo
tempo (Cmd
cmd,Cmd
val) =
  case Cmd
cmd of
    Cmd
0x09 -> MEDTempo
tempo{secondary :: Cmd
secondary = forall a. Integral a => a -> a -> a
mod (Cmd
valforall a. Num a => a -> a -> a
-Cmd
1) Cmd
0x20 forall a. Num a => a -> a -> a
+ Cmd
1}
    Cmd
0x0F ->
      if Cmd
0 forall a. Ord a => a -> a -> Bool
< Cmd
val Bool -> Bool -> Bool
&& Cmd
val forall a. Ord a => a -> a -> Bool
< Cmd
0xF0
        then MEDTempo
tempo{primary :: Cmd
primary = forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
val}
        else MEDTempo
tempo
    Cmd
_ -> MEDTempo
tempo


toTime :: Fractional a => MEDTempo -> a
toTime :: forall a. Fractional a => MEDTempo -> a
toTime (MEDTempo Mode
mode_ Cmd
tempo1 Cmd
tempo2) =
  forall a. Fractional a => Mode -> Cmd -> a
timeFromPrimary Mode
mode_ Cmd
tempo1 forall a. Num a => a -> a -> a
* forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
tempo2

-- numbers taken from uade/amigasrc/players/med/common/proplayer.a
ciabFreq :: Fractional a => a
ciabFreq :: forall a. Fractional a => a
ciabFreq = a
715909

timerDiv :: Fractional a => a
timerDiv :: forall a. Fractional a => a
timerDiv = a
474326

_sttempo :: Fractional a => a
_sttempo :: forall a. Fractional a => a
_sttempo = a
2416.3

{-
Measured from output of uade.
In theory it should be 300 (50 Hz times 6 divisions).
-}
sttempoMeasured :: Fractional a => a
sttempoMeasured :: forall a. Fractional a => a
sttempoMeasured = a
293.70

octaTempo :: Fractional a => a
octaTempo :: forall a. Fractional a => a
octaTempo = a
390.70

timeFromPrimary :: Fractional a => Mode -> Int -> a
timeFromPrimary :: forall a. Fractional a => Mode -> Cmd -> a
timeFromPrimary Mode
mode_ Cmd
tempo =
  case Mode
mode_ of
    BPM Cmd
lpb -> a
60 forall a. Fractional a => a -> a -> a
/ (forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
tempo forall a. Num a => a -> a -> a
* a
6 forall a. Num a => a -> a -> a
* forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
lpb)
    Mode
Octa -> forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall a. Ord a => a -> a -> a
min Cmd
10 Cmd
tempo) forall a. Fractional a => a -> a -> a
/ forall a. Fractional a => a
octaTempo
    Mode
Speed ->
      if Cmd
tempoforall a. Ord a => a -> a -> Bool
<=Cmd
10
        -- then fromIntegral tempo * sttempo / ciabFreq
        then forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
tempo forall a. Fractional a => a -> a -> a
/ forall a. Fractional a => a
sttempoMeasured
        else forall a. Fractional a => a
timerDiv forall a. Fractional a => a -> a -> a
/ (forall a. Fractional a => a
ciabFreq forall a. Num a => a -> a -> a
* forall a b. (Integral a, Num b) => a -> b
fromIntegral Cmd
tempo)