duncanmalashock / elm-music-theory / Music.Voicing.FourPart

A chord voicing is an instance of a Chord that can be played or sung.


type alias Voicing =
Music.Internal.Voicing.FourPart.Voicing

Generating voicings

This package's recommended way of creating four-part Voicings is to use the Chord.voiceFourParts function along with VoicingMethods like the ones in this module:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ basic ]
    (Chord.majorSeventh PitchClass.c)

In this example, we pass the following to voiceFourParts:

  1. The Ranges of the instruments involved
  2. A list of VoicingMethods to be used
  3. The chord to voice

This uses the characteristics of the VoicingMethod to return a List of all possible Voicings.

From here, you may choose from this list based on some criteria:

myGeneratedVoicings
    |> List.filter
        (\voicing ->
            containsPitchInVoiceOne Pitch.e5 voicing
                && (span voicing >= Interval.perfectOctave)
        )
    |> List.sortWith (centerOrder Pitch.g4)
    |> List.head

This does the following:

  1. Filters the list to only include Voicings which have E5 in the top voice, and have a span of at least an octave from bottom to top voice
  2. Sorts the list so that the Voicings which are centered around the pitch G4 are at the beginning
  3. Takes the first item in the remaining list

These sorting and filtering processes allow you to account for considerations like voice leading, or the harmonization of a melody. You'll find comparison functions in this module for doing that.

Constructing a single voicing

There are cases where you may want to create a specific voicing you have in mind:

voicing : Pitches -> Music.Internal.Chord.Chord -> Result (List Music.PitchClass.PitchClass) Voicing

Create a voicing from pitches and a chord:

voicing
    { voiceOne = Pitch.c5
    , voiceTwo = Pitch.a4
    , voiceThree = Pitch.g4
    , voiceFour = Pitch.e4
    }
    (Chord.majorSix PitchClass.c)
    == Ok Voicing ...

If the pitches given do not match the chord, this function will return Err with the erroneous pitch classes:

voicing
    { voiceOne = Pitch.c5
    , voiceTwo = Pitch.d4
    , voiceThree = Pitch.g4
    , voiceFour = Pitch.e4
    }
    (Chord.majorSix PitchClass.c)
    == Err [ PitchClass.d ]

Helpers

chord : Voicing -> Music.Internal.Chord.Chord

Get the chord being voiced:

chord myVoicing == Chord.majorSixNine PitchClass.c

span : Voicing -> Music.Internal.Interval.Interval

Get the interval from the lowest to the highest pitch in the voicing:

 span myVoicing == Interval.minorSeventh

center : Voicing -> Basics.Int

Get the midpoint between the highest and lowest voices in semitones:

center myVoicing
    == Pitch.semitones Pitch.fSharp3

Voices

voiceOne : Voicing -> Music.Internal.Pitch.Pitch

Get the first (highest) pitch of the voicing:

voiceOne myVoicing == Pitch.d5

voiceTwo : Voicing -> Music.Internal.Pitch.Pitch

Get the second pitch of the voicing:

voiceTwo myVoicing == Pitch.a4

voiceThree : Voicing -> Music.Internal.Pitch.Pitch

Get the third pitch of the voicing:

voiceThree myVoicing == Pitch.g4

voiceFour : Voicing -> Music.Internal.Pitch.Pitch

Get the fourth (lowest) pitch of the voicing:

voiceFour myVoicing == Pitch.e4

containsPitch : Music.Internal.Pitch.Pitch -> Voicing -> Basics.Bool

Find out whether a voicing contains a specific pitch:

 containsPitch Pitch.d5 myVoicing == True

containsPitchInVoiceOne : Music.Internal.Pitch.Pitch -> Voicing -> Basics.Bool

Find out whether a voicing has a specific pitch in the first voice:

 containsPitchInVoiceOne Pitch.d5 myVoicing == True

containsPitchInVoiceTwo : Music.Internal.Pitch.Pitch -> Voicing -> Basics.Bool

Find out whether a voicing has a specific pitch in the second voice:

 containsPitchInVoiceTwo Pitch.a4 myVoicing == True

containsPitchInVoiceThree : Music.Internal.Pitch.Pitch -> Voicing -> Basics.Bool

Find out whether a voicing has a specific pitch in the third voice:

 containsPitchInVoiceThree Pitch.g4 myVoicing == True

containsPitchInVoiceFour : Music.Internal.Pitch.Pitch -> Voicing -> Basics.Bool

Find out whether a voicing has a specific pitch in the fourth voice:

 containsPitchInVoiceFour Pitch.e4 myVoicing == True

Comparing voicings

Common tones

commonTones : Voicing -> Voicing -> List Music.Internal.Pitch.Pitch

Get all pitches in common between two voicings:

commonTones bFlatVoicing bDimVoicing
    == [ Pitch.d4
       , Pitch.f4
       ]

Direction

usesContraryMotion : Voicing -> Voicing -> Basics.Bool

Find out whether the first and fourth voices move in opposite directions (known as contrary motion).

containsParallelFifths : Voicing -> Voicing -> Basics.Bool

Find out whether any two moving voices maintain a perfect fifth interval between them. Identifying parallel fifths and octaves is important in the study of counterpoint.

containsParallelOctaves : Voicing -> Voicing -> Basics.Bool

Find out whether any two moving voices maintain a perfect octave interval between them.

Distance

totalSemitoneDistance : Voicing -> Voicing -> Basics.Int

Given two voicings, find out how far in semitones all voices move.

semitoneDistanceVoiceOne : Voicing -> Voicing -> Basics.Int

Given two voicings, find out how far in semitones the first (top) voice moves.

semitoneDistanceVoiceTwo : Voicing -> Voicing -> Basics.Int

Given two voicings, find out how far in semitones the second voice moves.

semitoneDistanceVoiceThree : Voicing -> Voicing -> Basics.Int

Given two voicings, find out how far in semitones the third voice moves.

semitoneDistanceVoiceFour : Voicing -> Voicing -> Basics.Int

Given two voicings, find out how far in semitones the fourth (bottom) voice moves.

Low interval limits

isWithinLowIntervalLimits : Voicing -> Basics.Bool

A low interval limit is the lowest pitch at which the character of an interval cannot be heard clearly. I have heard this explained in terms of the harmonic series, but it seems to be taught as more of a guideline for arrangers than a physical absolute.

Sorting voicings

sortWeighted : List ( Voicing -> Voicing -> Basics.Order, Basics.Float ) -> List Voicing -> List Voicing

Sort by multiple ordering functions:

sortWeighted
    [ ( totalSemitoneDistanceOrder previousVoicing, 10.0 )
    , ( contraryMotionOrder previousVoicing, 5.0 )
    , ( commonTonesOrder previousVoicing, 2.0 )
    ]
    voicingList

orderWeighted : List ( Voicing -> Voicing -> Basics.Order, Basics.Float ) -> Voicing -> Voicing -> Basics.Order

Combine multiple ordering functions:

orderWeighted
    [ ( totalSemitoneDistanceOrder previousVoicing, 10.0 )
    , ( contraryMotionOrder previousVoicing, 5.0 )
    , ( commonTonesOrder previousVoicing, 2.0 )
    ]
    |> List.orderWith

centerOrder : Music.Internal.Pitch.Pitch -> Voicing -> Voicing -> Basics.Order

Compare voicings by how far their centers are from a goal pitch:

myVoicingList
    |> List.sortWith (centerOrder Pitch.g4)

Useful for finding voicings that are centered around a particular pitch.

commonTonesOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by how many pitches they have in common with a previous voicing:

myVoicingList
    |> List.sortWith (commonTonesOrder previousVoicing)

contraryMotionOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by whether they use contrary motion from a previous voicing:

myVoicingList
    |> List.sortWith (contraryMotionOrder previousVoicing)

"Contrary motion" here is in respect to the top and bottom voices.

totalSemitoneDistanceOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by absolute difference in semitones from a previous voicing:

myVoicingList
    |> List.sortWith (totalSemitoneDistanceOrder previousVoicing)

semitoneDistanceVoiceOneOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by absolute difference in semitones in voice one:

myVoicingList
    |> List.sortWith (semitoneDistanceVoiceOneOrder previousVoicing)

semitoneDistanceVoiceTwoOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by absolute difference in semitones in voice two:

myVoicingList
    |> List.sortWith (semitoneDistanceVoiceTwoOrder previousVoicing)

semitoneDistanceVoiceThreeOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by absolute difference in semitones in voice three:

myVoicingList
    |> List.sortWith (semitoneDistanceVoiceThreeOrder previousVoicing)

semitoneDistanceVoiceFourOrder : Voicing -> Voicing -> Voicing -> Basics.Order

Compare voicings by absolute difference in semitones in voice four:

myVoicingList
    |> List.sortWith (semitoneDistanceVoiceFourOrder previousVoicing)

Conversion


type alias Pitches =
{ voiceOne : Music.Internal.Pitch.Pitch
, voiceTwo : Music.Internal.Pitch.Pitch
, voiceThree : Music.Internal.Pitch.Pitch
, voiceFour : Music.Internal.Pitch.Pitch 
}

The pitches contained in a voicing.

These are in order from highest (voiceOne) to lowest (voiceFour), the way you might read them on a staff.

toPitches : Voicing -> Pitches

Get all pitches contained in a voicing:

toPitches myVoicing
    == { voiceOne = Pitch.d5
       , voiceTwo = Pitch.a4
       , voiceThree = Pitch.g4
       , voiceFour = Pitch.e4
       }

toPitchList : Voicing -> List Music.Internal.Pitch.Pitch

Get all pitches contained in a voicing, as a List:

toPitchList myVoicing
    == [ Pitch.d5
       , Pitch.a4
       , Pitch.g4
       , Pitch.e4
       ]

toString : Voicing -> String

Get all pitches contained in a voicing, as a stringified list:

toPitchList myVoicing
    == "D5, A4, G4, E4"

Intervals


type alias Intervals =
{ fourToOne : Music.Internal.Interval.Interval
, fourToTwo : Music.Internal.Interval.Interval
, fourToThree : Music.Internal.Interval.Interval
, threeToOne : Music.Internal.Interval.Interval
, threeToTwo : Music.Internal.Interval.Interval
, twoToOne : Music.Internal.Interval.Interval 
}

toIntervals : Voicing -> Intervals

Get all intervals between each pitch in a voicing:

toIntervals myVoicing
    == { fourToOne = Interval.majorSixth
       , fourToTwo = Interval.majorSecond
       , fourToThree = Interval.majorThird
       , threeToOne = Interval.perfectFifth
       , threeToTwo = Interval.majorSecond
       , twoToOne = Interval.perfectFourth
       }

toIntervalList : Voicing -> List Music.Internal.Interval.Interval

Get all intervals between each pitch in a voicing as a List:

toIntervalList myVoicing
    == [ Interval.majorSecond
       , Interval.majorSecond
       , Interval.majorThird
       , Interval.perfectFourth
       , Interval.perfectFifth
       , Interval.majorSixth
       ]

Voicing methods

Basic

basic : VoicingMethod

A basic textbook method for voicing a chord in root position:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ basic ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "B4, G4, E4, C4"
       , -- 2 others...
       ]

Jazz

These methods were adapted from Jazz Arranging Techniques by Gary Lindsay.

Notes:

close : VoicingMethod

Voice a chord using the "four-way close" method:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ close ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "C4, B3, G3, E3"
       , -- 39 others...
       ]

This method voices all four notes "closely" within the span of an octave.

drop2 : VoicingMethod

Voice a chord using the "four-way drop-2" method:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ drop2 ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "C4, G3, E3, B2"
       , -- 55 others...
       ]

This method is the same as four-way close, but with the second pitch from the top dropped by an octave for a wider, semi-open sound.

drop3 : VoicingMethod

Voice a chord using the "four-way drop-3" method:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ drop3 ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "C4, G3, E3, B2"
       , -- 55 others...
       ]

Same as drop-2, but with the third pitch from the top dropped instead of the second.

drop2and4 : VoicingMethod

Voice a chord using the "four-way drop-2-and-4" method:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ drop2and4 ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "G4, C4, E3, B2"
       , -- 55 others...
       ]

A double drop voicing, with the second and fourth pitches from the top dropped for a wide, open sound.

spread : VoicingMethod

Voice a chord using the "four-way spread" method:

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ spread ]
    (Chord.majorSeventh PitchClass.c)
    |> List.map toString
    == [ "C4, B3, E3, G2"
       , -- 22 others...
       ]

Another open voicing method, with the root of the chord on the bottom for a dramatic effect.

Classical

Note: these classical methods were developed before jazz harmony, and so chord extensions and added tones will not be included.

rootPosition : VoicingMethod

Voice a chord in "root position":

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ rootPosition ]
    (Chord.major PitchClass.c)
    |> List.map toString
    == [ "G4, E4, G3, C3"
       , -- 30 others...
       ]

A root position voicing is any voicing where the root is in the lowest voice.

firstInversion : VoicingMethod

Voice a chord in "first inversion":

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ firstInversion ]
    (Chord.major PitchClass.c)
    |> List.map toString
    == [ "C6, C5, G4, E3"
       , -- 32 others...
       ]

A first inversion voicing is any voicing where the third of the chord is in the lowest voice.

secondInversion : VoicingMethod

Voice a chord in "second inversion":

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ secondInversion ]
    (Chord.major PitchClass.c)
    |> List.map toString
    == [ "E4, G3, C3, G2"
       , -- 32 others...
       ]

A second inversion voicing is any voicing where the fifth of the chord is in the lowest voice.

thirdInversion : VoicingMethod

Voice a chord in "third inversion":

Chord.voiceFourParts
    { voiceOne = Range.sopranoVoice
    , voiceTwo = Range.altoVoice
    , voiceThree = Range.tenorVoice
    , voiceFour = Range.bassVoice
    }
    [ thirdInversion ]
    (Chord.dominantSeventh PitchClass.c)
    |> List.map toString
    == [ "E4, C4, C3, B♭2"
       , -- 32 others...
       ]

A third inversion voicing is any voicing where the seventh of the chord is in the lowest voice.

Note: this will return an empty list when used with triad chord types like major and minor, because a seventh must be included.

Custom voicing methods

Voicing methods are a deep and nuanced topic, that I hope to make approachable. I've modeled this API with three main concepts of chord voicing in mind:

Here are the steps in building a voicing method:

Categorize the factors in the chord

Since chords can vary, use some method of categorizing the factors of a chord into groups that you can guarantee are present. Examples of this type of categorization function are Chord.categorizeFactors and Chord.availableTensions.

Specify the placement and uniqueness of factors

Select factors using the with... functions in this section.

Finish and specify spacing limits

Use the placeSelectedFactors function, passing a SpacingLimits.

Example:

myCustomVoicingMethod : VoicingMethod
myCustomVoicingMethod =
    custom
        ChordType.categorizeFactors
        (\factors ->
            selectFactors
                |> withFactorFrom
                    (List.filterMap identity
                        [ Just Interval.perfectUnison
                        , factors.sixthOrSeventh
                        ]
                    )
                |> withFactor factors.fifth
                |> withFactor factors.third
                |> withFactor Interval.perfectUnison
                |> placeSelectedFactors spacingLimits
        )

spacingLimits =
    { twoToOne =
        Interval.range
            Interval.augmentedUnison
            Interval.perfectOctave
    , threeToTwo =
        Interval.range
            Interval.augmentedUnison
            Interval.perfectOctave
    , fourToThree =
        Interval.range
            Interval.augmentedUnison
            Interval.perfectOctave
    }


type alias VoicingMethod =
Music.Internal.Voicing.FourPart.VoicingMethod


type alias SpacingLimits =
{ twoToOne : Music.Internal.Interval.Range
, threeToTwo : Music.Internal.Interval.Range
, fourToThree : Music.Internal.Interval.Range 
}

method : (Music.Internal.ChordType.ChordType -> Maybe categorized) -> (categorized -> List Music.Internal.Voicing.FourPart.VoicingClass) -> VoicingMethod

Begin a custom voicing method:

method Chord.categorizeFactors
    (\categorized ->
        ...
    )

selectFactors : Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> Music.Internal.Voicing.FourPart.VoicingClass)

Begin selecting factors for a voicing method.

withFactor : Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select a chord factor for use in a voice.

withUniqueFactor : Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select a factor that has not yet been used.

withFactorFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select a factor from a list of options.

withUniqueFactorFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select a factor that has not yet been used, from a list of options.

withTwoFactorsFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select two factors from a list of options.

withUniqueTwoFactorsFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select two factors that have not yet been used, from a list of options.

withThreeFactorsFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select three factors from a list of options.

withUniqueThreeFactorsFrom : List Music.Internal.Interval.Interval -> Music.Internal.VoicingClass.VoicingClassBuilder (Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> Music.Internal.Interval.Interval -> a) -> Music.Internal.VoicingClass.VoicingClassBuilder a

Select two factors that have not yet been used, from a list of options.

placeSelectedFactors : SpacingLimits -> Music.Internal.VoicingClass.VoicingClassBuilder Music.Internal.Voicing.FourPart.VoicingClass -> List Music.Internal.Voicing.FourPart.VoicingClass

Finish selecting factors for a voicing method and place them according to the spacing limits.

combineVoicingMethods : List VoicingMethod -> VoicingMethod

Combine multiple VoicingMethods together:

myComboVoicingMethod =
    combineVoicingMethods [ voicingMethodOne, voicingMethodTwo, voicingMethodThree ]

This is useful if you want to define multiple VoicingMethods that act as a group (e.g. inversions or similar methods that use different chord factors).