{-# OPTIONS -fglasgow-exts -fno-implicit-prelude #-}
module Haskore.Interface.Signal.Write where

What does the MIDI volume mean?
First one must distinguish between the velocity
(this is the force when playing)
and the volume.
The meaning of velocity depends on the instrument.

By digitizing sounds from the Yamaha SY-35
I found out that an increase of the MIDI volume by 16
doubles the amplitude, i.e. 16 steps correspond to 3 dB.

import qualified Haskore.Interface.Signal.InstrumentMap as InstrMap
import qualified Haskore.Interface.Signal.Note as Note

import qualified Haskore.Music          as Music
import qualified Haskore.Music.Rhythmic as RhyMusic
import qualified Haskore.Performance as Performance
import qualified Haskore.Performance.Player  as Player
import qualified Haskore.Performance.Context as Context
import qualified Haskore.Performance.BackEnd as PerformanceBE
import qualified Haskore.Performance.Default as DefltPf
-- import qualified Haskore.Basic.Pitch as Pitch
import qualified Haskore.Basic.Tempo as Tempo

import qualified Data.EventList.Relative.TimeBody as EventList
-- import qualified Numeric.NonNegative.Class   as NonNeg
import qualified Number.NonNegative as NonNegW

import qualified Synthesizer.Plain.Signal as Sig
import qualified Synthesizer.Plain.Cut as Cut
import           Synthesizer.Utility(mapSnd)

import qualified Algebra.Module    as Module
import qualified Algebra.RealField as RealField
import qualified Algebra.Ring      as Ring
import qualified Algebra.Additive  as Additive
import Algebra.Module((*>))

import PreludeBase
import NumericPrelude

import Prelude (RealFrac, Fractional, Floating, )

type Time   = Double
type Volume = Double
type NonNegTime = NonNegW.Double

{- | Convert a standard music into a list of numeric values
     that represent a mono or stereo audio signal. -}
fromRhythmicMusic ::
   (RealFrac time, Floating time, RealField.C time,
    Module.C dyn v, Fractional dyn, RealField.C dyn,
    Ord instr, Ord drum) =>
   -> InstrMap.InstrumentTable time v instr
   -> (Player.Name -> Player.T (NonNegW.T time) dyn (RhyMusic.Note drum instr))
   -> Context.T (NonNegW.T time) dyn (RhyMusic.Note drum instr)
   -> RhyMusic.T drum instr
   -> Sig.T v
fromRhythmicMusic sampleRate instrMap =
   fromMusic sampleRate
      (Note.fromRhythmicNote undefined (InstrMap.lookup instrMap))

{- | Convert a generic music into an audio signal. -}
fromMusic ::
   (RealFrac time, Floating time, RealField.C time,
    Additive.C v, Ord dyn, Fractional dyn,
    Ord note) =>
   -> Note.FromNote time dyn v note
   -> (Player.Name -> Player.T (NonNegW.T time) dyn note)
   -> Context.T (NonNegW.T time) dyn note
   -> Music.T note
   -> Sig.T v
fromMusic sampleRate noteMap pMap con =
   fromPerformance sampleRate noteMap .
      Performance.fromMusic pMap con

fromPerformance ::
   (RealFrac time, Floating time, RealField.C time,
    Additive.C v,
    Ord note) =>
   -> Note.FromNote time dyn v note
   -> Performance.T (NonNegW.T time) dyn note
   -> Sig.T v
fromPerformance sampleRate noteMap =
   Cut.arrange .
      (NonNegW.fromNumberMsg "Signal.Write.fromPerformance" sampleRate) .
   EventList.mapBody (eventToPiece sampleRate) .
   PerformanceBE.fromPerformance noteMap

{- | Convert a generic note the sound of a single tone. -}
eventToPiece ::
   (RealFrac time, Floating time,
    RealField.C time) =>
   -> PerformanceBE.Event (NonNegW.T time) (Note.T time v)
   -> Sig.T v
eventToPiece sampleRate event =
   let dur   = PerformanceBE.eventDur  event
       sound = PerformanceBE.eventNote event
   in  take
          (round (sampleRate * NonNegW.toNumber dur))
          (Note.toSignal sound sampleRate)

{- | can be used to turn an instrument mapper -}
detuneTone :: Ring.C time =>
   -> (time -> time -> Sig.T v)
   -> (time -> time -> Sig.T v)
detuneTone detune noteToSignal =
   \ sampleRate freq ->
        noteToSignal sampleRate (freq * detune)

detuneInstrs :: Ring.C time =>
   -> [(name, time -> time -> Sig.T v)]
   -> [(name, time -> time -> Sig.T v)]
detuneInstrs detune = map (mapSnd (detuneTone detune))

amplify :: Module.C a v =>
   a ->
   (time -> time -> Sig.T v) ->
   (time -> time -> Sig.T v)
amplify v sig sampleRate freq = map (v*>) (sig sampleRate freq)

nonNegTimeInstr ::
   (time -> time -> Sig.T v) -> (NonNegW.T time -> NonNegW.T time -> Sig.T v)
nonNegTimeInstr f sampleRate freq =
   f (NonNegW.toNumber sampleRate) (NonNegW.toNumber freq)

contextMetro :: Time -> Music.Dur -> Context.T NonNegTime Volume note
contextMetro setting dur =
   Context.setDur (Tempo.metro (NonNegW.fromNumberMsg "Signal.Write.contextMetro" setting) dur) $