-- |
-- Module      :  Melodics.Executable
-- Copyright   :  (c) OleksandrZhabenko 2019-2020
-- License     :  MIT
-- Maintainer  :  olexandr543@yahoo.com
--
-- A program and a library that can be used as a musical instrument synthesizer or for Ukrainian speech synthesis
-- especially for poets, translators and writers.
--

module Melodics.Executable (
  circle
  , workWithInput
  , rawToSoundFile
  , printInfoF
  , recFileName
)
where

import Data.Char (isSpace, isControl)
import Data.Maybe (isJust,fromJust)
import System.IO
import System.Process (callProcess)
import System.Directory (removeFile)
import Control.Exception (onException)
import EndOfExe (showE)
import Melodics.Ukrainian (appendS16LEFile, convertToProperUkrainian)
import UkrainianLControl

-- | Is used to repeat the cycle of creation of the sound files in the current directory for the @mmsyn6ukr@  executable.
circle :: String -> IO ()
circle :: String -> IO ()
circle String
zs = IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
onException ((Int -> IO ()) -> [Int] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (String -> Int -> IO ()
workWithInput String
zs) [Int
1..]) (do
    String -> IO ()
putStr String
"Notice, there was (may be) CmdLineArgument exception. To avoid it, please, specify the command line argument (if needed) in the form \"ABC\""
    String -> IO ()
putStr String
" where A is either a letter \'f\', \'o\', \'w\' or a digit and B and C are both digits! The exception (may be) arose from "
    String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"the command line arguments " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
zs String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". Please, check also whether the SoX was installed with the support for needed codec.")

-- | Interactively creates sound files in the current directory for the Ukrainian text input. Is used internally in the 'circle'
workWithInput :: String -> Int -> IO ()
workWithInput :: String -> Int -> IO ()
workWithInput String
zs Int
_ = do
  [String
nameSF,String
ys] <- String -> [Int] -> IO [String]
nameAndControl String
zs [Int
1,Int
2]
  String -> IOMode -> (Handle -> IO ()) -> IO ()
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
withBinaryFile (String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".raw") IOMode
AppendMode (Vector String -> Handle -> IO ()
appendS16LEFile (String -> Vector String
convertToProperUkrainian String
ys))
  String -> IO ()
putStrLn String
"The .raw file was created by the program. If there is SoX installed then it will run further. "
  let ts :: Maybe String
ts = String -> Maybe String
showE String
"sox"
  if Maybe String -> Bool
forall a. Maybe a -> Bool
isJust Maybe String
ts
    then String -> String -> String -> IO ()
rawToSoundFile String
zs String
nameSF (Maybe String -> String
forall a. HasCallStack => Maybe a -> a
fromJust Maybe String
ts)
    else IO ()
printInfoF

-- | Is used to retriev the user-defined file name for the record.
recFileName :: IO String
recFileName :: IO String
recFileName = do
  String -> IO ()
putStrLn String
"Please, specify the name of the resulting sound file. Please, do NOT use '}' character and space or control characters!"
  String
nameOfSoundFile <- IO String
getLine
  let nameSF :: String
nameSF = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (\Char
x -> Bool -> Bool
not (Char -> Bool
isSpace Char
x) Bool -> Bool -> Bool
&& Bool -> Bool
not (Char -> Bool
isControl Char
x) Bool -> Bool -> Bool
&& Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'}') String
nameOfSoundFile
  String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
nameSF

getCtrl :: String -> IO String
getCtrl :: String -> IO String
getCtrl String
zs = do
  String
xs <- IO String
getLine
  let ys :: String
ys = Int -> String -> String
forall a. Int -> [a] -> [a]
take (String -> Int
nSymbols (String -> Int) -> (String -> String) -> String -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, (String, String)) -> String
forall a b. (a, b) -> a
fst ((String, (String, String)) -> String)
-> (String -> (String, (String, String))) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> (String, (String, String))
genControl (String -> Int) -> String -> Int
forall a b. (a -> b) -> a -> b
$ String
zs) String
xs
  String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
ys

recFNAndCtrl :: String -> Int -> IO String
recFNAndCtrl :: String -> Int -> IO String
recFNAndCtrl String
zs Int
n
  | Int -> Bool
forall a. Integral a => a -> Bool
odd Int
n = IO String
recFileName
  | Bool
otherwise = String -> IO String
getCtrl String
zs

nameAndControl :: String -> [Int] -> IO [String]
nameAndControl :: String -> [Int] -> IO [String]
nameAndControl String
zs = (Int -> IO String) -> [Int] -> IO [String]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (String -> Int -> IO String
recFNAndCtrl String
zs)

-- | Converts RAW sound to the sound file of the needed format in the current directory accordingly to the 'genControl' for the first 'String' argument.
-- Is used internally in the 'workWithInput'.
rawToSoundFile :: String -> String -> FilePath -> IO ()
rawToSoundFile :: String -> String -> String -> IO ()
rawToSoundFile String
zs String
nameSF String
executablePath
  | String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
zs = do
     String -> [String] -> IO ()
callProcess String
executablePath [String
"-r22050",String
"-c1",String
"-L",String
"-esigned-integer",String
"-b16", String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".raw", String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".wav"]
     String -> IO ()
removeFile (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".raw"
  | Bool
otherwise = do
     let ws :: (String, String)
ws = (String, (String, String)) -> (String, String)
forall a b. (a, b) -> b
snd ((String, (String, String)) -> (String, String))
-> (String -> (String, (String, String)))
-> String
-> (String, String)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> (String, (String, String))
genControl (String -> (String, String)) -> String -> (String, String)
forall a b. (a -> b) -> a -> b
$ String
zs
     String -> [String] -> IO ()
callProcess String
executablePath [String
"-r22050",String
"-c1",String
"-L",String
"-esigned-integer",String
"-b16", String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".raw", (String, String) -> String
forall a b. (a, b) -> a
fst (String, String)
ws, String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ (String, String) -> String
forall a b. (a, b) -> b
snd (String, String)
ws]
     String -> IO ()
removeFile (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
nameSF String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".raw"

-- | Prints informational message about ending of the possible for the given data program operation on sound files. Is used internally in the 'workWithInput'.
-- Is used internally in the 'workWithInput'.
printInfoF :: IO ()
printInfoF :: IO ()
printInfoF = do
  String -> IO ()
putStr String
"You have a resulting file in a raw PCM format with bitrate 22050 Hz and 1 channel (mono) in the .raw format. "
  String -> IO ()
putStr String
"You can further process it by yourself manually, otherwise, please, install FFMpeg or LibAV executables in the directory mentioned in the variable PATH"
  String -> IO ()
putStrLn String
" and then run: "
  String -> IO ()
putStrLn String
"\"name_of_FFMpeg_or_LibAV_executable\" -f s16le -acodec pcm_s16le -ac 1 -ar 22050 -i \"name_Of_the_sound_file\" \"the_same_name_without_.raw_ending_and_with_.wav_ending\""
  String -> IO ()
putStrLn String
""
  String -> IO ()
putStrLn String
"OR you can install SoX executable in the directory mentioned in the variable PATH and then run: "
  String -> IO ()
putStrLn String
"\"Path_to_the_SoX_executable\" -b16 -r22050 -c1 -e signed-integer -L \"name_of_the_file_in_raw_format_with_new._prefix\" \"name_of_the_file_in_raw_format_with_new._prefix\" in the terminal."