-- | -- Module : Composition.Sound.Functional.Params -- Copyright : (c) OleksandrZhabenko 2020 -- License : MIT -- Stability : Experimental -- Maintainer : olexandr543@yahoo.com -- -- Helps to create experimental music from a file (or its part) and a Ukrainian text. -- It can also generate a timbre for the notes. Uses SoX inside. Is more complicated than -- dobutokO2 and uses its functionality. {-# LANGUAGE BangPatterns, LambdaCase #-} {-# OPTIONS_GHC -threaded #-} module Composition.Sound.Functional.Params ( Params (..) -- * Type synonyms with different semantics , Durations , Strengths , Intervals -- * New generalizations for scales and modes with Params , filterInParams , sortNoDup , toneD , toneE , liftInParams , liftInParamsV , lengthP , elemP , elemCloseP , showD , isStrParams , isListParams -- ** Application of the Params , overSoXSynthGen2FDN_SG4GPar , overSoXSynthGen2FDN_SG6GPar , overSoXSynthGen2FDN_SG2GPar , overSoXSynthGen2FDN_Sf3GPar , overSoXSynthGen2FDN_Sf3G2GPar -- * Creating melody from overtones , overMeloPar -- * Additional functions , str2DurationsDef , signsFromString , apply6Gf , apply6GSilentFile , vStrToVIntG , strToIntG , defInt , syllableStr , overSoXSynth2FDN_Sf32G , intervalsFromString , soundGenF32G , helpF0 , helpF1 , doubleVecFromVecOfFloat ) where import CaseBi.Arr (getBFstLSorted',getBFstL') import Numeric import Data.List (sort,zip4,elemIndex) import Data.Maybe (isNothing,fromJust,isJust,fromMaybe,mapMaybe) import GHC.Arr import System.Process import System.Exit import EndOfExe import System.Directory import Languages.Phonetic.Ukrainian.Syllable.Arr import Melodics.ByteString.Ukrainian.Arr (convertToProperUkrainianS) import Sound.SoXBasics (upperBnd,selMaxAbs) import MMSyn7l import qualified Data.Foldable as F import Composition.Sound.IntermediateF import Composition.Sound.Functional.Basics import Data.Foldable.Ix -- | Representation of the scales and modes for the notes. Can be extended further, but for a lot of situations the following realization is sufficient. -- See, for example, 'filterInParams' and so on. 'String' is (are) used as a general classification name, for some of them there are provided two -- 'String' to classify. Lists are used to specify remainders in some meaning. See also, 'liftInParams' and 'toneE' ('toneD') functions, 'elemP' and -- 'elemCloseP', 'lengthP' and 'showD'. data Params = P2 Int Int | P2s Int Int String | P3sf Int Int Int String | P4lsf Int Int Int [Int] String | P32sf Int Int Int String String | P3lf Int Int [Int] deriving (Eq, Ord, Show) -- | Is used to represent a set of durations parameters of the sounds and pauses. The positive value corresponds to the sound -- and the negative one -- to the pause. type Durations = Array Int Float -- | Is used to represent a set of volumes in the amplitude scale for SoX \"vol\" effect. type Strengths = Array Int Float -- | Is used to represent a set of intervals for notes (each element is a number of semi-tones between parts of interval). -- Positive values corresponds to lower notes and negative to higher ones. type Intervals = Array Int Int -- | Additional function to produce signs from the given 'String' of the Ukrainian text. Ukrainian vowels and voiced consonants gives \"+\" sign (+1), voiceless -- and sonorous consonants gives \"-\" sign (-1). \"сь\" and \"ць\" gives "0". Other symbols are not taken into account. signsFromString :: Int -> String -> Array Int Int signsFromString n1 = (\rs -> listArray (0,length rs - 1) rs) . take n1 . concatMap (fmap (\case UZ _ W -> 1 UZ _ D -> 1 UZ _ K -> 1 UZ _ L -> (-1) UZ _ M -> (-1) UZ _ S -> (-1) UZ _ O -> (-1) _ -> 0) . concat) . createSyllablesUkrS . take (3 * n1) . cycle -- | Generalized version of the 'overSoXSynthGen2FDN_SG4G' where instead of lifting with 'liftInEnkuV' 'liftInParamsV' is used. It allows e. g. to -- use some tonality. For more information, please, refer to 'filterInParams'. overSoXSynthGen2FDN_SG4GPar :: FilePath -> Params -> (Float -> OvertonesO) -> Float -> Durations -> String -> ((Float -> OvertonesO) -> (Float, Float) -> Int -> String -> IO ()) -> IO () overSoXSynthGen2FDN_SG4GPar file params f y arr2 wws h = do n <- duration1000 file vecA <- freqsFromFile file n let vecB = liftInParamsV params . map fromIntegral $ vecA zeroN = numVZeroesPre vecB in mapM_ (\(j, x) -> do h f (x, (unsafeAt arr2 (j `rem` numElements arr2))) j wws renameFile "result.wav" $ "result0" ++ prependZeroes zeroN (show (j + 1)) ++ ".wav") . zip [0..] $ vecB endFromResult -- | Generalized version of the 'overSoXSynthGen2FDN_SG6G' where instead of lifting with 'liftInEnkuV' 'liftInParamsV' is used. It allows e. g. to -- use some tonality. For more information, please, refer to 'filterInParams'. overSoXSynthGen2FDN_SG6GPar :: FilePath -> Params -> (Float -> OvertonesO) -> Float -> Durations -> String -> ((Float -> OvertonesO) -> (Float, Float) -> Int -> String -> IO ()) -> Strengths -> Float -> IO () overSoXSynthGen2FDN_SG6GPar file params f y v2 wws h v6 limV | F.null v6 = putStrLn "You did not provide a volume adjustments array! " | otherwise = do n <- duration1000 file xs <- freqsFromFile file n let ys = liftInParamsV params . map fromIntegral $ xs !l6 = numElements v6 !l2 = numElements v2 zeroN = numVZeroesPre ys in mapM_ (\(j, x) -> do h f (x, (unsafeAt v2 (j `rem` l2))) j wws renameFile "result.wav" $ "result0" ++ prependZeroes zeroN (show (j + 1)) ++ ".wav" apply6GSilentFile ("result0" ++ prependZeroes zeroN (show (j + 1)) ++ ".wav") limV (unsafeAt v6 (j `rem` l6))) . zip [0..] $ ys endFromResult -- | Generalized version of the 'overSoXSynthGen2FDN_SG2G' where instead of lifting with 'liftInEnkuV' 'liftInParamsV' is used. It allows e. g. to -- use some tonality. For more information, please, refer to 'filterInParams'. overSoXSynthGen2FDN_SG2GPar :: FilePath -> Params -> (Float -> OvertonesO) -> Float -> String -> String -> ((Float -> OvertonesO) -> (Float, Float) -> Int -> String -> String -> IO ()) -> String -> IO () overSoXSynthGen2FDN_SG2GPar file params f y zs wws h ys = do n <- duration1000 file vecA <- freqsFromFile file n let vecB = liftInParamsV params . map fromIntegral $ vecA zeroN = numVZeroesPre vecB v2 = str2DurationsDef n zs y !l2 = numElements v2 in mapM_ (\(j, x) -> do h f (x, (unsafeAt v2 (j `rem` l2))) j wws ys renameFile ("result." ++ if drop 3 ys == "f" then "flac" else "wav") $ "result0" ++ prependZeroes zeroN (show (j + 1)) ++ if drop 3 ys == "f" then ".flac" else ".wav") . zip [0..] $ vecB endFromResult2G ys -- | Generalized version of the 'overSoXSynthGen2FDN_Sf3G' where instead of lifting with 'liftInEnkuV' 'liftInParamsV' is used. It allows e. g. to -- use some tonality. For more information, please, refer to 'filterInParams'. overSoXSynthGen2FDN_Sf3GPar :: FilePath -> Params -> (Float -> OvertonesO) -> Float -> Float -> String -> String -> ((Float -> OvertonesO) -> (Float, Float, Float) -> Int -> String -> IO ()) -> IO () overSoXSynthGen2FDN_Sf3GPar file params f y t0 zs wws h = do n <- duration1000 file vecA <- freqsFromFile file n let vecB = liftInParamsV params . map fromIntegral $ vecA zeroN = numVZeroesPre vecB v2 = str2DurationsDef n zs y !l2 = numElements v2 in mapM_ (\(j, x) -> do h f (x, (unsafeAt v2 (j `rem` l2)), t0) j wws renameFile "result.wav" $ "result0" ++ prependZeroes zeroN (show (j + 1)) ++ ".wav") . zip [0..] $ vecB endFromResult -- | Generalized version of the 'overSoXSynthGen2FDN_Sf3G2G' where instead of lifting with 'liftInEnkuV' 'liftInParamsV' is used. It allows e. g. to -- use some tonality. For more information, please, refer to 'filterInParams'. overSoXSynthGen2FDN_Sf3G2GPar :: FilePath -> Params -> (Float -> OvertonesO) -> Float -> Float -> String -> String -> ((Float -> OvertonesO) -> (Float, Float, Float) -> Int -> String -> String -> IO ()) -> String -> IO () overSoXSynthGen2FDN_Sf3G2GPar file params f y t0 zs wws h ys = do n <- duration1000 file vecA <- freqsFromFile file n let vecB = liftInParamsV params . map fromIntegral $ vecA zeroN = numVZeroesPre vecB v2 = str2DurationsDef n zs y !l2 = numElements v2 in mapM_ (\(j, x) -> do h f (x, (unsafeAt v2 (j `rem` l2)), t0) j wws ys renameFile ("result." ++ if drop 3 ys == "f" then "flac" else "wav") $ "result0" ++ prependZeroes zeroN (show (j + 1)) ++ if drop 3 ys == "f" then ".flac" else ".wav") . zip [0..] $ vecB endFromResult2G ys -- | A way to get from a 'Params' a corresponding 'Array' 'Int' of 'Float' (if any) and so to work with them further. May contain some issues -- so please, before production usage check thoroughly. -- For information there were used the following: -- -- https://en.wikipedia.org/wiki/Mode_(music) -- -- https://en.wikipedia.org/wiki/Ukrainian_Dorian_scale -- -- https://en.wikipedia.org/wiki/List_of_musical_scales_and_modes -- -- https://en.wikipedia.org/wiki/Octatonic_scale -- -- several other articles in the English Wikipedia -- -- and in Ukrainian: -- Смаглій Г., Маловик Л. Теорія музики : Підруч. для навч. закл. освіти, культури і мистецтв / Г.А. Смаглій. -- Х. : Вид-во \"Ранок\", 2013. -- 392 с. -- ISBN 978-617-09-1294-7 -- filterInParams :: Params -> Maybe (Array Int Float) filterInParams (P3lf n2 nL zs) -- generalized sound series, e. g. the chromatic ones etc. | all (>= 0) ([nL,107 - nL - n2,n2 - 2] ++ zs) = if any (\(i, _) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes then Just (h4 . filter (\(i, _) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) else Nothing | otherwise = Nothing filterInParams (P32sf nT n2 nL xs ys) -- dur and moll in various their modifications | all (>= 0) [107 - nL - n2,nT,nL,nT - nL,nL + n2 - nT,n2 - 12] = case xs of "dur" -> getBFstL' Nothing (zip ["FloatH","H","Full","Full moll","M","N"] . fmap Just $ [h4 . filter (\(i,_) -> toneD i nL nT [2,3,6,8,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,3,5,9,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) `notElem` [1,3,5]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) `notElem` [1,6]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,3,5,9,11]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,3,5,8,10]) . zip [0..] . sliceToList nL n2 $ notes]) ys "moll" -> getBFstL' Nothing (zip ["FloatH1","H","Full","Full dur","M","N"] . fmap Just $ [h4 . filter (\(i,_) -> toneD i nL nT [1,4,5,9,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,4,6,9,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) `notElem` [1,4,6]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) `notElem` [1,6]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,4,6,8,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneD i nL nT [1,4,6,9,11]) . zip [0..] . sliceToList nL n2 $ notes]) ys _ -> Nothing | otherwise = Nothing filterInParams (P4lsf nT n2 nL zs xs) | all (>= 0) ([107 - nL - n2,nT,nL,nT - nL,nL + n2 - nT,n2 - 2] ++ zs) = case xs of "ditonic" -> if (length . filter (\(i,_) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) $ replicate n2 True) i) $ (zip [0..] . sliceToList nL n2 $ notes)) /= 2 then Nothing else if (unsafeAt notes nT) `elem` (map snd . filter (\(i,_) -> getBFstLSorted' False (zip (take 2 . sortNoDup . filter (< n2) $ zs) $ replicate n2 True) i) . zip [0..] . sliceToList nL n2 $ notes) then Just (h4 . filter (\(i,_) -> getBFstLSorted' False (zip (take 2 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) else Nothing "tritonic" -> if (length . filter (\(i,_) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) $ (zip [0..] . sliceToList nL n2 $ notes)) /= 3 then Nothing else if (unsafeAt notes nT) `elem` (map snd . filter (\(i,_) -> getBFstLSorted' False (zip (take 3 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) then Just (h4 . filter (\(i,_) -> getBFstLSorted' False (zip (take 3 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) else Nothing "tetratonic" -> if (length . filter (\(i,_) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) $ (zip [0..] . sliceToList nL n2 $ notes)) /= 4 then Nothing else if (unsafeAt notes nT) `elem` (map snd . filter (\(i,_) -> getBFstLSorted' False (zip (take 4 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) then Just (h4 . filter (\(i,_) -> getBFstLSorted' False (zip (take 4 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) else Nothing "octatonic" -> if (length . filter (\(i,_) -> getBFstLSorted' False (zip (sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) $ (zip [0..] . sliceToList nL n2 $ notes)) /= 8 then Nothing else if (unsafeAt notes nT) `elem` (map snd . filter (\(i,_) -> getBFstLSorted' False (zip (take 8 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) then Just (h4 . filter (\(i,_) -> getBFstLSorted' False (zip (take 8 . sortNoDup . filter (< n2) $ zs) . replicate n2 $ True) i) . zip [0..] . sliceToList nL n2 $ notes) else Nothing _ -> Nothing | nL >= 0 && nL <= 107 && n2 == 1 && xs == "monotonic" = Just (listArray (0,0) . (:[]) $ (unsafeAt notes nL)) | otherwise = Nothing filterInParams (P2 nL n2) | all (>= 0) [107 - nL - n2,nL,n2 - 2] = Just ((\rs -> listArray (0,length rs - 1) rs) . sliceToList nL n2 $ notes) | otherwise = Nothing filterInParams (P2s nL n2 xs) | all (>= 0) [107 - nL - n2,nL,n2 - 12] = getBFstLSorted' Nothing (zip ["Egyptian pentatonic", "Prometheus hexatonic scale", "Ukrainian Dorian scale", "augmented hexatonic scale", "blues major pentatonic", "blues minor pentatonic", "blues scale", "major hexatonic scale", "major pentatonic", "minor hexatonic scale", "minor pentatonic", "tritone hexatonic scale", "two-semitone tritone hexatonic scale", "whole tone scale"] . map Just $ [h4 . filter (\(i,_) -> toneE i nL nL [0,2,5,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,4,6,9,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,3,6,7,9,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,3,4,7,8,11]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,5,7,9]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,3,5,8,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,3,5,6,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,3,5,6,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,4,5,7,9]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,4,7,9]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,3,5,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,3,5,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,1,4,6,7,10]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,1,3,7,8,9]) . zip [0..] . sliceToList nL n2 $ notes, h4 . filter (\(i,_) -> toneE i nL nL [0,2,4,6,8,10]) . zip [0..] . sliceToList nL n2 $ notes]) xs | otherwise = Nothing filterInParams (P3sf nT nL n2 xs) | all (>= 0) [101 - nL,nT,nL,nT - nL,nL + 6 - nT] && n2 == 6 = case xs of "Dorian tetrachord" -> if (nT - nL) `elem` [0,1,3,5] then Just (h4 . filter (\(i,_) -> toneE i nL nT [0,1,3,5]) . zip [0..] . sliceToList nL 6 $ notes) else Nothing "Phrygian tetrachord" -> if (nT - nL) `elem` [0,2,3,5] then Just (h4 . filter (\(i,_) -> toneE i nL nT [0,2,3,5]) . zip [0..] . sliceToList nL 6 $ notes) else Nothing "Lydian tetrachord" -> if (nT - nL) `elem` [0,2,4,5] then Just (h4 . filter (\(i,_) -> toneE i nL nT [0,2,4,5]) . zip [0..] . sliceToList nL 6 $ notes) else Nothing _ -> Nothing | all (>= 0) [94 - nL,nT,nL,nT - nL,nL + 13 - nT] && n2 == 13 = getBFstLSorted' Nothing (zip ["modern Aeolian mode", "modern Dorian mode", "modern Ionian mode", "modern Locrian mode", "modern Lydian mode", "modern Mixolydian mode", "modern Phrygian mode"] $ fmap (h3 nT n2 nL) [[1,4,6,9,11], [1,4,6,8,11], [1,3,6,8,10], [2,4,7,9,11], [1,3,5,8,10], [1,3,6,8,11], [2,4,6,9,11]]) xs | otherwise = Nothing h3 :: Int -> Int -> Int -> [Int] -> Maybe (Array Int Float) h3 nT n2 nL zs | nT == nL = Just ((\rs -> listArray (0,length rs - 1) rs) . map snd . filter (\(i, _) -> toneD i nL nT zs) . zip [0..] . sliceToList nL n2 $ notes) | otherwise = Nothing h4 :: [(a,b)] -> Array Int b h4 = (\rs -> listArray (0,length rs - 1) rs) . map snd {-# INLINE h4 #-} -- | For the list of @a@ from the @Ord@ class it builds a sorted in the ascending order list without duplicates. -- -- > sortNoDup [2,1,4,5,6,78,7,7,5,4,3,2,5,4,2,4,54,3,5,65,4,3,54,56,43,5,2] = [1,2,3,4,5,6,7,43,54,56,65,78] -- sortNoDup :: Ord a => [a] -> [a] sortNoDup = sortNoDup' . sort where sortNoDup' (x:x1@(y:_)) | x == y = sortNoDup' x1 | otherwise = x:sortNoDup' x1 sortNoDup' (x:_) = [x] sortNoDup' _ = [] -- | Checks whether its first 'Int' argument does not belong to those ones that are included into the list argument on the reminders basis. -- The opposite to 'toneE' with the same arguments. The list argument must be sorted in the ascending order. toneD :: Int -> Int -> Int -> [Int] -> Bool toneD i nL nT zs = getBFstLSorted' True (zip zs . replicate 12 $ False) ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) -- | Checks whether its first 'Int' argument does belong to those ones that are included into the list argument on the reminders basis. -- The opposite to 'toneD' with the same arguments. The list argument must be sorted in the ascending order. toneE :: Int -> Int -> Int -> [Int] -> Bool toneE i nL nT zs = getBFstLSorted' False (zip zs . replicate 12 $ True) ((nL + i - nT + (((nT - nL) `quot` 12) + 1) * 12) `rem` 12) -- | Analogous to 'liftInEnku' lifts a frequency into a tonality (or something that can be treated alike one) specified by 'Params'. If not -- reasonably one exists then the result is 11440 (Hz). liftInParams :: Float -> Params -> Float liftInParams x params | lengthP params == 0 || (isNothing . whichOctaveG $ x) = 11440.0 | otherwise = let !ys = map (abs . log . (\t -> t / x) . (\i -> unsafeAt notes (12 * fromJust (whichOctaveG x)) * 2 ** (fromIntegral i / fromIntegral (lengthP params)))) [0..lengthP params - 1] in unsafeAt (fromJust . filterInParams $ params) (fromJust . elemIndex (minimum ys) $ ys) -- | Application of the 'liftInParams' to a 'Array' 'Int'. liftInParamsV :: Params -> [Float] -> [Float] liftInParamsV params = filter (/= 11440.0) . map (\x -> liftInParams x params) -- | Gets a length of the 'Array' 'Int' of 'Float' being represented as 'Params'. This is a number of the notes contained in the 'Params'. lengthP :: Params -> Int lengthP = fromMaybe 0 . fmap numElements . filterInParams -- | Check whether a given 'Float' value (frequency of a note) is in the vector of Floats that corresponds to the given 'Params'. elemP :: Float -> Params -> Bool elemP note = fromMaybe False . fmap (note `F.elem`) . filterInParams -- | Check whether a given 'Float' value (frequency of the closest note to the given frequency) is in the vector of Floats that -- corresponds to the given 'Params'. elemCloseP :: Float -> Params -> Bool elemCloseP note = fromMaybe False . fmap (closestNote note `F.elem`) . filterInParams -- | A way to show not the (somewhat algebraic) structure of the 'Params' (as the usual 'show' does), but the contained frequencies in it. showD :: Params -> String showD = show . filterInParams -- | Check whether for the given arguments there are the notes and whether 'String' is a name signature for the scale in 'Params' (can they be used -- together to correspond to a non-empty set of notes). isStrParams :: String -> Params -> Bool isStrParams xs (P2s x y zs) = if isJust (filterInParams (P2s x y zs)) then xs == zs else False isStrParams xs (P3sf x y z zs) = if isJust (filterInParams (P3sf x y z zs)) then xs == zs else False isStrParams xs (P4lsf x y z ts zs) = if isJust (filterInParams (P4lsf x y z ts zs)) then xs == zs else False isStrParams xs (P32sf x y z zs ys) = if isJust (filterInParams (P32sf x y z zs ys)) then (xs == zs || xs == ys || xs == (ys ++ " " ++ zs)) else False isStrParams _ _ = False -- | Check whether for the given arguments there are the notes and whether list of 'Int' is a part of the constructed 'Params' (can they be used -- together to correspond to a non-empty set of notes). isListParams :: [Int] -> Params -> Bool isListParams xs (P4lsf x y z ts zs) = if isJust (filterInParams (P4lsf x y z ts zs)) then xs == ts else False isListParams xs (P3lf x y zs) = if isJust (filterInParams (P3lf x y zs)) then xs == zs else False isListParams _ _ = False -- | Generates melody for the given parameters. The idea is that every application of the function @f :: Float -> OvertonesO@ to its argument -- possibly can produce multiple overtones being represented as list of tuples of pairs of 'Float'. We can use the first element in the -- tuple to obtain a new sound parameters and the second one -- to obtain its new duration in the melody. Additional function @g :: Float -> Float@ -- is used to avoid the effect of becoming less and less -- closer to the zero for the higher overtones so the durations will become also less. -- Besides it allows to rescale the durations in a much more convenient way. -- -- The first 'Float' parameter is a multiplication coefficient to increase or to decrease the durations (values with an absolute values greater than -- one correspond to increasing inside the @g@. function applied afterwards with function composition and the values with an absolute values less -- than one and not equal to zero correspond to decreasing inside the @g@ function. -- The second 'Float' parameter is a usual frequency which is used instead of the 11440.0 (Hz) value. -- The third 'Float' parameter is a main argument -- the frequency for which the 'OvertonesO' are generated as a first step of the computation. overMeloPar :: (Float -> OvertonesO) -> (Float -> Float) -> Params -> Float -> Float -> Float -> IO () overMeloPar f g params coeff freq0 freq = do let v = f freq vFreqs = map ((\z -> if z == 11440.0 then freq0 else z) . flip liftInParams params . fst) v vD = map (g . (* coeff) . snd) v v2 = map f vFreqs vS = map (\z -> showFFloat (Just 4) (abs z) "") vD !l3 = length v2 h42 j (x,v3,y,ts) | y > 0.0 = do (_,_,herr) <- readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "testA.wav", "synth", ts,"sine",showFFloat Nothing (fst x) ""] "" print herr partialTest_k1G v3 0 ts ((\rs -> listArray (0, l3 - 1) rs) . replicate l3 $ 0.0) mixTest renameFile "result.wav" $ "result" ++ prependZeroes (numVZeroesPre v) (show j) ++ ".wav" | y < 0.0 = do (_,_,herr) <- readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "result.wav", "synth", ts,"sine",showFFloat Nothing (fst x) "","vol","0"] "" putStr herr renameFile "result.wav" $ "result" ++ prependZeroes (numVZeroesPre v) (show j) ++ ".wav" | otherwise = putStrLn "Zero length of the sound! " mapM_ (\(j, zz) -> h42 j zz) . zip [0..] . zip4 v v2 vD $ vS -- | A default way to get 'Durations' for the sounds up to 0.35.2.0 version of the package including. It is based on the number of Ukrainian -- sounds representations (see, 'convertToProperUkrainianS') in a Ukrainian syllables or somewhat generated by the same rules as they. -- The rhythm using the function is very often not binary but its ratios are almost always a ratios of the small natural numbers (1, 2, 3, 4, 5, 6, 7 etc.). str2DurationsDef :: Int -> String -> Float -> Durations str2DurationsDef n zs y = let (!t, !ws) = splitAt 1 . syllableStr n $ zs !l = length ws - 1 in amap (\yy -> y * fromIntegral (yy * length ws) / fromIntegral (head t)) . listArray (0,l) $ ws apply6GSilentFile :: FilePath -> Float -> Float -> IO () apply6GSilentFile file limV vol = do upp <- upperBnd file ampL2 <- fmap ((\zz -> read zz::Float) . fst) (selMaxAbs file (0,upp)) if abs ampL2 <= abs limV then putStr "" else apply6Gf vol file -- | Apply volume adjustment to the sound file. It must not be silent. Otherwise, it leads to likely noise sounding or errors. -- The code is adapted from the MMSyn7l module from the @mmsyn7l@ package. apply6Gf :: Float -> FilePath -> IO () apply6Gf vol file = do (code,_,_) <- readProcessWithExitCode (fromJust (showE "sox")) ([file,file ++ "effects" ++ efw2 file] ++ ["norm","vol", showFFloat (Just 4) vol ""]) "" case code of ExitSuccess -> renameFile (file ++ "effects" ++ efw2 file) file _ -> do removeFile $ file ++ "effects" ++ efw2 file putStrLn $ "MMSyn7l.soxE \"" ++ file ++ "\" has not been successful. The file has not been changed at all. " -- | Function is used to generate a rhythm of the resulting file \'end.wav\' from the Ukrainian text and a number of sounds either in the syllables or in the words without vowels. syllableStr :: Int -> String -> [Int] syllableStr n xs = let ps = take n . cycle . concat . fmap (fmap (length)) . createSyllablesUkrS $ xs y = sum ps in case y of 0 -> [0] _ -> y:ps -- | Generalized variant of the 'overSoXSynth2FDN_Sf31G' with a possibility to specify sound quality using the second 'String' parameter. -- For more information, please, refer to 'soxBasicParams'. overSoXSynth2FDN_Sf32G :: (Float -> OvertonesO) -> (Float, Float, Float) -> Int -> String -> Array Int Float -> String -> IO () overSoXSynth2FDN_Sf32G f (x, y, t0) j zs vdB ys | null . convertToProperUkrainianS $ zs = overSoXSynthG f x | otherwise = do let l0 = length zs soundGenF32G [\x2 -> closestNote (if x2 /= 0.0 then abs x2 else unsafeAt notes 0),\x2 -> fromMaybe (unsafeAt notes 0) (dNote (unsafeAt (intervalsFromString zs) (abs (j `rem` l0))) (closestNote (if x2 /= 0.0 then abs x2 else unsafeAt notes 0)))] (replicate 2 x) [1,unsafeAt (intervalsFromString zs) (abs (j `rem` l0))] f (x, y, t0) j vdB ys if null ys then mixTest else mixTest2G ys -- | Function is used to get numbers of intervals from a Ukrainian 'String'. It is used internally in the 'uniqOverSoXSynthN4' function. intervalsFromString :: String -> Intervals intervalsFromString = vStrToVIntG defInt . convertToProperUkrainianS -- | Generatlized version of the 'vStrToVInt' with a possibility to specify your own 'Intervals'. vStrToVIntG :: Intervals -> String -> Intervals vStrToVIntG v xs = listArray (0,l) . map (strToIntG v) $ xs where l = length xs - 1 -- | Default values for 'strToInt'. All the intervals are not greater than one full octave. defInt :: Intervals defInt = listArray (0,28) [5,3,7,11,1,12,4,11,4,12,2,9,3,12,5,10,7,7,7,12,10,7,10,2,12,7,2,12,8] {-# INLINE defInt #-} -- | Generatlized version of the 'strToInt' with a possibility to specify your own 'Intervals'. strToIntG :: Intervals -> Char -> Int strToIntG v = getBFstLSorted' 0 (zip "ABCEFabcdefghijklmnoprstuvxyz" . elems $ v) {-# INLINE strToIntG #-} -- | Generates a melodic line (a somewhat complex changing sound) with a possibility to specify sound quality using the 'String' argument. For more information, -- please, refer to 'soxBasicParams'. soundGenF32G :: [Float -> Float] -> [Float] -> [Int] -> (Float -> OvertonesO) -> (Float, Float, Float) -> Int -> Array Int Float -> String -> IO () soundGenF32G vf vd vi f (x, y, t0) j vdB ys = do let vD = helpF1 vf vd vi vDz = mapMaybe id vD -- The previous one without Nothings and Justs vNotes = doubleVecFromVecOfFloat f t0 . map Just $ vDz ts = showFFloat (Just 4) (abs y) "" -- duration of the sound to be generated mapM_ (\(i, w, u, vv) -> do _ <- readProcessWithExitCode (fromJust (showE "sox")) ((if null ys then id else soxBasicParams ys) (((\wwws -> adjust_dbVol wwws vv)) ["-r22050", "-n", "test" ++ helpF0 i ++ ".wav", "synth",ts, "sine", showFFloat Nothing w "","vol", if compare y 0.0 == GT then "1.0" else "0"])) "" partialTest_k2G u i ts vdB ys) . zip4 [0..] vDz vNotes . elems $ vdB helpF0 :: Int -> String helpF0 = getBFstLSorted' "ZZ0" (zip [0..] $ (map (:[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++ concatMap (\z -> map ((z:) . (:[])) "ABCDEFGHIJKLMNOPQRSTUVWXYZ") "ABCDEFGHIJKLMNOPQRSTUVWXYZ")) helpF1 :: [Float -> Float] -> [Float] -> [Int] -> [Maybe Float] helpF1 vf vd = map (\(f1,x,i2) -> case i2 of 0 -> Nothing _ -> Just $ f1 x) . zip3 vf vd -- | Generates a list of 'OvertonesO' that represents the melodic line. doubleVecFromVecOfFloat :: (Float -> OvertonesO) -> Float -> [Maybe Float] -> [OvertonesO] doubleVecFromVecOfFloat f t0 = map (\note1 -> if isNothing note1 then [] else filter (\(_,!z) -> compare (abs z) t0 == GT) . f . fromJust $ note1)