module Synthesizer.LLVM.Server.Common where

import qualified Sound.ALSA.Sequencer.Event as Event
import qualified Sound.ALSA.Sequencer.Address as Addr
import qualified Sound.ALSA.Sequencer.Client as Client
import qualified Sound.ALSA.Sequencer.Port as Port
import qualified Sound.ALSA.Sequencer.Queue as Queue
import qualified Sound.ALSA.Sequencer.RealTime as RealTime
import qualified Sound.ALSA.Sequencer.Event as Event

import qualified Sound.ALSA.PCM as ALSA
import qualified Synthesizer.Storable.ALSA.Play as Play
import qualified Synthesizer.PiecewiseConstant.ALSA.MIDI as PC
import qualified Synthesizer.PiecewiseConstant.ALSA.MIDIControllerSet as PCS

import qualified Synthesizer.LLVM.Parameterized.Signal as SigP
import qualified Synthesizer.LLVM.Parameter as Param

import qualified LLVM.Extra.Representation as Rep
import qualified LLVM.Core as LLVM

import qualified Data.EventList.Relative.BodyTime  as EventListBT

import qualified Synthesizer.Storable.Signal      as SigSt
import qualified Data.StorableVector.Lazy         as SVL

import Foreign.Storable (Storable, )

import qualified Synthesizer.Generic.Signal    as SigG

import qualified Sound.MIDI.Message.Channel       as ChannelMsg

import qualified Sound.Sox.Frame         as SoxFrame

import Control.Arrow ((^<<), )

import qualified Numeric.NonNegative.Wrapper as NonNegW

import qualified Algebra.RealRing as RealRing
import qualified Algebra.Field     as Field
import qualified Algebra.Ring      as Ring
import qualified Algebra.ToInteger as ToInteger
import qualified Algebra.Additive  as Additive

import Data.Word (Word8, )

import NumericPrelude.Numeric (zero, round, )
import Prelude hiding (Real, round, break, )



channel :: ChannelMsg.Channel
channel = ChannelMsg.toChannel 0

sampleRate :: Num a => a
-- sampleRate = 24000
-- sampleRate = 48000
sampleRate = 44100

latency :: Int
latency = 0
-- latency = 256
-- latency = 480

chunkSize :: SVL.ChunkSize
chunkSize = Play.defaultChunkSize


lazySize :: SigG.LazySize
lazySize =
   let (SVL.ChunkSize size) = chunkSize
   in  SigG.LazySize size

periodTime :: Field.C t => t
periodTime =
   let (SVL.ChunkSize size) = chunkSize
   in  ToInteger.fromIntegral size Field./ Ring.fromInteger sampleRate


type Real = Float


($/) :: (Functor f) => f (a -> b) -> a -> f b
f $/ x = fmap ($x) f


{-# INLINE play #-}
play ::
   (RealRing.C t, Additive.C y, ALSA.SampleFmt y) =>
   t -> t -> SigSt.T y -> IO ()
play period rate =
   Play.auto period (round rate) .
   SigSt.append (SigSt.replicate chunkSize latency zero)
--   FiltG.delayPosLazySize chunkSize latency
--   FiltG.delayPos latency

-- ToDo: do not record the empty chunk that is inserted for latency
{-# INLINE playAndRecord #-}
playAndRecord ::
   (RealRing.C t, Additive.C y, ALSA.SampleFmt y, SoxFrame.C y) =>
   FilePath -> t -> t -> SigSt.T y -> IO ()
playAndRecord fileName period rate =
   Play.autoAndRecord period fileName (round rate) .
   SigSt.append (SigSt.replicate chunkSize latency zero)


piecewiseConstant ::
   (Storable a,
    LLVM.MakeValueTuple a al,
    Rep.Memory al am,
    LLVM.IsSized am as) =>
   Param.T p (PC.T a) -> SigP.T p al
piecewiseConstant pc =
   SigP.piecewiseConstant
      (EventListBT.mapTime
         (NonNegW.fromNumber . fromIntegral . NonNegW.toNumber) ^<< pc)
--   SigP.piecewiseConstant (PC.subdivideInt ^<< pc)

transposeModulation ::
   (Functor stream) =>
   Real ->
   stream (PC.BendModulation Real) ->
   stream (PC.BendModulation Real)
transposeModulation freq =
   fmap (PC.shiftBendModulation (freq/sampleRate))


{-# INLINE amplitudeFromVelocity #-}
amplitudeFromVelocity :: Real -> Real
amplitudeFromVelocity vel = 4**vel


-- cf. synthesizer-alsa:Synthesizer.Storable.ALSA.Server.Test
makeNote :: Event.NoteEv -> Word8 -> Event.T
makeNote typ pit =
   Event.Cons
      { Event.highPriority = False
      , Event.tag = 0
      , Event.queue = Queue.direct
      , Event.timestamp =
           Event.RealTime $ RealTime.fromInteger 0
      , Event.source = Addr.Cons {
           Addr.client = Client.subscribers,
           Addr.port = Port.unknown
        }
      , Event.dest = Addr.Cons {
           Addr.client = Client.subscribers,
           Addr.port = Port.unknown
        }
      , Event.body =
           Event.NoteEv typ
              (Event.simpleNote 0 pit 64)
      }