├── dissonant ├── __init__.py ├── tuning.py ├── dissonance.py └── models.py ├── Makefile ├── LICENSE ├── setup.py ├── tests └── tuning_test.py ├── .gitignore └── README.md /dissonant/__init__.py: -------------------------------------------------------------------------------- 1 | from .dissonance import * 2 | from .models import * 3 | from .tuning import * 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | py.test 3 | 4 | install: 5 | pip install dissonant 6 | 7 | install_dev: 8 | pip install -e . 9 | 10 | uninstall: 11 | pip uninstall dissonant 12 | 13 | clean: 14 | rm -r build/ dist/ dissonant.egg-info/ 15 | 16 | # twine - a tool for uploading packages to PyPI 17 | install_twine: 18 | pip install twine 19 | 20 | build: 21 | python setup.py sdist 22 | python setup.py bdist_wheel --universal 23 | 24 | publish: 25 | twine upload dist/* 26 | 27 | test_publish: 28 | python setup.py sdist upload -r pypitest 29 | 30 | test_install: 31 | pip install --verbose --index-url https://testpypi.python.org/pypi/ dissonant 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bohumír Zámečník 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='dissonant', 4 | version='0.1.1', 5 | description='Musical chord dissonance models', 6 | url='https://github.com/bzamecnik/dissonant', 7 | author='Bohumir Zamecnik', 8 | author_email='bohumir.zamecnik@gmail.com', 9 | zip_safe=False, 10 | packages=['dissonant'], 11 | install_requires=[ 12 | 'numpy', 13 | ], 14 | extra_requires={ 15 | 'notebooks': [ 16 | 'ipywidgets', 17 | 'jupyter', 18 | 'matplotlib', 19 | 'scipy', 20 | ] 21 | }, 22 | setup_requires=['setuptools-markdown'], 23 | long_description_markdown_filename='README.md', 24 | classifiers=[ 25 | # How mature is this project? Common values are 26 | # 3 - Alpha 27 | # 4 - Beta 28 | # 5 - Production/Stable 29 | 'Development Status :: 3 - Alpha', 30 | 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: Science/Research', 33 | 'Topic :: Multimedia :: Sound/Audio :: Analysis', 34 | 35 | 'License :: OSI Approved :: MIT License', 36 | 37 | 'Programming Language :: Python :: 2', 38 | 'Programming Language :: Python :: 3', 39 | 40 | 'Operating System :: POSIX :: Linux', 41 | 'Operating System :: MacOS :: MacOS X' 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /tests/tuning_test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from dissonant.tuning import harmonic_tone, freq_to_pitch, pitch_to_freq 4 | 5 | 6 | def test_harmonic_tone(): 7 | assert np.allclose(harmonic_tone(440, 1)[0], np.array([[440]])) 8 | assert np.allclose(harmonic_tone(440, 1)[1], np.array([[1.]])) 9 | 10 | assert np.allclose(harmonic_tone(440, 5)[0], np.array([[440, 880, 1320, 1760, 2200]])) 11 | assert np.allclose(harmonic_tone(440, 5)[1], np.array([[1, 0.5, 0.33333333, 0.25, 0.2]])) 12 | 13 | assert np.allclose(harmonic_tone([440, 441], 1)[0], np.array([[440], [441]])) 14 | assert np.allclose(harmonic_tone([440, 441], 1)[1], np.array([[1], [1]])) 15 | 16 | assert np.allclose(harmonic_tone([440, 441], 5)[0], 17 | np.array([[440, 880, 1320, 1760, 2200], [441, 882, 1323, 1764, 2205]])) 18 | assert np.allclose(harmonic_tone([440, 441], 5)[1], 19 | np.array([[1, 0.5, 0.33333333, 0.25, 0.2], [1, 0.5, 0.33333333, 0.25, 0.2]])) 20 | 21 | def test_freq_to_pitch_and_back(): 22 | def test_both(pitches, freqs, **kwrgs): 23 | assert np.allclose(pitch_to_freq(pitches, **kwrgs), freqs) 24 | assert np.allclose(freq_to_pitch(freqs, **kwrgs), pitches) 25 | 26 | # scalar 27 | test_both(0, 440) 28 | # verctorized 29 | test_both([0, 4, 7, 12], [440., 554.36526195, 659.25511383, 880.]) 30 | # different base freq 31 | test_both([0, 4, 7, 12], [430., 541.76605145, 644.27204306, 860.], 32 | base_freq=430) 33 | # different steps per octave 34 | test_both([0, 4, 7, 11, 12], [440., 566.13255512, 683.93876398, 880., 937.23615871], 35 | steps_per_octave=11) 36 | -------------------------------------------------------------------------------- /dissonant/tuning.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | __all__ = ['harmonic_tone', 'freq_to_pitch', 'pitch_to_freq'] 4 | 5 | def harmonic_tone(base_freqs, n_partials=1, profile='exp'): 6 | """ 7 | Creates a harmonic tone out of one or more base frequencies. 8 | 9 | Input: 10 | base_freqs - single value, or an numpy array of base frequencies 11 | n_partials - number of partial in the harmonic tone 12 | 13 | Output: 14 | Tuple of (freqs, amplitudes). 15 | 16 | Freqs are a 2D array of the frequencies of partials 17 | of shape (len(freqs), n_partials) 18 | 19 | Amplitudes are of the same shape. 20 | 21 | The amplitude values for a single harmonic tone are going down 22 | inversely in this case. 23 | """ 24 | idx = 1 + np.arange(n_partials) 25 | freqs = np.outer(np.atleast_2d(base_freqs), idx.reshape(1, -1)) 26 | # eg. amplitudes inversely going down 27 | if profile == 'exp': 28 | # exponential fall-off 29 | amp_profile = 0.88 ** idx 30 | elif profile == 'inverse': 31 | amp_profile = 1 / idx 32 | elif profile == 'constant': 33 | amp_profile = np.ones(len(idx)) 34 | else: 35 | raise ValueError("Amplitude profile can be ['exp', 'inverse', 'constant'], was", profile) 36 | 37 | amplitudes = np.tile(amp_profile, (len(base_freqs), 1)) 38 | return freqs, amplitudes 39 | 40 | def freq_to_pitch(freq, base_freq=440.0, steps_per_octave=12): 41 | return np.log2(np.array(freq) / base_freq) * steps_per_octave 42 | 43 | def pitch_to_freq(pitch, base_freq=440.0, steps_per_octave=12): 44 | return (2 ** (np.array(pitch) / steps_per_octave)) * base_freq 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /dissonant/dissonance.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from . import models 4 | 5 | __all__ = ['dissonance', 'dissonance_pair'] 6 | 7 | def dissonance(freqs, amps, model='sethares1993', aggregation=lambda d: d.sum()): 8 | """ 9 | Computes dissonance score for chord composed of muliple frequences, 10 | each possible with different amplitudes using a given dissonance model. 11 | """ 12 | # Get rid of practically zero terms: 13 | nonzero_amps = amps >= 1e-6 14 | freqs, amps = freqs[nonzero_amps], amps[nonzero_amps] 15 | 16 | # The frequencies are sorted in order to generate pairs where f_1 <= f_2. 17 | # Otherwise bad things happen. 18 | freqs = freqs.flatten() 19 | freq_idx = freqs.argsort() 20 | freqs = freqs[freq_idx] 21 | amps = amps.flatten()[freq_idx] 22 | n = len(freqs) 23 | idx_pairs = np.array([(i, j) for i in range(n) for j in range(n) if i < j]) 24 | idx_1 = idx_pairs[:, 0] 25 | idx_2 = idx_pairs[:, 1] 26 | dissonances = dissonance_pair( 27 | freqs[idx_1], freqs[idx_2], 28 | amps[idx_1], amps[idx_2], 29 | model) 30 | return aggregation(dissonances) 31 | 32 | def dissonance_pair(f_1, f_2, a_1, a_2, model): 33 | """ 34 | Computes the dissonance metric for a pair(s) of sinusoidal tones with given 35 | frequency and amplitude using the specified model. 36 | 37 | The parameters can be either a single value or a numpy array. In the latter 38 | case the computation is vectorized for the whole array. 39 | 40 | Parameters: 41 | f_1 - lower frequencies 42 | f_2 - upper frequencies 43 | a_1 - amplitudes for f_1 44 | a_2 - amplitudes for f_2 45 | model - known model name (see list_models()) 46 | 47 | Returns: 48 | dissonance - a single value or array 49 | """ 50 | assert_nonnegative((f_1, f_2, a_1, a_2)) 51 | if type(model) == str: 52 | model = models.models[model] 53 | return model.dissonance_pair(f_1, f_2, a_1, a_2) 54 | 55 | def assert_nonnegative(values): 56 | for v in values: 57 | assert np.all(v >= 0) 58 | -------------------------------------------------------------------------------- /dissonant/models.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | __all__ = [ 4 | 'Sethares1993', 'Vassilakis2001', 'Cook2002', 'Cook2006', 'Cook2009', 5 | 'model_by_name' 6 | ] 7 | 8 | class Sethares1993: 9 | """ 10 | Amplitude fall-off: exponential with base 0.88 starting from 1.0. 11 | """ 12 | a = 3.5 13 | b = 5.75 14 | d_max = 0.24 15 | s_1 = 0.0207 16 | s_2 = 18.96 17 | 18 | def dissonance_pair(self, f_1, f_2, a_1, a_2): 19 | s = self.d_max / (self.s_1 * f_1 + self.s_2) 20 | x = s * (f_2 - f_1) 21 | spl = a_1 * a_2 22 | d = np.exp(-self.a * x) - np.exp(-self.b * x) 23 | return spl * d 24 | 25 | class Vassilakis2001: 26 | """ 27 | Fixes compared to the paper: 28 | - the 0.5 term removed compared to the original formula 29 | since we counts pairs of partials only once, not twice 30 | """ 31 | a = 3.5 32 | b = 5.75 33 | d_max = 0.24 34 | s_1 = 0.0207 35 | s_2 = 18.96 36 | spl_exponent = 0.1 37 | afd_exponent = 3.11 38 | 39 | def dissonance_pair(self, f_1, f_2, a_1, a_2): 40 | spl = a_1 * a_2 41 | af_degree = (2 * a_2) / (a_1 + a_2) 42 | s = self.d_max / (self.s_1 * f_1 + self.s_2) 43 | x = s * (f_2 - f_1) 44 | d = np.exp(-self.a * x) - np.exp(-self.b * x) 45 | return (spl ** self.spl_exponent) * (af_degree ** self.afd_exponent) * d 46 | 47 | class Cook2002: 48 | """ 49 | Dissonance at semitone 1.0 normalized to 1.0. 50 | 51 | Fixes compared to the paper: 52 | - `c` is defined exactly, not with limited precision 53 | - logarithm should be of base 2 54 | - log2(f_2 / f_1) needs to be multiplied by 12 to get the 12-TET semitone 55 | interval 56 | """ 57 | a = 1.2 58 | b = 4.0 59 | # c ~= 3.5350857058976985 (3.53 in the paper which is not precise) 60 | c = 1 / (np.exp(-a) - np.exp(-b)) 61 | 62 | def dissonance_pair(self, f_1, f_2, a_1, a_2): 63 | # difference of tones in 12-TET semitone intervals 64 | x = 12 * np.log2(f_2 / f_1) 65 | return self.c * (np.exp(-self.a * x) - np.exp(-self.b * x)) 66 | 67 | class Cook2006: 68 | """ 69 | Maximum dissonance normalized to 1.0. 70 | 71 | Fixes compared to the paper: 72 | - there's one more minus sign when applying beta_1, beta_2 73 | - logarithm should be of base 2 74 | - log2(f_2 / f_1) needs to be multiplied by 12 to get the 12-TET semitone 75 | interval 76 | - there should be bracket, not floor around the difference of exponentials 77 | (probably a printing error) 78 | """ 79 | beta_1 = -0.8 80 | beta_2 = -1.6 81 | beta_3 = 4.0 82 | gamma = 1.25 83 | 84 | def dissonance_pair(self, f_1, f_2, a_1, a_2): 85 | spl = a_1 * a_2 86 | # frequency ratio -> 12-TET semitone interval 87 | x = 12 * np.log2(f_2 / f_1) 88 | x_to_gamma = x ** self.gamma 89 | d = np.exp(self.beta_1 * x_to_gamma) - np.exp(self.beta_2 * x_to_gamma) 90 | return spl * self.beta_3 * d 91 | 92 | class Cook2009: 93 | """ 94 | Maximum dissonance normalized to 1.0. 95 | 96 | Fixes compared to the paper: 97 | - there's one more minus sign when applying beta_1, beta_2 98 | - logarithm should be of base 2 99 | - log2(f_2 / f_1) needs to be multiplied by 12 to get the 12-TET semitone 100 | interval 101 | 102 | It also contains a model of tension of a triad. 103 | """ 104 | beta_1 = -0.8 105 | beta_2 = -1.6 106 | beta_3 = 4.0 107 | alpha = 0.6 108 | 109 | def dissonance_pair(self, f_1, f_2, a_1, a_2): 110 | spl = a_1 * a_2 111 | # frequency ratio -> 12-TET semitone interval 112 | x = 12 * np.log2(f_2 / f_1) 113 | d = np.exp(self.beta_1 * x) - np.exp(self.beta_2 * x) 114 | return spl * self.beta_3 * d 115 | 116 | def triad_tension(self, f_1, f_2, f_3, a_1, a_2, a_3): 117 | x = np.log2(f_2 / f_1) 118 | y = np.log2(f_3 / f_2) 119 | # originally named v (greek nu) 120 | spl = a_1 * a_2 * a_3 121 | return spl * exp(-((y - x) / self.alpha)**2) 122 | 123 | models = { 124 | 'sethares1993': Sethares1993(), 125 | 'vassilakis2001': Vassilakis2001(), 126 | 'cook2002': Cook2002(), 127 | 'cook2006': Cook2006(), 128 | 'cook2009': Cook2009(), 129 | } 130 | 131 | def model_by_name(name): 132 | return models[name] 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chord dissonance models 2 | 3 | This package implements various models of perceptual chord dissonance. Given a musical chord composed of individual tones, each composed of partials, a dissonance model provides a score that estimate how dissonant (harsh) does it sound to human listener. 4 | 5 | It contains implementation of several models of dissoance with corrections of errors found in the formulas in the papers. See below. 6 | 7 | ## Installation 8 | 9 | ``` 10 | pip install dissonant 11 | ``` 12 | 13 | Why `dissonant`? PyPI package `dissonance` was already taken. 14 | 15 | ## Usage 16 | 17 | Dissonance of a C major chord with harmonic tones in 12-TET tuning with base 440 Hz using the Sethares1993 model: 18 | 19 | ``` 20 | from dissonant import harmonic_tone, dissonance, pitch_to_freq 21 | freqs, amps = harmonic_tone(pitch_to_freq([0, 4, 7, 12]), n_partials=10) 22 | d = dissonance(freqs, amps, model='sethares1993') 23 | ``` 24 | 25 | Dissonance curve of a sliding interval of two harmonic tones: see [notebooks/dissonance_curve.ipynb](notebooks/dissonance_curve.ipynb). 26 | 27 | ## Papers 28 | 29 | - [1965, Plomp, Levelt - Tonal Consonance and Critical Bandwidth](http://www.mpi.nl/world/materials/publications/levelt/Plomp_Levelt_Tonal_1965.pdf) 30 | - 1979, Hutchinson, Knopoff - The significance of the acoustic component of consonance in Western triad 31 | - [1993, Sethares - Local Consonance and the Relationship Between Timbre and Scale](http://sethares.engr.wisc.edu/paperspdf/consonance.pdf) 32 | - http://sethares.engr.wisc.edu/papers/consance.html 33 | - [2000, Vassilakis - Auditory roughness estimation of complex spectra](http://www.escom.org/proceedings/ICMPC2000/Wed/Vassilak.htm) 34 | - PDF is not public, only HTML 35 | - [2001, Vassilakis - Perceptual and Physical Properties of Amplitude Fluctuation and their Musical Significance](http://www.acousticslab.org/papers/VassilakisP2001Dissertation.pdf) - PhD thesis 36 | - [2002, Cook - Tone of Voice and Mind](http://www.res.kutc.kansai-u.ac.jp/~cook/PDFs/Tone_of_Voice_and_Mind.pdf) - book 37 | - [2004, Sethares - Tuning Timbre Spectrum Scale](http://eceserv0.ece.wisc.edu/~sethares/ttss.html) - book 38 | - [2005, Vassilakis - Auditory Roughness as Means of Musical Expression](http://www.acousticslab.org/papers/Vassilakis2005SRE.pdf) 39 | - [2006, Benson - David Benson Music: A Mathematical Offering](http://homepages.abdn.ac.uk/mth192/pages/html/music.pdf) ([book page](http://homepages.abdn.ac.uk/mth192/pages/html/maths-music.html)) 40 | - [2006, Cook, Fujisawa - The Psychophysics of Harmony Perception: Harmony is a Three-Tone Phenomenon](http://www.res.kutc.kansai-u.ac.jp/~cook/PDFs/EMR2006.pdf) 41 | - [2009, Cook - Harmony Perception: Harmoniousness Is More Than the Sum of Interval Consonance](http://www.res.kutc.kansai-u.ac.jp/~cook/PDFs/MusPerc2009.pdf) 42 | - [2010, Vassilakis, Kendall - Psychoacoustic and congitive aspects of auditory roughness - definitions, models and applications](http://acousticslab.org/papers/VassilakisKendall2010.pdf) 43 | - [2013, Dillon - Calculating the Dissonance of a Chord according to Helmholtz theory](https://arxiv.org/pdf/1306.1344v5) 44 | 45 | ### Extra 46 | 47 | - [2001, McKinney et al. - Neural correlates of musical dissonance in the inferior colliculus](http://research.meei.harvard.edu/NeuralCoding/Papers/mckinneyISH.pdf) 48 | - [2007, Vassilakis - SRA: A Web-based Research Tool for Spectral and Roughness Analysis of Sound Signals](http://musicalgorithms.ewu.edu/learnmoresra/files/vassilakis2007smc.pdf) 49 | - http://musicalgorithms.ewu.edu/algorithms/Roughness.html 50 | - http://musicalgorithms.ewu.edu/learnmoresra/moremodel.html 51 | - Kameoka, A. & Kuriyagawa, M. (1969a). Consonance theory, part I: Consonance of dyads. Journal of the Acoustical Society of America, Vol. 45, No. 6, pp. 1451-1459. 52 | - Kameoka, A. & Kuriyagawa, M. (1969b). Consonance theory, part II: Consonance of complex tones and its computation method. Journal of the Acoustical Society of America, Vol. 45, No. 6, pp. 1460-1469. 53 | - Mashinter, Keith. (1995). Discrepancies in Theories of Sensory Dissonance arising from the Models of Kameoka & Kuriyagawa, and Hutchinson & Knopoff. Undergraduate thesis submitted for the double honours degrees in Applied Mathematics and Music, University of Waterloo. 54 | - Phil Keenan ([@ingthrumath](https://twitter.com/ingthrumath)) 55 | - http://meandering-through-mathematics.blogspot.com/2015/03/consonance-and-dissonance-in-music.html 56 | - http://meandering-through-mathematics.blogspot.com/2016/06/experimenting-with-sounds-consonance.html 57 | 58 | ## Other resources 59 | 60 | - https://en.wikipedia.org/wiki/Consonance_and_dissonance 61 | - http://www.res.kutc.kansai-u.ac.jp/~cook/ 62 | - http://www.res.kutc.kansai-u.ac.jp/~cook/05%20harmony.html 63 | - http://www.res.kutc.kansai-u.ac.jp/~cook/Harmony.c 64 | - N. D. Cook, T. Fujisawa and K. Takami. This program is an improved version of the algorithm reported in Tone of Voice and Mind (Benjamins, Amsterdam, 2002). It calculates the dissonance, tension, instability and modality of chords containing 2-5 tones and assuming the presence of 1-5 upper partials. 65 | - video: [CogMIR 2013 - Norman D Cook - Visual Display of the Acoustical Properties of Harmony](https://www.youtube.com/watch?v=CrmnaiyS5EE) 66 | - http://sethares.engr.wisc.edu/consemi.html#anchor149613 67 | - http://sethares.engr.wisc.edu/forrestjava/html/TuningAndTimbre.html 68 | - http://sethares.engr.wisc.edu/comprog.html - MATLAB, C++, Lisp 69 | - https://gist.github.com/endolith/3066664 70 | - http://www.davideverotta.com/A_folders/Theory/m_dissonance.html 71 | - http://www.music-cog.ohio-state.edu/Music829B/diss.html 72 | 73 | ## Models 74 | 75 | There are several acoustic models of dissonance. They may work on different data and provide several various metrics. All of them are based on some perceptual experiments. 76 | 77 | Input signals: 78 | 79 | - two plain sinusoidal tones 80 | - two harmonic tones - integer multiples of the base frequency 81 | - chord (two or more) of harmonic tones 82 | - signals with continuous spectrum 83 | 84 | ### PlompLevelt1965 85 | 86 | They measured the perceived dissonance of pairs of sinusoidal tones and of complex tones with 6 harmonics. They provide only experimental data, not a parametric model. 87 | 88 | ### Sethares1993 89 | 90 | First, he explicitly parameterizes the data of PlompLevelt1965 (for a pair of sinusoids and for any number of complex tones) with constants found by fitting the curve to the data. 91 | 92 | Dissonance `d(x)` for the difference `x` between a pair of frequencies. This ignores the absolute frequencies. 93 | 94 | ``` 95 | d(x) = exp(-a * x) - exp(-b * x) 96 | Constants: 97 | a = 3.5 98 | b = 5.75 99 | ``` 100 | 101 | Dissonance for a pair of frequencies `(f_1, f_2)` (for f_1 < f_2) and their amplitudes `(a_1, a_2)`. This takes into account the absolute frequencies. `d_max` is just the maximum of d(x). 102 | 103 | ``` 104 | d_pair(f_1, f_2, a_1, a_2) = 105 | s = d_max / (s_1 * f_1 + s_2) 106 | x = s * (f_2 - f_1) 107 | a_1 * a_2 * (exp(-a * x) - exp(-b * x)) 108 | 109 | Constants: 110 | a = 3.5 111 | b = 5.75 112 | d_max = 0.24 113 | s_1 = 0.0207 # 0.021 in the paper 114 | s_2 = 18.96 # 19 in the paper 115 | ``` 116 | 117 | Expressed in terms of `d(x)`: 118 | 119 | ``` 120 | d_pair(f_1, f_2, a_1, a_2) = a_1 * a_2 * d((f_2 - f_1) * d_max / (s_1 * f_1 + s_2)) 121 | ``` 122 | 123 | Model of dissonance of a single complex tone (containing various partials) is just the sum of dissonances for all pairs of partials. In case the partials are integer multiples of a base frequency we can call tone "harmonic", otherwise just a general "timbre". 124 | 125 | ``` 126 | freqs = (f_1, f_2, ... f_n) 127 | amps = (a_1, a_2, ... a_n) 128 | d_complex(freqs, amps) = 0.5 * sum([d(f_i, f_j, a_i, a_j) for i in range(n) for j in range(n)]) 129 | 130 | or equally: 131 | 132 | d_complex(freqs, amps) = sum([ 133 | d(freqs[i], freqs[j], amps[i], amps[j]) 134 | for i in range(n) 135 | for j in range(n) 136 | if i < j]) 137 | ``` 138 | 139 | Then we can model dissonance of a pair of complex tones (timbres). Basically it's still a sum of dissonances of all pairs of partials. We can however express `freqs_2 = alpha * freqs_1` and plot `d_complex()` for fixed `freqs_1` and varying `alpha` to get a "dissonance curve". 140 | 141 | Note that this model can be used to model dissonance of intervals of complex tones, as well as chords (any number of tones). 142 | 143 | Note the constants `s_1`, `s_2` are defined in the paper with low precision. Better precision is provided in the code by Mr. Sethares: http://sethares.engr.wisc.edu/comprog.html. 144 | 145 | #### Questions? 146 | 147 | Why we just sum the dissonance and not compute mean? Which aggregation operation makes more sense? 148 | 149 | ### Vassilakis2001 150 | 151 | There's a modification to the `d_pair()` function which should make it depend more reliably on "SPL" and "AF-degree", in particular put more accent on the relative amplitudes of interfering sinusoids rather than on thir absolute amplitudes. 152 | 153 | Defined in Eq.(6.23) on page 197 (219 in the PDF). 154 | 155 | ``` 156 | a_2 should be >= a_1 157 | d_pair(f_1, f_2, a_1, a_2) = 158 | (a_1 * a_2) ^ 0.1 * 0.5 * 159 | ((2 * a_2) / (a_1 + a_2)) ^ 3.11 * 160 | (exp(-a * s * (f_2 - f_1)) - exp(-b * s * (f_2 - f_1))) 161 | 162 | Where: 163 | spl = a_1 * a_2 164 | af_degree = (2 * a_2) / (a_1 + a_2) 165 | a = 3.5 166 | b = 5.75 167 | d_max = 0.24 168 | s_1 = 0.0207 169 | s_2 = 18.96 170 | 171 | It can be expressed in terms of d(x): 172 | 173 | d_pair(f_1, f_2, a_1, a_2) = 174 | spl = a_1 * a_2 175 | af_degree = (2 * a_2) / (a_1 + a_2) 176 | s = d_max / (s_1 * f_1 + s_2) 177 | x = s * (f_2 - f_1) 178 | spl ^ 0.1 * 0.5 * af_degree ^ 3.11 * d(x) 179 | ``` 180 | 181 | In comparison in the Sethares1993 model it is: 182 | 183 | ``` 184 | d_pair(f_1, f_2, a_1, a_2) = 185 | spl = a_1 * a_2 186 | s = d_max / (s_1 * f_1 + s_2) 187 | x = (f_2 - f_1) * x 188 | spl * d(x) 189 | ``` 190 | 191 | Note that in order to handle the case if a_1 < a_2 we could define: 192 | 193 | ``` 194 | af_degree(a_1, a_2) = (2 * min(a_1, a_2)) / (a_1 + a_2) 195 | ``` 196 | 197 | Looking at Vassilakis2010 this is exactly what they do, extending Vassilakis2001, otherwise the model is the same. 198 | 199 | Extending this to a set of complex tones is the same as in Sethares1993 - just aggregate `d_pair()` for all pairs via a sum. 200 | 201 | Note there's an additional `0.5` factor in the Vassilakis2001 model compared to Sethares1993. IMHO the meaning is to compensate for pairs of partials being summed twice. In order to make the models comparable we should remove this term and take only pairs of partials only once in all models. 202 | 203 | ### Cook2002 204 | 205 | Dissonance model (Cook2002, Appendix 2, page 276 and on, eg. A2-1): 206 | 207 | ``` 208 | Original - wrong: 209 | d_pair(x, a_1, a_2) = 210 | mu_a = (a_1 + a_2) / 2 211 | mu_a * (exp(-a * x) - exp(-b * x)) 212 | 213 | Fixed: 214 | d_pair(x, a_1, a_2) = 215 | mu_a = (a_1 + a_2) / 2 216 | mu_a * c * (exp(-a * x) - exp(-b * x)) 217 | 218 | Where: 219 | mu_a ... mean amplitude, within [0.0; 1.0] 220 | x ... interval between the frequencies (in semitones) 221 | a = 1.2 222 | b = 4.0 223 | c = 3.5351 = 1 / (np.exp(-1.2) - np.exp(-4)) 224 | ``` 225 | 226 | Parameters were "chosen to give a maximal dissonance value at roughly one quartertone, a dissonance of 1.00 at an interval of one semitone, and smaller values for larger intervals". 227 | 228 | The constants `a` and `b` are quite different than in the Sethares1993 model. Also the amplitudes are aggregated by mean, instead of product or minimum. The constant `c` is missing in the paper but needs to be on the place as in the formula above. 229 | 230 | ### Cook2006 231 | 232 | Other metrics: dissonance, tension, modality, tonal instability. Based on three tones, not just an interval of two. 233 | 234 | Dissonance model (Cook2006, eq. 3). 235 | 236 | Note that in the article it's actually defined in a wrong way: 237 | - there's one more minus sign when applying `beta_1`, `beta_2` 238 | - logarithm should be of base 2 239 | - `log2(f_2 / f_1)` needs to be multiplied by 12 to get the 12-TET semitone interval 240 | - there should be bracket, not floor around the difference of exponentials (probably a printing error) 241 | 242 | Note: for the sake of readability we replace here original greek `nu` with `v`. 243 | 244 | ``` 245 | Original - wrong: 246 | d_pair(f_1, f_2, a_1, a_2) = 247 | x = log(f_2 / f_1) 248 | v = a_1 * a_2 249 | v * beta_3 * 250 | floor( 251 | exp(-beta_1 * x ^ gamma) - 252 | exp(-beta_2 * x ^ gamma)) 253 | 254 | Fixed: 255 | d_pair(f_1, f_2, a_1, a_2) = 256 | x = 12 * log2(f_2 / f_1) 257 | v = a_1 * a_2 258 | v * beta_3 * 259 | (exp(beta_1 * x ^ gamma) - 260 | exp(beta_2 * x ^ gamma)) 261 | 262 | Constants: 263 | beta_1 = -0.8 # "interval of maximal dissonance" 264 | beta_2 = -1.6 # "steepness of the fall from maximal dissonance" 265 | beta_3 = 4.0 266 | gamma = 1.25 267 | ``` 268 | 269 | Total dissonance of a chords is the sum of dissonances of all pairs of partials. 270 | 271 | ### Cook2009 272 | 273 | Simplified version of Cook2006. Still the definition is wrong (see above). 274 | 275 | There's no gamma exponent. 276 | 277 | ``` 278 | Original - wrong: 279 | d_pair(f_1, f_2, a_1, a_2) = 280 | x = log(f_2 / f_1) 281 | v = a_1 * a_2 282 | v * beta_3 * 283 | (exp(-beta_1 * x) - 284 | exp(-beta_2 * x)) 285 | 286 | Fixed: 287 | d_pair(f_1, f_2, a_1, a_2) = 288 | x = 12 * log2(f_2 / f_1) 289 | v = a_1 * a_2 290 | v * beta_3 * 291 | (exp(beta_1 * x) - 292 | exp(beta_2 * x)) 293 | 294 | Constants: 295 | beta_1 = -0.8 # "interval of maximal dissonance" 296 | beta_2 = -1.6 # "steepness of the fall from maximal dissonance" 297 | beta_3 = 4.0 298 | ``` 299 | 300 | Tension of a triad (not implemented yet): 301 | 302 | ``` 303 | tension(f_1, f_2, f_3) = 304 | x = log(f_2 / f_1) 305 | y = log(f_3 / f_2) 306 | v = a_1 * a_2 * a_3 307 | v * exp(-((y - x) / alpha)^2) 308 | 309 | alpha = ~0.6 310 | ``` 311 | --------------------------------------------------------------------------------