-- | This module defines notion of pitch. module Temporal.Music.Pitch ( Hz, Interval, c1, a1, transpose, -- * Pitch Pitch(..), fromStep, Bend, Octave, Step, -- * Scale Scale(..), fromIntervals, scaleStep, scaleLength, -- * PitchLike PitchLike(..), mapPitch, -- * Render absPitch, pitchAsDouble, scaleAt ) where import Data.Default import qualified Data.Vector as V -- | Cycles per second type Hz = Double -- | Multiplier of Hz type Interval = Double -- | Middle C (261.626 Hz) c1 :: Hz c1 = 261.626 -- | Middle A (440 Hz) a1 :: Hz a1 = 440 -- Pitch -- | 'Pitch' denotes 'Hz' value. But it's not a double for ease of -- performing some musical transformations, transposition, bend, -- inversion, changing scales. 'Pitch' can be converted to 'Hz' with -- function 'hz'. Pitch contains 'Scale', and point on the tone plane. -- The point is a triple @(bend, octave, step)@. 'Bend' -- denotes divergens from vertices of scale grid. 'Octave' and 'Step' -- are integers. data Pitch = Pitch { pitchScale :: Scale, pitchBend :: Bend, pitchOctave :: Octave, pitchStep :: Step } deriving (Show, Eq) -- | 'Bend' represents tone's diversion from scale grid. type Bend = Double type Octave = Int type Step = Int -- | Calculates position on tone plane for value of type 'Pitch'. pitchAsDouble :: Pitch -> Double pitchAsDouble p = pitchBend p + fromIntegral (pitchAsInt p) -- | Calculates integer position on tone plane for value of type 'Pitch' -- without bend bias. pitchAsInt :: Pitch -> Int pitchAsInt p = pitchOctave p * (scaleLength $ pitchScale p) + pitchStep p instance Default Pitch where def = Pitch def def def def -- Scale -- | 'Scale' defines 2D grid (octave, step) in space of 'Hz' units. -- 'Bend' is a level of diversion from grid vertices. -- 1-level bend is equal to 1 step. For tones with fractional bends frequency -- is calculated with linear interpolation by nearest values in scale. -- Example: -- -- > s = Scale f0 d intervals -- -- Here scale @s@ defines 2D grid that mapps center point @(0, 0)@ to -- frequency @f0@ 'Hz'. Value 'd' is octave interval. Octave interval -- is divided on steps. Steps are 'Interval' s from base frequency @f0@ -- to desired frequency. Let's define equal temperament scale: -- -- > eqt = Scale c1 2 $ Vector.fromList $ (map ((2 **) . (/12)) [0..11]) data Scale = Scale { scaleBase :: Hz, scaleOctave :: Interval, scaleSteps :: V.Vector Interval } deriving (Show, Eq) instance Default Scale where def = eqt c1 where eqt = fromIntervals 2 (map ((2 **) . (/12)) [0 .. 11]) -- | 'Scale' constructor. fromIntervals :: Interval -> [Interval] -> (Hz -> Scale) fromIntervals octave steps = \f0 -> Scale f0 octave $ V.fromList steps -- | Scale value on doubles scaleAt :: Scale -> Double -> Hz scaleAt s x = scaleAtInt s d * bendCoeff s n r where (d, r) = properFraction x n = mod d $ scaleLength s -- | 'Pitch' can be used alongside with many -- other parameters (they can define timbre or loudness). -- Class 'PitchLike' provides getters and setters for -- data types that contain value of type 'Pitch'. -- In "Temporal.Music.Score" module you can find many -- functions that are defined in terms of this class. Once you -- have chosen some note representation you can make an instance -- for it and use all pitch-modifiers. class PitchLike a where setPitch :: Pitch -> a -> a getPitch :: a -> Pitch -- | Pitch modifier. mapPitch :: PitchLike a => (Pitch -> Pitch) -> a -> a mapPitch f x = setPitch (f $ getPitch x) x instance PitchLike Pitch where setPitch = const id getPitch = id -- | Constructs 'Pitch' from some step value. 'Bend' and -- 'Octave' are set to zeroes. 'Scale' is set to default scale -- which is defined in 'HasScale' class. fromStep :: Int -> Pitch fromStep a = def{ pitchStep = a } -- | Gives scale multiplier scaleStep :: Scale -> Int -> Interval scaleStep s x = (scaleOctave s ^^ o) * scaleSteps s V.! n where (o, n) = divMod x $ scaleLength s -- | Gives number of steps in one octave. scaleLength :: Scale -> Int scaleLength = V.length . scaleSteps -- | Transpose cycles per second by some interval. transpose :: Interval -> Hz -> Hz transpose k a = k * a --------------------------------------------------------- -- rendering renderPitch :: Pitch -> Hz renderPitch p = pitchScale p `scaleAt` pitchAsDouble p -- | Calculates cycles per second for a pitch. absPitch :: PitchLike a => a -> Hz absPitch = renderPitch . getPitch -- | scale value on integers scaleAtInt :: Scale -> Int -> Hz scaleAtInt s x = scaleBase s * scaleStep s x bendCoeff :: Scale -> Int -> Double -> Hz bendCoeff s n x | abs x < 1e-6 = 1 | x > 0 = flip loginterpCoeff x $ getTones s n $ n + 1 | otherwise = flip loginterpCoeff (abs x) $ getTones s n $ n - 1 where getTones s n1 n2 = (getTone s n1, getTone s n2) getTone s x | x >= 0 && x < n = scaleSteps s V.! x | x == n = o | x == -1 = scaleSteps s V.! (n-1) / o | otherwise = error $ "scaleStep: out of bounds" where n = scaleLength s o = scaleOctave s loginterpCoeff :: (Double, Double) -> Double -> Double loginterpCoeff (l, r) x = (r / l) ** x