import { AbsoluteNote } from '../note/absolute-note'; import { IChord } from '../chord/ichord'; import { CompleteChord } from '../chord/complete-chord'; export type PartWritingRule = (settings: any, ...chords: IChord[]) => boolean; export type PartWritingPreference = (...chords: CompleteChord[]) => number; export declare const voiceRange: AbsoluteNote[][]; export interface PartWritingRules { [ruleName: string]: PartWritingRule; } export interface PartWritingPreferences { [ruleName: string]: PartWritingPreference; } type ParamOfType = { [P in keyof T]: Parameters[0] extends U ? P : never; }[keyof T]; type ParamNotOfType = { [P in keyof T]: Parameters[0] extends U ? never : P; }[keyof T]; /** * Interface telling what rules should be run for the singular and multi checks and enforcing rules interface */ export interface PartWritingParameters { rules: T; singularRules: (keyof T)[]; ruleParameters: { [ruleName in ParamOfType]?: false; } & { [ruleName in ParamNotOfType]: Parameters[0] | false; }; preferences: U; singularPreferences: (keyof U)[]; preferencesOrdering: (keyof U)[]; } /** * Contains methods that allow for the vertical checking of chords (to verify good part-writing) across a series of rules */ export declare namespace PartWriting { type RuleUnion = T & typeof defaultPartWritingParameters.rules; type PreferencesUnion = T & typeof defaultPartWritingParameters.preferences; /** * Extend the default parameters with new rules, preferences * @param newRules the new rules * @param newPreferences the new preferences * @param newSingularRules the new rules that are singular * @param newRuleParameters the new parameters (can disable old rules by setting false) * @param newSingularPreferences the new preferences that are singular * @param newPreferencesOrdering the ordering of all the preferences */ export function extendDefaultParameters({ newRules, newPreferences, newSingularRules, newRuleParameters, newSingularPreferences, newPreferencesOrdering }?: { newRules?: T; newPreferences?: U; newSingularRules?: (keyof T)[]; newRuleParameters?: Partial<{ [ruleName in keyof RuleUnion]: Parameters[ruleName]>[0] | boolean; }>; newSingularPreferences?: (keyof U)[]; newPreferencesOrdering?: (keyof PreferencesUnion)[]; }): PartWritingParameters; export namespace Rules { /** * Rules about how a singular chord should look */ namespace VoicingRules { /** * Checks that the chord maintains proper vocal ranges * @param chord the chord to check */ function range(settings: { ranges: AbsoluteNote[][]; }, { voices }: IChord): boolean; /** * Checks that the notes in the chord properly spells the chord * @param settings * @param chord the chord under consideration */ function spelling(_: undefined, { romanNumeral, intervals, voices }: IChord): boolean; /** * Checks that the chord does not double the leading tone * @param chord the chord to check */ function leadingToneDoubling(_: undefined, { romanNumeral, romanNumeralFinalized, intervals, flags }: IChord): boolean; /** * Checks that the chord does not double the seventh * @param chord the chord to check */ function seventhDoubling(_: undefined, { romanNumeral, intervals }: IChord): boolean; /** * Checks that the chord meets the basic doubling requirements * @param chord the chord to check * @param prev the chord before this chord */ function completeness(_: undefined, chord: IChord, prev?: IChord): boolean; /** * Checks that the chord does not have too much space between the voice parts or that one voice is above another * @param chord the chord to check */ function spacingAndCrossing(_: undefined, { voices }: IChord): boolean; /** * Checks that a accented (cadential) 64 does not double the tonic * @param chord */ function accented64Doubling(_: undefined, { romanNumeral, intervals }: IChord): boolean; } /** * Rules about how a chord should be in relation to the previous chords */ namespace HorizontalRules { /** * Checks that the chord has no parallel unisons, fifths, or octaves from the previous * @param chord the chord to check * @param prev the chord before this chord */ function parallels(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that the chord has no parallels by contrary motion (e.g. 15th to 8ve) * @param chord the chord to check * @param prev the chord before this chord */ function contraryFifths(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks whether any of the parts cross with where the notes where previously * @param chord the chord to check * @param prev the chord before this chord */ function voiceOverlap(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks whether there are hidden fifths in the soprano and bass * Hidden fifths being perfect fifths arrived at through similar motion where the soprano is not moving up by step * @param chord the chord to check * @param prev the chord before this chord */ function hiddenFifths(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices, romanNumeral }: IChord): boolean; /** * Checks whether notes in the previous chord resolve correctly * @param chord the chord to check * @param prev the chord before this chord */ function leadingToneResolution(settings: { disallowFrustratedLeadingTone: boolean; }, { voices: currVoices, romanNumeral: currRomanNumeral, romanNumeralFinalized }: IChord, { voices: prevVoices, romanNumeral: prevRomanNumeral, intervals }: IChord): boolean; /** * Checks whether the seventh is prepared correctly (by step or unison) * V is exempt * @todo is applied V exempt? * @param chord the chord to check * @param prev the chord before this chord */ function seventhPreparation(_: undefined, { intervals, romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral, voices: prevVoices }: IChord): boolean; /** * Checks that the seventh of a chord resolves down, except in a few exceptions * @param settings * @param chord the chord under consideration * @param prev the chord before `chord` * @param before the chord before `prev` */ function seventhResolution(settings: { scope: number; }, chord: IChord, prev: IChord, ...before: IChord[]): boolean; /** * Checks if there is a melodic A2 or too large of intervals * @param chord * @param prev * @todo A2 is sometimes acceptable */ function invalidIntervals(_: undefined, { voices: currVoices, romanNumeral }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that an accented 64 is properly prepared * @param settings * @param chord * @param prev */ function accented64Preparation(_: undefined, { romanNumeral, voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that an accented 64 resolves properly * @param settings * @param chord * @param prev */ function accented64Resolution(_: undefined, { romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral, voices: prevVoices }: IChord): boolean; function cadenceType(_: undefined, { flags, romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral }: IChord): boolean; /** * Require restorative motion (i.e. follow jumps with steps in opposite direction) * @param chord the chord under consideration * @param prev the previous chord * @todo consider arpeggiation logic * @todo consider if there are cases where we might hold a note before stepping down * @todo consider the logic on the leading side of preferring the step as well */ function voiceJumpStepsOpposite(_: undefined, { voices: currVoices }: IChord, { voices: middleVoices }: IChord, { voices: firstVoices }?: IChord): boolean; /** * Checks that the voices of a sequence maintain the same voicing over evey other chord * @param _ * @param chord * @param prev */ function sequence(_: undefined, { voices: currVoices, romanNumeral: currRomanNumeral }: IChord, { romanNumeral: middleRomanNumeral }: IChord, prev: IChord): boolean; function rapidKeyChange({ scope }: { scope: number; }, chord: IChord, ...prev: IChord[]): boolean; } /** * Creates a generator that returns the keys of failed rules * @param parameters * @param chords */ function checkAll(parameters: PartWritingParameters, chords: IChord[]): Generator; /** * Checks that no rule fails * @param parameters * @param chords */ function testAll(parameters: PartWritingParameters, chords: IChord[]): boolean; /** * Checks a singular chord and returns the index of the failing * @param parameters * @param chordToCheck */ function checkSingular(parameters: PartWritingParameters, chordToCheck: IChord): Generator; /** * Checks that no check is failed for a singular chord * @param parameters * @param chordToCheck */ function testSingular(parameters: PartWritingParameters, chordToCheck: IChord): boolean; } export namespace Preferences { namespace VoicingRules { /** * Prefer that chords have certain doublings over others * @param chord the chord under consideration */ function checkDoubling(chord: CompleteChord): -2 | -1 | 0; /** * Prefer that voices do not cross * @param chord the chord under consideration */ function checkVoiceCrossing(chord: CompleteChord): number; /** * Prefer that voices remain within their core range * @param chord the chord under consideration */ function checkRange(chord: CompleteChord): number; /** * Prefer that voices do not share the same pitch * @param chord the chord under consideration */ function checkSharedPitch(chord: CompleteChord): number; /** * Prefer chord progressions using sequences * @param chord the chord to look at */ function checkSequence(chord: CompleteChord): 1 | 0; } /** * Rules about how a chord should be in relation to the previous chords */ namespace HorizontalRules { /** * Prefer that inner voices do not overlap * @param chord the chord under consideration * @param prev the previous chord */ function checkVoiceOverlap(chord: CompleteChord, prev: CompleteChord): number; /** * Prefer smaller movements in soprano and inner voices * @param chord the chord under consideration * @param prev the previous chord */ function checkVoiceDisjunction({ voices: currVoices }: CompleteChord, { voices: middleVoices }: CompleteChord): number; /** * Prefer that the bass jumps down by an octave in cadential V progressions (e.g. V - V7) * @param chord the chord under consideration * @param prev the previous chord */ function checkBassOctaveJump(chord: CompleteChord, prev: CompleteChord): 1 | -1 | 0; function checkSequenceTarget({ voices: currVoices, romanNumeral: currRomanNumeral }: CompleteChord, { romanNumeral: middleRomanNumeral }: CompleteChord, prev: CompleteChord): -1 | 0; /** * Prefer proper succession of chromatic tones, as might result in ii - V/V * @param chord the chord under consideration * @param prev the previous chord */ function checkCrossRelations({ voices: currVoices, romanNumeral }: CompleteChord, { voices: prevVoices, romanNumeral: prevRomanNumeral, intervals: prevInterval }: CompleteChord): 0 | 5 | 9 | 10; /** * Prefer using a chord that is different from the previous */ function checkRepetition(chord: CompleteChord, previous: CompleteChord): 1 | 0; /** * Prefers that a pivot chord has a predominant function in the new key */ function modulationToPredominant(chord: CompleteChord): -1 | 0; /** * Prefers that there are fewer modulations */ function fewerModulations(chord: CompleteChord): -1 | 0; } /** * Evaluate the chord on all the preferences * @param chordToCheck the chord to evaluate */ function evaluateSingle(parameters: PartWritingParameters, chordToCheck: CompleteChord): number[]; /** * Create a lazy array of the results of the preference checks * The checks will only be run if the index is called and the value is not already calculated * @param chordToCheck the chord to run the rules */ function lazyEvaluateSingle(parameters: PartWritingParameters, chordToCheck: CompleteChord): number[]; /** * Evaluates all preferences for a given chord * @param chordToCheck the chord to check * @param prev the chord before the chord under consideration */ function evaluateAll(parameters: PartWritingParameters, chordToCheck: CompleteChord, prev: CompleteChord): number[]; /** * Create a lazy array of the results of the preference checks * The checks will only be run if the index is called and the value is not already calculated * @param chordToCheck the chord to run the rules on * @param prev the chord before the one under consideration */ function lazyEvaluateAll(parameters: PartWritingParameters, chordToCheck: CompleteChord, prev: CompleteChord): number[]; } export {}; } declare const defaultPartWritingRules: { /** * Checks that the chord has no parallel unisons, fifths, or octaves from the previous * @param chord the chord to check * @param prev the chord before this chord */ parallels(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that the chord has no parallels by contrary motion (e.g. 15th to 8ve) * @param chord the chord to check * @param prev the chord before this chord */ contraryFifths(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks whether any of the parts cross with where the notes where previously * @param chord the chord to check * @param prev the chord before this chord */ voiceOverlap(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks whether there are hidden fifths in the soprano and bass * Hidden fifths being perfect fifths arrived at through similar motion where the soprano is not moving up by step * @param chord the chord to check * @param prev the chord before this chord */ hiddenFifths(_: undefined, { voices: currVoices }: IChord, { voices: prevVoices, romanNumeral }: IChord): boolean; /** * Checks whether notes in the previous chord resolve correctly * @param chord the chord to check * @param prev the chord before this chord */ leadingToneResolution(settings: { disallowFrustratedLeadingTone: boolean; }, { voices: currVoices, romanNumeral: currRomanNumeral, romanNumeralFinalized }: IChord, { voices: prevVoices, romanNumeral: prevRomanNumeral, intervals }: IChord): boolean; /** * Checks whether the seventh is prepared correctly (by step or unison) * V is exempt * @todo is applied V exempt? * @param chord the chord to check * @param prev the chord before this chord */ seventhPreparation(_: undefined, { intervals, romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral, voices: prevVoices }: IChord): boolean; /** * Checks that the seventh of a chord resolves down, except in a few exceptions * @param settings * @param chord the chord under consideration * @param prev the chord before `chord` * @param before the chord before `prev` */ seventhResolution(settings: { scope: number; }, chord: IChord, prev: IChord, ...before: IChord[]): boolean; /** * Checks if there is a melodic A2 or too large of intervals * @param chord * @param prev * @todo A2 is sometimes acceptable */ invalidIntervals(_: undefined, { voices: currVoices, romanNumeral }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that an accented 64 is properly prepared * @param settings * @param chord * @param prev */ accented64Preparation(_: undefined, { romanNumeral, voices: currVoices }: IChord, { voices: prevVoices }: IChord): boolean; /** * Checks that an accented 64 resolves properly * @param settings * @param chord * @param prev */ accented64Resolution(_: undefined, { romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral, voices: prevVoices }: IChord): boolean; cadenceType(_: undefined, { flags, romanNumeral: currRomanNumeral, voices: currVoices }: IChord, { romanNumeral: prevRomanNumeral }: IChord): boolean; /** * Require restorative motion (i.e. follow jumps with steps in opposite direction) * @param chord the chord under consideration * @param prev the previous chord * @todo consider arpeggiation logic * @todo consider if there are cases where we might hold a note before stepping down * @todo consider the logic on the leading side of preferring the step as well */ voiceJumpStepsOpposite(_: undefined, { voices: currVoices }: IChord, { voices: middleVoices }: IChord, { voices: firstVoices }?: IChord): boolean; /** * Checks that the voices of a sequence maintain the same voicing over evey other chord * @param _ * @param chord * @param prev */ sequence(_: undefined, { voices: currVoices, romanNumeral: currRomanNumeral }: IChord, { romanNumeral: middleRomanNumeral }: IChord, prev: IChord): boolean; rapidKeyChange({ scope }: { scope: number; }, chord: IChord, ...prev: IChord[]): boolean; /** * Checks that the chord maintains proper vocal ranges * @param chord the chord to check */ range(settings: { ranges: AbsoluteNote[][]; }, { voices }: IChord): boolean; /** * Checks that the notes in the chord properly spells the chord * @param settings * @param chord the chord under consideration */ spelling(_: undefined, { romanNumeral, intervals, voices }: IChord): boolean; /** * Checks that the chord does not double the leading tone * @param chord the chord to check */ leadingToneDoubling(_: undefined, { romanNumeral, romanNumeralFinalized, intervals, flags }: IChord): boolean; /** * Checks that the chord does not double the seventh * @param chord the chord to check */ seventhDoubling(_: undefined, { romanNumeral, intervals }: IChord): boolean; /** * Checks that the chord meets the basic doubling requirements * @param chord the chord to check * @param prev the chord before this chord */ completeness(_: undefined, chord: IChord, prev?: IChord): boolean; /** * Checks that the chord does not have too much space between the voice parts or that one voice is above another * @param chord the chord to check */ spacingAndCrossing(_: undefined, { voices }: IChord): boolean; /** * Checks that a accented (cadential) 64 does not double the tonic * @param chord */ accented64Doubling(_: undefined, { romanNumeral, intervals }: IChord): boolean; }; declare const defaultPartWritingPreferences: { /** * Prefer that inner voices do not overlap * @param chord the chord under consideration * @param prev the previous chord */ checkVoiceOverlap(chord: CompleteChord, prev: CompleteChord): number; /** * Prefer smaller movements in soprano and inner voices * @param chord the chord under consideration * @param prev the previous chord */ checkVoiceDisjunction({ voices: currVoices }: CompleteChord, { voices: middleVoices }: CompleteChord): number; /** * Prefer that the bass jumps down by an octave in cadential V progressions (e.g. V - V7) * @param chord the chord under consideration * @param prev the previous chord */ checkBassOctaveJump(chord: CompleteChord, prev: CompleteChord): 1 | -1 | 0; checkSequenceTarget({ voices: currVoices, romanNumeral: currRomanNumeral }: CompleteChord, { romanNumeral: middleRomanNumeral }: CompleteChord, prev: CompleteChord): -1 | 0; /** * Prefer proper succession of chromatic tones, as might result in ii - V/V * @param chord the chord under consideration * @param prev the previous chord */ checkCrossRelations({ voices: currVoices, romanNumeral }: CompleteChord, { voices: prevVoices, romanNumeral: prevRomanNumeral, intervals: prevInterval }: CompleteChord): 0 | 5 | 9 | 10; /** * Prefer using a chord that is different from the previous */ checkRepetition(chord: CompleteChord, previous: CompleteChord): 1 | 0; /** * Prefers that a pivot chord has a predominant function in the new key */ modulationToPredominant(chord: CompleteChord): -1 | 0; /** * Prefers that there are fewer modulations */ fewerModulations(chord: CompleteChord): -1 | 0; /** * Prefer that chords have certain doublings over others * @param chord the chord under consideration */ checkDoubling(chord: CompleteChord): -2 | -1 | 0; /** * Prefer that voices do not cross * @param chord the chord under consideration */ checkVoiceCrossing(chord: CompleteChord): number; /** * Prefer that voices remain within their core range * @param chord the chord under consideration */ checkRange(chord: CompleteChord): number; /** * Prefer that voices do not share the same pitch * @param chord the chord under consideration */ checkSharedPitch(chord: CompleteChord): number; /** * Prefer chord progressions using sequences * @param chord the chord to look at */ checkSequence(chord: CompleteChord): 1 | 0; }; export declare const defaultPartWritingParameters: PartWritingParameters; export {};