-- |
-- Module      :  DobutokO.Sound.Faded
-- Copyright   :  (c) OleksandrZhabenko 2020
-- License     :  MIT
-- Stability   :  Experimental
-- Maintainer  :  olexandr543@yahoo.com
--
-- Helps to create experimental music. 
-- Uses SoX fade (in a special 2D way) and frequency modulation.

{-# OPTIONS_GHC -threaded #-}
{-# LANGUAGE CPP #-}

module DobutokO.Sound.Faded (
  -- * Create special faded effects
  overChangeVolG
  , overChangeVolGC
  , overChangeVolGF
  , overChangeVol
  , overChangeVolC
  , overChangeVolF
  -- * Mixing function
  , mixGTest
  -- * Generate several files
  , basicF
  , basicFC
  , basicF2
  , basicF2C
  -- * Auxiliary functions
  , endingWF
  , charFadeType
  , argString
  , freqChange
) where

#ifdef __GLASGOW_HASKELL__
#if __GLASGOW_HASKELL__>=710
/* code that applies only to GHC 7.10.* and higher versions */
import GHC.Base (mconcat)
#endif
#endif
import System.Exit (ExitCode (ExitSuccess))
import Data.List (isPrefixOf,isSuffixOf)
import Data.Maybe (fromJust)
import System.Process
import EndOfExe (showE)
import MMSyn7l (fadeEndsTMB,fadeEndsTMN)
import Numeric (showFFloat)
import qualified Data.Vector as V
import System.Directory
import DobutokO.Sound.Functional.Basics
import DobutokO.Sound.IntermediateF (soxBasicParams)
import Data.Vector.DoubleZip (evalSndFV)

#ifdef __GLASGOW_HASKELL__
#if __GLASGOW_HASKELL__==708
/* code that applies only to GHC 7.8.* */
mconcat = concat
#endif
#endif

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale with possibily changing frequency (they are specified by 
-- the first and the second 'Double' arguments). Uses SoX inside especially \"fade\" and \"synth\" effects. For the equal frequencies generates specifically 
-- faded output. 
overChangeVolG :: String -> String -> Int -> Double -> Double -> Double -> Double -> ((Double,Double), (Double,Double)) -> IO ()
overChangeVolG ys cs j freq1 freq2 x0 xdelta ((t0,v0), (t1,v1))
 | x0 /= 0 && compare (abs x0) 1.0 /= GT && compare freq1 16 == GT && compare freq1 20000 == LT =
  case compare (v1 * v0) 0 of
    GT -> do
     (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys ["","-n","test1.wav","synth",
       showFFloat Nothing (if t1 == t0 then abs x0 else abs (t1 - t0)) "", "sine", showFFloat (Just 4) freq1 (freqChange (drop 1 cs) freq2 freq1), "fade",
        charFadeType (if null cs then 'l' else head cs)] ++
         if compare ((v1 - v0) * (t1 - t0)) 0 /= LT then [showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) ""]
           else ["0", "-0.0", showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "vol",
             showFFloat (Just 4) (signum v1 * abs (v1 - v0)) ""]) ""
     if code1 /= ExitSuccess then error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr1
     else do
      (code2,_,herr2) <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys ["","-n","test0.wav","synth",
        showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "sine",
          showFFloat (Just 4) freq1 (freqChange (drop 1 cs) freq2 freq1), "vol", showFFloat (Just 4) (min v0 v1) ""]) ""
      if code2 == ExitSuccess
        then do
          (code3,_,herr3) <- readProcessWithExitCode (fromJust (showE "sox")) ["-m","test0" ++ endingWF ys,"test1" ++ endingWF ys, "testG" ++
            prependZeroes 6 (show j) ++ endingWF ys, "vol", "2"] ""
          if code3 == ExitSuccess
            then removeFile ("test0" ++ endingWF ys) >> removeFile ("test1" ++ endingWF ys)
            else error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr3
        else print herr2 >> error "DobutokO.Sound.Times.overChangeVol: Operation not successful. "
    LT -> do
     overChangeVolG ys cs j freq1 freq2 x0 xdelta ((t0,v0), ((v0 * t1 - v1 * t0) / (v0 - v1),0)) >> renameFile ("testG" ++ prependZeroes 6 (show j) ++
       endingWF ys) ("temp0" ++ endingWF ys)
     overChangeVolG ys cs j freq1 freq2 x0 xdelta (((v0 * t1 - v1 * t0) / (v0 - v1),0), (t1,v1)) >> renameFile ("testG" ++ prependZeroes 6 (show j) ++
       endingWF ys) ("temp1" ++ endingWF ys)
     (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) ["temp0" ++ endingWF ys,"temp1" ++ endingWF ys, "testG" ++
       prependZeroes 6 (show j) ++ endingWF ys] ""
     if code1 == ExitSuccess then removeFile ("temp0" ++ endingWF ys) >> removeFile ("temp1" ++ endingWF ys)
     else error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr1
    _  ->
     case v1 of
      0 ->
       if v0 == 0
        then do
          (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys ["","-n","testG" ++ prependZeroes 6 (show j) ++
            ".wav","delay", showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "trim", showFFloat Nothing
              (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) ""]) ""
          if code1 /= ExitSuccess then error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr1
          else return ()
        else do
          (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys ["","-n","testG" ++ prependZeroes 6 (show j) ++
            ".wav","synth", showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "sine",
              showFFloat (Just 4) freq1 (freqChange (drop 1 cs) freq2 freq1), "fade", charFadeType (if null cs then 'l' else head cs)] ++
               if compare t0 t1 == GT then [showFFloat Nothing
                (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) ""] else ["0", "-0.0", showFFloat Nothing
                  (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "vol", showFFloat (Just 4) v0 ""]) ""
          if code1 /= ExitSuccess then error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr1
          else return ()
      _ -> do
       (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys ["","-n","testG" ++ prependZeroes 6 (show j) ++ ".wav",
         "synth", showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "", "sine",
          showFFloat (Just 4) freq1 (freqChange (drop 1 cs) freq2 freq1), "fade", charFadeType (if null cs then 'l' else head cs)] ++
           if compare t1 t0 == GT
            then [showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0
             else abs (t1 - t0)) ""] else ["0", "-0.0", showFFloat Nothing (if compare (abs (t1 - t0)) xdelta /= GT then abs x0 else abs (t1 - t0)) "",
               "vol", showFFloat (Just 4) v1 ""]) ""
       if code1 /= ExitSuccess then error $ "DobutokO.Sound.Times.overChangeVol: " ++ herr1
       else return ()
 | otherwise = error "DobutokO.Sound.Times.overChangeVol: sound for these conditions is not defined. "

freqChange :: String -> Double -> Double -> String
freqChange xs freq freq1
 | compare freq 16 /= LT && compare freq 20000 /= GT = if freq /= freq1 then
   case xs of
    "l" -> ':':showFFloat (Just 4) freq ""
    "s" -> '+':showFFloat (Just 4) freq ""
    "e" -> '/':showFFloat (Just 4) freq ""
    _ -> '-':showFFloat (Just 4) freq ""
     else ""
 | otherwise = error "DobutokO.Sound.Faded.freqChange: undefined for this value of the frequency (the first Double argument). "

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale. Uses SoX inside especially \"fade\" 
-- and \"synth\" effects. A frequency does not change and is specified (in Hz) by the first 'Double' argument.
overChangeVol :: String -> Char -> Int -> Double -> Double -> Double -> ((Double,Double), (Double,Double)) -> IO ()
overChangeVol ys c j freq1 = overChangeVolG ys [c] j freq1 freq1

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale. Uses SoX inside especially \"fade\" 
-- and \"synth\" effects. A frequency does not change and is specified (in Hz) by the first 'Double' argument. Is a curried variant of the 
-- 'overChangeVol' in its two last arguments.
overChangeVolC :: String -> Char -> Int -> Double -> Double -> Double -> (Double,Double) -> (Double,Double) -> IO ()
overChangeVolC ys c j freq x0 xdelta w1 = overChangeVol ys c j freq x0 xdelta . (,) w1

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale with possibily changing frequency (they are specified by 
-- the first and the second 'Double' arguments). Uses SoX inside especially \"fade\" and \"synth\" effects. For the equal frequencies generates specifically 
-- faded output. Is a curried variant of the 'overChangeVolG' in its two last arguments.
overChangeVolGC :: String -> String -> Int -> Double -> Double -> Double -> Double -> (Double,Double) -> (Double,Double) -> IO ()
overChangeVolGC ys cs j freq1 freq2 x0 xdelta w1 = overChangeVolG ys cs j freq1 freq2 x0 xdelta . (,) w1

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale. Uses SoX inside especially \"fade\" 
-- and \"synth\" effects. Is a somewhat flipped variant of the 'overChangeVol' with changed order of the arguments (is provided here for convenience).
overChangeVolF :: String -> Char -> Int -> Double -> Double -> (Double,Double) -> (Double,Double) ->  Double -> IO ()
overChangeVolF ys c j x0 xdelta w1 w2 freq  = overChangeVol ys c j freq x0 xdelta (w1,w2)

-- | Generates sound the volume of which comes through the given 2D points in the time-volume scale with possibily changing frequency (they are specified by 
-- the first and the second 'Double' arguments). Uses SoX inside especially \"fade\" and \"synth\" effects. For the equal frequencies generates specifically 
-- faded output. Is a somewhat flipped variant of the 'overChangeVolG' with changed order of the arguments (is provided here for convenience).
overChangeVolGF :: String -> String -> Int -> Double -> Double -> (Double,Double) -> (Double,Double) ->  Double -> Double -> IO ()
overChangeVolGF ys cs j x0 xdelta w1 w2 freq1 freq2  = overChangeVolG ys cs j freq1 freq2 x0 xdelta (w1,w2)

-- | A simplified variant of the 'soxBasicParameters' function with defining only file extension.
endingWF :: String -> String
endingWF ys
 | not (null ys) = if last ys == 'f' then ".flac" else ".wav"
 | otherwise = ".wav"

-- | Converts a character into a corresponding string using as a default one \"l\" (a logarithmic one). An output can specify then the fade type for SoX.
charFadeType :: Char -> String
charFadeType c =
  case c of
   'h' -> "h"
   'p' -> "p"
   't' -> "t"
   _  -> "l"

-- | Using SoX mixes all the \"testG*\" (of the WAV or FLAC extension specified by the 'String' argument -- see 'endingWF') in the current directory. 
-- If there are \"resultG.*" (wav or flac respectively) file in the directory, it is overwritten. Also the "testG*" files are deleted afterwards if the 
-- mixing is successful.
mixGTest :: String -> IO ()
mixGTest ys = do
  dir <- listDirectory "."
  (code1,_,herr1) <- readProcessWithExitCode (fromJust (showE "sox")) (mconcat [["-m"], filter (\xs -> "testG" `isPrefixOf` xs &&
    endingWF ys `isSuffixOf` xs) dir, ["resultG" ++ endingWF ys]]) ""
  if code1 /= ExitSuccess then error $ "DobutokO.Sound.Times.mixGTest: " ++ herr1
  else mapM_ removeFile . filter (\xs -> "testG" `isPrefixOf` xs && endingWF ys `isSuffixOf` xs) $ dir

-- | Generates a sequence of sounds using 'overChangeVol' so that their time-volume characteristic is going through the 2D points obtained 
-- with the last two arguments.
-- Uses 'fadeEndsTMB', the arguments for which are specified by the second symbol in the second 'String' and by the third 'Double' argument.
basicF :: String -> String -> Double -> Double -> Double -> Double -> (Double -> Double) -> V.Vector Double -> IO ()
basicF ys x2s freq x0 xdelta per f v = do
  let (xs1,xs2) = splitAt 1 x2s
      c1 = if null xs1 then 'l' else head xs1
      c2 = if null xs2 then 'l' else head xs2
  v1 <- evalSndFV f v
  V.imapM_ (\i x -> do
    overChangeVol ys c1 i freq x0 xdelta x
    fadeEndsTMB c2 per $ "testG" ++ prependZeroes 6 (show i) ++ endingWF ys) v1

-- | Splits its argument (like 'splitAt') into two 'String' with the length of (if possible) 4 and 2 characters. The rest of the argument is not used.
argString :: String -> (String,String)
argString xs = (take 4 xs,take 2 . drop 4 $ xs)

-- | Generates a sequence of sounds using 'overChangeVol' so that their time-volume characteristic is going through the 2D points obtained 
-- with the last two arguments.
-- The 'String' should consist of 6 alphanumeric characters. The first four as for the 'soxBasicParams' and the last two -- letters from the \"hlpqt\". 
-- Otherwise, the default values are used (\"221w\" for the first and \"ll\" for the second one).
basicFC :: String -> Double -> Double -> Double -> Double -> (Double -> Double) -> V.Vector Double -> IO ()
basicFC xs freq x0 xdelta per f v = let (ys,x2s) = argString xs in basicF ys x2s freq x0 xdelta per f v

-- | Generates a sequence of sounds using 'overChangeVol' so that their time-volume characteristic is going through the 2D points obtained 
-- with the last two arguments.
-- Uses 'fadeEndsTMN', the arguments for which are specified by the second symbol in the second 'String' and by the third and fourth 'Double' arguments.
basicF2 :: String -> String -> Double -> Double -> Double -> Double -> Double -> (Double -> Double) -> V.Vector Double -> IO ()
basicF2 ys x2s freq x0 xdelta per1 per2 f v = do
  let (xs1,xs2) = splitAt 1 x2s
      c1 = if null xs1 then 'l' else head xs1
      c2 = if null xs2 then 'l' else head xs2
  v1 <- evalSndFV f v
  V.imapM_ (\i x -> do
    overChangeVol ys c1 i freq x0 xdelta x
    fadeEndsTMN c2 per1 per2 $ "testG" ++ prependZeroes 6 (show i) ++ endingWF ys) v1

-- | Generates a sequence of sounds using 'overChangeVol' so that their time-volume characteristic is going through the 2D points obtained 
-- with the last two arguments.
-- The 'String' should consist of 6 alphanumeric characters. The first four as for the 'soxBasicParams' and the last two -- letters from the \"hlpqt\". 
-- Otherwise, the default values are used (\"221w\" for the first and \"ll\" for the second one).
basicF2C :: String -> Double -> Double -> Double -> Double -> Double -> (Double -> Double) -> V.Vector Double -> IO ()
basicF2C xs freq x0 xdelta per1 per2 f v = let (ys,x2s) = argString xs in basicF2 ys x2s freq x0 xdelta per1 per2 f v