music-pitch-1.8: src/Music/Pitch/Common/Number.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
module Music.Pitch.Common.Number where
import Control.Lens
import Music.Pitch.Common.Diatonic
{-
-- ** Number
Number,
HasNumber(..),
unison,
prime,
second,
third,
fourth,
fifth,
sixth,
seventh,
octave,
ninth,
tenth,
twelfth,
duodecim,
thirteenth,
fourteenth,
fifteenth,
-}
-- |
-- The number portion of an interval (i.e. second, third, etc).
--
-- Note that the interval number is always one step larger than number of steps spanned by
-- the interval (i.e. a third spans two diatonic steps). Thus 'number' does not distribute
-- over addition:
--
-- > number (a + b) = number a + number b - 1
--
newtype Number = Number { getNumber :: Int }
deriving (Eq, Ord, Num, Enum, Real, Integral)
instance Show Number where { show = show . getNumber }
instance HasNumber Number where number = id
-- | A synonym for @1@.
unison :: Number
unison = 1
-- | A synonym for @1@.
prime :: Number
prime = 1
-- | A synonym for @2@.
second :: Number
second = 2
-- | A synonym for @3@.
third :: Number
third = 3
-- | A synonym for @4@.
fourth :: Number
fourth = 4
-- | A synonym for @5@.
fifth :: Number
fifth = 5
-- | A synonym for @6@.
sixth :: Number
sixth = 6
-- | A synonym for @7@.
seventh :: Number
seventh = 7
-- | A synonym for @8@.
octave :: Number
octave = 8
-- | A synonym for @9@.
ninth :: Number
ninth = 9
-- | A synonym for @10@.
tenth :: Number
tenth = 10
-- | A synonym for @11@.
eleventh :: Number
eleventh = 11
-- | A synonym for @12@.
twelfth :: Number
twelfth = 12
-- | A synonym for @12@.
duodecim :: Number
duodecim = 12
-- | A synonym for @13@.
thirteenth :: Number
thirteenth = 13
-- | A synonym for @14@.
fourteenth :: Number
fourteenth = 14
-- | A synonym for @15@.
fifteenth :: Number
fifteenth = 15
class HasNumber a where
-- |
-- Returns the number portion of an interval.
--
-- The interval number is negative if and only if the interval is negative.
--
-- See also 'quality', 'octaves' and 'semitones'.
--
number :: a -> Number
-- TODO rename numberDiatonicSteps
diatonicSteps :: Iso' Number DiatonicSteps
diatonicSteps = iso n2d d2n
where
n2d n | n > 0 = fromIntegral (n - 1)
n2d n | n == 0 = error "diatonicSteps: Invalid number 0"
n2d n | n < 0 = fromIntegral (n + 1)
d2n n | n >= 0 = fromIntegral (n + 1)
d2n n | n < 0 = fromIntegral (n - 1)