{-# LANGUAGE FlexibleInstances #-} -- | Common (Western classical) pitches, intervals and related types. module Music.Pitch.Common.Types ( -- * Even octaves and steps Octaves, DiatonicSteps, ChromaticSteps, Semitones, -- * Intervals Number, Quality(..), QualityType(..), IntervalBasis(..), Interval(..), -- * Pitch Name(..), Accidental, Pitch(..), ) where import Data.Typeable import Data.AdditiveGroup import Music.Pitch.Literal import Music.Pitch.Alterable import Music.Pitch.Augmentable {-| Number of chromatic steps. May be negative, indicating a downward interval. -} newtype ChromaticSteps = ChromaticSteps { getChromaticSteps :: Int } deriving (Eq, Ord, Show, Enum, Num, Real, Integral) {-| Number of diatonic steps. May be negative, indicating a downward interval. -} newtype DiatonicSteps = DiatonicSteps { getDiatonicSteps :: Int } deriving (Eq, Ord, Show, Enum, Num, Real, Integral) {-| Number of octaves. May be negative, indicating a downward interval. -} newtype Octaves = Octaves { getOctaves :: Int } deriving (Eq, Ord, Num, Enum, Real, Integral) {-| Number of semitones. May be negative, indicating a downward interval. -} type Semitones = ChromaticSteps {-| The /number/ component of an interval (fourth, fifth) etc. May be negative, indicating a downward interval. -} newtype Number = Number { getNumber :: Integer } deriving (Eq, Ord, Num, Enum, Real, Integral) {-| The /quality/ component of an interval (minor, major, augmented). Generalized from single\/double augmented\/diminished to any number of steps. -} data Quality = Major | Minor | Perfect | Augmented Integer | Diminished Integer deriving (Eq, Ord, Show) {- TODO we really want to use Positive instead of Integer Alternatively, we could do it as a recursive type data Quality = Major | Minor | Perfect | Augment Quality | Diminish Quality -} {-| The alteration implied by a quality is dependent on whether it is attached to a major\/minor vs. a perfect-style number. This type represents the two possibilities. -} data QualityType = PerfectType | MajorMinorType deriving (Eq, Ord, Read, Show) {-| Accidental, represented as number of alterations. Generalized from natural and single\/double sharp\/flat to any number of steps. -} newtype Accidental = Accidental { getAccidental :: Integer } deriving (Eq, Ord, Num, Enum, Real, Integral) {-| Pitch name component. -} data Name = C | D | E | F | G | A | B deriving (Eq, Ord, Show, Enum) {-| This type represents standard basis for intervbals. -} data IntervalBasis = Chromatic | Diatonic deriving (Eq, Ord, Show, Enum) {-| Interval type. -} newtype Interval = Interval { getInterval :: (ChromaticSteps, DiatonicSteps) } deriving (Eq, Typeable) {-| Pitch type. -} newtype Pitch = Pitch { getPitch :: Interval } deriving (Eq, Ord, Typeable) instance Show Octaves where show = show . getOctaves instance Show Number where show = show . getNumber instance Show Accidental where show n | n == 0 = "natural" | n == 1 = "sharp" | n == (-1) = "flat" | n == 2 = "doubleSharp" | n == (-2) = "doubleFlat" | n > 0 = "sharp * " ++ show (getAccidental n) | n < 0 = "flat * " ++ show (negate $ getAccidental n) instance Alterable Accidental where sharpen = succ flatten = pred -- | Magic instance that allow us to write @c sharp@ instead of @sharpen c@. instance (IsPitch a, Alterable a) => IsPitch (Accidental -> a) where fromPitch l 1 = sharpen (fromPitch l) fromPitch l (-1) = flatten (fromPitch l) -- Requires FlexibleInstances {-| Lexicographical ordering, comparing the 'd2' component of the Interval first, as it's tied to the Number which is expected to be 'bigger' than the Quality, assuming ordinary tuning systems -} instance Ord Interval where Interval a `compare` Interval b = swap a `compare` swap b where swap (x,y) = (y,x) instance AdditiveGroup ChromaticSteps where zeroV = 0 (^+^) = (+) negateV = negate instance AdditiveGroup DiatonicSteps where zeroV = 0 (^+^) = (+) negateV = negate {- 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 -} {- Common pitch representation. Intervals and pitches can be added using '.+^'. To get the interval between two pitches, use '.-.'. Pitches are normally entered using the following literals. > c d e f g a b Notes with accidentals can be written by adding the @s@ or @b@ suffices (or two for double sharps and flats). > cs, ds, es ... -- sharp > cb, db, eb ... -- flat > css, dss, ess ... -- double sharp > cbb, dbb, ebb ... -- double flat There is also a convenience syntax for entering pitches one octave up or down, using @'@ and @_@ respectively. > g a b c' > d c b_ c Because of some overloading magic, we can actually write @sharp@ and @flat@ as /postfix/ functions. This gives a better read: > cs == c sharp > db == c flat You can of course use typical functional transformation of pitch as well. For example 'sharpen' and 'flatten' are the ordinary (prefix) versions of 'sharp' and 'flat' > sharpen c == c sharp == cs > flatten d == d flat == ds > (sharpen . sharpen) c == c doubleSharp == css > (flatten . flatten) d == d doubleFlat == dss Note that there is no guarantee that your pitch representation use enharmonic equivalence, so @cs == db@ may or may not hold. > c .+^ minor third == eb > f .-. c == perfect fourth Pitches are described by name, accidental and octave number. > c == fromIntegral 0 > _P4 == perfect fourth == interval Perfect 5 > d5 == diminished fifth == diminish (perfect fifth) -} {- A musical interval such as minor third, augmented fifth, duodecim etc. We include direction in in this definition, so a downward minor third (written @-m3@) is distinct from an upward minor third (written @m3@). Note that @_P1@ and @-P1@ are synynoms. Not to be confused with a mathematical inverval in pitch space, which is called 'Ambitus'. Intervals and pitches form an affine-vector space pair with intervals and /vectors/ and pitches as /points/. To add an interval to a, use '.+^'. To get the interval between two pitches, use '.-.'. > c .+^ minor third == eb > f .-. c == perfect fourth Adding intervals preserves spelling. For example: > m3 ^+^ _M3 = _P5 > d5 ^+^ _M6 = m10 The scalar type of 'Interval' is 'Int', using '^*' to stack intervals of a certain type on top of each other. For example @_P5 ^* 2@ is a stack of 2 perfect fifths, or a major ninth. The 'Num' instance works as expected for '+', 'negate' and 'abs', and (arbitrarily) uses minor seconds for multiplication. If you find yourself '*', or 'signum' on intervals, consider switching to '*^' or 'normalized'. Intervals are generally described in terms of 'Quality' and 'Number'. To construct an interval, use the 'interval' constructor, the utility constructors 'major', 'minor', 'augmented' and 'diminished', or the interval literals: > m5 == minor fifth == interval Minor 5 > _P4 == perfect fourth == interval Perfect 5 > d5 == diminished fifth == diminish (perfect fifth) -} {- An accidental is either flat, natural or sharp. This representation allows for an arbitrary number of flats or sharps rather than just single and double. The 'Num' and 'Enum' instances treat 'Accidental' as the number of altered semitones, i.e. a double flat is @-2@, natural @0@ and so on. -} {- Interval quality is either perfect, major, minor, augmented, and diminished. This representation allows for an arbitrary number of augmentations or diminutions, so /augmented/ is represented by @Augmented 1@, /doubly augmented/ by @Augmented 2@ and so on. The quality of a compound interval is the quality of the simple interval on which it is based. -} {- An interval represented as a number of octaves, including negative intervals. > octaves a = semitones a `div` 12 > steps a = semitones a `mod` 12 -}