|
- import {
- CHORD_COMPONENT_ORDERS,
- ChordAnalysis,
- ChordBase,
- ChordComponent,
- ChordExtensionType,
- ChordModificationType,
- Interval,
- } from './common';
-
- const INTERVAL_COMPONENT_MAPPING = {
- [Interval.UNISON]: ChordComponent.ROOT,
- [Interval.MINOR_SECOND]: ChordComponent.SECOND,
- [Interval.MAJOR_SECOND]: ChordComponent.SECOND,
- [Interval.MINOR_THIRD]: ChordComponent.THIRD,
- [Interval.MAJOR_THIRD]: ChordComponent.THIRD,
- [Interval.PERFECT_FOURTH]: ChordComponent.FOURTH,
- [Interval.DIMINISHED_FIFTH]: ChordComponent.FIFTH,
- [Interval.PERFECT_FIFTH]: ChordComponent.FIFTH,
- [Interval.AUGMENTED_FIFTH]: ChordComponent.FIFTH,
- [Interval.MAJOR_SIXTH]: ChordComponent.SIXTH,
- [Interval.MINOR_SEVENTH]: ChordComponent.SEVENTH,
- [Interval.MAJOR_SEVENTH]: ChordComponent.SEVENTH,
- [Interval.OCTAVE]: ChordComponent.ROOT,
- [Interval.MINOR_NINTH]: ChordComponent.NINTH,
- [Interval.MAJOR_NINTH]: ChordComponent.NINTH,
- [Interval.AUGMENTED_NINTH]: ChordComponent.NINTH,
- [Interval.TENTH]: ChordComponent.THIRD,
- [Interval.MINOR_ELEVENTH]: ChordComponent.ELEVENTH,
- [Interval.MAJOR_ELEVENTH]: ChordComponent.ELEVENTH,
- [Interval.TWELFTH]: ChordComponent.FIFTH,
- [Interval.MINOR_THIRTEENTH]: ChordComponent.THIRTEENTH,
- [Interval.MAJOR_THIRTEENTH]: ChordComponent.THIRTEENTH,
- [Interval.AUGMENTED_THIRTEENTH]: ChordComponent.THIRTEENTH,
- } as const;
-
- const normalizeIntervalsFromRoot = (intervalsFromRoot: Interval[]) => intervalsFromRoot.reduce(
- (normalized, intervalFromRoot) => {
- const theNormalized = [...normalized];
- const { [intervalFromRoot]: component } = INTERVAL_COMPONENT_MAPPING;
- theNormalized[CHORD_COMPONENT_ORDERS.indexOf(component)] = intervalFromRoot;
- return theNormalized;
- },
- [] as Interval[],
- );
-
- const getChordBase = (normalizedIntervals: Interval[]) => {
- const [, second, third, fourth, fifth] = normalizedIntervals;
-
- switch (third) {
- case Interval.MINOR_THIRD:
- return {
- base: fifth === Interval.DIMINISHED_FIFTH ? ChordBase.DIMINISHED : ChordBase.MINOR,
- };
- case Interval.MAJOR_THIRD:
- case Interval.TENTH:
- if (fifth === Interval.AUGMENTED_FIFTH) {
- return {
- base: ChordBase.AUGMENTED,
- };
- }
- if (fifth === Interval.DIMINISHED_FIFTH) {
- return {
- base: ChordBase.MAJOR,
- modifications: [
- {
- type: ChordModificationType.LOWERED,
- component: ChordComponent.FIFTH,
- },
- ],
- };
- }
- return {
- base: ChordBase.MAJOR,
- };
- default:
- break;
- }
-
- if (second === Interval.MAJOR_SECOND) {
- return {
- modifications: [
- {
- type: ChordModificationType.SUSPENDED,
- component: ChordComponent.SECOND,
- },
- ],
- };
- }
-
- if (fourth === Interval.PERFECT_FOURTH) {
- return {
- modifications: [
- {
- type: ChordModificationType.SUSPENDED,
- component: ChordComponent.FOURTH,
- },
- ],
- };
- }
-
- return {};
- };
-
- const getChordSixth = (normalizedIntervals: Interval[], priorAnalysis: ChordAnalysis) => {
- const { extensions: priorExtensions = [] } = priorAnalysis;
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.FIFTH)]: fifth,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
- } = normalizedIntervals;
-
- if (fifth === Interval.PERFECT_FIFTH && sixth === Interval.MAJOR_SIXTH) {
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MAJOR,
- component: ChordComponent.SIXTH,
- }],
- };
- }
-
- return priorAnalysis;
- };
-
- const getChordSeventh = (
- normalizedIntervals: Interval[],
- priorAnalysis: ChordAnalysis,
- ) => {
- const { extensions: priorExtensions = [], base: oldBase, modifications = [] } = priorAnalysis;
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRD)]: third,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
- } = normalizedIntervals;
- if (oldBase === ChordBase.DIMINISHED && sixth === Interval.DIMINISHED_SEVENTH) {
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.DIMINISHED,
- component: ChordComponent.SEVENTH,
- }],
- };
- }
-
- if (seventh === Interval.MINOR_SEVENTH) {
- let newBase: ChordBase | undefined;
- if (third === Interval.MINOR_THIRD) {
- newBase = ChordBase.MINOR;
- } else if (third === Interval.MAJOR_THIRD || third === Interval.TENTH) {
- newBase = ChordBase.MAJOR;
- } else {
- newBase = oldBase;
- }
-
- if (oldBase === ChordBase.DIMINISHED) {
- return {
- ...priorAnalysis,
- base: newBase,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.DOMINANT,
- component: ChordComponent.SEVENTH,
- }],
- modifications: [
- ...modifications,
- {
- type: ChordModificationType.LOWERED,
- component: ChordComponent.FIFTH,
- },
- ],
- };
- }
-
- return {
- ...priorAnalysis,
- base: newBase,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.DOMINANT,
- component: ChordComponent.SEVENTH,
- }],
- };
- }
-
- if (seventh === Interval.MAJOR_SEVENTH) {
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MAJOR,
- component: ChordComponent.SEVENTH,
- }],
- };
- }
-
- return priorAnalysis;
- };
-
- const getChordNinth = (
- normalizedIntervals: Interval[],
- priorAnalysis: ChordAnalysis,
- ) => {
- const { extensions: priorExtensions = [] } = priorAnalysis;
- const { [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth } = normalizedIntervals;
-
- switch (ninth) {
- case Interval.MINOR_NINTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MINOR,
- component: ChordComponent.NINTH,
- }],
- };
- case Interval.MAJOR_NINTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MAJOR,
- component: ChordComponent.NINTH,
- }],
- };
- case Interval.AUGMENTED_NINTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.AUGMENTED,
- component: ChordComponent.NINTH,
- }],
- };
- default:
- break;
- }
-
- return priorAnalysis;
- };
-
- const getChordEleventh = (
- normalizedIntervals: Interval[],
- priorAnalysis: ChordAnalysis,
- ) => {
- const { extensions: priorExtensions = [] } = priorAnalysis;
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
- } = normalizedIntervals;
-
- switch (eleventh) {
- case Interval.MINOR_ELEVENTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MINOR,
- component: ChordComponent.ELEVENTH,
- }],
- };
- case Interval.MAJOR_ELEVENTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MAJOR,
- component: ChordComponent.ELEVENTH,
- }],
- };
- default:
- break;
- }
-
- return priorAnalysis;
- };
-
- const getChordThirteenth = (
- normalizedIntervals: Interval[],
- priorAnalysis: ChordAnalysis,
- ) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRTEENTH)]: thirteenth,
- } = normalizedIntervals;
- const { extensions: priorExtensions = [] } = priorAnalysis;
-
- switch (thirteenth) {
- case Interval.MINOR_THIRTEENTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MINOR,
- component: ChordComponent.THIRTEENTH,
- }],
- };
- case Interval.MAJOR_THIRTEENTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.MAJOR,
- component: ChordComponent.THIRTEENTH,
- }],
- };
- case Interval.AUGMENTED_THIRTEENTH:
- return {
- ...priorAnalysis,
- extensions: [...priorExtensions, {
- type: ChordExtensionType.AUGMENTED,
- component: ChordComponent.THIRTEENTH,
- }],
- };
- default:
- break;
- }
-
- return priorAnalysis;
- };
-
- const getBaseChordOmissions = (normalizedIntervals: Interval[]) => {
- const [
- root,
- second,
- third,
- fourth,
- ] = normalizedIntervals;
-
- const omissions = [];
-
- if (!second && !third && !fourth) {
- omissions.push(ChordComponent.THIRD);
- }
-
- if (root !== Interval.UNISON) {
- omissions.push(ChordComponent.ROOT);
- }
-
- return omissions;
- };
-
- const getSeventhChordOmissions = (normalizedIntervals: Interval[]) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.FIFTH)]: fifth,
- } = normalizedIntervals;
-
- const omissions = getBaseChordOmissions(normalizedIntervals);
-
- if (!fifth) {
- omissions.push(ChordComponent.FIFTH);
- }
-
- return omissions;
- };
-
- const getNinthChordOmissions = (normalizedIntervals: Interval[]) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
- } = normalizedIntervals;
-
- const omissions = getSeventhChordOmissions(normalizedIntervals);
-
- if (!seventh && !sixth) {
- omissions.push(ChordComponent.SEVENTH);
- }
-
- return omissions;
- };
-
- const getEleventhChordOmissions = (normalizedIntervals: Interval[]) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth,
- } = normalizedIntervals;
-
- const omissions = getNinthChordOmissions(normalizedIntervals);
-
- if (!ninth) {
- omissions.push(ChordComponent.NINTH);
- }
-
- return omissions;
- };
-
- const getThirteenthChordOmissions = (normalizedIntervals: Interval[]) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
- } = normalizedIntervals;
- const omissions = getEleventhChordOmissions(normalizedIntervals);
-
- if (!eleventh) {
- omissions.push(ChordComponent.ELEVENTH);
- }
-
- return omissions;
- };
-
- const getChordOmissions = (
- normalizedIntervals: Interval[],
- priorAnalysis: ChordAnalysis,
- ) => {
- const {
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
- [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRTEENTH)]: thirteenth,
- } = normalizedIntervals;
-
- if (thirteenth) {
- return {
- ...priorAnalysis,
- omissions: getThirteenthChordOmissions(normalizedIntervals),
- };
- }
- if (eleventh) {
- return {
- ...priorAnalysis,
- omissions: getEleventhChordOmissions(normalizedIntervals),
- };
- }
- if (ninth) {
- return {
- ...priorAnalysis,
- omissions: getNinthChordOmissions(normalizedIntervals),
- };
- }
- if (sixth || seventh) {
- return {
- ...priorAnalysis,
- omissions: getSeventhChordOmissions(normalizedIntervals),
- };
- }
-
- return {
- ...priorAnalysis,
- omissions: getBaseChordOmissions(normalizedIntervals),
- };
- };
-
- export const analyzeIntervals = (intervalsFromRoot: Interval[]) => {
- const normalizedIntervals = normalizeIntervalsFromRoot(intervalsFromRoot);
- let initAnalysis = getChordBase(normalizedIntervals) as ChordAnalysis;
- initAnalysis = getChordSixth(normalizedIntervals, initAnalysis);
- initAnalysis = getChordSeventh(normalizedIntervals, initAnalysis);
- initAnalysis = getChordNinth(normalizedIntervals, initAnalysis);
- initAnalysis = getChordEleventh(normalizedIntervals, initAnalysis);
- initAnalysis = getChordThirteenth(normalizedIntervals, initAnalysis);
- initAnalysis = getChordOmissions(normalizedIntervals, initAnalysis);
-
- const retValue = {} as ChordAnalysis;
- if (typeof initAnalysis.base !== 'undefined') {
- retValue.base = initAnalysis.base;
- }
-
- if ((initAnalysis.extensions?.length ?? 0) > 0) {
- retValue.extensions = initAnalysis.extensions;
- }
-
- if ((initAnalysis.modifications?.length ?? 0) > 0) {
- retValue.modifications = initAnalysis.modifications;
- }
-
- if ((initAnalysis.omissions?.length ?? 0) > 0) {
- retValue.omissions = initAnalysis.omissions;
- }
-
- return retValue;
- };
|