dobutokO2 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+116/−34 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- DobutokO.Sound: nOfZeroesLog :: Int -> Maybe Int
- DobutokO.Sound: numVZeroesPre :: Vector a -> Int
+ DobutokO.Sound: octaveDown :: Double -> Double
+ DobutokO.Sound: octaveUp :: Double -> Double
+ DobutokO.Sound: octavesT :: Vector (Double, Double)
+ DobutokO.Sound: putInOctave :: Int -> Double -> Maybe Double
+ DobutokO.Sound: putInOctaveV :: Int -> Vector Double -> Vector Double
+ DobutokO.Sound: whichOctave :: Double -> Maybe Int
- DobutokO.Sound: oberSoXSynthNGen :: FilePath -> String -> IO ()
+ DobutokO.Sound: oberSoXSynthNGen :: FilePath -> Int -> String -> IO ()
- DobutokO.Sound: uniqOberSoXSynthNGen :: FilePath -> String -> String -> IO ()
+ DobutokO.Sound: uniqOberSoXSynthNGen :: FilePath -> Int -> String -> String -> IO ()
Files
- CHANGELOG.md +4/−0
- DobutokO/Sound.hs +107/−32
- Main.hs +1/−1
- README.markdown +3/−0
- dobutokO2.cabal +1/−1
CHANGELOG.md view
@@ -3,3 +3,7 @@ ## 0.1.0.0 -- 2020-03-04 * First version. Released on an unsuspecting world.++## 0.2.0.0 -- 2020-03-05++* Second version. Added the functionality connected with octaves. Some documentation and code improvements.
DobutokO/Sound.hs view
@@ -6,7 +6,7 @@ -- Maintainer : olexandr543@yahoo.com -- -- A program and a library to create experimental music--- from the mono audio and a Ukrainian text.+-- from a mono audio and a Ukrainian text. module DobutokO.Sound ( -- * Basic functions for the executable@@ -23,6 +23,13 @@ , uniqOberSoXSynth , uniqOberSoXSynthN , uniqOberSoXSynthNGen+ -- ** Work with octaves+ , octavesT+ , octaveUp+ , octaveDown+ , whichOctave+ , putInOctave+ , putInOctaveV -- ** Auxiliary functions , notes , neighbourNotes@@ -30,8 +37,6 @@ , pureQuintNote , syllableStr , prependZeroes- , nOfZeroesLog- , numVZeroesPre ) where import Control.Exception (onException)@@ -53,7 +58,7 @@ -- notes V.! 57 = 440.0 -- A4 in Hz notes = V.generate 108 (\t -> fromIntegral 440 * 2 ** (fromIntegral (t - 57) / fromIntegral 12)) --- | Function returns either a nearest two musical notes if note is higher than C0 and lower than B8 or a nearest note duplicated in a tuple.+-- | 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 :: Double -> V.Vector Double -> (Double, Double) neighbourNotes x v | compare x (V.unsafeIndex v 0) /= GT = (V.unsafeIndex v 0, V.unsafeIndex v 0)@@ -79,7 +84,60 @@ pureQuintNote :: Double -> Double pureQuintNote x = x / 2 ** (fromIntegral 7 / fromIntegral 12) --- | Function is used to generate a rhythm of the resulting file from the Ukrainian text and number of sounds in the syllables or words without vowels.+-- | Returns an analogous note in the higher octave (its frequency in Hz).+octaveUp :: Double -> Double+octaveUp x = 2 * x++-- | Returns an analogous note in the lower octave (its frequency in Hz).+octaveDown :: Double -> Double+octaveDown x = x / fromIntegral 2++-- | Returns a 'V.Vector' of tuples with the lowest and highest frequencies for the notes in the octaves.+octavesT :: V.Vector (Double, Double)+octavesT = V.generate 9 (\i -> (V.unsafeIndex notes (i * 12), V.unsafeIndex notes (i * 12 + 11)))++-- | 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 :: Double -> 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, t2) -> compare (closestNote x) t1 == LT) $ octavesT+ | otherwise = Nothing+ +-- | Function lifts the given frequency to the given number of the octave (in American notation, from 0 to 8). This number is an 'Int' parameter.+-- The function also takes into account the lower pure quint for the closest note.+-- If it is not practical to determine the number, then the function returns 'Nothing'.+putInOctave :: Int -> Double -> Maybe Double+putInOctave n x+ | compare n 0 == LT || compare n 8 == GT = Nothing+ | compare (closestNote x) 24.4996 == GT =+ case compare (fromJust . whichOctave $ x) n of+ EQ -> Just (closestNote x)+ LT -> let z = log (V.unsafeIndex notes (n * 12) / closestNote x) / log 2.0+ 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) octaveUp $ closestNote x)+ else Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 2) octaveUp $ closestNote x)+ _ -> let z = log (closestNote x / V.unsafeIndex notes (n * 12)) / log 2.0+ 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) octaveDown $ closestNote x)+ else Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 1) octaveDown $ closestNote x)+ | otherwise = Nothing++-- | Function lifts the 'V.Vector' of 'Double' representing frequencies to the given octave with the 'Int' number. Better to use numbers in the range [1..8].+-- The function also takes into account the lower pure quint for the obtained note behaviour. If it is not practical to determine the octave, the resulting+-- frequency is omitted from the resulting 'V.Vector'.+putInOctaveV :: Int -> V.Vector Double -> V.Vector Double+putInOctaveV n = V.mapMaybe (\z -> putInOctave n z) ++-- | 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 . sylLengthsP2 . syllablesUkrP $ xs@@ -95,7 +153,7 @@ (V.generate 1024 (\i -> fromIntegral 1 / fromIntegral ((i + 1) * (i + 1)))) -- | For the given frequency of the note it generates a 'V.Vector' of the tuples, each one of which contains the harmonics' frequency and amplitude. For every given--- 'String' structure of the uniqueness (see the documentation for mmsyn7s package and its MMSyn7.Syllable module) it produces the unique timbre.+-- 'String' structure of the uniqueness (see the documentation for @mmsyn7s@ package and its 'MMSyn7.Syllable' module) it produces the unique timbre. uniqOberTonesV :: Double -> String -> V.Vector (Double, Double) uniqOberTonesV note xs = let ys = uniquenessPeriods xs@@ -106,7 +164,8 @@ V.takeWhile (\u -> compare (fst u) (V.unsafeIndex notes 107) /= GT && compare (snd u) 0.001 == GT) . V.unsafeSlice 1 (z2 - 1) . V.zip (V.generate z2 (\i -> note * fromIntegral (i + 1))) $ v2 --- | For the given frequency it generates a musical sound with a timbre. +-- | 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. oberSoXSynth :: Double -> IO () oberSoXSynth x = do let note0 = closestNote x@@ -125,7 +184,8 @@ _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result.wav","vol","0.3"]) "" mapM_ removeFile paths --- | Function to create a melody for the given arguments. 'String' is used to provide a rhythm.+-- | Function to create a melody for the given arguments. 'String' is used to provide a rhythm. 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. oberSoXSynthN :: Int -> String -> V.Vector Double -> IO () oberSoXSynthN n zs vec0 = V.imapM_ (\j x -> do let note0 = closestNote x -- zs is obtained from the command line arguments@@ -138,8 +198,8 @@ oberSoXSynthHelpN vec = V.imapM_ (\i (noteN, amplN) -> readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "test" ++ prependZeroes zeroN (show (i + 2)) ++ ".wav", "synth", show (V.unsafeIndex v2 i),"sine", show noteN, "vol", show amplN] "") vec oberSoXSynthHelpN2 vec = V.imapM_ (\i (noteN, amplN) -> readProcessWithExitCode (fromJust (showE "sox"))- ["-r22050", "-n", "testQ" ++ prependZeroes zeroN (show (i + 2)) ++ ".wav", "synth", show (V.unsafeIndex v2 i),"sine", show noteN, "vol", show amplN] "") vec - _ <- readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "test" ++ prependZeroes zeroN "1" ++ ".wav", "synth", "0.5","sine", show note0, "synth", "0.5","sine", "mix", show note1] ""+ ["-r22050", "-n", "testQ" ++ prependZeroes zeroN (show (i + 2)) ++ ".wav", "synth", show (V.unsafeIndex v2 i),"sine", show noteN, "vol", show amplN] "") vec + _ <- readProcessWithExitCode (fromJust (showE "sox")) ["-r22050", "-n", "test" ++ prependZeroes zeroN "1" ++ ".wav", "synth", "0.5","sine", show note0, "synth" , "0.5","sine", "mix", show note1] "" oberSoXSynthHelpN v0 oberSoXSynthHelpN2 v1 paths0 <- listDirectory "."@@ -147,9 +207,10 @@ _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result" ++ prependZeroes zeroN (show j) ++ ".wav","vol","0.3"]) "" mapM_ removeFile paths ) vec0 --- | Similar to 'oberSoXSynthN', but uses a sound file to obtain the information analogous to 'V.Vector' in the latter one.-oberSoXSynthNGen :: FilePath -> String -> IO ()-oberSoXSynthNGen file zs = do+-- | Similar to 'oberSoXSynthN', but uses a sound file to obtain the information analogous to 'V.Vector' in the latter one. Besides, the function lifts+-- the frequencies to the octave with the given by 'Int' parameter number (better to use from the range [1..8]). +oberSoXSynthNGen :: FilePath -> Int -> String -> IO ()+oberSoXSynthNGen file m zs = do duration0 <- durationA file let n = truncate (duration0 / 0.001) vecA <- V.generateM n (\k -> do { (_, _, herr) <- readProcessWithExitCode (fromJust (showE "sox")) [file, "-n", "trim", show (fromIntegral k * 0.001),@@ -158,7 +219,7 @@ noteN1 = takeWhile isDigit . dropWhile (not . isDigit) . concat . drop 13 . take 14 $ line0s ; if null noteN1 then return (11440::Int) else let noteN2 = read (takeWhile isDigit . dropWhile (not . isDigit) . concat . drop 13 . take 14 $ line0s)::Int in return noteN2 })- let vecB = V.map (closestNote . fromIntegral) . V.filter (/= (11440::Int)) $ vecA+ let vecB = putInOctaveV m . V.map fromIntegral . V.filter (/= (11440::Int)) $ vecA oberSoXSynthN n zs vecB path2s <- listDirectory "." let paths3 = sort . filter (isPrefixOf "result") $ path2s@@ -184,8 +245,10 @@ else 0::Int -- | For the given frequency and a Ukrainian text it generates a musical sound with the timbre obtained from the Ukrainian text (see the documentation for @mmsyn7s@--- package). The timbre for the another given text usually is another one, but can be the same. The last one is only if the uniqueness structure and length are the--- same for both 'String'. Otherwise, they differs. This gives an opportunity to practically and quickly synthesize differently sounding notes.+-- package). The timbre for another given text usually differs, but can be the same. The last one is only if the uniqueness structure and length are the+-- same for both 'String'. Otherwise, they differs. This gives an opportunity to practically and quickly synthesize differently sounding intervals.+-- 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. uniqOberSoXSynth :: Double -> String -> IO () uniqOberSoXSynth x wws = do let note0 = closestNote x@@ -205,8 +268,11 @@ mapM_ removeFile paths -- | Function to create a melody for the given arguments. The first 'String' is used to provide a rhythm. The second one -- to provide a timbre.--- The timbre for the another given text usually is another one, but can be the same. This gives an opportunity to practically and quickly--- synthesize differently sounding notes.+-- The timbre for another given text usually differs, but can be the same. This gives an opportunity to practically and quickly+-- synthesize differently sounding intervals.+-- The main component of the sound is in the given octave with a number given+-- by 'Int' parameter. Besides, another 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. uniqOberSoXSynthN :: Int -> String -> String -> V.Vector Double -> IO () uniqOberSoXSynthN n zs wws vec0 = V.imapM_ (\j x -> do let note0 = closestNote x -- zs ? vec0 -- are they related to the one object? No, they are obtained from different sources.@@ -228,9 +294,10 @@ _ <- readProcessWithExitCode (fromJust (showE "sox")) (["--combine", "mix"] ++ paths ++ ["result" ++ prependZeroes zeroN (show j) ++ ".wav","vol","0.3"]) "" mapM_ removeFile paths ) vec0 --- | Similar to 'uniqOberSoXSynthN', but uses a sound file to obtain the information analogous to 'V.Vector' in the latter one.-uniqOberSoXSynthNGen :: FilePath -> String -> String -> IO ()-uniqOberSoXSynthNGen file zs wws = do+-- | Similar to 'uniqOberSoXSynthN', but uses a sound file to obtain the information analogous to 'V.Vector' in the latter one. +-- Besides, the function lifts the frequencies to the octave with the given by 'Int' parameter number (better to use from the range [1..8]). +uniqOberSoXSynthNGen :: FilePath -> Int -> String -> String -> IO ()+uniqOberSoXSynthNGen file m zs wws = do duration0 <- durationA file let n = truncate (duration0 / 0.001) vecA <- V.generateM n (\k -> do {@@ -240,7 +307,7 @@ 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 })- let vecB = V.map (closestNote . fromIntegral) . V.filter (/= (11440::Int)) $ vecA+ let vecB = putInOctaveV m . V.map fromIntegral . V.filter (/= (11440::Int)) $ vecA uniqOberSoXSynthN n zs wws vecB path2s <- listDirectory "." let paths3 = sort . filter (isPrefixOf "result") $ path2s@@ -255,14 +322,15 @@ file = concat . drop 1 . take 2 $ args case arg1 of "1" -> do- mapM_ (recAndProcess file) [1,2]- oberSoXSynthNGen (file ++ ".wav") (unwords . drop 2 $ args)+ [_,_,octave] <- mapM (recAndProcess file) [1..3]+ let octave1 = read octave::Int+ oberSoXSynthNGen (file ++ ".wav") octave1 (unwords . drop 2 $ args) _ -> do- [_,_,wws] <- mapM (recAndProcess file) [1..3]- putStrLn $ "wws: " ++ wws- uniqOberSoXSynthNGen (file ++ ".wav") (unwords . drop 2 $ args) wws+ [_,_,octave,wws] <- mapM (recAndProcess file) [1..4]+ let octave1 = read octave::Int+ uniqOberSoXSynthNGen (file ++ ".wav") octave1 (unwords . drop 2 $ args) wws --- | Function records and process the sound data needed to generate the \"end.wav\" file in the 'dobutokO2' function.+-- | Function records and processes the sound data needed to generate the \"end.wav\" file in the 'dobutokO2' function. recAndProcess :: String -> Int -> IO String recAndProcess file x | x == 1 = onException (do@@ -283,9 +351,8 @@ recAndProcess file 1) | x == 2 = onException (do putStr "Please, specify the control parameter for the SoX \"noisered\" effect in the range from 0.0 to 1.0. "- putStrLn "The greater value causes more reduction with possibly removing some inmportant sound data. The default value is 0.5"+ putStrLn "The greater value causes more reduction with possibly removing some important sound data. The default value is 0.5" ctrlN <- getLine- putStrLn $ "ctrlN: " ++ ctrlN let noiseP = tail . dropWhile (/= '.') . filter (\t -> isDigit t || t == '.') $ ctrlN controlNoiseReduction $ '0':noiseP norm "_x.wav"@@ -302,11 +369,19 @@ putStrLn "The process was not successful may be because of the not valid data. Please, specify the valid data as requested." putStrLn "_______________________________________________________________________" recAndProcess file 2)+ | x == 3 = onException (do+ putStr "Please, specify the octave number, to which you would like all the main components (not taking into account their respective lower pure quints) "+ putStrLn "should belong. The number should be better in the range [1..8]"+ octave0 <- getChar+ let octave = (read [octave0]::Int) `mod` 9+ return $ show octave ) (do+ putStrLn "The process was not successful may be because of the not valid data. Please, specify the valid data as requested."+ putStrLn "_______________________________________________________________________"+ recAndProcess file 3) | otherwise = onException (do putStrLn "Please, input the Ukrainian text that will be used to create a special timbre for the notes: " wws <- getLine- putStrLn wws return wws) (do putStrLn "The process was not successful may be because of the not valid data. Please, specify the valid data as requested." putStrLn "_______________________________________________________________________"- recAndProcess file 3)+ recAndProcess file 4)
Main.hs view
@@ -6,7 +6,7 @@ -- Maintainer : olexandr543@yahoo.com -- -- A program and a library to create experimental music--- from the mono audio and a Ukrainian text.+-- from a mono audio and a Ukrainian text. module Main where
README.markdown view
@@ -14,6 +14,9 @@ After its executing (it takes some time) there is a file "end.wav" in the directory. This is the resulting melody generated. +The program now lifts the frequencies to the octave with the number, which you+can specify during its execution.+ ** Note: * Better to execute in the RAM. Need rather a lot of space on the disk for
dobutokO2.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: dobutokO2-version: 0.1.0.0+version: 0.2.0.0 synopsis: A program and a library to create experimental music from a mono audio and a Ukrainian text description: It can also create a timbre for the notes homepage: https://hackage.haskell.org/package/dobutokO2