| Safe Haskell | Safe-Inferred |
|---|---|
| Language | Haskell2010 |
Harmonic.Rules.Types.Harmony
Contents
- Functionality (Chord Quality)
- Abstract Types
- Movement
- Enharmonic Spelling
- Concrete State Types
- Triad Construction (exactly 3 pitches, with reduction)
- Chord Construction (preserves all pitches)
- Cadence Construction
- State Conversion
- Walk Projection (gen4)
- Tracing (for maximum verbosity diagnostics)
- DB Serialization (compatible with Neo4j format)
- Utilities
Description
This module establishes the Abstract/Concrete distinction:
Abstract (transposition-invariant):
Chord- A pitch structure defined by intervals from rootCadence- A movement type and target chord quality
Concrete (rooted in pitch space):
ChordState- A Chord with a specific root pitchCadenceState- A Cadence with a specific root pitch
Academic Lineage
Data Science In The Creative Process (South, 2018): the MusicData
module's chord naming conventions (nameFuncTriad, nameFuncChord)
ported verbatim from legacy MusicData.hs (lines 443-479).
The Harmonic Algorithm (South, 2016): the concept of harmonic extrapolation categories (Simple, Complex, Impractical) which informed the chord quality taxonomy used here.
Synopsis
- type Functionality = String
- toFunctionality :: [PitchClass] -> Functionality
- toFunctionalityChord :: [PitchClass] -> Functionality
- data Chord = Chord {}
- data Cadence = Cadence {}
- data Movement
- = Asc PitchClass
- | Desc PitchClass
- | Unison
- | Tritone
- | Empty
- toMovement :: PitchClass -> PitchClass -> Movement
- fromMovement :: Movement -> PitchClass
- data EnharmonicSpelling
- data EnharmonicPreference
- enharmonicPreference :: PitchClass -> EnharmonicPreference
- selectEnharm :: EnharmonicSpelling -> PitchClass -> PitchClass -> EnharmonicSpelling
- defaultEnharm :: PitchClass -> EnharmonicSpelling
- spellingToPreference :: EnharmonicSpelling -> EnharmonicPreference
- enharmonicFunc :: EnharmonicSpelling -> PitchClass -> NoteName
- inferSpelling :: [Int] -> EnharmonicSpelling
- noteNameImpliesSpelling :: NoteName -> Maybe EnharmonicSpelling
- isAmbiguousPattern :: [Int] -> Bool
- data ChordState = ChordState {
- stateChord :: Chord
- stateRoot :: NoteName
- data CadenceState = CadenceState {}
- toTriad :: (PitchClass -> NoteName) -> [Int] -> Chord
- flatTriad :: [Int] -> Chord
- sharpTriad :: [Int] -> Chord
- toChord :: (PitchClass -> NoteName) -> [Int] -> Chord
- flatChord :: [Int] -> Chord
- sharpChord :: [Int] -> Chord
- toCadence :: (Chord, Chord) -> Cadence
- fromChordState :: ChordState -> Chord
- fromCadenceState :: CadenceState -> Chord
- initCadenceState :: Int -> String -> [Int] -> CadenceState
- mkCadenceStatePCs :: NoteName -> Movement -> [Int] -> CadenceState
- walkTriadCadence :: Cadence -> Cadence
- walkTriadState :: CadenceState -> CadenceState
- data ToTriadTrace = ToTriadTrace {
- tttRawDbIntervals :: String
- tttRawDbMovement :: String
- tttRawDbFunctionality :: String
- tttRootPC :: Int
- tttRootNoteName :: String
- tttTones :: [Int]
- tttTransposedPitches :: [Int]
- tttNormalizedPs :: [Int]
- tttZeroForm :: [Int]
- tttDetectedRoot :: String
- tttFunctionality :: String
- tttFinalChord :: String
- tttStoredFunc :: String
- fromCadenceStateTraced :: CadenceState -> (Chord, ToTriadTrace)
- constructCadence :: (String, String) -> Cadence
- corpusFunctionality :: [PitchClass] -> Functionality
- deconstructCadence :: Cadence -> (Movement, [PitchClass])
- rootNote :: Chord -> NoteName
- inversions :: [PitchClass] -> [[PitchClass]]
- isInversion :: Cadence -> Bool
- normalForm :: [PitchClass] -> [PitchClass]
- primeForm :: [PitchClass] -> [PitchClass]
- zeroFormPC :: [PitchClass] -> [PitchClass]
Functionality (Chord Quality)
type Functionality = String Source #
Chord quality/functionality as a string (e.g., "maj", "min7", "dim") Preserved as String for compatibility with legacy naming conventions
toFunctionality :: [PitchClass] -> Functionality Source #
Derive functionality name from a pitch set using the legacy triad nameFunc This is for TRIADS (exactly 3 pitch classes)
toFunctionalityChord :: [PitchClass] -> Functionality Source #
Derive functionality name from a pitch set using the chord nameFunc This is for CHORDS (any number of pitch classes, extended harmonies)
Abstract Types
A Chord is an abstract pitch structure: root note name, functionality, and intervals from root (bass first, as integers for register info).
Constructors
| Chord | |
Fields | |
Instances
| Generic Chord Source # | |
| Show Chord Source # | |
| Eq Chord Source # | |
| Ord Chord Source # | |
| type Rep Chord Source # | |
Defined in Harmonic.Rules.Types.Harmony type Rep Chord = D1 ('MetaData "Chord" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) (C1 ('MetaCons "Chord" 'PrefixI 'True) (S1 ('MetaSel ('Just "chordNoteName") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: (S1 ('MetaSel ('Just "chordFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: S1 ('MetaSel ('Just "chordIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [Integer])))) | |
A Cadence is a movement type combined with a target chord quality. Abstract: defines "approach by descending 5th to a major chord" without specifying which root pitch.
Constructors
| Cadence | |
Fields | |
Instances
| Generic Cadence Source # | |
| Show Cadence Source # | |
| Eq Cadence Source # | |
| Ord Cadence Source # | |
Defined in Harmonic.Rules.Types.Harmony | |
| type Rep Cadence Source # | |
Defined in Harmonic.Rules.Types.Harmony type Rep Cadence = D1 ('MetaData "Cadence" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) (C1 ('MetaCons "Cadence" 'PrefixI 'True) (S1 ('MetaSel ('Just "cadenceFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: (S1 ('MetaSel ('Just "cadenceMovement") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Movement) :*: S1 ('MetaSel ('Just "cadenceIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [PitchClass])))) | |
Movement
Movement represents bass motion by a musical interval
Constructors
| Asc PitchClass | Ascending by n semitones (1-5) |
| Desc PitchClass | Descending by n semitones (1-5) |
| Unison | Pedal (no movement) |
| Tritone | Movement by 6 semitones |
| Empty | Placeholder for invalid/missing movement |
Instances
| Generic Movement Source # | |
| Read Movement Source # | |
| Show Movement Source # | |
| Eq Movement Source # | |
| Ord Movement Source # | |
Defined in Harmonic.Rules.Types.Harmony | |
| type Rep Movement Source # | |
Defined in Harmonic.Rules.Types.Harmony type Rep Movement = D1 ('MetaData "Movement" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) ((C1 ('MetaCons "Asc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass)) :+: C1 ('MetaCons "Desc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass))) :+: (C1 ('MetaCons "Unison" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "Tritone" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Empty" 'PrefixI 'False) (U1 :: Type -> Type)))) | |
toMovement :: PitchClass -> PitchClass -> Movement Source #
Convert two pitch classes to a Movement (bass motion direction/distance) Ported from legacy MusicData.hs toMovement
Movement direction is determined by the shorter path around the pitch class circle. Example: C(0) to G(7) = descending by 5 (shorter path is 5 semitones down via P 12) G(7) to C(0) = ascending by 5 (shorter path is 5 semitones up to P 12 ≡ P 0)
fromMovement :: Movement -> PitchClass Source #
Convert Movement back to PitchClass interval
Enharmonic Spelling
data EnharmonicSpelling Source #
Enharmonic spelling preference (sharp or flat) Stored in CadenceState to enable consistent spelling throughout a progression
Constructors
| SharpSpelling | |
| FlatSpelling |
Instances
data EnharmonicPreference Source #
Convert EnharmonicSpelling to the actual function (PitchClass -> NoteName) |Classify a pitch class by its natural enharmonic preference. C (PC 0) is AMBIGUOUS - it adopts the pairing's preference (prior or posterior). All other pitch classes have definite preferences.
Constructors
| FlatPref | |
| SharpPref | |
| AmbiguousPref |
Instances
| Show EnharmonicPreference Source # | |
Defined in Harmonic.Rules.Types.Harmony Methods showsPrec :: Int -> EnharmonicPreference -> ShowS # show :: EnharmonicPreference -> String # showList :: [EnharmonicPreference] -> ShowS # | |
| Eq EnharmonicPreference Source # | |
Defined in Harmonic.Rules.Types.Harmony Methods (==) :: EnharmonicPreference -> EnharmonicPreference -> Bool # (/=) :: EnharmonicPreference -> EnharmonicPreference -> Bool # | |
enharmonicPreference :: PitchClass -> EnharmonicPreference Source #
Get the natural enharmonic preference for a pitch class. C (0) is ambiguous and should be handled specially. F#/Gb (6) is always treated as sharp (F#).
selectEnharm :: EnharmonicSpelling -> PitchClass -> PitchClass -> EnharmonicSpelling Source #
Determine enharmonic spelling for posterior root given prior's actual spelling and both pitch classes.
Rules: (1) SAME PITCH CLASS: Always use prior's actual spelling (the root hasn't changed!) - Example: D# → D# keep Sharp, Eb → Eb keep Flat (2) C IS FLEXIBLE: When C is prior or posterior, the OTHER pitch's preference can override - Prior is C + posterior is definite: adopt posterior's preference - Posterior is C + prior is definite: adopt prior's actual spelling - Both C: persist current (no preference to guide) (3) BOTH DEFINITE (different pitch classes): Consensus-based switching a) If prior's actual spelling matches posterior's preference → persist (consensus on choice) b) If both have SAME preference AND it differs from prior's actual → switch (consensus to switch) c) Otherwise: persist prior's actual spelling (disagreement or already aligned)
Examples: D# → D# (same PC) → persist prior's actual spelling (Sharp or Flat, doesn't matter which) D# (actual Sharp) → E (SharpPref) → both prefer Sharp, actual differs → switch to Sharp!
defaultEnharm :: PitchClass -> EnharmonicSpelling Source #
Default enharmonic spelling for initial state based on root pitch class. C defaults to flat; all others use their natural preference.
spellingToPreference :: EnharmonicSpelling -> EnharmonicPreference Source #
Convert enharmonic spelling to the corresponding preference (for symmetry).
enharmonicFunc :: EnharmonicSpelling -> PitchClass -> NoteName Source #
Select the spelling function for a given EnharmonicSpelling — sharp
or flat. Used to render a pitch class as a note name consistently across a
progression, so a single chord grid does not mix F# and Gb.
inferSpelling :: [Int] -> EnharmonicSpelling Source #
Infer enharmonic spelling from absolute pitches (bass first). Three-layer system: Layer 1: 3-set match — bass matches AND remaining 2 PCs are subset of chord Layer 2: 2-set match — bass matches AND remaining 1 PC is subset of chord Layer 3: Root fallback — defaultEnharm on bass PC
noteNameImpliesSpelling :: NoteName -> Maybe EnharmonicSpelling Source #
Detect if a NoteName carries an explicit sharp/flat preference. Sharp variants (C', D', F', G', A') → Just SharpSpelling Flat variants (Db, Eb, Gb, Ab, Bb) → Just FlatSpelling Natural notes (C, D, E, F, G, A, B) → Nothing (use inference)
isAmbiguousPattern :: [Int] -> Bool Source #
Check if a chord's absolute pitches match an ambiguous pattern.
Concrete State Types
data ChordState Source #
A ChordState is a Chord with a concrete root pitch (anchored in pitch space).
Constructors
| ChordState | |
Fields
| |
Instances
| Generic ChordState Source # | |
Defined in Harmonic.Rules.Types.Harmony Associated Types type Rep ChordState :: Type -> Type # | |
| Show ChordState Source # | |
Defined in Harmonic.Rules.Types.Harmony Methods showsPrec :: Int -> ChordState -> ShowS # show :: ChordState -> String # showList :: [ChordState] -> ShowS # | |
| Eq ChordState Source # | |
Defined in Harmonic.Rules.Types.Harmony | |
| type Rep ChordState Source # | |
Defined in Harmonic.Rules.Types.Harmony type Rep ChordState = D1 ('MetaData "ChordState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) (C1 ('MetaCons "ChordState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateChord") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Chord) :*: S1 ('MetaSel ('Just "stateRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName))) | |
data CadenceState Source #
A CadenceState is a Cadence with a concrete root pitch and enharmonic spelling. This represents "descend by 5th to G major (flat spelling)" rather than abstract. The enharmonic spelling ensures consistent note naming throughout a progression.
Constructors
| CadenceState | |
Fields | |
Instances
| Generic CadenceState Source # | |
Defined in Harmonic.Rules.Types.Harmony Associated Types type Rep CadenceState :: Type -> Type # | |
| Show CadenceState Source # | |
Defined in Harmonic.Rules.Types.Harmony Methods showsPrec :: Int -> CadenceState -> ShowS # show :: CadenceState -> String # showList :: [CadenceState] -> ShowS # | |
| Eq CadenceState Source # | |
Defined in Harmonic.Rules.Types.Harmony | |
| type Rep CadenceState Source # | |
Defined in Harmonic.Rules.Types.Harmony type Rep CadenceState = D1 ('MetaData "CadenceState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) (C1 ('MetaCons "CadenceState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateCadence") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Cadence) :*: (S1 ('MetaSel ('Just "stateCadenceRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: S1 ('MetaSel ('Just "stateSpelling") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 EnharmonicSpelling)))) | |
Triad Construction (exactly 3 pitches, with reduction)
toTriad :: (PitchClass -> NoteName) -> [Int] -> Chord Source #
Build a Triad from an enharmonic function and integer list TRIADS: exactly 3 pitch classes. If >3 input, reduces to best triad. Uses elaborate inversion detection from legacy MusicData.hs
sharpTriad :: [Int] -> Chord Source #
Shortcut with sharp spelling for triads
Chord Construction (preserves all pitches)
toChord :: (PitchClass -> NoteName) -> [Int] -> Chord Source #
Build a Chord from an enharmonic function and integer list CHORDS: preserves all pitches, uses chord-specific naming for extended harmonies Ported from legacy MusicData.hs toChord (lines 896-941)
sharpChord :: [Int] -> Chord Source #
Shortcut with sharp spelling for chords
Cadence Construction
State Conversion
fromChordState :: ChordState -> Chord Source #
Convert ChordState to the underlying Chord
fromCadenceState :: CadenceState -> Chord Source #
Convert CadenceState back to a Chord (applying the root and spelling)
initCadenceState :: Int -> String -> [Int] -> CadenceState Source #
Initialize a CadenceState from movement, note name, and quality intervals. Enharmonic spelling is inferred from the chord's absolute pitch content.
mkCadenceStatePCs :: NoteName -> Movement -> [Int] -> CadenceState Source #
Build a CadenceState from a root, movement, and root-relative intervals,
preserving full cardinality. The non-truncating counterpart to
initCadenceState, which routes through flatTriad / toCadence and
silently reduces >3 PCs to a triad. Intervals are root-relative
(0 = the root; inserted if missing); stored in zero form (sorted,
deduped mod 12). Functionality dispatches on cardinality: <=3 uses the
triad namer, 4+ the chord namer. Spelling from the typed root when it
implies one (Eb → flat), otherwise inferred from absolute pitch content.
Walk Projection (gen4)
walkTriadCadence :: Cadence -> Cadence Source #
Project a Cadence onto its most consonant ROOTED embedded triad.
Identity for intervals (every corpus-generated cadence). For3:
enumerates the 3-subsets that keep the root ([0,a,b] over the nonzero
intervals), picks the least dissonant via the Layer-B mostConsonant
replica, and renames with the triad namer. Root, movement, and (via
walkTriadState) spelling are preserved, so the projected cadence's
show is always a corpus-shaped graph key. This is gen4's walk shadow:
the added tone can reinterpret the harmony (e.g. [0,3,6]+7 projects to
[0,3,7] minor) and thereby steer the next step.
walkTriadState :: CadenceState -> CadenceState Source #
State-level walk projection: walkTriadCadence on the cadence, root
and spelling untouched. Identity for triad states.
Tracing (for maximum verbosity diagnostics)
data ToTriadTrace Source #
Trace of toTriad transformation steps
Constructors
| ToTriadTrace | |
Fields
| |
Instances
| Show ToTriadTrace Source # | |
Defined in Harmonic.Rules.Types.Harmony Methods showsPrec :: Int -> ToTriadTrace -> ShowS # show :: ToTriadTrace -> String # showList :: [ToTriadTrace] -> ShowS # | |
| Eq ToTriadTrace Source # | |
Defined in Harmonic.Rules.Types.Harmony | |
fromCadenceStateTraced :: CadenceState -> (Chord, ToTriadTrace) Source #
Convert CadenceState to Chord with full transformation trace. Used for maximum verbosity diagnostics (gen''). Includes raw DB data plus all transformation stages.
DB Serialization (compatible with Neo4j format)
constructCadence :: (String, String) -> Cadence Source #
Construct a Cadence from DB text format (movement string, chord string)
Example: constructCadence ("desc 3", "[P 0,P 3,P 8]") => Cadence { cadenceFunctionality = "maj_1stInv", cadenceMovement = Desc (P 3), ... }
corpusFunctionality :: [PitchClass] -> Functionality Source #
Name a zero-form triad exactly as the corpus stores it. The graph's
node keys (show = movement + functionality) carry the names below;
any cadence whose show is used as a fetch key MUST be named through
here, or the walk silently drops to fallback-only after landing on
that form (a latent bug fixed 2026-08-19: constructCadence
previously named via bare toFunctionality, so every graph-selected
inversion candidate — [0,3,8] read back as min#5 instead of the
stored maj_1stInv — produced a keyless next fetch).
The table is the complete 55-form vocabulary transcribed verbatim from
a live corpus dump (2026-08-19,
MATCH (c:Cadence) RETURN DISTINCT c.chord, c.show). It reflects the
LEGACY naming rules the database was ingested under — including the
forms where the modernised nameFuncTriad deliberately diverges
([0,2,7] is stored sus4_1stInv, modern says sus2; [0,5,10] is
stored sus4_2ndInv, modern says 7sus4) — so it cannot be derived
from the current namers. If the corpus is ever re-ingested, regenerate
this table from the same query.
Non-triad input (fewer/more than 3 PCs) falls back to the current namers — such sets are never corpus keys.
deconstructCadence :: Cadence -> (Movement, [PitchClass]) Source #
Deconstruct a Cadence to DB text format (movement, pitch class list) Returns the Movement and [PitchClass] for serialization
Utilities
inversions :: [PitchClass] -> [[PitchClass]] Source #
Generate all cyclic inversions of a pitch set (in zero form) Each inversion is transformed to zero form for proper comparison
isInversion :: Cadence -> Bool Source #
Check if a cadence is an inversion (1st or 2nd) based on zero-form intervals.
Patterns match those in detectInversion.
normalForm :: [PitchClass] -> [PitchClass] Source #
Normalize a pitch set to its most compact form Ported from legacy MusicData.hs normalForm
primeForm :: [PitchClass] -> [PitchClass] Source #
Compute prime form (transposition and inversion invariant) Ported from legacy MusicData.hs primeForm
zeroFormPC :: [PitchClass] -> [PitchClass] Source #
Zero-form for pitch classes Subtracts the FIRST element (not minimum) then sorts, matching legacy behavior |Normalize pitch class set to zero form by subtracting first element then sorting. Examples: [P 4, P 7, P 11] → [P 0, P 3, P 7] (maj7 in root position) [P 8, P 11, P 3] → [P 0, P 3, P 7] (maj7 in 2nd inversion) Used by all Cadence construction paths (DB, toCadence, fallback) for semantic consistency.