Build, search, and play chords.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

analyze.ts 12 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. import {
  2. CHORD_COMPONENT_ORDERS,
  3. ChordAnalysis,
  4. ChordBase,
  5. ChordComponent,
  6. ChordExtensionType,
  7. ChordModificationType,
  8. Interval,
  9. } from './common';
  10. const INTERVAL_COMPONENT_MAPPING = {
  11. [Interval.UNISON]: ChordComponent.ROOT,
  12. [Interval.MINOR_SECOND]: ChordComponent.SECOND,
  13. [Interval.MAJOR_SECOND]: ChordComponent.SECOND,
  14. [Interval.MINOR_THIRD]: ChordComponent.THIRD,
  15. [Interval.MAJOR_THIRD]: ChordComponent.THIRD,
  16. [Interval.PERFECT_FOURTH]: ChordComponent.FOURTH,
  17. [Interval.DIMINISHED_FIFTH]: ChordComponent.FIFTH,
  18. [Interval.PERFECT_FIFTH]: ChordComponent.FIFTH,
  19. [Interval.AUGMENTED_FIFTH]: ChordComponent.FIFTH,
  20. [Interval.MAJOR_SIXTH]: ChordComponent.SIXTH,
  21. [Interval.MINOR_SEVENTH]: ChordComponent.SEVENTH,
  22. [Interval.MAJOR_SEVENTH]: ChordComponent.SEVENTH,
  23. [Interval.OCTAVE]: ChordComponent.ROOT,
  24. [Interval.MINOR_NINTH]: ChordComponent.NINTH,
  25. [Interval.MAJOR_NINTH]: ChordComponent.NINTH,
  26. [Interval.AUGMENTED_NINTH]: ChordComponent.NINTH,
  27. [Interval.TENTH]: ChordComponent.THIRD,
  28. [Interval.MINOR_ELEVENTH]: ChordComponent.ELEVENTH,
  29. [Interval.MAJOR_ELEVENTH]: ChordComponent.ELEVENTH,
  30. [Interval.TWELFTH]: ChordComponent.FIFTH,
  31. [Interval.MINOR_THIRTEENTH]: ChordComponent.THIRTEENTH,
  32. [Interval.MAJOR_THIRTEENTH]: ChordComponent.THIRTEENTH,
  33. [Interval.AUGMENTED_THIRTEENTH]: ChordComponent.THIRTEENTH,
  34. } as const;
  35. const normalizeIntervalsFromRoot = (intervalsFromRoot: Interval[]) => intervalsFromRoot.reduce(
  36. (normalized, intervalFromRoot) => {
  37. const theNormalized = [...normalized];
  38. const { [intervalFromRoot]: component } = INTERVAL_COMPONENT_MAPPING;
  39. theNormalized[CHORD_COMPONENT_ORDERS.indexOf(component)] = intervalFromRoot;
  40. return theNormalized;
  41. },
  42. [] as Interval[],
  43. );
  44. const getChordBase = (normalizedIntervals: Interval[]) => {
  45. const [, second, third, fourth, fifth] = normalizedIntervals;
  46. switch (third) {
  47. case Interval.MINOR_THIRD:
  48. return {
  49. base: fifth === Interval.DIMINISHED_FIFTH ? ChordBase.DIMINISHED : ChordBase.MINOR,
  50. };
  51. case Interval.MAJOR_THIRD:
  52. case Interval.TENTH:
  53. if (fifth === Interval.AUGMENTED_FIFTH) {
  54. return {
  55. base: ChordBase.AUGMENTED,
  56. };
  57. }
  58. if (fifth === Interval.DIMINISHED_FIFTH) {
  59. return {
  60. base: ChordBase.MAJOR,
  61. modifications: [
  62. {
  63. type: ChordModificationType.LOWERED,
  64. component: ChordComponent.FIFTH,
  65. },
  66. ],
  67. };
  68. }
  69. return {
  70. base: ChordBase.MAJOR,
  71. };
  72. default:
  73. break;
  74. }
  75. if (second === Interval.MAJOR_SECOND) {
  76. return {
  77. modifications: [
  78. {
  79. type: ChordModificationType.SUSPENDED,
  80. component: ChordComponent.SECOND,
  81. },
  82. ],
  83. };
  84. }
  85. if (fourth === Interval.PERFECT_FOURTH) {
  86. return {
  87. modifications: [
  88. {
  89. type: ChordModificationType.SUSPENDED,
  90. component: ChordComponent.FOURTH,
  91. },
  92. ],
  93. };
  94. }
  95. return {};
  96. };
  97. const getChordSixth = (normalizedIntervals: Interval[], priorAnalysis: ChordAnalysis) => {
  98. const { extensions: priorExtensions = [] } = priorAnalysis;
  99. const {
  100. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.FIFTH)]: fifth,
  101. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
  102. } = normalizedIntervals;
  103. if (fifth === Interval.PERFECT_FIFTH && sixth === Interval.MAJOR_SIXTH) {
  104. return {
  105. ...priorAnalysis,
  106. extensions: [...priorExtensions, {
  107. type: ChordExtensionType.MAJOR,
  108. component: ChordComponent.SIXTH,
  109. }],
  110. };
  111. }
  112. return priorAnalysis;
  113. };
  114. const getChordSeventh = (
  115. normalizedIntervals: Interval[],
  116. priorAnalysis: ChordAnalysis,
  117. ) => {
  118. const { extensions: priorExtensions = [], base: oldBase, modifications = [] } = priorAnalysis;
  119. const {
  120. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRD)]: third,
  121. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
  122. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
  123. } = normalizedIntervals;
  124. if (oldBase === ChordBase.DIMINISHED && sixth === Interval.DIMINISHED_SEVENTH) {
  125. return {
  126. ...priorAnalysis,
  127. extensions: [...priorExtensions, {
  128. type: ChordExtensionType.DIMINISHED,
  129. component: ChordComponent.SEVENTH,
  130. }],
  131. };
  132. }
  133. if (seventh === Interval.MINOR_SEVENTH) {
  134. let newBase: ChordBase | undefined;
  135. if (third === Interval.MINOR_THIRD) {
  136. newBase = ChordBase.MINOR;
  137. } else if (third === Interval.MAJOR_THIRD || third === Interval.TENTH) {
  138. newBase = ChordBase.MAJOR;
  139. } else {
  140. newBase = oldBase;
  141. }
  142. if (oldBase === ChordBase.DIMINISHED) {
  143. return {
  144. ...priorAnalysis,
  145. base: newBase,
  146. extensions: [...priorExtensions, {
  147. type: ChordExtensionType.DOMINANT,
  148. component: ChordComponent.SEVENTH,
  149. }],
  150. modifications: [
  151. ...modifications,
  152. {
  153. type: ChordModificationType.LOWERED,
  154. component: ChordComponent.FIFTH,
  155. },
  156. ],
  157. };
  158. }
  159. return {
  160. ...priorAnalysis,
  161. base: newBase,
  162. extensions: [...priorExtensions, {
  163. type: ChordExtensionType.DOMINANT,
  164. component: ChordComponent.SEVENTH,
  165. }],
  166. };
  167. }
  168. if (seventh === Interval.MAJOR_SEVENTH) {
  169. return {
  170. ...priorAnalysis,
  171. extensions: [...priorExtensions, {
  172. type: ChordExtensionType.MAJOR,
  173. component: ChordComponent.SEVENTH,
  174. }],
  175. };
  176. }
  177. return priorAnalysis;
  178. };
  179. const getChordNinth = (
  180. normalizedIntervals: Interval[],
  181. priorAnalysis: ChordAnalysis,
  182. ) => {
  183. const { extensions: priorExtensions = [] } = priorAnalysis;
  184. const { [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth } = normalizedIntervals;
  185. switch (ninth) {
  186. case Interval.MINOR_NINTH:
  187. return {
  188. ...priorAnalysis,
  189. extensions: [...priorExtensions, {
  190. type: ChordExtensionType.MINOR,
  191. component: ChordComponent.NINTH,
  192. }],
  193. };
  194. case Interval.MAJOR_NINTH:
  195. return {
  196. ...priorAnalysis,
  197. extensions: [...priorExtensions, {
  198. type: ChordExtensionType.MAJOR,
  199. component: ChordComponent.NINTH,
  200. }],
  201. };
  202. case Interval.AUGMENTED_NINTH:
  203. return {
  204. ...priorAnalysis,
  205. extensions: [...priorExtensions, {
  206. type: ChordExtensionType.AUGMENTED,
  207. component: ChordComponent.NINTH,
  208. }],
  209. };
  210. default:
  211. break;
  212. }
  213. return priorAnalysis;
  214. };
  215. const getChordEleventh = (
  216. normalizedIntervals: Interval[],
  217. priorAnalysis: ChordAnalysis,
  218. ) => {
  219. const { extensions: priorExtensions = [] } = priorAnalysis;
  220. const {
  221. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
  222. } = normalizedIntervals;
  223. switch (eleventh) {
  224. case Interval.MINOR_ELEVENTH:
  225. return {
  226. ...priorAnalysis,
  227. extensions: [...priorExtensions, {
  228. type: ChordExtensionType.MINOR,
  229. component: ChordComponent.ELEVENTH,
  230. }],
  231. };
  232. case Interval.MAJOR_ELEVENTH:
  233. return {
  234. ...priorAnalysis,
  235. extensions: [...priorExtensions, {
  236. type: ChordExtensionType.MAJOR,
  237. component: ChordComponent.ELEVENTH,
  238. }],
  239. };
  240. default:
  241. break;
  242. }
  243. return priorAnalysis;
  244. };
  245. const getChordThirteenth = (
  246. normalizedIntervals: Interval[],
  247. priorAnalysis: ChordAnalysis,
  248. ) => {
  249. const {
  250. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRTEENTH)]: thirteenth,
  251. } = normalizedIntervals;
  252. const { extensions: priorExtensions = [] } = priorAnalysis;
  253. switch (thirteenth) {
  254. case Interval.MINOR_THIRTEENTH:
  255. return {
  256. ...priorAnalysis,
  257. extensions: [...priorExtensions, {
  258. type: ChordExtensionType.MINOR,
  259. component: ChordComponent.THIRTEENTH,
  260. }],
  261. };
  262. case Interval.MAJOR_THIRTEENTH:
  263. return {
  264. ...priorAnalysis,
  265. extensions: [...priorExtensions, {
  266. type: ChordExtensionType.MAJOR,
  267. component: ChordComponent.THIRTEENTH,
  268. }],
  269. };
  270. case Interval.AUGMENTED_THIRTEENTH:
  271. return {
  272. ...priorAnalysis,
  273. extensions: [...priorExtensions, {
  274. type: ChordExtensionType.AUGMENTED,
  275. component: ChordComponent.THIRTEENTH,
  276. }],
  277. };
  278. default:
  279. break;
  280. }
  281. return priorAnalysis;
  282. };
  283. const getBaseChordOmissions = (normalizedIntervals: Interval[]) => {
  284. const [
  285. root,
  286. second,
  287. third,
  288. fourth,
  289. ] = normalizedIntervals;
  290. const omissions = [];
  291. if (!second && !third && !fourth) {
  292. omissions.push(ChordComponent.THIRD);
  293. }
  294. if (root !== Interval.UNISON) {
  295. omissions.push(ChordComponent.ROOT);
  296. }
  297. return omissions;
  298. };
  299. const getSeventhChordOmissions = (normalizedIntervals: Interval[]) => {
  300. const {
  301. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.FIFTH)]: fifth,
  302. } = normalizedIntervals;
  303. const omissions = getBaseChordOmissions(normalizedIntervals);
  304. if (!fifth) {
  305. omissions.push(ChordComponent.FIFTH);
  306. }
  307. return omissions;
  308. };
  309. const getNinthChordOmissions = (normalizedIntervals: Interval[]) => {
  310. const {
  311. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
  312. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
  313. } = normalizedIntervals;
  314. const omissions = getSeventhChordOmissions(normalizedIntervals);
  315. if (!seventh && !sixth) {
  316. omissions.push(ChordComponent.SEVENTH);
  317. }
  318. return omissions;
  319. };
  320. const getEleventhChordOmissions = (normalizedIntervals: Interval[]) => {
  321. const {
  322. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth,
  323. } = normalizedIntervals;
  324. const omissions = getNinthChordOmissions(normalizedIntervals);
  325. if (!ninth) {
  326. omissions.push(ChordComponent.NINTH);
  327. }
  328. return omissions;
  329. };
  330. const getThirteenthChordOmissions = (normalizedIntervals: Interval[]) => {
  331. const {
  332. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
  333. } = normalizedIntervals;
  334. const omissions = getEleventhChordOmissions(normalizedIntervals);
  335. if (!eleventh) {
  336. omissions.push(ChordComponent.ELEVENTH);
  337. }
  338. return omissions;
  339. };
  340. const getChordOmissions = (
  341. normalizedIntervals: Interval[],
  342. priorAnalysis: ChordAnalysis,
  343. ) => {
  344. const {
  345. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SIXTH)]: sixth,
  346. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.SEVENTH)]: seventh,
  347. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.NINTH)]: ninth,
  348. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.ELEVENTH)]: eleventh,
  349. [CHORD_COMPONENT_ORDERS.indexOf(ChordComponent.THIRTEENTH)]: thirteenth,
  350. } = normalizedIntervals;
  351. if (thirteenth) {
  352. return {
  353. ...priorAnalysis,
  354. omissions: getThirteenthChordOmissions(normalizedIntervals),
  355. };
  356. }
  357. if (eleventh) {
  358. return {
  359. ...priorAnalysis,
  360. omissions: getEleventhChordOmissions(normalizedIntervals),
  361. };
  362. }
  363. if (ninth) {
  364. return {
  365. ...priorAnalysis,
  366. omissions: getNinthChordOmissions(normalizedIntervals),
  367. };
  368. }
  369. if (sixth || seventh) {
  370. return {
  371. ...priorAnalysis,
  372. omissions: getSeventhChordOmissions(normalizedIntervals),
  373. };
  374. }
  375. return {
  376. ...priorAnalysis,
  377. omissions: getBaseChordOmissions(normalizedIntervals),
  378. };
  379. };
  380. export const analyzeIntervals = (intervalsFromRoot: Interval[]) => {
  381. const normalizedIntervals = normalizeIntervalsFromRoot(intervalsFromRoot);
  382. let initAnalysis = getChordBase(normalizedIntervals) as ChordAnalysis;
  383. initAnalysis = getChordSixth(normalizedIntervals, initAnalysis);
  384. initAnalysis = getChordSeventh(normalizedIntervals, initAnalysis);
  385. initAnalysis = getChordNinth(normalizedIntervals, initAnalysis);
  386. initAnalysis = getChordEleventh(normalizedIntervals, initAnalysis);
  387. initAnalysis = getChordThirteenth(normalizedIntervals, initAnalysis);
  388. initAnalysis = getChordOmissions(normalizedIntervals, initAnalysis);
  389. const retValue = {} as ChordAnalysis;
  390. if (typeof initAnalysis.base !== 'undefined') {
  391. retValue.base = initAnalysis.base;
  392. }
  393. if ((initAnalysis.extensions?.length ?? 0) > 0) {
  394. retValue.extensions = initAnalysis.extensions;
  395. }
  396. if ((initAnalysis.modifications?.length ?? 0) > 0) {
  397. retValue.modifications = initAnalysis.modifications;
  398. }
  399. if ((initAnalysis.omissions?.length ?? 0) > 0) {
  400. retValue.omissions = initAnalysis.omissions;
  401. }
  402. return retValue;
  403. };