{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE InstanceSigs #-}

-- |
-- Module      : Harmonic.Rules.Types.Pitch
-- Description : Foundational pitch-class algebra as ℤ₁₂ cyclic group
--
-- This module defines 'Harmonic.Rules.Types.Pitch.PitchClass' as a proper cyclic group with modular
-- arithmetic. All pitch operations automatically wrap within the octave.
--
-- == Academic Lineage
--
-- /Data Science In The Creative Process/ (South, 2018): the MusicData
-- module's pitch-class algebra, where pitch classes are treated as
-- elements of ℤ₁₂ with modular arithmetic operations.
--
-- == Design
--
-- This module sits in the Rules (R) layer: the ℤ₁₂ algebra is part of the
-- language the system's validity rules are written in. (R, E and T describe
-- the creative system as a whole, not individual types — transposition and
-- interval measurement here are algebra, and become traversal or evaluation
-- only in the layers that use them for those purposes.)
--
-- Pitch classes are elements of ℤ₁₂, the integers modulo 12:
--
--   * Addition: transposition
--   * Subtraction: interval measurement
--   * Identity: unison (0)
--   * Inverse: octave complement

module Harmonic.Rules.Types.Pitch
  ( -- * Core Types
    PitchClass(..)  -- Export P constructor for pattern matching
  , mkPitchClass
  
    -- * Enharmonic Note Names
  , NoteName(..)
  , pitchClass
  , notesToPCs
  , sharp
  , flat
  , enharmFromNoteName
  
    -- * Transposition (Group Action)
  , transpose
  , interval
  , invert
  
    -- * Utilities
  , allPitchClasses
  , pcSet
  , zeroForm
  ) where

import GHC.Generics (Generic)
import Data.List (nub, sort)
import GHC.Base (modInt, quotInt, remInt)
import GHC.Real ((%))

-------------------------------------------------------------------------------
-- PitchClass as ℤ₁₂
-------------------------------------------------------------------------------

-- |PitchClass represents an element of ℤ₁₂, the cyclic group of integers mod 12.
-- The 'Num' instance ensures all arithmetic automatically wraps to [0..11].
newtype PitchClass = P { PitchClass -> Int
unPitchClass :: Int }
  deriving (Eq PitchClass
Eq PitchClass =>
(PitchClass -> PitchClass -> Ordering)
-> (PitchClass -> PitchClass -> Bool)
-> (PitchClass -> PitchClass -> Bool)
-> (PitchClass -> PitchClass -> Bool)
-> (PitchClass -> PitchClass -> Bool)
-> (PitchClass -> PitchClass -> PitchClass)
-> (PitchClass -> PitchClass -> PitchClass)
-> Ord PitchClass
PitchClass -> PitchClass -> Bool
PitchClass -> PitchClass -> Ordering
PitchClass -> PitchClass -> PitchClass
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: PitchClass -> PitchClass -> Ordering
compare :: PitchClass -> PitchClass -> Ordering
$c< :: PitchClass -> PitchClass -> Bool
< :: PitchClass -> PitchClass -> Bool
$c<= :: PitchClass -> PitchClass -> Bool
<= :: PitchClass -> PitchClass -> Bool
$c> :: PitchClass -> PitchClass -> Bool
> :: PitchClass -> PitchClass -> Bool
$c>= :: PitchClass -> PitchClass -> Bool
>= :: PitchClass -> PitchClass -> Bool
$cmax :: PitchClass -> PitchClass -> PitchClass
max :: PitchClass -> PitchClass -> PitchClass
$cmin :: PitchClass -> PitchClass -> PitchClass
min :: PitchClass -> PitchClass -> PitchClass
Ord, PitchClass -> PitchClass -> Bool
(PitchClass -> PitchClass -> Bool)
-> (PitchClass -> PitchClass -> Bool) -> Eq PitchClass
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PitchClass -> PitchClass -> Bool
== :: PitchClass -> PitchClass -> Bool
$c/= :: PitchClass -> PitchClass -> Bool
/= :: PitchClass -> PitchClass -> Bool
Eq, (forall x. PitchClass -> Rep PitchClass x)
-> (forall x. Rep PitchClass x -> PitchClass) -> Generic PitchClass
forall x. Rep PitchClass x -> PitchClass
forall x. PitchClass -> Rep PitchClass x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. PitchClass -> Rep PitchClass x
from :: forall x. PitchClass -> Rep PitchClass x
$cto :: forall x. Rep PitchClass x -> PitchClass
to :: forall x. Rep PitchClass x -> PitchClass
Generic)

-- |Show instance matches legacy format for DB compatibility: "P 0" not "P0"
instance Show PitchClass where
  show :: PitchClass -> String
show (P Int
n) = String
"P " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n

-- |Read instance handles legacy DB format "P 0" (with space after P)
instance Read PitchClass where
  readsPrec :: Int -> ReadS PitchClass
readsPrec Int
_ (Char
'P':Char
' ':String
rest) = [(Int -> PitchClass
mkPitchClass (String -> Int
forall a. Read a => String -> a
read String
num), String
remaining)]
    where (String
num, String
remaining) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (String
"0123456789-" :: String)) String
rest
  readsPrec Int
_ (Char
'P':String
rest) = [(Int -> PitchClass
mkPitchClass (String -> Int
forall a. Read a => String -> a
read String
num), String
remaining)]
    where (String
num, String
remaining) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (String
"0123456789-" :: String)) String
rest
  readsPrec Int
_ String
_ = []

-- |Smart constructor ensuring values stay in [0..11]
mkPitchClass :: Int -> PitchClass
mkPitchClass :: Int -> PitchClass
mkPitchClass Int
n = Int -> PitchClass
P (Int
n Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
{-# INLINE mkPitchClass #-}

-- |Bounded instance: pitch classes range from 0 to 11
instance Bounded PitchClass where
  minBound :: PitchClass
minBound = Int -> PitchClass
P Int
0
  maxBound :: PitchClass
maxBound = Int -> PitchClass
P Int
11

-- |Num instance implements ℤ₁₂ group operations.
-- All operations automatically wrap modulo 12.
instance Num PitchClass where
  + :: PitchClass -> PitchClass -> PitchClass
(+) (P Int
n1) (P Int
n2) = Int -> PitchClass
P ((Int
n1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
n2) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  (-) (P Int
n1) (P Int
n2) = Int -> PitchClass
P ((Int
n1 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
n2) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  * :: PitchClass -> PitchClass -> PitchClass
(*) (P Int
n1) (P Int
n2) = Int -> PitchClass
P ((Int
n1 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
n2) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  negate :: PitchClass -> PitchClass
negate (P Int
n)      = Int -> PitchClass
P ((Int
12 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
n) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  fromInteger :: Integer -> PitchClass
fromInteger Integer
n     = Int -> PitchClass
P (Integer -> Int
forall a. Num a => Integer -> a
fromInteger Integer
n Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  abs :: PitchClass -> PitchClass
abs               = PitchClass -> PitchClass
forall a. a -> a
id  -- Always positive in ℤ₁₂
  signum :: PitchClass -> PitchClass
signum PitchClass
_          = PitchClass
1   -- All non-zero elements are "positive"

-- |Integral instance for compatibility with legacy code
instance Integral PitchClass where
  toInteger :: PitchClass -> Integer
toInteger (P Int
n) = Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
n
  quotRem :: PitchClass -> PitchClass -> (PitchClass, PitchClass)
quotRem (P Int
a) (P Int
b)
    | Int
b Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0    = String -> (PitchClass, PitchClass)
forall a. HasCallStack => String -> a
error String
"PitchClass: divide by zero"
    | Bool
otherwise = (Int -> PitchClass
P (Int
a Int -> Int -> Int
`quotInt` Int
b), Int -> PitchClass
P (Int
a Int -> Int -> Int
`remInt` Int
b))
  
instance Real PitchClass where
  toRational :: PitchClass -> Rational
toRational (P Int
n) = Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
n Integer -> Integer -> Rational
forall a. Integral a => a -> a -> Ratio a
% Integer
1

-- |Enum instance provides cyclic enumeration
instance Enum PitchClass where
  succ :: PitchClass -> PitchClass
succ (P Int
n)
    | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
11   = Int -> PitchClass
P Int
0
    | Bool
otherwise = Int -> PitchClass
P (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
  pred :: PitchClass -> PitchClass
pred (P Int
n)
    | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0    = Int -> PitchClass
P Int
11
    | Bool
otherwise = Int -> PitchClass
P (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)
  toEnum :: Int -> PitchClass
toEnum Int
n      = Int -> PitchClass
P (Int
n Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
  fromEnum :: PitchClass -> Int
fromEnum (P Int
n) = Int
n

-------------------------------------------------------------------------------
-- Enharmonic Note Names (Ported from Legacy MusicData.hs)
-------------------------------------------------------------------------------

-- |All standard enharmonic note names within an octave
data NoteName 
  = C | C' | Db | D | D' | Eb | E | F | F' | Gb
  | G | G' | Ab | A | A' | Bb | B
  deriving (NoteName -> NoteName -> Bool
(NoteName -> NoteName -> Bool)
-> (NoteName -> NoteName -> Bool) -> Eq NoteName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NoteName -> NoteName -> Bool
== :: NoteName -> NoteName -> Bool
$c/= :: NoteName -> NoteName -> Bool
/= :: NoteName -> NoteName -> Bool
Eq, Eq NoteName
Eq NoteName =>
(NoteName -> NoteName -> Ordering)
-> (NoteName -> NoteName -> Bool)
-> (NoteName -> NoteName -> Bool)
-> (NoteName -> NoteName -> Bool)
-> (NoteName -> NoteName -> Bool)
-> (NoteName -> NoteName -> NoteName)
-> (NoteName -> NoteName -> NoteName)
-> Ord NoteName
NoteName -> NoteName -> Bool
NoteName -> NoteName -> Ordering
NoteName -> NoteName -> NoteName
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: NoteName -> NoteName -> Ordering
compare :: NoteName -> NoteName -> Ordering
$c< :: NoteName -> NoteName -> Bool
< :: NoteName -> NoteName -> Bool
$c<= :: NoteName -> NoteName -> Bool
<= :: NoteName -> NoteName -> Bool
$c> :: NoteName -> NoteName -> Bool
> :: NoteName -> NoteName -> Bool
$c>= :: NoteName -> NoteName -> Bool
>= :: NoteName -> NoteName -> Bool
$cmax :: NoteName -> NoteName -> NoteName
max :: NoteName -> NoteName -> NoteName
$cmin :: NoteName -> NoteName -> NoteName
min :: NoteName -> NoteName -> NoteName
Ord, ReadPrec [NoteName]
ReadPrec NoteName
Int -> ReadS NoteName
ReadS [NoteName]
(Int -> ReadS NoteName)
-> ReadS [NoteName]
-> ReadPrec NoteName
-> ReadPrec [NoteName]
-> Read NoteName
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS NoteName
readsPrec :: Int -> ReadS NoteName
$creadList :: ReadS [NoteName]
readList :: ReadS [NoteName]
$creadPrec :: ReadPrec NoteName
readPrec :: ReadPrec NoteName
$creadListPrec :: ReadPrec [NoteName]
readListPrec :: ReadPrec [NoteName]
Read, Int -> NoteName
NoteName -> Int
NoteName -> [NoteName]
NoteName -> NoteName
NoteName -> NoteName -> [NoteName]
NoteName -> NoteName -> NoteName -> [NoteName]
(NoteName -> NoteName)
-> (NoteName -> NoteName)
-> (Int -> NoteName)
-> (NoteName -> Int)
-> (NoteName -> [NoteName])
-> (NoteName -> NoteName -> [NoteName])
-> (NoteName -> NoteName -> [NoteName])
-> (NoteName -> NoteName -> NoteName -> [NoteName])
-> Enum NoteName
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: NoteName -> NoteName
succ :: NoteName -> NoteName
$cpred :: NoteName -> NoteName
pred :: NoteName -> NoteName
$ctoEnum :: Int -> NoteName
toEnum :: Int -> NoteName
$cfromEnum :: NoteName -> Int
fromEnum :: NoteName -> Int
$cenumFrom :: NoteName -> [NoteName]
enumFrom :: NoteName -> [NoteName]
$cenumFromThen :: NoteName -> NoteName -> [NoteName]
enumFromThen :: NoteName -> NoteName -> [NoteName]
$cenumFromTo :: NoteName -> NoteName -> [NoteName]
enumFromTo :: NoteName -> NoteName -> [NoteName]
$cenumFromThenTo :: NoteName -> NoteName -> NoteName -> [NoteName]
enumFromThenTo :: NoteName -> NoteName -> NoteName -> [NoteName]
Enum, NoteName
NoteName -> NoteName -> Bounded NoteName
forall a. a -> a -> Bounded a
$cminBound :: NoteName
minBound :: NoteName
$cmaxBound :: NoteName
maxBound :: NoteName
Bounded)

-- |Custom Show instance: renders sharp notation C' as C#, D' as D#, etc.
-- (matches legacy behavior and musical convention)
instance Show NoteName where
  show :: NoteName -> String
show NoteName
C  = String
"C"
  show NoteName
C' = String
"C#"
  show NoteName
Db = String
"Db"
  show NoteName
D  = String
"D"
  show NoteName
D' = String
"D#"
  show NoteName
Eb = String
"Eb"
  show NoteName
E  = String
"E"
  show NoteName
F  = String
"F"
  show NoteName
F' = String
"F#"
  show NoteName
Gb = String
"Gb"
  show NoteName
G  = String
"G"
  show NoteName
G' = String
"G#"
  show NoteName
Ab = String
"Ab"
  show NoteName
A  = String
"A"
  show NoteName
A' = String
"A#"
  show NoteName
Bb = String
"Bb"
  show NoteName
B  = String
"B"

-- |Extract pitch class from a note name
pitchClass :: NoteName -> PitchClass
pitchClass :: NoteName -> PitchClass
pitchClass NoteName
C  = Int -> PitchClass
P Int
0
pitchClass NoteName
C' = Int -> PitchClass
P Int
1
pitchClass NoteName
Db = Int -> PitchClass
P Int
1
pitchClass NoteName
D  = Int -> PitchClass
P Int
2
pitchClass NoteName
D' = Int -> PitchClass
P Int
3
pitchClass NoteName
Eb = Int -> PitchClass
P Int
3
pitchClass NoteName
E  = Int -> PitchClass
P Int
4
pitchClass NoteName
F  = Int -> PitchClass
P Int
5
pitchClass NoteName
F' = Int -> PitchClass
P Int
6
pitchClass NoteName
Gb = Int -> PitchClass
P Int
6
pitchClass NoteName
G  = Int -> PitchClass
P Int
7
pitchClass NoteName
G' = Int -> PitchClass
P Int
8
pitchClass NoteName
Ab = Int -> PitchClass
P Int
8
pitchClass NoteName
A  = Int -> PitchClass
P Int
9
pitchClass NoteName
A' = Int -> PitchClass
P Int
10
pitchClass NoteName
Bb = Int -> PitchClass
P Int
10
pitchClass NoteName
B  = Int -> PitchClass
P Int
11

-- |Map pitch class to sharp enharmonic spelling
sharp :: PitchClass -> NoteName
sharp :: PitchClass -> NoteName
sharp (P Int
0)  = NoteName
C
sharp (P Int
1)  = NoteName
C'
sharp (P Int
2)  = NoteName
D
sharp (P Int
3)  = NoteName
D'
sharp (P Int
4)  = NoteName
E
sharp (P Int
5)  = NoteName
F
sharp (P Int
6)  = NoteName
F'
sharp (P Int
7)  = NoteName
G
sharp (P Int
8)  = NoteName
G'
sharp (P Int
9)  = NoteName
A
sharp (P Int
10) = NoteName
A'
sharp (P Int
11) = NoteName
B
sharp PitchClass
_      = String -> NoteName
forall a. HasCallStack => String -> a
error String
"PitchClass out of range"

-- |Map pitch class to flat enharmonic spelling
flat :: PitchClass -> NoteName
flat :: PitchClass -> NoteName
flat (P Int
0)  = NoteName
C
flat (P Int
1)  = NoteName
Db
flat (P Int
2)  = NoteName
D
flat (P Int
3)  = NoteName
Eb
flat (P Int
4)  = NoteName
E
flat (P Int
5)  = NoteName
F
flat (P Int
6)  = NoteName
Gb
flat (P Int
7)  = NoteName
G
flat (P Int
8)  = NoteName
Ab
flat (P Int
9)  = NoteName
A
flat (P Int
10) = NoteName
Bb
flat (P Int
11) = NoteName
B
flat PitchClass
_      = String -> NoteName
forall a. HasCallStack => String -> a
error String
"PitchClass out of range"

-- |Helper function for mapping NoteName to enharmonic function.
-- This determines whether to use sharp or flat spelling based on 
-- the root note of a chord (for consistent enharmonic spelling).
-- Ported from legacy MusicData.hs
enharmFromNoteName :: NoteName -> (PitchClass -> NoteName)
enharmFromNoteName :: NoteName -> PitchClass -> NoteName
enharmFromNoteName NoteName
n = case NoteName
n of
  NoteName
C  -> PitchClass -> NoteName
flat
  NoteName
C' -> PitchClass -> NoteName
sharp
  NoteName
Db -> PitchClass -> NoteName
flat
  NoteName
D  -> PitchClass -> NoteName
sharp
  NoteName
D' -> PitchClass -> NoteName
sharp
  NoteName
Eb -> PitchClass -> NoteName
flat
  NoteName
E  -> PitchClass -> NoteName
sharp
  NoteName
F  -> PitchClass -> NoteName
flat
  NoteName
F' -> PitchClass -> NoteName
sharp
  NoteName
Gb -> PitchClass -> NoteName
flat
  NoteName
G  -> PitchClass -> NoteName
sharp
  NoteName
G' -> PitchClass -> NoteName
sharp
  NoteName
Ab -> PitchClass -> NoteName
flat
  NoteName
A  -> PitchClass -> NoteName
sharp
  NoteName
A' -> PitchClass -> NoteName
sharp
  NoteName
Bb -> PitchClass -> NoteName
flat
  NoteName
B  -> PitchClass -> NoteName
sharp

-- |Convert a list of NoteNames to pitch-class integers
-- Useful for defining chord progressions with note names
notesToPCs :: [NoteName] -> [Int]
notesToPCs :: [NoteName] -> [Int]
notesToPCs = (NoteName -> Int) -> [NoteName] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (PitchClass -> Int
unPitchClass (PitchClass -> Int) -> (NoteName -> PitchClass) -> NoteName -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. NoteName -> PitchClass
pitchClass)

-------------------------------------------------------------------------------
-- Transposition (Group Actions on ℤ₁₂)
-------------------------------------------------------------------------------

-- |Transpose a pitch class by an interval (group addition)
transpose :: Int -> PitchClass -> PitchClass
transpose :: Int -> PitchClass -> PitchClass
transpose Int
n (P Int
x) = Int -> PitchClass
P ((Int
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
n) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
{-# INLINE transpose #-}

-- |Calculate the interval between two pitch classes (group subtraction)
-- Returns the ascending interval from first to second.
interval :: PitchClass -> PitchClass -> PitchClass
interval :: PitchClass -> PitchClass -> PitchClass
interval (P Int
from) (P Int
to) = Int -> PitchClass
P ((Int
to Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
from) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
{-# INLINE interval #-}

-- |Invert a pitch class (octave complement)
-- The inverse of n is (12 - n) mod 12
invert :: PitchClass -> PitchClass
invert :: PitchClass -> PitchClass
invert (P Int
n) = Int -> PitchClass
P ((Int
12 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
n) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12)
{-# INLINE invert #-}

-------------------------------------------------------------------------------
-- Utilities
-------------------------------------------------------------------------------

-- |All 12 pitch classes
allPitchClasses :: [PitchClass]
allPitchClasses :: [PitchClass]
allPitchClasses = Int -> PitchClass
P (Int -> PitchClass) -> [Int] -> [PitchClass]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Int
0..Int
11]

-- |Convert a list of integers to a set of unique pitch classes
pcSet :: [Int] -> [PitchClass]
pcSet :: [Int] -> [PitchClass]
pcSet = [PitchClass] -> [PitchClass]
forall a. Ord a => [a] -> [a]
sort ([PitchClass] -> [PitchClass])
-> ([Int] -> [PitchClass]) -> [Int] -> [PitchClass]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [PitchClass] -> [PitchClass]
forall a. Eq a => [a] -> [a]
nub ([PitchClass] -> [PitchClass])
-> ([Int] -> [PitchClass]) -> [Int] -> [PitchClass]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> PitchClass) -> [Int] -> [PitchClass]
forall a b. (a -> b) -> [a] -> [b]
map Int -> PitchClass
mkPitchClass

-- |Normalize a pitch set to start from zero (transposition-invariant form)
-- Ported from legacy MusicData.hs
zeroForm :: [Int] -> [PitchClass]
zeroForm :: [Int] -> [PitchClass]
zeroForm [] = []
zeroForm [Int]
xs = 
  let sorted :: [Int]
sorted = [Int] -> [Int]
forall a. Ord a => [a] -> [a]
sort ([Int] -> [Int]) -> [Int] -> [Int]
forall a b. (a -> b) -> a -> b
$ (Int -> Int) -> [Int] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12) [Int]
xs
      minVal :: Int
minVal = [Int] -> Int
forall a. HasCallStack => [a] -> a
head [Int]
sorted
  in (Int -> PitchClass) -> [Int] -> [PitchClass]
forall a b. (a -> b) -> [a] -> [b]
map (\Int
x -> Int -> PitchClass
mkPitchClass (Int
x Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
minVal)) [Int]
sorted