Godtone (talk | contribs)
Favourite EDOs: MORE FAVOURITE EDOS!!! (long overdue)
Godtone (talk | contribs)
 
(41 intermediate revisions by the same user not shown)
Line 2: Line 2:
<br/>
<br/>


=== Simple ratios and where I think limits should be drawn ===
== [[User:Godtone/Bird's eye view of temperaments by accuracy|Bird's eye view of temperaments by accuracy]] (W.I.P) ==
If you want to contribute make sure the temperaments make sure to follow the format, such as specifying the exact note count needed for a set of odds separately from the smallest MOS scale that has at least or more than that number of notes. The focus is on practically-useful temperaments of general interest. Many temperaments have not yet been documented there. It's supposed to be a compilation of "cream of the crop" basically, sorted by accuracy so that you can determine your own accuracy-to-simplicity tradeoff that is best for you, but note many higher-accuracy temperaments there are simple ''despite'' being accurate, so I recommend looking at the note counts and set of targetted odds for higher-accuracy stuff first.
 
== zeta and optimal_edo_sequence ==
On a separate subpage I've shown the best scoring equal temperaments according to the zeta function, using a small modification to the code kindly provided at [[User:Sintel/Zeta plot python]]:
* [[User:Godtone/zeta]]
Whose significance is backed up by the more psychoacoustically-informed tuning metrics I've designed (<code>optimal_edo_sequence</code>s from [[#My Python 3 code]]):
* [[User:Godtone/optimal edo sequences]] (and [[User:Godtone/strict_optimal_edo_sequences]])
...as well as by my own knowledge of tuning theory.
 
== Simple ratios and where I think limits should be drawn ==
This is maybe the obvious place to start. I listen to a variety of dyads in order to judge and try to absorb their qualities and to figure out if and why I like them. My opinions of intervals have changed over time. Anyway, as all positive rationals are ratios of positive naturals (nonzero everyday numbers), I think superparticular intervals are a good place to start. I think the melodic Just Noticeable Difference is important here so that intervals have a reasonable chance at being singable, even if the harmonic JND is significantly lower (partly depending on timbre). For me a reasonable upper limit on the melodic JND is about 11 cents as more than that and I hear something as pretty definitively mistuned, although that doesn't necessarily imply unusabibility as an approximation in a low-complexity system (one with a small amount of average tones per octave). This means that in the series of superparticular intervals (of the form (n+1)/n), the first two that are too close in size to be comfortably distinguished are 14/13 and 13/12, whose difference is 169/168 or about 10.274c.
This is maybe the obvious place to start. I listen to a variety of dyads in order to judge and try to absorb their qualities and to figure out if and why I like them. My opinions of intervals have changed over time. Anyway, as all positive rationals are ratios of positive naturals (nonzero everyday numbers), I think superparticular intervals are a good place to start. I think the melodic Just Noticeable Difference is important here so that intervals have a reasonable chance at being singable, even if the harmonic JND is significantly lower (partly depending on timbre). For me a reasonable upper limit on the melodic JND is about 11 cents as more than that and I hear something as pretty definitively mistuned, although that doesn't necessarily imply unusabibility as an approximation in a low-complexity system (one with a small amount of average tones per octave). This means that in the series of superparticular intervals (of the form (n+1)/n), the first two that are too close in size to be comfortably distinguished are 14/13 and 13/12, whose difference is 169/168 or about 10.274c.
I also think that powers of 2 in the denominator of an interval, broadly/generally speaking, helps the interval feel less disorienting due to a stronger suggestion of the fundamental, so beyond 13/12, for a bit, the superparticulars of the form (2n+1)/(2n) should be prioritised. This concludes at the following superparticular intervals being of particular (no pun intended) importance to a 'general melodic semi-harmonic system':<br/>
I also think that powers of 2 in the denominator of an interval, broadly/generally speaking, helps the interval feel less disorienting due to a stronger suggestion of the fundamental, so beyond 13/12, for a bit, the superparticulars of the form (2n+1)/(2n) should be prioritised. This concludes at the following superparticular intervals being of particular (no pun intended) importance to a 'general melodic semi-harmonic system':<br/>
Line 95: Line 105:
* 31: The next EDO that melodically approximates the 11-limit, and considerably better. Extremely nice arrangement of intervals that feels weirdly intuitive and ideal. Colourful EDO. Basically ideal meantone tuning as more notes than this is overkill for meantone if you don't specifically want meantone.
* 31: The next EDO that melodically approximates the 11-limit, and considerably better. Extremely nice arrangement of intervals that feels weirdly intuitive and ideal. Colourful EDO. Basically ideal meantone tuning as more notes than this is overkill for meantone if you don't specifically want meantone.
* 32: 16 EDO with a sharp fifth. I like it primarily because of it being a power of 2. Exploration into this EDO could be interesting. 80 EDO offers a reasonably good approximation of it through a 16L16s MOSS.
* 32: 16 EDO with a sharp fifth. I like it primarily because of it being a power of 2. Exploration into this EDO could be interesting. 80 EDO offers a reasonably good approximation of it through a 16L16s MOSS.
* 34: The first good approximation of the 5-prime-limit due to being the first reasonably accurate tuning of [[Kleismic family|kleismic]] and [[srutal archagall]] which are IMO the best 5-limit temperaments that observe the [[81/80|syntonic comma]]. 19 is also a tuning for kleismic but feels like it doesn't do justice to the accuracy and pristineness of kleismic to me, plus its harmonic interpretation is pretty lacking. And 22 is very obviously to Has the sharp 3/2's of 17 EDO, and as 17 EDO is a good colour system, 34 EDO is a natural extension. Also is a very logical "completion" of 17 due to giving a very logical 2.3.5.13.17(.23)-subgroup interpretation of the sqrt(2).sqrt(3) subgroup with some really intriguing possibilities. If you're lacking in inspiration and its wide array of supported MOSSes aren't inspiration enough, try taking a look at the diaschismic-tetracot continuum ([[2048/2025]])<sup>n</sup> / ([[20000/19683]]).
* 34: The first good approximation of the 5-prime-limit due to being the first reasonably accurate tuning of [[Kleismic family|kleismic]] and [[srutal archagall]] which are IMO the best 5-limit temperaments that observe the [[81/80|syntonic comma]]. 19 is also a tuning for kleismic but feels like it doesn't do justice to the accuracy and pristineness of kleismic to me, plus its harmonic interpretation is pretty lacking. And 22 is very obviously too high-damage for srutal archagall. Has the sharp 3/2's of 17 EDO, and as 17 EDO is a good colour system, 34 EDO is a natural extension. Also is a very logical "completion" of 17 due to giving a very logical 2.3.5.13.17(.23)-subgroup interpretation of the sqrt(2).sqrt(3) subgroup with some really intriguing possibilities. If you're lacking in inspiration and its wide array of supported MOSSes aren't inspiration enough, try taking a look at the diaschismic-tetracot continuum ([[2048/2025]])<sup>n</sup> / ([[20000/19683]]).
* 35: A subset of 140 EDO, a ridiculously strong generalist system, which endows it with magical tuning qualities.
* 35: A subset of 140 EDO, a ridiculously strong generalist system, which endows it with magical tuning qualities.
* 36: Because of being a superset of 12, quite overlooked. It is actually a very good subgroup temperament! A natural extension of 12 EDO's colour palette, preferring to avoid the neutral and semi- intervals of 24 EDO. I should note though that while both 24 and 36 are reasonably good systems, I do not think they should be used together, as there are preferable EDOs in the high end range, such as 80 EDO.
* 36: Because of being a superset of 12, quite overlooked. It is actually a very good subgroup temperament! A natural extension of 12 EDO's colour palette, preferring to avoid the neutral and semi- intervals of 24 EDO. I should note though that while both 24 and 36 are reasonably good systems, I do not think they should be used together, as there are preferable EDOs in the high end range, such as 80 EDO.
Line 204: Line 214:


==== Generalised colours of supertonics, subtonics, leads and contraleads ====
==== Generalised colours of supertonics, subtonics, leads and contraleads ====
Note: This scheme is a bit idiosyncratic and is instead intuitive in nature; specifically, a more systematic view would usually consider 11/10 as a submajor second, and 10/9 as a major second, but here I interpreted 11/10 instead as a sharp flavour of neutral.
<pre>
<pre>
inframinor (AKA 'ultraminor') second aka one quarter-tone: (many things)
inframinor (AKA 'ultraminor') second aka one quarter-tone: (many things)
Line 274: Line 285:
...
...
</pre>
</pre>
==== 87 EDO interval colours/types (seconds and thirds) ====
<pre>
<pre>
80 EDO MOSS gens table:
41.4c fifth-tone
 
55.2c ultraminor (AKA quarter-tone)
(1\10: 120c)
69.0c subminor (AKA third-tone)
(~1\9: 135c (~~8L1s))
82.8c neominor
(1\8: 150c)
96.6c novaminor
7L1s: 165c
110.3c minor
6L1s: 180c (7L6s) (note 195c (6L7s) is extreme with L/s=6.5)
124.1c supraminor
5L1s: 210c (6L5s), 225c (5L6s)
137.9c subneutral (AKA minor neutral)
(1\5: 240c)
151.7c superneutral (AKA major neutral)
5L4s: 255c
165.5c submajor
4L5s: 270c, 285c
179.3c major
(1\4: 300c)
193.1c novamajor
4L7s: 315c
206.9c neomajor
(~~1\11: 330c (~7L4s))
220.7c supermajor
7L3s: 345c
234.5c ultramajor
3L4s: 360c
248.3c semifourth
3L7s: 375c, 390c
262.1c ultraminor
3L8s: 405c, 420c, 435c (all also ofc 3L5s; biased to 435c)
275.9c subminor
(3\8: 450c)
289.7c neominor
5L3s: 465c
303.4c novaminor
(2\5: 480c)
317.2c minor
5L2s: 495c, 510c
331.0c supraminor
7L2s: 525c
344.8c subneutral
2L7s: 540c, 555c, 570c, 585c (all also ofc 2L5s; biased to 540c)
358.6c superneutral
</pre>
372.4c submajor
 
386.2c major
==== RINGER 80 ====
400.0c novamajor
80 EDO is a great no-limit system for conceptualising and internalising harmonic series interval categories/structures through '''RINGER 80''' which contains the entirety of the no-127's no-135's no-141's 145-odd-limit and in which ~84.44% of all intervals present are mapped consistently. This '''RINGER 80''' uses the best-performing val for 125-odd-limit consistency by various metrics (squared error, sum of error, number of inconsistencies, number of inconsistencies if we require <25% error, etc.). To achieve the CS ([[constant structure]]) property, the primes '''31, 47, 53, 61, 67, 73, 79, 107, 109''' are sharpened by 1 step compared to their flat patent val mapping (AKA are mapped to their second-best mapping); all other primes are patent val. This scale has a few remarkable properties. Firstly, all the intervals that are not inconsistent are mapped - at worst - to their second-best mapping, meaning you will never have a categorical/interval mapping exceeding 15 cents (which seems like a very reasonable result for a RINGER scale). Secondly, all of the "filler harmonics" beyond the 125-odd-limit fit in an obvious way; note how there are no warts beyond the 113-prime-limit (which the 125-odd-limit corresponds to due to a '''record prime gap''' from 113 to 127) meaning all composite harmonics were either already part of the 113-prime-limit or if prime the primes were patent val and sharp-tending. "In an obvious way" also means that every superparticular '''(n+1)/n''' in the 125-odd-limit that was mapped to 2 steps is split into '''(2n+2)/(2n+1)''' and '''(2n+1)/(2n)''', retaining the lowest possible complexity. Finally, note that while composite odd harmonics start going missing after the 125th harmonic, that prime harmonics are very much not lacking. This scale exists inside the no-127's no-151's no-163's 179-prime-limit, meaning that '''all primes up to and including 179''' are present excepting those three, making it full of prime flavour (on top of its high compositeness due to the 125-odd-limit corresponding to a record-prime-gap). My only personal qualm with this scale is that prime 73 is not patent val when I'd like it to be, but keeping it warted allows the introduction of the 145th harmonic which adds a lot of low-complexity and consistent intervals, and not warting it means no longer using the best-performing mapping and using the "147.2th" harmonic instead of the much-preferable "145th". Specifically, all intervals made in ratio with the 145th harmonic that simplify are mapped consistently. Furthermore, warting prime 73 means that 73/63 is mapped correctly as an extremely accurate approximation of the 255c semifourth (80 EDO is a circle of 73/63's). Finally, its worth noting that I used [[#My_Python_3_code]] to find and hone this scale.
413.8c neomajor
427.6c supermajor
441.4c ultramajor
455.2c semisixth
</pre>
== Miscellaneous ==
==== 80 EDO MOSS gens table ====
<pre>
<pre>
mode 63 of the harmonic series (corresponding to 125-odd-limit) with added odds from mode 63*2=126 in square brackets:
80 EDO MOSS gens table:
63:64:[129]:65:[131]:66:[133]:67:68:[137]:69:[139]:70:71:[143]:72:[145]:73:74:[149]
75:76:[153]:77:78:[157]:79:80:[161]:81:82:83:[167]:84:85:86:[173]:87:88:89
[179]:90:91:92:[185]:93:94:95:96:97:[195]:98:99:100:101:102:103:104:[209]:105
106:107:108:109:110:111:112:113:114:115:116:117:118:119:120:121:122:123:124:125(:126)
(the above is split into 20 harmonics per line AKA ~300c worth of harmonic content)


in lowest terms as a /105 scale (corresponding to a primodal /107, /109 or /113 first-octave scale or to a primodal /53 or /59 second-octave scale or even to a primodal /29 third-octave scale or /7 fifth-octave scale):
(1\10: 120c)
105:106:107:108:109:110:111:112:113:114:115:116:117:118:119:120:121:122:123:124:125:126:128:129:130:131:132:133:134:136:137:138:139:140:142:143:144:145:146:148:149:150:152:153:154:156:157:158:160:161:162:164:166:167:168:170:172:173:174:176:178:179:180:182:184:185:186:188:190:192:194:195:196:198:200:202:204:206:208:209:210
(~1\9: 135c (~~8L1s))
</pre>
(1\8: 150c)
 
7L1s: 165c
==== 87 EDO interval colours/types (seconds and thirds) ====
6L1s: 180c (7L6s) (note 195c (6L7s) is extreme with L/s=6.5)
<pre>
5L1s: 210c (6L5s), 225c (5L6s)
41.4c fifth-tone
(1\5: 240c)
55.2c ultraminor (AKA quarter-tone)
5L4s: 255c
69.0c subminor (AKA third-tone)
4L5s: 270c, 285c
82.8c neominor
(1\4: 300c)
96.6c novaminor
4L7s: 315c
110.3c minor
(~~1\11: 330c (~7L4s))
124.1c supraminor
7L3s: 345c
137.9c subneutral (AKA minor neutral)
3L4s: 360c
151.7c superneutral (AKA major neutral)
3L7s: 375c, 390c
165.5c submajor
3L8s: 405c, 420c, 435c (all also ofc 3L5s; biased to 435c)
179.3c major
(3\8: 450c)
193.1c novamajor
5L3s: 465c
206.9c neomajor
(2\5: 480c)
220.7c supermajor
5L2s: 495c, 510c
234.5c ultramajor
7L2s: 525c
248.3c semifourth
2L7s: 540c, 555c, 570c, 585c (all also ofc 2L5s; biased to 540c)
262.1c ultraminor
</pre>
275.9c subminor
 
289.7c neominor
==== RINGER 80 ====
303.4c novaminor
80 EDO is a great no-limit system for conceptualising and internalising harmonic series interval categories/structures through '''RINGER 80''' which contains the entirety of the no-127's no-135's no-141's 145-odd-limit and in which ~84.44% of all intervals present are mapped consistently. This '''RINGER 80''' uses the best-performing val for 125-odd-limit consistency by various metrics (squared error, sum of error, number of inconsistencies, number of inconsistencies if we require <25% error, etc.). To achieve the CS ([[constant structure]]) property, the primes '''31, 47, 53, 61, 67, 73, 79, 107, 109''' are sharpened by 1 step compared to their flat patent val mapping (AKA are mapped to their second-best mapping); all other primes are patent val. This scale has a few remarkable properties. Firstly, all the intervals that are not inconsistent are mapped - at worst - to their second-best mapping, meaning you will never have a categorical/interval mapping exceeding 15 cents (which seems like a very reasonable result for a RINGER scale). Secondly, all of the "filler harmonics" beyond the 125-odd-limit fit in an obvious way; note how there are no warts beyond the 113-prime-limit (which the 125-odd-limit corresponds to due to a '''record prime gap''' from 113 to 127) meaning all composite harmonics were either already part of the 113-prime-limit or if prime the primes were patent val and sharp-tending. "In an obvious way" also means that every superparticular '''(n+1)/n''' in the 125-odd-limit that was mapped to 2 steps is split into '''(2n+2)/(2n+1)''' and '''(2n+1)/(2n)''', retaining the lowest possible complexity. Finally, note that while composite odd harmonics start going missing after the 125th harmonic, that prime harmonics are very much not lacking. This scale exists inside the no-127's no-151's no-163's 179-prime-limit, meaning that '''all primes up to and including 179''' are present excepting those three, making it full of prime flavour (on top of its high compositeness due to the 125-odd-limit corresponding to a record-prime-gap). My only personal qualm with this scale is that prime 73 is not patent val when I'd like it to be, but keeping it warted allows the introduction of the 145th harmonic which adds a lot of low-complexity and consistent intervals, and not warting it means no longer using the best-performing mapping and using the "147.2th" harmonic instead of the much-preferable "145th". Specifically, all intervals made in ratio with the 145th harmonic that simplify are mapped consistently. Furthermore, warting prime 73 means that 73/63 is mapped correctly as an extremely accurate approximation of the 255c semifourth (80 EDO is a circle of 73/63's). Finally, its worth noting that I used [[#My_Python_3_code]] to find and hone this scale.
317.2c minor
<pre>
331.0c supraminor
mode 63 of the harmonic series (corresponding to 125-odd-limit) with added odds from mode 63*2=126 in square brackets:
344.8c subneutral
63:64:[129]:65:[131]:66:[133]:67:68:[137]:69:[139]:70:71:[143]:72:[145]:73:74:[149]
358.6c superneutral
75:76:[153]:77:78:[157]:79:80:[161]:81:82:83:[167]:84:85:86:[173]:87:88:89
372.4c submajor
[179]:90:91:92:[185]:93:94:95:96:97:[195]:98:99:100:101:102:103:104:[209]:105
386.2c major
106:107:108:109:110:111:112:113:114:115:116:117:118:119:120:121:122:123:124:125(:126)
400.0c novamajor
(the above is split into 20 harmonics per line AKA ~300c worth of harmonic content)
413.8c neomajor
 
427.6c supermajor
in lowest terms as a /105 scale (corresponding to a primodal /107, /109 or /113 first-octave scale or to a primodal /53 or /59 second-octave scale or even to a primodal /29 third-octave scale or /7 fifth-octave scale):
441.4c ultramajor
105:106:107:108:109:110:111:112:113:114:115:116:117:118:119:120:121:122:123:124:125:126:128:129:130:131:132:133:134:136:137:138:139:140:142:143:144:145:146:148:149:150:152:153:154:156:157:158:160:161:162:164:166:167:168:170:172:173:174:176:178:179:180:182:184:185:186:188:190:192:194:195:196:198:200:202:204:206:208:209:210
455.2c semisixth
</pre>
</pre>


Line 416: Line 429:
| [[2L 7s]] || joanatonic || jo- || jo || From [[joan]] temperament.
| [[2L 7s]] || joanatonic || jo- || jo || From [[joan]] temperament.
|-
|-
| [[4L 5s]] || orwelloid || or- || or || From [[orwell]] temperament.
| [[4L&nbsp;5s]] || gramitonic || gram- || gm || From "grave minor third". Formerly "orwelloid".
|-
|-
| [[5L 4s]] || semiquartal || sequar- || seq || From ''half-fourth''.
| [[5L 4s]] || semiquartal || sequar- || seq || From ''half-fourth''.
Line 453: Line 466:


== My Python 3 code ==
== My Python 3 code ==
IMPORTANT NOTE: there seems to be a bug for subgroup mappings at the moment, pending investigation, but ideally usage of subgroups should be made far easier too:
IMPORTANT NOTE: there seems to be bugs for subgroup mappings at the moment, pending investigation, but ideally usage of subgroups should be made far easier too:
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
>>> sg = [2, 3, 7, 11, 13, 17, 19]
>>> sg = [2, 3, 7, 11, 13, 17, 19]
Line 593: Line 606:
def sub_iv(r1, r2):
def sub_iv(r1, r2):
return iv( r1[0]*r2[1] - r2[0]*r1[1], r1[1]*r2[1] )
return iv( r1[0]*r2[1] - r2[0]*r1[1], r1[1]*r2[1] )
# IMPORTANT: assume x and y are nonnegative!
def iv_greater_than(x, y):
result = div_iv(x,y)
return result[0] > result[1]
def iv_greater_or_equal(x, y):
result = div_iv(x,y)
return result[0] >= result[1]
def iv_less_than(x, y):
return not iv_greater_or_equal(x, y)
def iv_less_or_equal(x, y):
return not iv_greater_than(x, y)
def as_float(x, p=None):
def as_float(x, p=None):
if type(x)==list: # monzo (factored)
if type(x)==list: # monzo (factored)
Line 1,155: Line 1,181:
# returns [] if cs or details of earliest counterexample if not;
# returns [] if cs or details of earliest counterexample if not;
# "earliest" is defined as "search all 1-steps from beginning to end, then all 2-steps, etc."
# "earliest" is defined as "search all 1-steps from beginning to end, then all 2-steps, etc."
def cs(scale_in):
def notcs(scale_in): # the alias cs for this function is deprecated due to being named confusingly but defined for convenience/backwards-compatibility
stepmap = dict()
stepmap = dict()
scale = scale_in.copy()
scale = scale_in.copy()
Line 1,169: Line 1,195:
stepmap[interval] = j
stepmap[interval] = j
return []
return []
cs = notcs # the alias cs for this function is deprecated due to being named confusingly but defined for convenience/backwards-compatibility


def rotate(scale,n,p=(2,1)):
def rotate(scale,n,p=(2,1)):
Line 1,218: Line 1,245:
print(edo,'EDO interpretation')
print(edo,'EDO interpretation')
if not sg:
if not sg:
sg = lim(ol)
sg = lim(max(ol,max(add)))
odds = [odd for odd in range(1,ol+1,2) if in_subgroup(odd,sg) and odd not in no] + add
odds = [odd for odd in range(1,ol+1,2) if in_subgroup(odd,sg) and odd not in no] + add
sedo = 0
sedo = 0
Line 1,313: Line 1,340:
# IMPORTANT: on Jan 9 i corrected rel_err**2 * et2 to rel_err**2 * et2**2 in et_badness which optimal_edo_sequence depends on;
# IMPORTANT: on Jan 9 i corrected rel_err**2 * et2 to rel_err**2 * et2**2 in et_badness which optimal_edo_sequence depends on;
#            strict_optimal_edo_sequence is unaffected however.
#            strict_optimal_edo_sequence is unaffected however.
def et_badness(ivs,v,badness=lambda rel_err,x,et2: rel_err**2 * et2**2,weighting=lambda x: iv_complexity(x),combine='avg',et2=0):
def et_badness(ivs,v,badness=lambda rel_err,x,et2: rel_err**2 * et2**2,weighting=lambda x: iv_complexity(x),combine='avg',mapping=True,et2=0):
# if the weighting is unspecified, use the default of:
# if the weighting is unspecified, use the default of:
# weighting an interval x's (by default squared) error contribution proportional to its odd-limit complexity (iv_complexity(x));
# weighting an interval x's (by default squared) error contribution proportional to its odd-limit complexity (iv_complexity(x));
Line 1,319: Line 1,346:
# and not underweighting simpler intervals and accounting for that more complex intervals will tend to be used
# and not underweighting simpler intervals and accounting for that more complex intervals will tend to be used
# in a harmonic/chordal context which justifies their otherwise-higher effective felt error through templating
# in a harmonic/chordal context which justifies their otherwise-higher effective felt error through templating
if weighting==1 or weighting in ['unweighted','trivial','basic']:
if type(weighting)==str:
weighting = lambda x: 1
if weighting in [None,'none','unweighted','trivial','basic','constant']:
elif weighting=='sensitive':
weighting = 0
weighting = lambda x: iv_complexity(x)**2
elif weighting in ['','default','proportional']:
weighting = 1
elif weighting in ['sensitive','dyadic']:
weighting = 2
elif weighting=='simple':
weighting = -0.5 # as we may square after when doing a MSE, we use the square root
if type(weighting) in [int,float]:
weighting = lambda x,w=weighting: iv_complexity(x)**w
global ivs_cache
global ivs_cache
global ivs_int_cache
global ivs_int_cache
Line 1,366: Line 1,400:
# alternatively, feed the real number of divisions of the octave that generates your val for each val you want to compare.   
# alternatively, feed the real number of divisions of the octave that generates your val for each val you want to compare.   
global patent_vals
global patent_vals
if type(v)==int:
if type(v)==int and v:
if v in patent_vals:
if v in patent_vals:
v = patent_vals[v]
v = patent_vals[v]
Line 1,374: Line 1,408:
elif type(v)==float:
elif type(v)==float:
v = val(lim(255),ed(v))
v = val(lim(255),ed(v))
elif not mapping and not v:
pass # not using a val
elif mapping and not v:
raise(Exception('mapping=True but no val v is given'))
# deduce the number of arguments in the badness function to allow only using the first n arguments as needed
# deduce the number of arguments in the badness function to allow only using the first n arguments as needed
num_args = badness.__code__.co_argcount
num_args = badness.__code__.co_argcount
Line 1,387: Line 1,425:
et2 = 1/v[0]
et2 = 1/v[0]
# finally, return the result:
# finally, return the result:
return combine([ badness3args(abs( map_iv(v,x) - steps(x,et2) ), x, et2) * weighting(x) for x in ivs ]) / (ivs_sum_weights_cache if using_avg else 1)
if not mapping: # new path
return combine([ badness3args(abs( step_err(x,et2) ), x, et2) * weighting(x) for x in ivs ]) / (ivs_sum_weights_cache if using_avg else 1)
else:
return combine([ badness3args(abs( map_iv(v,x) - steps(x,et2) ), x, et2) * weighting(x) for x in ivs ]) / (ivs_sum_weights_cache if using_avg else 1)
# if you just give a list or set of intervals (a,b), the default behaviour is to judge the badness of an edo as:
# if you just give a list or set of intervals (a,b), the default behaviour is to judge the badness of an edo as:
# * sum of squares of errors with each interval's contribution weighted proportional to its odd-limit complexity,
# * sum of squares of errors with each interval's contribution weighted proportional to its odd-limit complexity,
Line 1,394: Line 1,435:
# IMPORTANT: on Jan 9 i corrected rel_err**2 * et2 to rel_err**2 * et2**2 in et_badness which optimal_edo_sequence depends on;
# IMPORTANT: on Jan 9 i corrected rel_err**2 * et2 to rel_err**2 * et2**2 in et_badness which optimal_edo_sequence depends on;
#            strict_optimal_edo_sequence is unaffected however.
#            strict_optimal_edo_sequence is unaffected however.
def optimal_edo_sequence(ivs_or_edo_badness,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg'):
def optimal_edo_sequence(ivs_or_edo_badness,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg',mapping=True,times_better=1+2**-29):
et_badness_judger = ivs_or_edo_badness
et_badness_judger = ivs_or_edo_badness
if type(ivs_or_edo_badness) in [int,set,list]: # user gave intervals (default et_badness)
if type(ivs_or_edo_badness) in [int,set,list]: # user gave intervals (default et_badness)
ivs = ivs_or_edo_badness
ivs = ivs_or_edo_badness
et_badness_judger = lambda edo: et_badness(ivs,edo,weighting=weighting,combine=combine)
et_badness_judger = lambda edo: et_badness(ivs,edo,weighting=weighting,combine=combine,mapping=mapping)
# else user gave et_badness manually (custom)
# else user gave et_badness manually (custom)
best_edo = et_badness_judger(1)
best_edo = et_badness_judger(1)
Line 1,404: Line 1,445:
for edo in edo_set:
for edo in edo_set:
current = et_badness_judger(edo)
current = et_badness_judger(edo)
if current < best_edo:
if current < best_edo * times_better:
best_edo = current
best_edo = current
best_edos.append(edo)
best_edos.append(edo)
Line 1,412: Line 1,453:
# this gives much sparser but also much more interesting lists.
# this gives much sparser but also much more interesting lists.
# default weighting of an interval x is proportional to its odd-limit complexity iv_complexity(x).
# default weighting of an interval x is proportional to its odd-limit complexity iv_complexity(x).
def strict_optimal_edo_sequence(ivs,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg'):
def strict_optimal_edo_sequence(ivs,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg',mapping=True):
return optimal_edo_sequence(lambda edo: et_badness(ivs,edo,lambda rel_err: rel_err**2,weighting,combine),edo_set)
return optimal_edo_sequence(lambda edo: et_badness(ivs,edo,lambda rel_err: rel_err**2,weighting,combine,mapping),edo_set)
 
# returns a set of odds in the subgroup that are sorted by their octave-reduced size
# which map to every distinct number of steps/degrees of the val given, therefore,
# dwarf(lim(p),N) is the set of odds for the dwarf scale of N EDO in the p-limit
def dwarf(sg,v):
if type(v)==int: # to be able to specify N for the patent val for N EDO
v = val( lim(max(sg)), ed(v) )
result = [0] * v[0] # implies you can have a tritave dwarf if sg and v agree on the subgroup
odd = 1
while 0 in result:
if in_subgroup(odd,sg) and result[ map_iv(v,(odd,1)) % v[0] ]==0:
result[ map_iv(v,(odd,1)) % v[0] ] = odd
odd += 2
return result
 
def scalestr(liststr):
commastrs = []
if ',' in liststr:
commastrs = [comma.strip() for comma in liststr.split(',')]
else:
commastrs = liststr.split()
return [iv( int(comma.split('/')[0]), int(comma.split('/')[1]) ) for comma in commastrs]
 
# find positive (possibly contorted) comma for a^n ~ b and by default print the result
def continuumpt(a,b,n,printcomma=1): # assumes n >= 0
comma = div_iv( (a[0]**n[0], a[1]**n[0]), (b[0]**n[1], b[1]**n[1]) )
if comma[0] < comma[1]:
comma = recip_iv(comma)
if printcomma==1:
return print(striv(comma))
elif printcomma>=2:
print('['+' '.join([ str(k) for k in fact(comma) ])+']',end='')
if printcomma==2:
return print()
elif printcomma==3:
return print(' = '+striv(comma))
else:
return comma


# note: the default can be used to get n octaves of an interval set (ivs) contained in the 1/1 to 2/1 range
def interleave(n,ivs,offset=(2,1)): # https://en.xen.wiki/w/Interleaving
totalivs = []
for i in range(n):
totalivs = totalivs + [mul_iv( *([x] + [offset]*i) ) for x in ivs if x not in totalivs]
totalivs.sort(key=lambda x: as_float(x))
return totalivs
def mediant_path(x):
x = convert(x,tuple)
x = iv(x[0], x[1])
bottom, middle, top = (0, 1), (1, 1), (1, 0)
result = ''
while x != middle:
if iv_less_than(x, middle):
result += 'D'
top = middle
else:
result += 'U'
bottom = middle
middle = (bottom[0] + top[0], bottom[1] + top[1])
return result
# the length of the mediant path of whichever octave-revoicing gives the interval x the least length
def mediant_complexity(x,revoicing_octs=7):
mincomplexity = 2**30
for octs in range(revoicing_octs+1):
mincomplexity = min(mincomplexity,len(mediant_path( div_iv(x,(2**octs,1)) )))
mincomplexity = min(mincomplexity,len(mediant_path( mul_iv(x,(2**octs,1)) )))
return mincomplexity
# showmode: minimum number of equated interval pairs (incl. distinct octave-complements).
# if minimum isnt reached, only the spacing interval is shown. 0 means dont print anything.
# for odd-limits, to look for "multiple indistinction commas", use 3 for showmode.
def spaces_in_set(ivs,max_cents = 100,showmode = 1):
spaces = dict()
if type(max_cents) in [tuple,float,int]:
max_cents = steps(max_cents,ed(1200))
for lower in ivs:
for upper in ivs:
diff = div_iv(upper,lower)
if 0 < steps(diff,ed(1200)) <= max_cents:
if diff not in spaces:
spaces[diff] = [(upper,lower)]
else:
spaces[diff].append((upper,lower))
commas = [comma for comma in spaces]
commas.sort(key=lambda x: steps(x,1))
if showmode:
for comma in commas:
print(striv(comma)+':',', '.join([
'('+striv(ivpair[0])+')/('+striv(ivpair[1])+')' for ivpair in spaces[comma]
if len(spaces[comma])>=showmode
]))
print()
return spaces
# there is only finitely many EDOs which provide some simplification of a set of intervals as contrasted to all-distinct
def efficient_edos( n, inconsistencies=0, min_simplifications=1, edos=range(1,1000) ):
if type(n)==int:
n = odd_lim(n)
elif type(n)==list and type(n[0])==int:
n = odd_lim(1,[],n)
results = []
for edo in edos:
v = edo
if type(v)==int:
v = val( lim(max([ prime_idx(len(fact(x))-1) for x in n ])), ed(edo) )
# else v is assumed to be a mapping
m = dict()
for x in n: # collect mappings of intervals
sedo = map_iv(v,x)
if sedo in m:
m[sedo].append(x)
else:
m[sedo] = [x]
if len(inconsistent_ivs_by_val(n,v)) <= inconsistencies:
if len(n) - len([ sedo for sedo in m ]) >= min_simplifications:
results.append(edo)
return results
</syntaxhighlight>
</syntaxhighlight>


Line 1,474: Line 1,634:
[[311edo]] gets 0 wrong in the 27-odd-limit
[[311edo]] gets 0 wrong in the 27-odd-limit


== Novation Launchpad isomorphic keyboard code ==
== Novation Launchpad isomorphic keyboard code ==
The code and usage/debugging instructions are the same as for the Novation Launchpad Pro MK3, so see [[#Novation Launchpad Pro MK3 isomorphic keyboard code]].
The code and usage/debugging instructions are the same as for the Novation Launchpad Pro MK3, so see [[#Novation Launchpad Pro MK3 isomorphic keyboard code]].
 
 
Note that in theory, any Launchpad with programmer mode should work, so any MK3 model such as the Launchpad X and the cheaper (but not velocity-sensitive) mini version of that should work.
Note that any Launchpad with programmer mode should work, so any MK3 model such as the Launchpad X and the cheaper (but not velocity-sensitive) mini version of that should work. The Launchpad X has successfully been used as an isomorphic keyboard with this code, but currently requires manually specifying the MIDI ports; how to do this is explained below.
 
 
A launchpad X is potentially recommendable as having more comfortable/similar side/control buttons so that you get what feels more like a proper 9x9 grid (also due to being less costly), as the Pro MK3 has hard side-buttons that require significantly more pressure, though in both cases note that these buttons do not have pressure sensitivity so depend on the default velocity outputted by the program.
A launchpad X is potentially recommendable as having more comfortable/similar side/control buttons so that you get what feels more like a proper 9x9 grid (also due to being less costly), as the Pro MK3 has hard side-buttons that require significantly more pressure, though in both cases note that these buttons do not have pressure sensitivity so depend on the default velocity outputted by the program.
 
(More generally, Launchpads are '''''very''''' affordable xen MIDI instruments, nearly optimal for xen in that (with the help of the code below) they provide a fully customisable isomorphic MIDI keyboard with custom highlighting options. Also note that the tuning itself is not specified by the program; only the layout, though for the aforementioned large tunings the code will give you a .scl file to use, with all such .scl files using the same MIDI mapping, but requiring the base MIDI note/reference being 0.)
 
== Novation Launchpad Pro MK3 isomorphic keyboard code ==
This code is licensed under the AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html), a version of the [[wikipedia:Affero General Public License|AGPL]] corresponding to the [[wikipedia:GNU General Public License#Version 3|GPLv3]].
 
'''It should be compatible with any Launchpad that has [[#Putting the Launchpad in programmer mode|programmer mode]]; it has been confirmed to work for the Launchpad X so will likely also work for a Launchpad Mini MK3.'''
 
ALSO: This should go without saying but take care of your launchpad! If you wipe it don't expect it to be properly responsive until fully dry and even then wiping it can have risks if the liquid goes inside. Similarly, do not bang it and probably don't leave it upside down (especially if for long periods of time) in case it causes the velocity sensitivity to go weird. Treat it as an instrument. If you do all this, it should be fine to use it for its intended purpose: just don't press ''too'' hard; try not press with more velocity than the buttons actually require, if possible. (Because of my silliness (not heeding such obvious procedures) there is a possibility that I may have weared my launchpad slightly in that I think I now require more pressure for the pressure-sensitive pads. I thought it responsible to add this note about taking care of your launchpad, as we are technically using it in a nonstandard way by treating the buttons as though they were just insensitive pads that require extra velocity/pressure.)
 
IMPORTANT: for the Python 3 code to work, it requires the launchpad to be plugged in with updated firmware and be in [[#Putting the Launchpad in programmer mode|programmer mode]]! Also requires [https://www.tobias-erichsen.de/software/loopmidi.html loopMIDI] on Windows.
 
=== Installation issues/troubleshooting strategies/recommendations ===
The most potentially difficult part is that depending on what OS you are using, how you have Python installed, what version, etc. there can be complications so that running <code>pip install rtmidi</code> and <code>pip install mido</code> in a terminal does not work, or only appears to work so that there's issues when the code tries to use mido. I list potential issues below, so please read carefully for something applicable to your case if it isn't going smoothly.
 
'''IMPORTANT: A strange error has been found: Python 3.14 and later versions have removed a functionality used by mido internally, so downgrading to Python 3.12 is required if nothing here works.'''
 
'''IMPORTANT:''' Before deeming an installation of mido/rtmidi to "not be working", make sure that the issue is not just one of what MIDI ports are being autoselected; see (6.) onwards for details.
 
0. Technically not an issue with rtmidi, but an easy one to solve: you forgot to update the firmware of your launchpad to be up-to-date via the official site: https://components.novationmusic.com/
 
1. The easiest issue is if you have a version of Python 2 installed (regardless of whether you have Python 3 installed), then sometimes <code>pip</code> and <code>python</code> refer to Python 2, so use <code>pip3</code> and <code>python3</code> instead wherever applicable. Alternatively, if you don't need Python 2, uninstalling it may help.
 
2. If you installed Python 3 but it doesn't work on command line, maybe you haven't used an official installer, as that usually shouldn't be an issue, or if you did use an official installer, you may not have marked the checkbox that makes it change PATH environment variables for you automatically, in which case adding the location of the Python 3 (and potentially pip) executables should fix it.
 
3. <code>rtmidi</code> doesn't seem to exist when trying to install it; try installing <code>python-rtmidi</code> instead.
 
3.1. <code>rtmidi</code> seems to install but there's issues when trying to use it; installing <code>python-rtmidi</code> may help in such a case; if that still doesn't help, try uninstalling rtmidi (<code>pip uninstall rtmidi</code>) and following the instructions of (7.1.). {{nowrap| (As a note of my experience: }} in the code, as a comment, I have <code>pip install mido && pip install python-rtmidi</code> (with a note about the loopMIDI requirement on Windows) as the default/simplest installation goal/recommendation, present at the beginning of the code where I <code>import mido</code>, because lately it seems that's the most reliable, however some systems may need <code>rtmidi</code> instead of <code>python-rtmidi</code> or may need both; I'm not sure what the most common case is.)
 
4. If you are getting a long error where towards the end there is <code>error: Microsoft Visual C++ 14.0 or greater is required.</code> (which might happen if you are using an older version of Windows) then try <code>pip install --only-binary :all: rtmidi</code> which will save you a huge amount of trouble if it works. Versions apparently should be available for almost every relevant version of Python 3, so if it isn't, try a different version of Python 3 if you know how. If your system doesn't use Python 3 and you installed it, it should definitely be safe to uninstall the current version.
 
4.1. I haven't seen this issue, but possibly if <code>pip install --only-binary :all: rtmidi</code> seems to work but then brings an error when trying to use the code, try <code>pip install --only-binary :all: python-rtmidi</code> in case it helps.
 
5. If you are on Windows, make sure you have [https://www.tobias-erichsen.de/software/loopmidi.html loopMIDI] installed and running with the default port name! (The default name should work; the code searches for the first name starting with "loop".) Though it hasn't been observed, if loopMIDI itself doesn't seem to be working, check that it's not a firewall issue.
 
6. '''If after all these remedies it doesn't work,''' then it's likely because the attempt at autoselecting the name of the MIDI input and MIDI output failed. To fix this:
 
6.0. A trivial but easy to forget fix: make sure that the USB you're using isn't faulty by trying a different USB that you can confirm works/isn't faulty.
 
6.1. First check that the Launchpad, ''after being set into programmer mode'' (see [[#Putting the Launchpad in programmer mode]]), is responsive, by using it as a MIDI input on [[Scale Workshop]] and checking if one of the Launchpad-related options makes Scale Workshop clearly press/release notes based on what you are pressing/releasing (so not as a mess of vaguely correlated noisy data, but as obviously directly responding to you touching the Launchpad). If this doesn't work (no [[#Success indicators]]), see (step 0.) and try restarting your computer (which is much more likely to be a relevant fix if you are using Windows).
 
6.2. Look at the output of running <code>iso()</code>; specifically, it'll tell you <code>midi outputs detected:</code> followed by a list of the ''text strings'' that correspond to valid MIDI outputs detected (here meaning valid devices/targets which the code can ''output''/''send'' MIDI data to); so you want to find the one that corresponds to telling the Launchpad how to light up, EG {{nowrap| <code>iso(midi_out_col{{=}}'MIDIOUT2 (LPX MIDI) 2')</code> }} if you saw {{nowrap| <code>MIDIOUT2 (LPX MIDI) 2</code> }} in the list.  If this is the only fix that was needed, you should see all [[#Success indicators]].
 
6.3. '''If this did not fix it,''' you should get a new error with new information to help you; you should see <code>midi_inputs_detected:</code> followed by a list of the ''text strings'' that correspond to valid MIDI inputs (here meaning valid devices/targets that the code can ''input''/''read'' MIDI data from); so you want to find the one that corresponds to receiving the data about what pads and buttons are pressed on the Launchpad, EG {{nowrap| <code>iso(midi_out_col{{=}}'MIDIOUT2 (LPX MIDI) 2',midi_input{{=}}'MIDIIN2 (LPX MIDI) 1')</code> }} if you saw {{nowrap| <code>MIDIIN2 (LPX MIDI) 1</code> }} in the list, where note we need to keep the {{nowrap| <code>midi_out_col=...</code> }} part so that we don't go back to failing at the first error. Again, if this was the last fix that was needed, you should see all [[#Success indicators]]. If you ''still'' can't get it working, see the final remedies below:
 
7. '''Final remedies''' (mainly for Windows being janky):
 
7.1. Try uninstalling and reinstalling <code>mido</code> and <code>python-rtmidi</code> with <code>pip</code> (or if on your system that corresponds to Python 2 rather than Python 3, then with <code>pip3</code> instead):
<pre>
pip uninstall python-rtmidi
pip uninstall mido
pip install mido
pip install python-rtmidi
</pre>
 
7.2. Restart the system (this is especially likely to fix things if you use Windows). (Possibly followed by uninstalling and reinstalling if that didn't fix it or fix something at least.)
 
7.3. Try installing (or if you did that, uninstalling and reinstalling) <code>rtmidi</code> in case it helps (note the distinctness from <code>python-rtmidi</code>). I vaguely recall this being a valid installation target in some cases. However, if installing <code>rtmidi</code> is valid (it installs) but it doesn't help, uninstall it and then uninstall and reinstall <code>python-rtmidi</code> and <code>mido</code> as shown in (7.1) above.
 
'''Mix and match these solutions as you find them potentially relevant to your situation, and hopefully you should be able to get it working.''' Also, remember, the order of doing these can matter; for example, if the installation is subtly bugged for whatever incomprehensible reason, then that'll invalidate all the other steps you took so you'll have to do them again with a non-bugged installation.
 
Feel free to send me a friend request to ask me for help on Discord (<code>@osmiumic</code>).
 
==== Putting the Launchpad in programmer mode ====
'''Launchpad X''': Once plugged in by USB and on, "hold Session, then press bottom Scene Launch button" (which I think should be orange?); the pads may flash to display a running text saying "Program" for confirmation. Once successfully put in programmer mode, the Launchpad should become unlit and appear unresponsive. '''This means it worked.''' To get it out of programmer mode and into the default mode, simply unplug it and plug it in again.


(More generally, Launchpads are '''''very''''' affordable xen MIDI instruments, nearly optimal for xen in that (with the help of the code below) they provide a fully customisable isomorphic MIDI keyboard with custom highlighting options. Also note that the tuning itself is not specified by the program; only the layout, though for the aforementioned large tunings the code will give you a .scl file to use, with all such .scl files using the same MIDI mapping, but requiring the base MIDI note/reference being 0.)
'''Launchpad Pro MK3:''' Once plugged in by USB and on, hold the small "Setup" button (bottom left) and press and release the "Print to Clip" button (bottom right). The Launchpad should become unlit and appear unresponsive. '''This means it worked.''' To get it out of programmer mode and into the default mode, simply unplug it and plug it in again.


== Novation Launchpad Pro MK3 isomorphic keyboard code ==
==== Success indicators ====
This code is licensed under the AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html), a version of the [[wikipedia:Affero General Public License|AGPL]] corresponding to the [[wikipedia:GNU General Public License#Version 3|GPLv3]].
If your fixes worked, then assuming you didn't specify a colouring so that you didn't specify anything other than running either <code>iso()</code>, <code>iso(midi_out_col='...')</code> or <code>iso(midi_out_col='...',midi_input='...')</code>, the launchpad should turn fully cyan. Then, assuming the call to <code>iso(...)</code> is currently running in your terminal/console, you should be able to see MIDI data being printed to the terminal/console whenever you press and release a pad or button on your Launchpad. As a result, and assuming that if on Windows you set up loopMIDI correctly and that it's running, you should be able to select the appropriate instance of RtMidi or loopMIDI as a MIDI input in Scale Workshop and play whatever scale you have loaded via the Launchpad. ''Remember to allow the website MIDI permissions first before looking at its list of MIDI inputs, as there can be a small (few seconds) latency for it to update the list of detected MIDI inputs, and that list may not update unless you close the browser and open it again to hard-refresh the page.''
 
'''It should be compatible with any Launchpad that has programmer mode; it has been confirmed to work for the Launchpad X so will likely also work for a Launchpad Mini MK3.'''
 
NOTE: I discovered it was possible to use custom colour palettes after I'd spent a lot of time making these rainbows using the rather limited and uneven set of factory colours. I haven't tested using this yet, but for those who want to take that route to achieve a more even rainbow, here is the link: https://fw.mat1jaczyyy.com/
 
ALSO: This should go without saying but take care of your launchpad! If you wipe it don't expect it to be properly responsive until fully dry and even then wiping it can have risks if the liquid goes inside. Similarly, do not bang it and probably don't leave it upside down (especially if for long periods of time) in case it causes the velocity sensitivity to go weird. Treat it as an instrument. If you do all this, it should be fine to use it for its intended purpose: just don't press ''too'' hard; try not press with more velocity than the buttons actually require, if possible. (Because of my silliness (not heeding such obvious procedures) there is a possibility that I may have weared my launchpad slightly in that I think I now require more pressure for the pressure-sensitive pads. I thought it responsible to add this note about taking care of your launchpad, as we are technically using it in a nonstandard way by treating the buttons as though they were just insensitive pads that require extra velocity/pressure.)
 
IMPORTANT: for the Python 3 code to work, it requires the launchpad to be plugged in with updated firmware and be in programmer mode! Also requires loopMIDI on Windows.
 
=== Installation issues/troubleshooting strategies/recommendations: ===
The most potentially difficult part is that depending on what OS you are using, how you have Python installed, what version, etc. there can be complications so that running <code>pip install rtmidi</code> in a terminal does not work. I list potential issues below, so please read carefully for something applicable to your case if it isn't going smoothly.
 
0. Technically not an issue with rtmidi, but an easy one to solve: you forgot to update the firmware of your launchpad to be up-to-date via the official site: https://components.novationmusic.com/
 
1. The easiest issue is if you have a version of Python 2 installed (regardless of whether you have Python 3 installed), then sometimes <code>pip</code> and <code>python</code> refer to Python 2, so use <code>pip3</code> and <code>python3</code> instead wherever applicable. Alternatively, if you don't need Python 2, uninstalling it may help.
 
2. If you installed Python 3 but it doesn't work on command line, maybe you haven't used an official installer, as that usually shouldn't be an issue, or if you did use an official installer, you may not have marked the checkbox that makes it change PATH environment variables for you automatically, in which case adding the location of the Python 3 (and potentially pip) executables should fix it.
 
3. <code>rtmidi</code> seems to install but there's issues when trying to use it; installing <code>python-rtmidi</code> may help in such a case. That's why I have "pip install mido && pip install python-rtmidi" with a note about the loopMIDI requirement on Windows as the default/simplest installation recommendation present at the beginning of the code where I <code>import mido</code>.
 
4. If you are getting a long error where towards the end there is <code>error: Microsoft Visual C++ 14.0 or greater is required.</code> (which might happen if you are using an older version of Windows) then try <code>pip install --only-binary :all: rtmidi</code> which will save you a huge amount of trouble if it works. Versions apparently should be available for almost every relevant version of Python 3, so if it isn't, try a different version of Python 3 if you know how. If your system doesn't use Python 3 and you installed it, it should definitely be safe to uninstall the current version.
 
4.1. I haven't seen this issue, but possibly if <code>pip install --only-binary :all: rtmidi</code> seems to work but then brings an error when trying to use the code, try <code>pip install --only-binary :all: python-rtmidi</code> in case it helps.
 
5. If you are on Windows, make sure you have [https://www.tobias-erichsen.de/software/loopmidi.html loopMIDI] installed and running with the default port name! (The default name should work; the code searches for a name starting with "loop".)
 
'''Mix and match these solutions as you find them potentially relevant to your situation, and hopefully you should be able to get it working.'''
 
Feel free to send me a friend request to ask me for help on Discord (<code>@osmiumic</code>).


=== Isomorphic keyboard code for launchpads with programmer mode ===
=== Isomorphic keyboard code for launchpads with programmer mode ===
Line 1,798: Line 1,994:
if j!=0 or i!=0: # equiv. if (i,j) != (0,0) (i think)
if j!=0 or i!=0: # equiv. if (i,j) != (0,0) (i think)
steps = i*x - i//every_x*x_reducer + j*y - j//every_y*y_reducer
steps = i*x - i//every_x*x_reducer + j*y - j//every_y*y_reducer
print( str(steps)+'\\'+str(edo) if args.get('backslash') or args.get('backslashes') else int(.5 + 10000 * steps/edo*1200)/10000 )
print( str(steps)+'\\'+str(edo) if args.get('backslash') or args.get('backslashes') else int(.5 + 1000**2 * steps/edo*1200)/1000**2 )
print()
print()
# for the launchpad pro MK3 specifically:
# for the launchpad pro MK3 specifically: