theHarmonicAlgorithm-3.0.0: Real-time harmonic progression generation for TidalCycles live performance
Safe HaskellSafe-Inferred
LanguageHaskell2010

Harmonic.Rules.Types.Harmony

Description

This module establishes the Abstract/Concrete distinction:

Abstract (transposition-invariant):

  • Chord - A pitch structure defined by intervals from root
  • Cadence - A movement type and target chord quality

Concrete (rooted in pitch space):

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

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

data Chord Source #

A Chord is an abstract pitch structure: root note name, functionality, and intervals from root (bass first, as integers for register info).

Instances

Instances details
Generic Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Chord :: Type -> Type #

Methods

from :: Chord -> Rep Chord x #

to :: Rep Chord x -> Chord #

Show Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

showsPrec :: Int -> Chord -> ShowS #

show :: Chord -> String #

showList :: [Chord] -> ShowS #

Eq Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

(==) :: Chord -> Chord -> Bool #

(/=) :: Chord -> Chord -> Bool #

Ord Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

compare :: Chord -> Chord -> Ordering #

(<) :: Chord -> Chord -> Bool #

(<=) :: Chord -> Chord -> Bool #

(>) :: Chord -> Chord -> Bool #

(>=) :: Chord -> Chord -> Bool #

max :: Chord -> Chord -> Chord #

min :: Chord -> Chord -> Chord #

type Rep Chord Source # 
Instance details

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]))))

data Cadence Source #

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.

Instances

Instances details
Generic Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Cadence :: Type -> Type #

Methods

from :: Cadence -> Rep Cadence x #

to :: Rep Cadence x -> Cadence #

Show Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

(==) :: Cadence -> Cadence -> Bool #

(/=) :: Cadence -> Cadence -> Bool #

Ord Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Cadence Source # 
Instance details

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

data Movement Source #

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

Instances details
Generic Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Movement :: Type -> Type #

Methods

from :: Movement -> Rep Movement x #

to :: Rep Movement x -> Movement #

Read Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Show Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Ord Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Movement Source # 
Instance details

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

Instances

Instances details
Generic EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep EnharmonicSpelling :: Type -> Type #

Read EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Show EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Ord EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep EnharmonicSpelling = D1 ('MetaData "EnharmonicSpelling" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.0.0-1Jyc2qhqYzxBLoiBHPE0av" 'False) (C1 ('MetaCons "SharpSpelling" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "FlatSpelling" 'PrefixI 'False) (U1 :: Type -> Type))

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.

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 EnharmonicSpellingsharp 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 

Instances

Instances details
Generic ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep ChordState :: Type -> Type #

Show ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep ChordState Source # 
Instance details

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.

Instances

Instances details
Generic CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep CadenceState :: Type -> Type #

Show CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep CadenceState Source # 
Instance details

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

flatTriad :: [Int] -> Chord Source #

Shortcut with flat spelling for triads

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)

flatChord :: [Int] -> Chord Source #

Shortcut with flat spelling for chords

sharpChord :: [Int] -> Chord Source #

Shortcut with sharp spelling for chords

Cadence Construction

toCadence :: (Chord, Chord) -> Cadence Source #

Convert a pair of Chords to a Cadence

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

Instances details
Show ToTriadTrace Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq ToTriadTrace Source # 
Instance details

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

rootNote :: Chord -> NoteName Source #

Extract the root note from a Chord

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.