packages feed

algorithmic-composition-basic-0.2.2.0: Composition/Sound/Functional/Basics.hs

-- |
-- Module      :  Composition.Sound.Functional.Basics
-- 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 #-}
{-# OPTIONS_HADDOCK show-extensions #-}
{-# OPTIONS_GHC -threaded #-}

module Composition.Sound.Functional.Basics (
  -- * Type synonyms with different semantics
  SoundsO
  , OvertonesO
  , NotePairs
  -- * Work with notes (general)
  , notes
  , neighbourNotes
  , closestNote
  , pureQuintNote 
  , overTones
  , overTonesALaClarinet
  -- * Work with overtones
  , overSoXSynth
  , overSoXSynthALaClarinet
  -- ** Generalized functions
  , overSoXSynthG
  , overSoXSynthGG
  -- * Work with enky (extension to octaves functionality)
  , nkyT
  , whichOctave
  , whichOctaveG
  , whichEnka 
  , enkuUp 
  , enkuDown 
  , liftInEnkuV
  , liftInEnku
  -- ** Work with octaves
  , octavesT
  -- * Combining intermediate files
  , mixTest
  , mixTest2
  -- * Working with files
  , freqsFromFile
  , endFromResult
  -- * Use additional function and Ukrainian texts and generates melody
  , dNote
  -- ** 2G generalized auxiliary functions
  , mixTest2G
  , mixTest22G
  , endFromResult2G
  -- ** Auxiliary functions
  , partialTest_k1G
  , partialTest_k2G
  , prependZeroes 
  , nOfZeroesLog 
  , numVZeroesPre
  , duration1000
  , adjust_dbVol
) where

import CaseBi.Arr (getBFstLSorted')
import Data.Char (isDigit)
import System.Exit (ExitCode( ExitSuccess ))
import Numeric
import Data.List (isPrefixOf,sort)
import Data.Maybe (fromJust,isJust,fromMaybe,mapMaybe)
import qualified Data.Foldable as F (find)
import GHC.Arr
import Sound.SoXBasics (durationA)
import System.Process
import EndOfExe
import System.Directory
import Composition.Sound.IntermediateF

-- | Is used to represent a sequence of intervals, each note being a 'Float' value (its frequency in Hz).
type SoundsO = Array Int (Float, Float)

-- | Is used to represent a set of overtones for the single sound, the first 'Float' value is a frequency and the second one -- an amplitude.
type OvertonesO = [(Float, Float)]

-- | Is used to represent a set of pairs of notes for each element of which the 'Float' values (notes frequencies in Hz) are somewhat
-- musically connected one with another..
type NotePairs = Array Int (Float, Float)

-- | Gets 'Int' frequencies from the given 'FilePath' using SoX. The frequencies are \"rough\" according to the SoX documentation and
-- the duration is too small so they can be definitely other than expected ones. Is used as a source of variable numbers (somewhat close each to another
-- in their order but not neccessarily). .
freqsFromFile :: FilePath -> Int -> IO [Int]
freqsFromFile file n = mapM (\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 }) [0..n-1]

-- | Combines (mixes) all \"test\*" files in the given directory. The files should be similar in parameters and must be sound files for SoX to work
-- on them properly. Afterwards, the function deletes these combined files.
mixTest :: IO ()
mixTest = do
  paths0 <- listDirectory "."
  let paths = filter (isPrefixOf "test") $ paths0
      l = length paths
  if l <= 100 then do
   _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result.wav"]) ""
   return ()
  else do
    let zipp = intoHundreds paths
    ress <- mapM (\(i, hundList) -> readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ hundList ++ ["result" ++ prependZeroes 20 (show i) ++ ".wav"]) "" >> return ("result" ++ prependZeroes l (show i) ++ ".wav")) . zip [0..] $ zipp
    _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ ress ++ ["result.wav"]) ""
    ress2 <- listDirectory "."
    let ress3 = filter (isPrefixOf "result0") ress2
    mapM_ removeFile ress3
  mapM_ removeFile paths

intoHundreds :: [a] -> [[a]]
intoHundreds xs
 | null xs = []
 | otherwise = ts : intoHundreds zs
     where (ts,zs) = splitAt 100 xs

-- | Similar to 'mixTest', but allows to change the sound quality parameters for the resulting file. For more information, please, refer to
-- 'soxBasicParams'.
mixTest2G :: String -> IO ()
mixTest2G ys = do
  paths0 <- listDirectory "."
  let paths = filter (isPrefixOf "test") $ paths0
      l = length paths
  if l <= 100 then do
   _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ soxBasicParams ys ["","result.wav"]) ""
   return ()
  else do
    let zipp = intoHundreds paths
    ress <- mapM (\(i, hundList) -> readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ hundList ++ soxBasicParams ys ["","result" ++ prependZeroes 20 (show i) ++ ".wav"]) "" >> return (head . soxBasicParams ys $ ["","result" ++ prependZeroes l (show i) ++ ".wav"])) . zip [0..] $ zipp
    _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ ress ++ soxBasicParams ys ["","result.wav"]) ""
    ress2 <- listDirectory "."
    let ress3 = filter (isPrefixOf "result0") ress2
    mapM_ removeFile ress3
  mapM_ removeFile paths

-- | Combines (mixes) all \"test\*" files in the given directory. The files should be similar in parameters and must be sound files for SoX to work
-- on them properly. Afterwards, the function deletes these combined files. The name of the resulting file depends on the first two command line
-- arguments so that it is easy to produce unique names for the consequent call for the function.
-- The function works properly only for the amount of the files not greater than several hundreds. If possible, use 'mixTest' function instead.
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"]) ""
  mapM_ removeFile paths

-- | Similar to 'mixTest', but allows to change the sound quality parameters for the resulting file. For more information, please, refer to
-- 'soxBasicParams'. The name of the resulting file depends on the first two command line
-- arguments so that it is easy to produce unique names for the consequent call for the function.
-- The function works properly only for the amount of the files not greater than several hundreds. If possible, use 'mixTest2G' function instead.
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"]) ""
  mapM_ removeFile paths  

-- | Gets an \"end.wav\" file from the intermediate \"result\*.wav\" files in the current directory. If it is not successful, produces the notification
-- message and exits without error. If you would like to create the file if there are too many intermediate ones, please, run
-- \"dobutokO2 8\" or \"dobutokO2 80\" in the current directory.
endFromResult :: IO ()
endFromResult = do
  path2s <- listDirectory "."
  let paths3 = sort . filter (isPrefixOf "result") $ path2s
      l = length paths3
  if l <= 100 then do
   (code,_,_) <- readProcessWithExitCode (fromJust (showE "sox")) (paths3 ++ ["end.wav"]) ""
   procPlusEnd code
  else do
    let zipp = intoHundreds paths3
    ress <- mapM (\(i, hundList) -> readProcessWithExitCode (fromJust (showE "sox")) (hundList ++ ["end" ++ prependZeroes 20 (show i) ++ ".wav"]) "" >> return ("end" ++ prependZeroes l (show i) ++ ".wav")) . zip [0..] $ zipp
    _ <- readProcessWithExitCode (fromJust (showE "sox")) (ress ++ ["end.wav"]) ""
    ress2 <- listDirectory "."
    let ress3 = filter (isPrefixOf "end0") ress2
    mapM_ removeFile ress3

procPlusEnd :: ExitCode -> IO ()
procPlusEnd code =
  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."

-- | Similar to 'endFromResult', but uses additional 'String' argument to change sound quality parameters. For more information, please, refer to
-- 'soxBasicParams'.
endFromResult2G :: String -> IO ()
endFromResult2G ys = do
  path2s <- listDirectory "."
  let paths3 = sort . filter (isPrefixOf "result") $ path2s
      l = length paths3
  if l <= 100 then do
   (code,_,_) <- readProcessWithExitCode (fromJust (showE "sox")) (paths3 ++ soxBasicParams ys ["","end.wav"]) ""
   procPlusEnd2G code ys
  else do
    let zipp = intoHundreds paths3
    ress <- mapM (\(i, hundList) -> readProcessWithExitCode (fromJust (showE "sox")) (hundList ++ soxBasicParams ys ["","end" ++ prependZeroes 20 (show i) ++ ".wav"]) "" >> return (head . soxBasicParams ys $ ["","end" ++ prependZeroes l (show i) ++ ".wav"])) . zip [0..] $ zipp
    _ <- readProcessWithExitCode (fromJust (showE "sox")) (ress ++ soxBasicParams ys ["","end.wav"]) ""
    ress2 <- listDirectory "."
    let ress3 = filter (isPrefixOf "end0") ress2
    mapM_ removeFile ress3

procPlusEnd2G :: ExitCode -> String -> IO ()
procPlusEnd2G code ys =
  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."            

-- | Generates part of the \"test\*\" files with the additional volume adjustment in dB given by 'Array' 'Int' 'Float'.
partialTest_k1G :: OvertonesO -> Int -> String -> Array Int Float -> IO ()
partialTest_k1G ks k ts arr2 = partialTest_k2G ks k ts arr2 []

-- | Generalized version of the 'partialTest_k1G' with a possibility to change sound quality parameters using the additional second 'String' argument.
-- For more information, please, refer to 'soxBasicParams'.
partialTest_k2G :: OvertonesO -> Int -> String -> Array Int Float -> String -> IO ()
partialTest_k2G ks k ts arr2 ys =
  let zeroN = numVZeroesPre ks in mapM_ (\(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 ""]
            (unsafeAt arr2 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) (((\wwws -> adjust_dbVol wwws (unsafeAt arr2 i))) ["-r22050", "-n", "test" ++ show k ++ show (i + 2) ++ ".wav",
       "synth", ts,"sine", showFFloat Nothing (abs noteN) "", "vol", showFFloat Nothing amplN ""])) "" >> putStr "") . zip [0..] $ ks

-- | Auxiliary function to get from a sound file specified a duration parameter @n@ that can be used further.
duration1000 :: FilePath -> IO Int
duration1000 file = fmap (\t -> truncate (t / 0.001)) . durationA $ file

-- | Function to get from the number of semi-tones and a note a 'Maybe' note for the second lower note in the interval if any. If there is
-- no need to obtain such a note, then the result is 'Nothing'.
dNote :: Int -> Float -> Maybe Float
dNote n note
  | n == 0 || note  < unsafeAt notes 0 || note > unsafeAt notes 107 = Nothing
  | otherwise = Just (note / 2 ** (fromIntegral n / 12))

-- | 'Array' of musical notes in Hz.
notes :: Array Int Float
-- notes V.! 57 = 440.0   -- A4 in Hz
notes = listArray (0,107) . map (\t ->  440 * 2 ** (fromIntegral (t - 57) / 12)) $ [0..107]

-- | Function returns either the nearest two musical notes if frequency is higher than one for C0 and lower than one for B8
-- or the nearest note duplicated in a tuple.
neighbourNotes :: Float -> (Int,Int) -> (Float, Float)
neighbourNotes x (!ll,!mm)
  | x <= unsafeAt notes ll = (unsafeAt notes ll, unsafeAt notes ll)
  | x >= unsafeAt notes mm = (unsafeAt notes mm, unsafeAt notes mm)
  | mm - ll > 1 = if x <= unsafeAt notes ((mm + ll) `quot` 2)
      then neighbourNotes x (ll, (mm + ll) `quot` 2)
      else neighbourNotes x ((mm + ll) `quot` 2, mm)
  | otherwise = (unsafeAt notes ll, unsafeAt notes mm)

-- | Returns the closest note to the given frequency in Hz.  
closestNote :: Float -> Float
closestNote x
 | x > 0.0 =
    let (x0, x2) = neighbourNotes x (0,107)
        r0       = x / x0
        r2       = x2 / x in 
    if r2 > r0 then x0 else x2
 | otherwise = 0.0

-- | Additional function to prepend zeroes to the given 'String'. The number of them are just that one to fulfill the length to the given 'Int' parameter.
prependZeroes :: Int -> String -> String 
prependZeroes n xs 
  | if n <= 0 || null xs then True else n <= length xs = xs
  | otherwise = replicate (n - length xs) '0' ++ xs
{-# INLINE prependZeroes #-}  

nOfZeroesLog :: Int -> Maybe Int
nOfZeroesLog x
  | x <= 0 = Nothing
  | otherwise = Just (truncate (logBase 10 (fromIntegral x)) + 1)
{-# INLINE nOfZeroesLog #-}  

-- | Is a minimal number of decimal places that are just enough to represent a length of the list given. For an [] returns 0.
numVZeroesPre :: [a] -> Int
numVZeroesPre ks = fromMaybe (0 :: Int) (nOfZeroesLog . length $ ks)
{-# INLINE numVZeroesPre #-}      

-- | Similarly to 'liftInOctaveV' returns a ['Float'] (actually frequencies) for the n-th elements set of notes (see 'nkyT') instead of octaves.
-- A second 'Int' parameter defines that @n@. 
liftInEnkuV :: Int -> Int -> [Float] -> [Float]
liftInEnkuV n ku = mapMaybe (liftInEnku n ku)

-- | Similarly to 'liftInOctave' returns a 'Maybe' number (actually frequency) for the n-th elements set of notes (see 'nkyT').
-- A second 'Int' parameter defines that @n@. Not all pairs return 'Just' @x@. 
liftInEnku :: Int -> Int -> Float -> Maybe Float
liftInEnku n ku x
  | n < 0 || n > ((108 `quot` ku) - 1) = Nothing
  | getBFstLSorted' False (zip [2,3,4,6,9,12] . repeat $ True) ku && closestNote x > 24.4996 =
      case compare (fromJust . whichEnka ku $ x) n of
        EQ -> Just (closestNote x)
        LT -> let z  = logBase 2.0 (unsafeAt notes (n * ku) / closestNote x)
                  z1 = truncate z in
                   if abs (z - fromIntegral z1) > 0.999 || abs (z - fromIntegral z1) < 0.001
                     then Just (last . take  (fromIntegral z1 + 1) . iterate (enkuUp ku) $ closestNote x)
                     else Just (last . take  (fromIntegral z1 + 2) . iterate (enkuUp ku) $ closestNote x)
        _  -> let z  = logBase 2.0 (closestNote x / unsafeAt notes (n * ku))
                  z1 = truncate z in
                   if abs (z - fromIntegral z1) > 0.999 || abs (z - fromIntegral z1) < 0.001
                     then Just (last . take (fromIntegral z1 + 2) . iterate (enkuDown ku) $ closestNote x)
                     else Just (last . take (fromIntegral z1 + 1) . iterate (enkuDown ku) $ closestNote x)
  | otherwise = Nothing

-- | Similarly to 'whichOctave' returns a 'Maybe' number for the n-th elements set of notes (see 'nkyT').
-- An 'Int' parameter defines that @n@.
whichEnka :: Int -> Float -> Maybe Int
whichEnka n x
  | getBFstLSorted' False (zip [2,3,4,6,9,12] . repeat $ True) n && closestNote x >= unsafeAt notes 0 && closestNote x < unsafeAt notes (108 - n) = Just (round (logBase (2 ** (fromIntegral n / 12.0)) ((fst . fromJust . F.find (\(t1, _) -> closestNote x < t1) $ nkyT n) / unsafeAt notes 0) - 1))
  | getBFstLSorted' False (zip [2,3,4,6,9,12] . repeat $ True) n && closestNote x >= unsafeAt notes (108 - n) = Just (108 `quot` n)
  | otherwise = Nothing 

-- | Returns an analogous note in the higher n-th elements set (its frequency in Hz) (see 'nkyT'). An 'Int' parameter defines this @n@.
enkuUp  :: Int -> Float -> Float
enkuUp n x
  | getBFstLSorted' False (zip [2..11] . repeat $ True) n = 2 ** (fromIntegral n / 12) * x
  | otherwise = 2 * x
{-# INLINE enkuUp #-}  

-- | Returns an analogous note in the lower n-th elements set (its frequency in Hz) (see 'nkyT'). An 'Int' parameter defines this @n@.
enkuDown  :: Int -> Float -> Float
enkuDown n x
  | getBFstLSorted' False (zip [2..11] . repeat $ True) n = 2 ** (fromIntegral (-n) / 12) * x
  | otherwise = x / 2
{-# INLINE enkuDown #-}

-- | Returns a 'Array' 'Int' of tuples with the lowest and highest frequencies for the notes in the sets consisting of @n@ consequential notes
-- (including semi-tones). An 'Int' argument defines this @n@. It can be 2, 3, 4, 6, 9, or 12 (the last one is for default octaves, see 'octavesT').
-- So for different valid @n@ you obtain doubles, triples and so on. The function being applied returns a 'Array' 'Int' of such sets with
-- their respective lowest and highest frequencies.
nkyT :: Int -> NotePairs
nkyT n
  | getBFstLSorted' False (zip [2,3,4,6,9,12] . repeat $ True) n = amap (\i -> (unsafeAt notes (i * n),
       unsafeAt notes (i * n + (n - 1)))) . listArray (0,k) $ [0..k]
  | otherwise = octavesT
     where !k = (108 `quot` n) - 1

-- | Returns a 'Array' 'Int' of tuples with the lowest and highest frequencies for the notes in the octaves.
octavesT :: NotePairs
octavesT = amap (\i -> (unsafeAt notes (i * 12), unsafeAt notes (i * 12 + 11))) . listArray (0,8) $ [0..8]

-- | For the given frequency it generates a musical sound with a timbre. The main component of the sound includes the lower pure quint,
-- which can be in the same octave or in the one with the number lower by one. Please, check before executing
-- whether there is no \"x.wav\", \"test*\", \"result*\" and \"end.wav\" files in the current directory, because they can be overwritten.
overSoXSynth :: Float -> IO ()
overSoXSynth = overSoXSynthG overTones
{-# INLINE overSoXSynth #-}

-- | Similar to 'overSoXSynth' but uses 'overTonesALaClarinet' instead of 'overTones'.
overSoXSynthALaClarinet :: Float -> IO ()
overSoXSynthALaClarinet = overSoXSynthG overTonesALaClarinet
{-# INLINE overSoXSynthALaClarinet #-}

-- | Generalized variant of the 'overSoXSynth' with the possibility to set the variant of the overtones for the notes as the
-- first argument.
-- @ since 0.2.0.0 removed some extra processment with uncertain semantics.
overSoXSynthG :: (Float -> OvertonesO) -> Float -> IO ()
overSoXSynthG = overSoXSynthGG [pureQuintNote]
{-# INLINE overSoXSynthG #-}

-- | Generalized variant of the 'overSoXSynth' with the possibility to set the variant of the overtones for the notes as the
-- first argument and the harmonizers rules by the first list argument. The first list is intended to contain no more than 9 elements (this will generate containing no more than 10 different notes chord at once).
overSoXSynthGG :: [(Float -> Float)] -> (Float -> OvertonesO) -> Float -> IO ()
overSoXSynthGG progressionHarmonizerList g x = do
  let !note0 = if x /= 0.0 then closestNote (abs x) else unsafeAt notes 0
      !notes1 = map (\f -> f note0) progressionHarmonizerList
      !vs    = map g (note0 : notes1)
      overSoXSynthHelpG j = mapM_ (\(i, (noteN, !amplN)) -> readProcessWithExitCode (fromJust (showE "sox"))
        ["-r22050", "-n", "test" ++ show j ++ show (i + 2) ++ ".wav", "synth", "0.5","sine", showFFloat Nothing noteN "", "vol", showFFloat Nothing amplN ""] "") . zip [0..]
  mapM_ (\(j, vel) -> overSoXSynthHelpG j vel) . zip [0..] $ vs
  mixTest  

-- | Returns a pure quint lower than the given note.
pureQuintNote :: Float -> Float
pureQuintNote x = x / 2 ** (7 / 12)
{-# INLINE pureQuintNote #-}

-- | For the given frequency of the note it generates a list of the tuples, each one of which contains the harmonics' frequency and amplitude.
overTones :: Float -> OvertonesO
overTones note =
  takeWhile (\(!w,!z) -> w <= unsafeAt notes 107 && abs z > 0.001) . map (\i ->
    (note * fromIntegral (i + 2), 1 / fromIntegral ((i + 1) * (i + 1)))) $ [0..1023]

overTonesALaClarinet :: Float -> OvertonesO
overTonesALaClarinet note =
  takeWhile (\(!w,!z) -> w <= unsafeAt notes 107 && abs z > 0.001) . map (\i ->
    (note * fromIntegral (2 * i + 1), 1 / fromIntegral ((i + 1) * (i + 1)))) $ [0..512]

-- | Function can be used to determine to which octave (in the American notation for the notes, this is a number in the note written form,
-- e. g. for C4 this is 4) the frequency belongs (to be more exact, the closest note for the given frequency -- see 'closestNote' taking into account
-- its lower pure quint, which can lay in the lower by 1 octave). If it is not practical to determine the number, then the function returns 'Nothing'.
whichOctave :: Float -> Maybe Int
whichOctave x
  | closestNote x >= 24.4996 && closestNote x < unsafeAt notes 95 = Just (round (logBase 2 ((fst . fromJust . F.find (\(t1, _) -> closestNote x < t1) $ octavesT) / unsafeAt notes 0) - 1))
  | closestNote x >= unsafeAt notes 95 = Just 8
  | otherwise = Nothing

-- | Generalized version of the 'whichOctave'.
whichOctaveG :: Float -> Maybe Int
whichOctaveG x 
  | closestNote x >= unsafeAt notes 0 && x <= unsafeAt notes 95 = Just (round (logBase 2 ((fst . fromJust . F.find (\(t1, _) -> closestNote x < t1) $ octavesT) / unsafeAt notes 0) - 1))
  | closestNote x >= unsafeAt notes 95 = Just 8
  | otherwise = Nothing

-- | Is used internally in the 'readProcessWithExitCode' to adjust volume for the sound with additional dB value given by 'Float' argument.
adjust_dbVol :: [String] -> Float -> [String]
adjust_dbVol xss y
 | y == 0.0 = xss
 | otherwise = xss ++ ["vol",showFFloat Nothing y "dB"]