UNPKG

tonal-pcset-dft

Version:

Discrete fourier transform applied to pitch class sets

121 lines (109 loc) 3.59 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tonalNote = require('tonal-note'); var tonalArray = require('tonal-array'); /** * `tonal-pcset-dft` is a implementation of the Discrete Fourier Transform * applied to pitch class sets. * * The DFT provides a kind of harmonic blueprint by which we may characterize a * pitch set (a chord, for example) and compare it to others in a way that * correspond to our perception of sounding similar. * * David Lewin was the first to note the connection between the Fourier * transform and a chord’s harmonic content. Other theorists have incorporated * this mathematical technique into their work, including Vuza (1993), * Quinn (2006 and 2007), and Amiot (2007) * * References: * - [1] [Set-Class Similarity, Voice Leading, and the Fourier Transform (Dmitri Tymoczko)](http://dmitri.mycpanel.princeton.edu/files/publications/fourier.pdf) * - [2] [Continuous Harmonic Spaces (Clifton Callender)]() * * __This module is not YET included in the tonal facade__ * * @example * var dft = require('tonal-pcset-dft') * dft.spectra('C E G#') // => [3, 0, 0, 3, 0, 0, 3] * * @module pcset-dft */ var { PI, sin, cos, pow, sqrt } = Math; function pcset (notes) { return Object.keys(tonalArray.map(tonalNote.chroma, notes).reduce(function (set, ch) { set[ch] = true; return set }, {})) } /** * Get the Fourier components of a pitch class set * * It applies the Discrete Fourier Transform to the pitch class set with * numbers from 0 to 6 (the _Niquist_ frequency: N / 2 where N is the number of * possible pitch classes) * @param {String|Array} notes - the notes or pitch class set * @return {Array} the fourier components * @example * dft.dft('C E G#') // => [ [3, 0], [0, 0], [0, 0], [3, 0], [0, 0], [0, 0], [3, 0] ]) */ function dft (notes) { var pcs = pcset(notes); return [0, 1, 2, 3, 4, 5, 6].map(function (n) { return truncate(component(n, pcs)) }) } /** * Get the nth component of a given pitch class set * @private */ function component (n, pcs) { return pcs.reduce(function (complex, p) { // calculate the complex number for n var v = 2 * PI * p * n / 12; complex[0] += cos(v); complex[1] += sin(v); return complex }, [0, 0]) } var MIN = 1e-10; /** * Set 0 very small numbers in a complex number * @private */ function truncate (cpx) { if (cpx[0] < MIN) cpx[0] = 0; if (cpx[1] < MIN) cpx[1] = 0; return cpx } /** * The spectra of pitch-class sets in twelve-tone equal temperament * is the magnitudes of the first six harmonics (in addition to harmonic zero) * * @param {String|Array} notes - the notes or pitch set * @return {Array<Number>} the magnitudes of the dft * @example * dft.spectra('C E G#') // => [3, 0, 0, 3, 0, 0, 3] */ function spectra (notes) { var comp = dft(notes); return comp.map(function (complex) { return sqrt(pow(complex[0], 2) + pow(complex[1], 2)) }) } /** * A natural way to measure the distance between two spectra is to take * the Euclidean distance between their corresponding spectra * @param {String|Array} set1 - the first pitch class set or notes * @param {String|Array} set2 - the second pitch class set or notes * @return the Euclidean distance between both */ function distance (set1, set2) { var sp1 = spectra(set1); var sp2 = spectra(set2); return Math.sqrt(sp1.reduce(function (v, _, i) { return v + Math.pow(sp1[i] - sp2[i], 2) }, 0)) } exports.pcset = pcset; exports.dft = dft; exports.spectra = spectra; exports.distance = distance;