{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{- |
Convert MIDI events of a MIDI controller to a control signal.
-}
module Synthesizer.LLVM.MIDI (
   frequencyFromBendModulation,
   frequencyFromBendModulationPacked,
   Gen.applyModulation,
   ) where

import qualified Synthesizer.MIDI.Generic as Gen
import qualified Synthesizer.LLVM.MIDI.BendModulation as BM
import qualified Synthesizer.LLVM.Frame.SerialVector as Serial

import Synthesizer.LLVM.CausalParameterized.Process (($>), )
import qualified Synthesizer.LLVM.CausalParameterized.Functional as Func
import qualified Synthesizer.LLVM.CausalParameterized.Process as CausalP
import qualified Synthesizer.LLVM.Parameterized.SignalPacked as SigPS
import qualified Synthesizer.LLVM.Parameterized.Signal as SigP
import qualified Synthesizer.LLVM.Parameter as Param
import qualified Synthesizer.LLVM.Wave as Wave

import qualified LLVM.Extra.ScalarOrVector as SoV
import qualified LLVM.Extra.Vector as Vector
import qualified LLVM.Extra.Memory as Memory
import qualified LLVM.Extra.Arithmetic as A
import qualified LLVM.Extra.Class as Class
import qualified LLVM.Core as LLVM
import LLVM.Core (IsSized, SizeOf, )

import qualified Type.Data.Num.Decimal as TypeNum
import Type.Data.Num.Decimal.Number ((:*:), )

import Foreign.Storable (Storable, )

import qualified Algebra.RealField      as RealField
import qualified Algebra.Additive       as Additive

import Control.Arrow (second, (<<<), (<<^), )
import Control.Monad ({- liftM, -} liftM2, )

-- import NumericPrelude.Base
import NumericPrelude.Numeric
import Prelude (($), )


{-
{-# INLINE piecewiseConstantInit #-}
piecewiseConstantInit ::
   (Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    Memory.C yl ym, LLVM.IsSized ym ys) =>
   y -> EventListTT.T LazyTime y -> SigP.T p yl
piecewiseConstantInit initial evs =
   SigP.piecewiseConstant $#
   (PC.subdivideInt $
    EventListMT.consBody initial evs)


{-# INLINE controllerLinear #-}
controllerLinear ::
   (Field.C y, Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    Memory.C yl ym, LLVM.IsSized ym ys) =>
   Channel -> Controller ->
   (y,y) -> y ->
   Filter (SigP.T p yl)
controllerLinear chan ctrl bnd initial =
   liftM (piecewiseConstantInit initial .
          EventListTT.mapBody (MV.controllerLinear bnd)) $
   getControllerEvents chan ctrl


{-# INLINE controllerExponential #-}
controllerExponential ::
   (Trans.C y, Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    Memory.C yl ym, LLVM.IsSized ym ys) =>
   Channel -> Controller ->
   (y,y) -> y ->
   Filter (SigP.T p yl)
controllerExponential chan ctrl bnd initial =
   liftM (piecewiseConstantInit initial .
          EventListTT.mapBody (MV.controllerExponential bnd)) $
   getControllerEvents chan ctrl


{- |
@pitchBend channel range center@:
emits frequencies on an exponential scale from
@center/range@ to @center*range@.
-}
{-# INLINE pitchBend #-}
pitchBend ::
   (Trans.C y, Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    Memory.C yl ym, LLVM.IsSized ym ys) =>
   Channel ->
   y -> y ->
   Filter (SigP.T p yl)
pitchBend chan range center =
   liftM (piecewiseConstantInit center .
          EventListTT.mapBody (MV.pitchBend range center)) $
   getSlice (maybePitchBend chan)
--   getPitchBendEvents chan

{-# INLINE channelPressure #-}
channelPressure ::
   (Trans.C y, Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    Memory.C yl ym, LLVM.IsSized ym ys) =>
   Channel ->
   y -> y ->
   Filter (SigP.T p yl)
channelPressure chan maxVal initVal =
   liftM (piecewiseConstantInit initVal .
          EventListTT.mapBody (MV.controllerLinear (0,maxVal))) $
   getSlice (maybeChannelPressure chan)


{-# INLINE bendWheelPressure #-}
bendWheelPressure ::
   (Ring.C a, LLVM.IsConst a,
    RealField.C y, Trans.C y,
    LLVM.IsConst y, SoV.Fraction y, a ~ SoV.Scalar y, SoV.Replicate y,
    Storable y, Class.MakeValueTuple y (LLVM.Value y), LLVM.IsSized y ys) =>
   Channel ->
   Int -> y -> y -> y ->
   Filter (SigP.T p (LLVM.Value y))
bendWheelPressure chan
     pitchRange speed wheelDepth pressDepth =
   do bend  <- pitchBend chan
                  (2^?(fromIntegral pitchRange/12) `asTypeOf` speed) 1
      fm    <- controllerLinear chan VoiceMsg.modulation (0,wheelDepth) 0
      press <- channelPressure chan pressDepth 0
      return $
         SigP.envelope bend $
         SigP.mapSimple (A.add A.one) $
         SigP.envelope
            (SigP.mix fm press)
            (SigP.osciSimple Wave.approxSine2 zero $# speed)
-}


frequencyFromBendModulation ::
   (Additive.C y, Storable y, Class.MakeValueTuple y, Class.ValueTuple y ~ yl,
    A.PseudoRing yl, A.Fraction yl, A.IntegerConstant yl, Memory.C yl) =>
   Param.T p y ->
   CausalP.T p (BM.T yl) yl
frequencyFromBendModulation speed =
   frequencyFromPair SigP.osciSimple speed
   <<^
   (\(BM.Cons b m) -> (b,m))


frequencyFromBendModulationPacked ::
   (RealField.C a, LLVM.IsConst a, LLVM.IsFloating a,
    Memory.FirstClass a, Memory.Stored a ~ am, IsSized a, IsSized am,
    LLVM.IsPrimitive am,
    Storable a, Class.MakeValueTuple a, Class.ValueTuple a ~ (LLVM.Value a),
    Vector.Real a, SoV.IntegerConstant a,
    TypeNum.Positive n,
    TypeNum.Positive (n :*: SizeOf a),
    TypeNum.Positive (n :*: SizeOf am)) =>
   Param.T p a ->
   CausalP.T p (BM.T (LLVM.Value a)) (Serial.Value n a)
frequencyFromBendModulationPacked speed =
   frequencyFromPair SigPS.osciSimple speed
   <<<
   CausalP.mapSimple
      (\(BM.Cons b m) ->
         liftM2 (,) (Serial.upsample b) (Serial.upsample m))

frequencyFromPair, _frequencyFromPair ::
   (Additive.C y, A.PseudoRing yl, A.IntegerConstant yl, A.Fraction yl) =>
   ((forall r. yl -> LLVM.CodeGenFunction r yl) ->
    Param.T p y -> Param.T p y -> SigP.T p yl) ->
   Param.T p y ->
   CausalP.T p (yl,yl) yl
frequencyFromPair osci speed =
   Func.withGuidedArgs (Func.atom, Func.atom) $ \(b, m) ->
      b * (1 + m * Func.fromSignal (osci Wave.approxSine2 zero speed))

_frequencyFromPair osci speed =
   CausalP.envelope
   <<<
   second (1 + (CausalP.envelope $> osci Wave.approxSine2 zero speed))