{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -threaded #-}
module DobutokO.Sound.Functional.Basics (
SoundsO
, OvertonesO
, NotePairs
, notes
, neighbourNotes
, closestNote
, pureQuintNote
, overTones
, overSoXSynth
, nkyT
, whichOctave
, whichOctaveG
, whichEnka
, enkuUp
, enkuDown
, liftInEnkuV
, liftInEnku
, octavesT
, mixTest
, mixTest2
, freqsFromFile
, endFromResult
, dNote
, mixTest2G
, mixTest22G
, endFromResult2G
, partialTest_k
, partialTest_k1G
, partialTest_k2G
, prependZeroes
, nOfZeroesLog
, numVZeroesPre
, duration1000
, adjust_dbVol
) where
import CaseBi (getBFst')
import Data.Char (isDigit)
import System.Exit (ExitCode( ExitSuccess ))
import Numeric
import Data.List (isPrefixOf,sort)
import Data.Maybe (fromJust,isJust,fromMaybe)
import qualified Data.Vector as V
import SoXBasics (durationA)
import System.Process
import EndOfExe
import System.Directory
import DobutokO.Sound.IntermediateF
type SoundsO = V.Vector (Float, Float)
type OvertonesO = V.Vector (Float, Float)
type NotePairs = V.Vector (Float, Float)
freqsFromFile :: FilePath -> Int -> IO (V.Vector Int)
freqsFromFile file n = V.generateM n (\k -> do {
(_, _, herr) <- readProcessWithExitCode (fromJust (showE "sox")) [file, "-n", "trim", showFFloat Nothing (fromIntegral k * 0.001) "",
"0.001", "stat"] ""
; let line0s = lines herr
noteN0 = takeWhile isDigit . dropWhile (not . isDigit) . concat . drop 13 . take 14 $ line0s
; if null noteN0 then return (11440::Int)
else let noteN1 = read (takeWhile isDigit . dropWhile (not . isDigit) . concat . drop 13 . take 14 $ line0s)::Int in return noteN1 })
mixTest :: IO ()
mixTest = do
paths0 <- listDirectory "."
let paths = filter (isPrefixOf "test") $ paths0
_ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result.wav","vol","0.3"]) ""
mapM_ removeFile paths
mixTest2G :: String -> IO ()
mixTest2G ys = do
paths0 <- listDirectory "."
let paths = filter (isPrefixOf "test") $ paths0
_ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ soxBasicParams ys ["","result.wav","vol","0.3"]) ""
mapM_ removeFile paths
mixTest2 :: Int -> Int -> IO ()
mixTest2 zeroN j = do
paths0 <- listDirectory "."
let paths = filter (isPrefixOf "test") $ paths0
_ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result" ++ prependZeroes zeroN (show j) ++ ".wav",
"vol","0.3"]) ""
mapM_ removeFile paths
mixTest22G :: Int -> Int -> String -> IO ()
mixTest22G zeroN j ys = do
paths0 <- listDirectory "."
let paths = filter (isPrefixOf "test") $ paths0
_ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ soxBasicParams ys ["","result" ++ prependZeroes zeroN (show j) ++
".wav","vol","0.3"]) ""
mapM_ removeFile paths
endFromResult :: IO ()
endFromResult = do
path2s <- listDirectory "."
let paths3 = sort . filter (isPrefixOf "result") $ path2s
(code,_,_) <- readProcessWithExitCode (fromJust (showE "sox")) (paths3 ++ ["end.wav"]) ""
case code of
ExitSuccess -> putStrLn "The final file \"end.wav\" was successfully created. You can now manually change or delete \"result*\" files in the directory. "
_ -> do
exi <- doesFileExist "end.wav"
if exi then removeFile "end.wav"
else putStr "Your final file \"end.wav\" was not created by some reason, but the intermediate files in the respective order are present. " >>
putStrLn "Use them manually as needed."
endFromResult2G :: String -> IO ()
endFromResult2G ys = do
path2s <- listDirectory "."
let paths3 = sort . filter (isPrefixOf "result") $ path2s
(code,_,_) <- readProcessWithExitCode (fromJust (showE "sox")) (paths3 ++ soxBasicParams ys ["","end.wav"]) ""
case code of
ExitSuccess -> putStrLn $ "The final file \"end." ++ if drop 3 ys == "f" then "flac" else "wav" ++ "\" was successfully created. You can now manually change or delete \"result*\" files in the directory. "
_ -> do
exi <- doesFileExist $ "end." ++ if drop 3 ys == "f" then "flac" else "wav"
if exi then removeFile $ "end." ++ if drop 3 ys == "f" then "flac" else "wav"
else putStr "Your final file \"end.wav\" was not created by some reason, but the intermediate files in the respective order are present. " >>
putStrLn "Use them manually as needed."
partialTest_k :: OvertonesO -> Int -> String -> IO ()
partialTest_k vec k ts = partialTest_k2G vec k ts V.empty []
partialTest_k1G :: OvertonesO -> Int -> String -> V.Vector Float -> IO ()
partialTest_k1G vec k ts vdB = partialTest_k2G vec k ts vdB []
partialTest_k2G :: OvertonesO -> Int -> String -> V.Vector Float -> String -> IO ()
partialTest_k2G vec k ts vdB ys =
let zeroN = numVZeroesPre vec in V.imapM_ (\i (noteN, !amplN) -> if i /= 0 && i `rem` 50 == 0
then do
_ <- readProcessWithExitCode (fromJust (showE "sox")) (soxBasicParams ys (adjust_dbVol ["-r22050", "-n", "test" ++ show k ++ show (i + 2) ++ ".wav",
"synth", ts,"sine", showFFloat Nothing (abs noteN) "", "vol", showFFloat Nothing amplN ""]
(V.unsafeIndex vdB i))) ""
path1s <- listDirectory "."
let path2s = sort . filter (isPrefixOf $ "test" ++ show k) $ path1s
(code,_,herr0) <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ path2s ++ soxBasicParams ys ["","test-" ++ show k ++
prependZeroes zeroN (show (i `quot` 50)) ++ ".wav"]) ""
case code of
ExitSuccess -> mapM_ removeFile path2s
_ -> do
exi <- doesFileExist $ "test-" ++ show k ++ prependZeroes zeroN (show (i `quot` 50)) ++ if drop 3 ys == "f" then ".flac" else ".wav"
if exi then putStrLn (herr0) >> removeFile ("test-" ++ show k ++ prependZeroes zeroN (show (i `quot` 50)) ++ if drop 3 ys == "f" then ".flac" else ".wav")
else putStrLn herr0
else readProcessWithExitCode (fromJust (showE "sox")) ((if null ys then id else soxBasicParams ys) ((if V.null vdB then id
else (\wwws -> adjust_dbVol wwws (V.unsafeIndex vdB i))) ["-r22050", "-n", "test" ++ show k ++ show (i + 2) ++ ".wav",
"synth", ts,"sine", showFFloat Nothing (abs noteN) "", "vol", showFFloat Nothing amplN ""])) "" >> putStr "") vec
duration1000 :: FilePath -> IO Int
duration1000 file = fmap (\t -> truncate (t / 0.001)) . durationA $ file
dNote :: Int -> Float -> Maybe Float
dNote n note
| n == 0 || compare note (V.unsafeIndex notes 0) == LT || compare note (V.unsafeIndex notes 107) == GT = Nothing
| otherwise = Just (note / 2 ** (fromIntegral n / 12))
notes :: V.Vector Float
notes = V.generate 108 (\t -> 440 * 2 ** (fromIntegral (t - 57) / 12))
neighbourNotes :: Float -> V.Vector Float -> (Float, Float)
neighbourNotes x v
| compare x (V.unsafeIndex v 0) /= GT = (V.unsafeIndex v 0, V.unsafeIndex v 0)
| compare x (V.unsafeIndex v (V.length v - 1)) /= LT = (V.unsafeIndex v (V.length v - 1), V.unsafeIndex v (V.length v - 1))
| compare (V.length v) 2 == GT = if compare x (V.unsafeIndex v (V.length v `quot` 2)) /= GT
then neighbourNotes x (V.unsafeSlice 0 (V.length v `quot` 2 + 1) v)
else neighbourNotes x (V.unsafeSlice (V.length v `quot` 2) (V.length v - (V.length v `quot` 2)) v)
| otherwise = (V.unsafeIndex v 0, V.unsafeIndex v (V.length v - 1))
closestNote :: Float -> Float
closestNote x
| compare x 0.0 == GT =
let (x0, x2) = neighbourNotes x notes
r0 = x / x0
r2 = x2 / x in
if compare r2 r0 == GT
then x0
else x2
| otherwise = 0.0
prependZeroes :: Int -> String -> String
prependZeroes n xs
| if compare n 0 /= GT || null xs then True else compare n (length xs) /= GT = xs
| otherwise = replicate (n - length xs) '0' ++ xs
{-# INLINE prependZeroes #-}
nOfZeroesLog :: Int -> Maybe Int
nOfZeroesLog x
| compare x 0 /= GT = Nothing
| otherwise = Just (truncate (logBase 10 (fromIntegral x)) + 1)
{-# INLINE nOfZeroesLog #-}
numVZeroesPre :: V.Vector a -> Int
numVZeroesPre v = fromMaybe (0 :: Int) (nOfZeroesLog . V.length $ v)
{-# INLINE numVZeroesPre #-}
liftInEnkuV :: Int -> Int -> V.Vector Float -> V.Vector Float
liftInEnkuV n ku = V.mapMaybe (liftInEnku n ku)
liftInEnku :: Int -> Int -> Float -> Maybe Float
liftInEnku n ku x
| compare n 0 == LT || compare n ((108 `quot` ku) - 1) == GT = Nothing
| getBFst' (False, V.fromList . zip [2,3,4,6,9,12] $ repeat True) ku && compare (closestNote x) 24.4996 == GT =
case compare (fromJust . whichEnka ku $ x) n of
EQ -> Just (closestNote x)
LT -> let z = logBase 2.0 (V.unsafeIndex notes (n * ku) / closestNote x)
z1 = truncate z in
if abs (z - fromIntegral z1) > 0.999 || abs (z - fromIntegral z1) < 0.001
then Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 1) (enkuUp ku) $ closestNote x)
else Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 2) (enkuUp ku) $ closestNote x)
_ -> let z = logBase 2.0 (closestNote x / V.unsafeIndex notes (n * ku))
z1 = truncate z in
if abs (z - fromIntegral z1) > 0.999 || abs (z - fromIntegral z1) < 0.001
then Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 2) (enkuDown ku) $ closestNote x)
else Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 1) (enkuDown ku) $ closestNote x)
| otherwise = Nothing
whichEnka :: Int -> Float -> Maybe Int
whichEnka n x
| getBFst' (False,V.fromList . zip [2,3,4,6,9,12] $ repeat True) n && compare (closestNote x) 24.4996 == GT = (\t ->
case isJust t of
True -> fmap (\z ->
case z of
0 -> z
_ -> z - 1) t
_ -> Just ((108 `quot` n) - 1)) . V.findIndex (\(t1, _) -> compare (closestNote x) t1 == LT) . nkyT $ n
| otherwise = Nothing
enkuUp :: Int -> Float -> Float
enkuUp n x
| getBFst' (False, V.fromList . zip [2..11] $ repeat True) n = 2 ** (fromIntegral n / 12) * x
| otherwise = 2 * x
{-# INLINE enkuUp #-}
enkuDown :: Int -> Float -> Float
enkuDown n x
| getBFst' (False, V.fromList . zip [2..11] $ repeat True) n = 2 ** (fromIntegral (-n) / 12) * x
| otherwise = x / 2
{-# INLINE enkuDown #-}
nkyT :: Int -> NotePairs
nkyT n
| getBFst' (False,V.fromList . zip [2,3,4,6,9,12] $ repeat True) n = V.generate (108 `quot` n) (\i -> (V.unsafeIndex notes (i * n),
V.unsafeIndex notes (i * n + (n - 1))))
| otherwise = octavesT
octavesT :: NotePairs
octavesT = V.generate 9 (\i -> (V.unsafeIndex notes (i * 12), V.unsafeIndex notes (i * 12 + 11)))
overSoXSynth :: Float -> IO ()
overSoXSynth x = do
let note0 = if x /= 0.0 then closestNote (abs x) else V.unsafeIndex notes 0
note1 = pureQuintNote note0
v0 = overTones note0
v1 = overTones note1
overSoXSynthHelp = V.imapM_ (\i (noteN, !amplN) -> readProcessWithExitCode (fromJust (showE "sox"))
["-r22050", "-n", "test0" ++ show (i + 2) ++ ".wav", "synth", "0.5","sine", showFFloat Nothing noteN "", "vol", showFFloat Nothing amplN ""] "")
overSoXSynthHelp2 = V.imapM_ (\i (noteN, !amplN) -> readProcessWithExitCode (fromJust (showE "sox"))
["-r22050", "-n", "test1" ++ show (i + 2) ++ ".wav", "synth", "0.5","sine", showFFloat Nothing noteN "", "vol", showFFloat Nothing amplN ""] "")
_ <- readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "test01.wav", "synth", "0.5","sine", showFFloat Nothing note0 "", "synth", "0.5","sine", "mix", showFFloat Nothing note1 "", "vol","0.5"] ""
overSoXSynthHelp v0
overSoXSynthHelp2 v1
mixTest
pureQuintNote :: Float -> Float
pureQuintNote x = x / 2 ** (7 / 12)
{-# INLINE pureQuintNote #-}
overTones :: Float -> OvertonesO
overTones note =
V.takeWhile (\(!w,!z) -> compare w (V.unsafeIndex notes 107) /= GT && compare (abs z) 0.001 == GT) . V.zip (V.generate 1024 (\i ->
note * fromIntegral (i + 2))) $ (V.generate 1024 (\i -> 1 / fromIntegral ((i + 1) * (i + 1))))
whichOctave :: Float -> Maybe Int
whichOctave x
| compare (closestNote x) 24.4996 == GT = (\t ->
case isJust t of
True -> fmap (\z ->
case z of
0 -> z
_ -> z - 1) t
_ -> Just 8) . V.findIndex (\(t1, _) -> compare (closestNote x) t1 == LT) $ octavesT
| otherwise = Nothing
whichOctaveG :: Float -> Maybe Int
whichOctaveG x
| compare (closestNote x) (V.unsafeIndex notes 0) == GT && compare x (V.unsafeIndex notes 107) /= GT = (\t ->
case isJust t of
True -> fmap (\z ->
case z of
0 -> z
_ -> z - 1) t
_ -> Just 8) . V.findIndex (\(t1, _) -> compare (closestNote x) t1 == LT) $ octavesT
| otherwise = Nothing
adjust_dbVol :: [String] -> Float -> [String]
adjust_dbVol xss y
| y == 0.0 = xss
| otherwise = xss ++ ["vol",showFFloat Nothing y "dB"]