xenharmonic-devs / sonic-weave

The SonicWeave DSL for manipulating musical frequencies, ratios and equal temperaments
MIT License
4 stars 4 forks source link

Add an option to solve an algebraic equation #107

Open inthar-raven opened 5 months ago

inthar-raven commented 5 months ago

One use case would be getting a generator value as the frequency ratio that is an exact solution for a delta signature constraint for a given chord in a MOS. A delta is the frequency difference between adjacent notes of a chord, relative to a fixed root of the dyad. This value for the generator is given by a polynomial equation which turns out not to depend on the frequency of the root.

For instance, say we want the 3-step generator g (as a frequency ratio) for the 5L3s tuning that makes the chord P0s-m2s-P5s-M6s [as a tuple of frequency ratios relative to the root, (1, 2g^-2, 2g^-1, g^2]] into an exact +1+?+1 chord; the delta signature notation stipulates that the middle delta is free to vary but the outer two deltas must be equal (have the ratio 1:1 in Hz values). Then the generator is given by a solution to the equation (g^2 - 2g^-1)/(2g^-2 - 1) = 1 or equivalently, g^4 - 2g + g^2 - 2 = 0. This equation has a unique solution g = 1.3057 ~= 461.79 cents in the set of reals greater than 1.

For more information, see https://en.xen.wiki/w/Delta-rational_chord.

frostburn commented 5 months ago

I have a few questions. Currently SonicWeave has radicals as elementary objects i.e. 3^(1/5) is always exact. a1) Are you requesting a new elementary type similar to Root objects in some computer algebra systems? a2) If a1, what syntax would such objects correspond to? b1) Is this just about implementing a builtin algebraic solver that produces real numbers as output. b2) If b1, what kind of parameters should this solver function take?

inthar-raven commented 5 months ago

The choice between a1) and b1) is just an implementation detail as far as I'm concerned, and you should do whatever is the most efficient. The syntax could be:

solve [P(x)/Q(x) = c (P and Q polynomials, c a constant)] [domain as a list of inequalities, say "x > 1, x < sqrt(2)"].

frostburn commented 5 months ago

The choice between a1) and b1) is just an implementation detail as far as I'm concerned, and you should do whatever is the most efficient. The syntax could be:

solve [P(x)/Q(x) = c (P and Q polynomials, c a constant)] [domain as a list of inequalities, say "x > 1, x < sqrt(2)"].

That would require a whole new grammar inside the solve call or implementation of symbol types. I'd rather stick with value types for now...

Anyway, marking this as out of scope because writing a stable algebraic solver is hard and the existing solutions come with megabytes of bloat.

frostburn commented 3 months ago

Now that non-radical real quantities have a more established role in the runtime it could make sense to implement basic Newton's method for solving the zeros of non-linear functions given a starting guess.

 const f = (x) => 64 * x^6 - 112 * x^4 + 56 * x^2 - 7;
 const sinPiOver7 = newton(f, 0.433r, 100);

where newton takes a function to optimize as the first argument, the initial guess for the root as the second argument and an optional number of iterations as the last argument.

Technically we have access to the AST node of the function which could be translated to a floating point function for some speedup. This is error-prone and still an order of magnitude slower than a pure javascript program unless there's some trick to convince the JIT to compile a dynamically constructed piece of code. That in turn is a potential security hole where an attacker could get access to the JS runtime if we're not super careful about how the AST is translated to executable JS code.

frostburn commented 3 months ago

No wait. Newton requires the derivative as well. Automatic differentiation is out of scope so Newton becomes unwieldy.

A bisection search algorithm instead?