{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE Rank2Types #-}
module Synthesizer.Dimensional.RateAmplitude.File (
   write,
   writeTimeVoltage,
   writeTimeVoltageMonoDoubleToInt16,
   writeTimeVoltageStereoDoubleToInt16,
   renderTimeVoltageMonoDoubleToInt16,
   renderTimeVoltageStereoDoubleToInt16,
  ) where

import qualified Sound.Sox.Write as Write
import qualified Sound.Sox.Option.Format as SoxOpt
import qualified Sound.Sox.Frame as Frame
import qualified Synthesizer.Basic.Binary as BinSmp
import qualified Data.StorableVector.Lazy.Builder as Builder
import Foreign.Storable (Storable, )

import qualified Synthesizer.Dimensional.Rate as Rate
import qualified Synthesizer.Dimensional.Amplitude as Amp

import qualified Synthesizer.Dimensional.Process as Proc
import qualified Synthesizer.Dimensional.Signal.Private as SigA

import qualified Synthesizer.Frame.Stereo as Stereo

import qualified Synthesizer.Storable.Signal as SigSt

-- import qualified Synthesizer.Dimensional.Straight.Signal as SigS
import qualified Synthesizer.State.Signal as Sig

import qualified Algebra.ToInteger      as ToInteger
-- import qualified Algebra.Transcendental as Trans
import qualified Algebra.Module         as Module
import qualified Algebra.RealField      as RealField
import qualified Algebra.Field          as Field
import qualified Algebra.RealRing       as RealRing
-- import qualified Algebra.Ring           as Ring

import qualified Algebra.DimensionTerm as Dim
import qualified Number.DimensionTerm  as DN


import System.Exit(ExitCode)

import NumericPrelude.Numeric
import NumericPrelude.Base



type Signal u t v y yv =
   SigA.T (Rate.Dimensional u t) (Amp.Dimensional v y) (Sig.T yv)

{- |
The output format is determined by SoX by the file name extension.
The sample precision is determined by the provided 'Builder.Builder' function.

Example:

> import qualified Data.StorableVector.Lazy.Builder as Builder
>
> write (DN.frequency one) (DN.voltage one) (\i -> Builder.put (i::Int16)) "test.aiff" sound
-}
{-# INLINE write #-}
write ::
    (Bounded int, ToInteger.C int, Storable int, Frame.C int, BinSmp.C yv,
     Dim.C u, RealField.C t,
     Dim.C v, Module.C y yv, Field.C y) =>
   DN.T (Dim.Recip u) t ->
   DN.T v y ->
   (int -> Builder.Builder int) ->
   FilePath ->
   Signal u t v y yv ->
   IO ExitCode
write freqUnit amp put name sig =
   let opts =
          SoxOpt.numberOfChannels $
          BinSmp.numberOfSignalChannels $
          SigA.body sig
       sampleRate =
          DN.divToScalar (SigA.actualSampleRate sig) freqUnit
   in  Write.extended SigSt.hPut opts SoxOpt.none name
          (round sampleRate)
          (Builder.toLazyStorableVector SigSt.defaultChunkSize $
           Sig.monoidConcatMap (BinSmp.outputFromCanonical put) $
           -- ToDo: flip DN.divToScalar -> ampToScalar
           SigA.vectorSamples (flip DN.divToScalar amp) sig)


{-# INLINE writeTimeVoltage #-}
writeTimeVoltage ::
    (Bounded int, ToInteger.C int, Storable int, Frame.C int, BinSmp.C yv,
     RealField.C t,
     Module.C y yv, Field.C y) =>
   (int -> Builder.Builder int) ->
   FilePath ->
   Signal Dim.Time t Dim.Voltage y yv ->
   IO ExitCode
writeTimeVoltage =
   write (DN.frequency one) (DN.voltage one)



{-# INLINE writeTimeVoltageMonoDoubleToInt16 #-}
writeTimeVoltageMonoDoubleToInt16 ::
   FilePath ->
   Signal Dim.Time Double Dim.Voltage Double Double ->
   IO ExitCode
writeTimeVoltageMonoDoubleToInt16 name sig =
   let rate = DN.toNumberWithDimension Dim.frequency (SigA.actualSampleRate sig)
   in  Write.simple SigSt.hPut SoxOpt.none name (round rate)
          (SigA.toStorableInt16Mono sig)


{-# INLINE writeTimeVoltageStereoDoubleToInt16 #-}
writeTimeVoltageStereoDoubleToInt16 ::
   FilePath ->
   Signal Dim.Time Double Dim.Voltage Double (Stereo.T Double) ->
   IO ExitCode
writeTimeVoltageStereoDoubleToInt16 name sig =
   let rate = DN.toNumberWithDimension Dim.frequency (SigA.actualSampleRate sig)
   in  Write.simple SigSt.hPut SoxOpt.none name (round rate)
          (SigA.toStorableInt16Stereo sig)

{-# INLINE renderTimeVoltageMonoDoubleToInt16 #-}
renderTimeVoltageMonoDoubleToInt16 ::
   DN.T Dim.Frequency Double ->
   FilePath ->
   (forall s. Proc.T s Dim.Time Double (SigA.R s Dim.Voltage Double Double)) ->
   IO ExitCode
renderTimeVoltageMonoDoubleToInt16 rate name sig =
   writeTimeVoltageMonoDoubleToInt16 name (SigA.render rate sig)

{-# INLINE renderTimeVoltageStereoDoubleToInt16 #-}
renderTimeVoltageStereoDoubleToInt16 ::
   DN.T Dim.Frequency Double ->
   FilePath ->
   (forall s. Proc.T s Dim.Time Double (SigA.R s Dim.Voltage Double (Stereo.T Double))) ->
   IO ExitCode
renderTimeVoltageStereoDoubleToInt16 rate name sig =
   writeTimeVoltageStereoDoubleToInt16 name (SigA.render rate sig)