-- | -- Module : Composition.Sound.Keyboard -- Copyright : (c) OleksandrZhabenko 2020-2021 -- 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. {-# OPTIONS_GHC -threaded #-} module Composition.Sound.Keyboard ( -- * Working with input and files qwerty2dvorak , dvorak2qwerty , input2BL , input2BLN , input2BLMN , readFile2BL , readFile2BLN , readFile2BLMN , readFile2BLGen , readFile2BLGenN , readFile2BLGenMN -- * Conversions , readFileDoubles , readFileDoublesN , readFileDoublesMN , readFileDoublesGen , readFileDoublesGenN , readFileDoublesGenMN , takeDoubles , hashStr2 , convH ) where import CaseBi.Arr (getBFstL',getBFstLSorted') import Data.Char (isAsciiLower) import GHC.Arr import Data.Foldable.Ix import GHC.Int (Int64) import qualified Data.ByteString.Lazy.Char8 as BL (ByteString,map,zipWith,tail,filter,getContents,readFile,take,drop) -- | Converts a lazy 'BL.ByteString' into a list of 'Int' using 'hashStr2'. takeDoubles :: BL.ByteString -> [Int] takeDoubles xs = BL.zipWith hashStr2 xs $ BL.tail xs -- | Converts a string of lowercase ASCII letters being typed on the QWERTY keyboard layout into corresponding Dvorak keyboard layout. qwerty2dvorak :: BL.ByteString -> BL.ByteString qwerty2dvorak = BL.map (getBFstL' ' ' (zip ("/;<>" ++ ['a'..'z']) "wvszaxje.uidchtnmbrl'poygk,qf;")) -- | Vice versa to 'qwerty2dvorak'. dvorak2qwerty :: BL.ByteString -> BL.ByteString dvorak2qwerty = BL.map (getBFstL' ' ' (zip ("',.;" ++ ['a'..'z']) "qwezanihdyujgcvpmlsrxo;kf.,bt/")) -- | Hashes two lower case ascii characters. Is used for controlling frequencies and operators. hashStr2 :: Char -> Char -> Int hashStr2 x y = getBFstLSorted' 57 (zip ['a'..'z'] . map (\t -> getBFstLSorted' (26 * t + 18) (zip ['a'..'z'] [(26 * t + 4)..]) y) . concat $ [[0],[6..8],[1],[9..11],[4],[12..16],[2],[17..21],[3],[22..24],[5,25]]) x -- 679 is the greatest value ~ \"zz\"; there are 572 effectful val. -- | Get contents into lazy 'BL.ByteString' with filtering of all characters that are not a lower case ascii letters. input2BL :: IO (BL.ByteString) input2BL = fmap (convH [] (BL.filter isAsciiLower)) BL.getContents -- | Like 'input2BL', but takes only first @n@ symbols specified with the first 'Int64' argument. input2BLN :: Int64 -> IO (BL.ByteString) input2BLN n = fmap (convH [] (BL.take n . BL.filter isAsciiLower)) BL.getContents -- | Like 'input2BL', but takes only first @n@ symbols specified with the second 'Int64' argument dropping before this the first @m@ symbols specified -- with the first 'Int64' argument. input2BLMN :: Int64 -> Int64 -> IO (BL.ByteString) input2BLMN m n = fmap (convH [] (BL.take n . BL.drop m . BL.filter isAsciiLower)) BL.getContents -- | Reads a given file into a lazy 'BL.ByteString' with filtering of all characters that are not a lower case ascii letters. It has additional -- first command line argument to control the way of treating letters: as being typed (entered) properly (null 'String'), or needed to be converted -- from qwerty to dvorak layout (\"q\" 'String'), or vice versa (otherwise). readFile2BLGen :: String -> FilePath -> IO (BL.ByteString) readFile2BLGen ys = fmap (convH ys (BL.filter isAsciiLower)) . BL.readFile -- | Like 'readFile2BLGen', but reads only first @n@ symbols specified with the first 'Int64' argument. readFile2BLGenN :: Int64 -> String -> FilePath -> IO (BL.ByteString) readFile2BLGenN n ys = fmap (convH ys (BL.take n . BL.filter isAsciiLower)) . BL.readFile -- | Like 'readFile2BLGen', but reads only first @n@ symbols specified with the second 'Int64' argument dropping before this the first @m@ symbols specified -- with the first 'Int64' argument. readFile2BLGenMN :: Int64 -> Int64 -> String -> FilePath -> IO (BL.ByteString) readFile2BLGenMN m n ys = fmap (convH ys (BL.take n . BL.drop m . BL.filter isAsciiLower)) . BL.readFile -- | Auxiliary function to define how is a 'BL.ByteString' treated, see 'readFile2BLGen'. convH :: String -> (BL.ByteString -> BL.ByteString) -> (BL.ByteString -> BL.ByteString) convH ys f | null ys = f | ys == "q" = qwerty2dvorak . f | otherwise = dvorak2qwerty . f -- | Usual way the function 'readFile2BLGen' is used. The text in a file being read is treated as a properly typed (entered) one. So there is no -- keyboard layout conversion at all. readFile2BL :: FilePath -> IO (BL.ByteString) readFile2BL = readFile2BLGen [] {-# INLINE readFile2BL #-} -- | Like 'readFile2BL', but reads only first @n@ symbols specified with the first 'Int64' argument. readFile2BLN :: Int64 -> FilePath -> IO (BL.ByteString) readFile2BLN n = readFile2BLGenN n [] {-# INLINE readFile2BLN #-} -- | Like 'readFile2BL', but reads only first @n@ symbols specified with the second 'Int64' argument dropping before this the first @m@ symbols specified -- with the first 'Int64' argument. readFile2BLMN :: Int64 -> Int64 -> FilePath -> IO (BL.ByteString) readFile2BLMN m n = readFile2BLGenMN m n [] {-# INLINE readFile2BLMN #-} -- | After reading a file into a filtered lazy 'BL.ByteString' (see, 'readFile2BLGen') converts the resulting 'BL.ByteString' into a list -- of 'Int'. The arguments have the same meaning as for 'readFile2BLGen'. readFileDoublesGen :: String -> FilePath -> IO [Int] readFileDoublesGen ys = fmap (takeDoubles . convH ys (BL.filter isAsciiLower)) . BL.readFile -- | Like 'readFileDoublesGen', but returns only first @n@ elements of the list specified with the first 'Int64' argument. readFileDoublesGenN :: Int64 -> String -> FilePath -> IO [Int] readFileDoublesGenN n ys = fmap (take (fromIntegral n) . takeDoubles . convH ys (BL.filter isAsciiLower)) . BL.readFile -- | Like 'readFileDoublesGen', but returns only first @n@ symbols specified with the second 'Int64' argument dropping before this the first @m@ symbols specified -- with the first 'Int64' argument. readFileDoublesGenMN :: Int64 -> Int64 -> String -> FilePath -> IO [Int] readFileDoublesGenMN m n ys = fmap (s2L (fromIntegral m) (fromIntegral n) . takeDoubles . convH ys (BL.filter isAsciiLower)) . BL.readFile -- | Usual way the function 'readFileDoublesGen' is used. The text in a file being read is treated as a properly typed (entered) one. So there is no -- keyboard layout conversion at all. readFileDoubles :: FilePath -> IO [Int] readFileDoubles = readFileDoublesGen [] {-# INLINE readFileDoubles #-} -- | Like 'readFileDoubles', but returns only first @n@ elements of the list specified with the first 'Int64' argument. readFileDoublesN :: Int64 -> FilePath -> IO [Int] readFileDoublesN n = readFileDoublesGenN n [] {-# INLINE readFileDoublesN #-} -- | Like 'readFileDoubles', but returns only first @n@ elements of the list specified with the second 'Int64' argument -- dropping before this the first @m@ elements specified with the first 'Int64' argument. readFileDoublesMN :: Int64 -> Int64 -> FilePath -> IO [Int] readFileDoublesMN m n = readFileDoublesGenMN m n [] {-# INLINE readFileDoublesMN #-}