dobutokO2-0.41.0.0: DobutokO/Sound/Octaves.hs
-- |
-- Module : DobutokO.Sound.Octaves
-- 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.
{-# OPTIONS_GHC -threaded #-}
module DobutokO.Sound.Octaves (
-- * Work with octaves
octaveUp
, octaveDown
, liftInOctave
, liftInOctaveV
) where
import Data.Maybe (fromJust)
import qualified Data.Vector as V
import DobutokO.Sound.Functional.Basics
-- | Returns an analogous note in the higher octave (its frequency in Hz).
octaveUp :: Double -> Double
octaveUp x = 2 * x
{-# INLINE octaveUp #-}
-- | Returns an analogous note in the lower octave (its frequency in Hz).
octaveDown :: Double -> Double
octaveDown x = x / 2
{-# INLINE octaveDown #-}
-- | 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'.
liftInOctave :: Int -> Double -> Maybe Double
liftInOctave 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 = logBase 2.0 (V.unsafeIndex notes (n * 12) / 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) octaveUp $ closestNote x)
else Just (V.unsafeLast . V.iterateN (fromIntegral z1 + 2) octaveUp $ closestNote x)
_ -> let z = logBase 2.0 (closestNote x / V.unsafeIndex notes (n * 12))
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'.
liftInOctaveV :: Int -> V.Vector Double -> V.Vector Double
liftInOctaveV n = V.mapMaybe (liftInOctave n)
--------------------------------------------------------------------------------------------------------------------------------