Module

Harmonia.Voicing

Package
purescript-harmonia
Repository
afcondon/purescript-harmonia

Harmonia.Voicing — concrete pitch & voice leading.

Voicings carry a chord's pitch classes at concrete octaves — ordered low to high, as MIDI note numbers. The lift closeVoicing :: { centre } -> Chord -> Voicing produces a default close-position voicing at the given octave; everything else is a Voicing -> Voicing transformation that composes through ordinary function composition.

A Selector carves a sub-chord out of an existing chord or voicing — by position in the sorted pitch-class array (for Chord) or by position in the low-to-high voicing (for Voicing). The vocabulary is small: TakeLow / TakeHigh / TakeRange / TakeIndices / TakeEvery / DropS.

voiceLead / enumerateVoicings move between chords by minimum total semitone motion; play realises a whole Progression with voice-leading carry-through. Pure Prelude/Data.*; depends only on Harmonia.Chord.

#Voicing Source

newtype Voicing

A voicing is an ordered array of MIDI note numbers, sorted ascending (low to high). Each element is a concrete realisable note; the octave is implicit in the number (ndiv12).

MIDI convention: middle C (C4) is 60; octave 4 spans 60..71.

Constructors

Instances

#voicingMidi Source

voicingMidi :: Voicing -> Array Int

Extract the MIDI note numbers from a voicing.

#closeVoicing Source

closeVoicing :: { centre :: Int } -> Chord -> Voicing

Produce a default close-position voicing of a chord, placed so all notes sit in the named octave. Chord pitch classes are played in ascending pitch-class order at the centre octave.

closeVoicing { centre: 4 } (Chord [0, 4, 7]) = Voicing [60, 64, 67]

Doesn't read slash info from the source DegreeChord — the slash was baked into the Chord's pitch-class set at realize time and the bass-below-other-notes arrangement is the caller's choice via spread or octave-shifts.

#openTriad Source

openTriad :: Voicing -> Voicing

Open the triad: for a 3-note voicing [root, 3rd, 5th] produces [root, 5th, 3rd-an-octave-up]. More generally: lifts the second- from-bottom note up an octave and re-sorts. No-op on voicings shorter than 2 notes.

#rootless Source

rootless :: Voicing -> Voicing

Omit the bottom note (root).

#drop2 Source

drop2 :: Voicing -> Voicing

Drop-2 voicing: lower the 2nd-highest note an octave. Standard jazz transformation that "opens" a close-position voicing.

drop2 (Voicing [60, 64, 67, 71]) = Voicing [55, 60, 64, 71] (Cmaj7 close → G2 C E B drop-2)

#drop2and4 Source

drop2and4 :: Voicing -> Voicing

Drop the 2nd and 4th notes from the top, each down an octave. For 4-note voicings: lower the second-from-top and bottom note. Falls back to drop2 on voicings shorter than 4 notes.

#quartal Source

quartal :: Voicing -> Voicing

Restack the voicing's pitch classes in cycle-of-4ths order from the bottom note, placing each subsequent note at the lowest octave giving at least a perfect-4th interval (5 semitones) above the previous. Quartal voicings sound "open" and modal; works well on chords that contain a 4th-stack (sus4, m11, jazz quartals), less well on pure triads where the natural intervals are 3rds.

quartal (Voicing [60, 65, 70, 67]) = Voicing [60, 65, 70, 79] (sus chord C F Bb G → C F Bb G5; G placed above Bb)

#cluster Source

cluster :: Voicing -> Voicing

Compress the voicing into the smallest octave window — distinct pitch classes in ascending order at the bottom note's octave. Effectively closeVoicing applied to whatever PCs are present.

#spread Source

spread :: { high :: Int, low :: Int } -> Voicing -> Voicing

Distribute the voicing's notes across an octave range. Lowest note shifted to (or near) the low octave, highest to (or near) the high octave, intermediate notes spaced evenly across the range. Preserves pitch classes.

#VoicingStrategy Source

type VoicingStrategy = Voicing -> Voicing

A voicing strategy is a transformation on a voicing. Strategies compose through ordinary function composition.

#Selector Source

data Selector

A Selector carves a sub-chord out of a chord or voicing. Output is the same type as input, so selectors are composable and feed back into the Notation fabric like any other chord-shaped value.

Constructors

Instances

#takeChord Source

takeChord :: Selector -> Chord -> Chord

Apply a selector to a Chord's sorted pitch-class array. Positions are interpreted against the sort order; the lowest PC numerically is position 0. Output is itself a Chord, so selectors chain.

#takeVoicing Source

takeVoicing :: Selector -> Voicing -> Voicing

Apply a selector to a Voicing. Positions are interpreted against the low-to-high order; the bottom voice is position 0.

#Progression Source

type Progression = Array DegreeChord

A progression is an ordered list of chord recipes.

#voiceLead Source

voiceLead :: Voicing -> Chord -> Voicing

Voice-lead from the current voicing into the next chord. Returns a voicing of nextChord whose notes are as close as possible to currentVoicing — common pitch classes stay at the same MIDI number, non-common ones move to the nearest octave.

Implementation: enumerate every permutation of the next chord's distinct pitch classes (factorial in chord size — fine for ≤7 voices), pair each current voice with one PC via nearest-octave placement, score by total |motion|, return the minimum.

If the current voicing and next chord have different sizes, falls back to closeVoicing centred on the current voicing's bottom octave. Voice-counts-changing-mid-progression is V-D territory.

#enumerateVoicings Source

enumerateVoicings :: Voicing -> Chord -> Array (Tuple Voicing Int)

All distinct (voicing, motion) candidates for voice-leading from a current voicing into a next chord, sorted by motion ascending. The smallest-motion result is voiceLead's output; subsequent entries are progressively less smooth alternatives.

Same size constraint as voiceLead — returns a single fallback entry on size mismatch.

#play Source

play :: Key -> VoicingStrategy -> Progression -> Array Voicing

play with the default centre octave 4 (middle C area).

#playFrom Source

playFrom :: Int -> Key -> VoicingStrategy -> Progression -> Array Voicing

Glue: realise a progression in a key, applying the voicing strategy to the first chord and voice-leading every subsequent one from the previous voicing. Returns one Voicing per DegreeChord in the progression.

centre is the octave the first voicing centres on; subsequent voicings drift via voice-leading. See play for a centre-4 default.

#nearestNote Source

nearestNote :: Int -> Int -> Int

Place a pitch class at the octave whose MIDI number is closest to the target. Checks the natural octave, one above, and one below.