User:Arseniiv/Fitting: Difference between revisions
fitting to an arbitrary pattern of steps |
m →Code: a link to a gist |
||
(2 intermediate revisions by the same user not shown) | |||
Line 25: | Line 25: | ||
If we have a pattern of steps in mind, say sLMLsLMsML, we can try fitting a scale to a scale of this form by writing an error term for each note in the period of the new scale. These terms will be integer linear combinations of our chosen step sizes <math>s, M, L</math>, an overal scale shift <math>d</math> (see [[#To edX]]) and numbers (cent values of intervals). | If we have a pattern of steps in mind, say sLMLsLMsML, we can try fitting a scale to a scale of this form by writing an error term for each note in the period of the new scale. These terms will be integer linear combinations of our chosen step sizes <math>s, M, L</math>, an overal scale shift <math>d</math> (see [[#To edX]]) and numbers (cent values of intervals). | ||
Having all those errors in symbolic form, it’s particularly easy to minimize RMS error constrained by all steps of the scale constituting exactly the period of the old scale, using Lagrange multiplier method. We end up with a linear system comprising of equations in step sizes, <math>d</math> and a single Lagrange multiplier related to the periodicity constraint, which is then solved to get the optimal step sizes. All these computations can be done using <code>sympy</code> Python package. In the code below I also use <code> | Having all those errors in symbolic form, it’s particularly easy to minimize RMS error constrained by all steps of the scale constituting exactly the period of the old scale, using Lagrange multiplier method. We end up with a linear system comprising of equations in step sizes, <math>d</math> and a single Lagrange multiplier related to the periodicity constraint, which is then solved to get the optimal step sizes. All these computations can be done using <code>sympy</code> Python package. In the code below I also use <code>more_itertools</code> to make my life a tad easier. | ||
==== Code ==== | ==== Code ==== | ||
Should work in Python 3.12 and several previous versions. Install fresh <code> | ''NB. This code may later be lagging behind [https://gist.github.com/arseniiv/ab128927adc3955867c1cc1bfe6b4cd7 the version at Gist]. The version there is a self-contained module with an example like one in this article.'' | ||
Should work in Python 3.12 and several previous versions. Install fresh <code>more_itertools</code> and <code>sympy</code>. | |||
<pre> | <pre> | ||
Line 40: | Line 42: | ||
import sympy as sp | import sympy as sp | ||
def | def _cents(ratio: float | str) -> float: | ||
if isinstance(ratio, str): | if isinstance(ratio, str): | ||
ratio = float(Fraction(ratio)) | ratio = float(Fraction(ratio)) | ||
Line 48: | Line 50: | ||
"""Make a scale of pitches in cents from intervals delimited by spaces.""" | """Make a scale of pitches in cents from intervals delimited by spaces.""" | ||
return tuple(map( | return tuple(map(_cents, intervals.split())) | ||
def match_pattern(scale: Sequence[float], pattern: str) -> dict[str, float] | None: | def match_pattern(scale: Sequence[float], pattern: str) -> dict[str, float] | None: | ||
Line 88: | Line 90: | ||
note_errors = [vars_[SHIFT]] | note_errors = [vars_[SHIFT]] | ||
sym_pitches = [sp.sympify(0)] | sym_pitches = [sp.sympify(0)] | ||
for letter, pitch in zip(pattern, | for letter, pitch in zip(pattern, scale): | ||
last_pitch = sym_pitches[-1] + vars_[letter] | last_pitch = sym_pitches[-1] + vars_[letter] | ||
sym_pitches.append(last_pitch) | sym_pitches.append(last_pitch) | ||
Line 96: | Line 98: | ||
note_errors.pop() | note_errors.pop() | ||
sym_pitches.pop(0) | sym_pitches.pop(0) | ||
assert len(note_errors) == len( | assert len(note_errors) == len(scale) | ||
error_sqr_sum = sum(error ** 2 for error in note_errors) | error_sqr_sum = sum(error ** 2 for error in note_errors) | ||
Line 170: | Line 172: | ||
print('\n'.join(map(str, fit.pitches))) | print('\n'.join(map(str, fit.pitches))) | ||
</pre> | </pre> | ||
== Notes == | == Notes == | ||
<references /> | <references /> |