User:Godtone

From Xenharmonic Wiki
Jump to navigation Jump to search

I'll be putting basically all my microtonal thoughts, theories and even some coding here. It'll increase in organisation as I add stuff and figure out how to prettify stuff.

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. 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':
2/1, 3/2, 4/3, 5/4, 6/5, 7/6, 8/7, 9/8, 10/9, 11/10, 12/11, 13/12, 15/14, 17/16, 19/18.
I stopped at 19/18 because (19/18)/(21/20) = 380/378 = 190/189 which is again under 11 cents, as well as because superparticulars beyond 19/18 aren't really musically/melodically interesting to me. Note that this also corresponds to the 19-odd-limit, a subset of the 19-prime-limit, with 169/168 and 190/189 tempered. From there, we can choose to temper the missing superparticulars (the ones with odd denominators) to any of the adjacent superparticulars. Note that I am considering all of these intervals as intervals to move upwards with; 16/15 is an interval that to me works better for going downwards as it implies 15/8 = (3/2)(5/4) when you measure it relative to an imagined octave below the initial tone, and notice that 15/8 is expressible in terms of simple existing superparticulars (and thus as is 16/15). Also note that this does not mean I think the 19-limit should be where we stop. I think if we apply the idea of prime limit to Just Intonation music, the 23-limit is a far better stopping point due to being just before a record prime gap, and as there are various intervals using the prime 23 and more generally using the 27-odd-limit that I like.

Categories

Categories are about distinctions: the number of distinctions made and how they relate to other distinctions. As such, I will list various distinctions here, from the simplest to the most complex I consider interesting and based on their number and hierarchy, giving my thoughts and opinions along the way:

  1. If we assume octave equivalence (meaning multiplying or dividing all intervals by 2 until they are within the range of 1/1 to 2/1 and so that all intervals that differ only by powers of 2 are considered equivalent), then performing an octave complement creates an interesting and nontrivial relation between intervals, splitting the octave into 2 pieces: from 0c to 600c and from 600c to 1200c.
  2. If we use an xLys MOSS (Moment Of Symmetry Scale) to create interval categories, the most basic interesting MOSS is one with 5 notes in an octave, and the most consonant is the Pythagorean 2L3s MOSS, which extends naturally to a 5L2s MOSS while maintaining reasonable consonance in the Dorian mode, which I consider to be 5L2s Pyth's maximally consonant mode due to all intervals (measured relative to the root) containing no more than 3 factors of 3 in the numerator or denominator. These correspond to unequal 5-tone and 7-tone scales, which can be well approximated in 12 EDO as a good trade-off between accuracy and tone-efficiency. I also consider 5 to 9 notes to be the optimal number of notes for scales or they start to sound too much like chords (4) or start to sound too "chromatic" (10) although this doesn't at all mean scales outside this range aren't useful and arguably 4 and 10 are edge cases. Furthermore, there are some more symmetric 12 EDO scales of great note: the diminished scale (4L4s), an 8-note scale, and the whole tone scale (6 EDO), a 6-note scale. Considering the diminished scale has 4 EDO, a 4-note scale, as its core, this about covers most of the scale sizes, and hence much of 12 EDO music implicitly makes use of combinations of these scales. Note however that scales themselves are defined by their pieces just as much as or even more than their pieces define the scales, AKA that the "mode" of a song is often somewhat of a myth; only songs written in modes truly have a "mode" as opposed to impressions of modes.
  3. If we examine the traditional 7 interval categories in 5L2s diatonic scales, then each interval comes in two basic "types": major and minor. If we merge these types as in 7 EDO, then we get intervals neither major nor minor, thus named "neutral", which can often be approximated using the 11th harmonic. This gives 3 "types" per category. Furthermore, if we push the categories to their extremes, we get supermajor and subminor, which can often be approximated using the 7th harmonic. This gives 5 "types" per category. There is a subtlety about neutral intervals though that I consider important which is they generally sound more "minor" and "sad" than "major", and thus should generally be slightly flat relative to an "exact midpoint" between minor and major, which begs the question, what would be the fifth-complement of a "minor-ish neutral"? A "major-ish neutral"? If we use 11/9 as a standard example, its fifth complement is 27/22, but it'd be useful if there was a simpler - perhaps more consonant - interval we can use in its place, and there is! It differs from 16/13 by (11/9)(16/13)/(3/2) = 352/351 which gives a sharp 3/2, and thus provides a nice link to the 13th harmonic. Meanwhile, if distinguishing two types of neutrals is too precise, we can instead consider (27/22)/(11/9) = (11/9)^2/(3/2) = 243/242 which gives a flat 3/2, which would thus be compatible with meantone.
  4. In the case that we consider 11/9 and 16/13 as approximate fifth-complements, this clearly creates quite a precise distinction, and we thus must add at least 2 more distinctions in the spectrum. There is quite a large gap between subminor and minor or between major and supermajor, so adding some middleground subtly different from 12 EDO minor and major would be fitting, as the most basic minor and major are traditionally 5-limit and are closer together than in 12 EDO. For this purpose we will consider 12 EDO on its much more accurate "19-limit" (and in that sense "novemdecimal") 2.3.17.19 subgroup, thus creating a rather familiar "noveminor" and "novemajor" (short for novemdecimal), which, at least in the case of thirds, can be equated with "Pyth minor" and "Pyth major" due to identification by (9/8)^2/(24/19) = 513/512 (with 24/19 as the fifth-complement of the harmonic minor third of 19/16), leaving two options for the expression of this category depending on which makes more sense for a temperament. Note that using the "nove(m)-" prefix for something that could be either "novemdecimal" or "Pyth" may be confusing and so I propose altering slightly to "novaminor" and "novamajor", using the analogy that novemdecimal and Pyth is exaggerated from classical major and minor and builds from both it and from itself, like a star creating new, stronger elements, and creating a "brighter" (as opposed to "sweet"/"solemn") sound to triads, and just as a star's nova is initially bright and fades over time, this "brightness" of Pyth/novemdecimal major/minor has faded over time due to familiarisation/desensitisation. Next, we will consider - if needed - optional additional/finer categories between novaminor and subminor and between novamajor and supermajor. These "new" and "subtly exaggerated from familiar" categories I think fit with the prefix of "neo" and can be considered represented by 13/11 and 14/11 which again can be equated with a sharp 3/2 through identification by (13/11)(14/11)/(3/2) = 364/363 and again creates an interesting link between the 11th and 13th harmonics. Then even subtler versions of the usual major and minor categories can be added - subtle in the sense of closer to but distinct from neutral - these are supraminor and submajor. Finally, for completeness, even more extreme versions of subminor and supermajor can be added that push into the "neither major nor minor at all" territory; these are ultraminor and ultramajor. The final list looks like this:
    1. ultraminor
    2. subminor
    3. neominor
    4. novaminor
    5. (classic) minor
    6. supraminor
    7. subneutral (or "minor neutral" if you prefer)
    8. superneutral (or "major neutral" if you prefer)
    9. submajor
    10. (classic) major
    11. novamajor
    12. neomajor
    13. supermajor
    14. ultramajor
  5. More specifically though, the order in which "types" are introduced is approximately:
    1. minor, major (2 types)
    2. minor, neutral, major (3 types)
    3. (ultraminor or) subminor, (supra)minor, (sub)major, supermajor (or ultramajor) (4 types)
    4. (ultraminor or) subminor, minor, neutral, major, supermajor (or ultramajor) (5 types)
    5. (ultraminor,) subminor, novaminor/neominor, minor, neutral, major, novamajor/neomajor, supermajor(, ultramajor) (7 (or 9) types)
    6. [same as 7 (or 9) types but with neutral split into subneutral & superneutral] (8 (or 10) types)
    7. [same as 8 (or 10) types but with either neo- & nova- distinguished (+2) or with supraminor & submajor added (+2)] (10 (or 12) types)
    8. [both neo- & nova- and supraminor & submajor distinguished] (12 (or 14) types)
    Note that in the above, ultraminor and ultramajor are not necessarily to be included as they represent the points at which the basic "minor vs major" distinction ceases to make sense and becomes unclear/too dependent on context, so they represent absolute limits. Also note that these categories are mainly centered on thirds and to a lesser extent seconds as I consider thirds and seconds to be the most important intervals in terms of providing musical meaning, however this doesn't mean other interval sizes should be ignored or taken less seriously.
  6. To better understand what these interval types mean, study of a variety of intervals and their qualities is required. Due to the in-depth-ness of this study, this is something that is far from complete, and which I will dedicate its own large section to: the "Intervals" section. This study is also something that is subjective just as 12 EDO music is subjective, but that doesn't mean we can't try to blaze a trail for an approximate framework for the interpretation of microtonal music by trying to stick to some guiding principles.


Colourful EDOs

My above progression of "types"/"colours" can be used as a perhaps-interesting alternative to finding "good" EDOs for music by judging them not based on approximation of rationals of interest but instead based on their "colour palette"; not that these two methods are contradictory, and in fact I believe a combination of both is desirable. However, as a demonstration and a starting point, we will look at EDOs providing progressively more complex colour palettes, starting from a broad equalised 7-note approximation of the 5L2s diatonic scale (AKA 7 EDO) and considering only the 'seconds' and 'thirds' (and thus by octave complement, their inversions), with fourths and fifths not considered except to the extent that they should ideally not be too "out of tune", with "out-of-tune-ness" judged relative to approximating either 4/3 or 11/8 (but not both), as these are the simplest J.I fourths. Furthermore, we want the colour palettes to be generally symmetric about the "neutral" type (whether a system has a neutral type or not), so this excludes a large number of EDOs; this is intentional, as otherwise we would end up listing every EDO, and as it is a symmetry which I think is important or at least an interesting restriction.

  1. 7 EDO is the simplest/"trivial" EDO as it provides only the (at times very approximate) "neutral" colour. Note that its fourths are very out-of-tune; this EDO is mainly included as a trivial case. This corresponds to "1 type".
  2. 12 EDO is the next simplest as it provides (nova)major and (nova)minor seconds and thirds. Also a tone-efficient pure Pyth approximation so very good fourths. This corresponds to "2 types".
  3. 15 EDO, in terms of colours, is similar to 12 EDO except that its minor third is a little sharper and that it now has 3 types of second which are roughly subminor, neutral and supermajor. The "subminor" and "supermajor" designations are used due to symmetry; in actuality the subminor second is closer to a neominor second and the supermajor second is closer to an ultramajor second. The prefixes may be omitted, or more exact colour terms may be used, making it have a "superneutral second". Note this EDO is more of an honourable mention due both to somewhat significant asymmetry and due to very off fifths, and also to explain why I won't include it in the "final" list..
  4. 17 EDO is the first EDO to truly have minor, neutral and major for both seconds and thirds, and is thus quite significant as a potential next step up from 12 EDO. More exactly, these come in the flavours of neominor, neutral and neomajor.
  5. 19 EDO is the next step up, having seconds and thirds of the ultraminor, minor, major and ultramajor varieties. It does this by conflating an ultramajor second as an ultraminor third, creating quite a distinct interval that escapes 5L2s categorisation. Note that 18 EDO does this too, but is less symmetric. Thus 19 (or 18 - if you are so inclined - which represents a sharpening of seconds and thirds) is the next step up as corresponding to "4 types".

Note that 17 and 19 have an interesting symmetry with each-other: while 17's new type is per 5L2s category and represents something between the types of major and minor, 19's new type is between 5L2s categories. Thus 17 respects diatonic interval categories more, which actually makes 19 the more novel system to me, for example I quite like the very distinct sound of the 5L4s "semaphore" scale; dyads and triads alike.
Furthermore, note that after this point I focus on EDOs with 'good enough' fourths.

  1. 22 EDO is next, having subminor, supraminor, submajor and supermajor seconds and thirds, although only approximately, and in this spirit "supraminor and submajor" can be shortened to "minor and major" for brevity. 22 is distinct from 19 in that it does not have any of its types overlap between categories.
  2. 24 EDO is next, being easy to categorise as a colour extension of 12, and thus it has seconds and thirds of the ultraminor, novaminor, neutral, novamajor and ultramajor varieties, with the ultramajor second equal to the ultraminor third.
  3. 26 EDO is next and mirrors 22 in its thirds but has one more type of second, and so the seconds are approximately: subminor, minor, neutral, major, supermajor.
  4. 29 EDO next, (approximately) with seconds of types ("fifth-tone" =) ~ultraminor, neominor, supraminor, submajor, neomajor, ("semifourth" =) ~ultramajor, and with thirds of types ("semifourth" =) ultraminor, neominor, supraminor, submajor, neomajor, ("semisixth" =) ulramajor.
  5. 31 EDO is then quite ideal, as it gives us seconds and thirds of subminor, minor, neutral, major and supermajor types!
  6. 34 EDO is ideal if your main gripe with 31 EDO is the lack of semifourths/semisixths (which is my main gripe with it), although note that instead of a subminor third it has a flat neominor third, but its shrubmajor third (4.2) is roughly the same as 31 EDO's; this corresponds to their opposite tendencies as systems.

Beyond this point, it really becomes about narrowing down EDOs based on approximative or other considerations than just 'types'/'colours'. Having said this, there are still yet a few EDOs to be considered, but we will require hereon a fifth strictly between that of 31edo and that of 34edo (AKA that of 17edo).

  1. 41 EDO is ideal if your main gripe with 31 EDO is impurity of the fifth and lack of novamajor/novaminor as well as the unideal flatness of the supermajor third, although personally that is not my main gripe with 31 EDO.
  2. 43 EDO is ideal if your main gripe with 31 EDO is lack of semifourths/semisixths and submajor/supraminor if you don't mind having a gothminor third (2.8) instead of a subminor one (2.7) and a neomajor third (4.1) instead of a shrubmajor one (4.2 or often we consider it less accurately a supermajor third (4.3) in 31 EDO for symmetry with its subminor third (2.7)).
  3. 53 EDO is ideal if you want something that fixes the gripes stated in 34 EDO, 41 EDO and 43 EDO; that is: lack of semifourths/semisixths, lack of novamajor/novaminor & impurity of the fifth & lack of a proper supermajor third, and lack of (semifourths/semisixths &) submajor/supraminor / superneutral/subneutral, respectively, making it unusually complete & comprehensive as a system, corresponding to garibaldi+nestoria+catakleismic.

Beyond 53 EDO, what EDOs are notable as colour palettes becomes too subjective and dependent on various hyper-specific criteria that may have only very niche applicability to anyone, so beyond 53 EDO you are better off looking at the systems from a tuning perspective first and a colour palette perspective second rather than a colour palette perspective first and a tuning perspective second; 53 EDO is sort of unique as the middleground between these two ways of thinking, representing both a fairly complete palette and an excellent tuning system in the no-17's 19-limit (with optional higher primes added depending on context/what you wanna achieve). It should perhaps be noted, however, that 58edo is a colour palette with similar elegance to 53 but with less tuning accuracy.

Favourite EDOs

DISCLAIMER: The reasonings for the EDOs I note here are guaranteed to be incomplete; EDOs are fundamentally deep systems and the more I've learned the more reasons I've found to appreciate the various EDOs I speak of here. Therefore keep in mind that whatever I say is a rude oversimplification scratching the surface of its possibilities and deep elegances. Also, I've kept my old entries and reasonings here as they were based on somewhat different ways of thinking about these things so I believe still have value; for example, I now generally quite dislike 22edo (and archy generally) as an approximation to harmony but I admit there is a lot of interesting music in it and it is something a beginner should consider and which has proven value to beginners (both listeners and musicians).
12, 13, 16, 17, 19, 20, 22, 24, 26, 31, 32, 34, 36, 37, 50, 53, 58, 65 (or 130), 68, 72, 77, 80, 87, 111, 311.
EDOs < 12 not included as usually better conceptualised in a superset of that EDO and because otherwise I'd list too many consecutive EDOs.
Favourite EDOs best to worst, not listed = even worse, my opinion obviously, also my opinions are still in development about many of these:

  • 12: Pythagorean Meantone: the musical language. From a circle-of-nths relative-consistency point of view, it is very strong in the 2.3.5.19(.17) subgroup. Not to be underestimated. Has melodic hints of the 7-limit through the inaccuracy of its 5. Has been called "the EDO chosen by God" by some - I'm definitely inclined to agree in the context of casual non-xen Western music.
  • 13: Distorted 12. As such, almost xenharmonic by definition, due to maximising opportunities for alienness. The next good EDO after 12. Dreamy scales that I like a lot but I'm not sure about if that alone means they're good to use. I hope it does as 13 has huge potential if so.
  • 16: The first interesting superset of 4 other than 12. Also a mavila tuning, not that I like Mavila too much.
  • 17: Notable as the first step up from 12 in colour palette. Good fifths that are slightly worse than in 12 but in the sharp direction. Kinda a bright feel. It took me a while to deduce this, but its harmonic magic lies in its 2.3.25.13.17/15(.23) subgroup, especially in the glorious neogothic/neopythagorean pentads afforded by fiventeen which is tuned excellently. Also if someone tells you 17 has ~11 ask them to prove it with harmonic examples.
  • 19: Flattish/solemn meantone tuning with xenmelodic potential. The semifourth in semaphore has a very neat sound but I wouldn't say it approximates the 7-limit. If anything, 19 is 2.3.5.37 with it representing a circle of 37/32's, thus also being the first good approximation of the 2.37 subgroup, and thus of 37/32, which represents probably my favourite interval of 19.
  • 20: The first EDO to have both the 5L5s and 4L4s symmetrical scales, and significant for that reason alone. Can sound quite atonal, however:
    • Its 10 EDO subset has a very strong circle of 16/13's and 15/14's.
    • Its 5 EDO subset has a strong (and remarkably small) circle of 23/20's.
    • Its 4 EDO subset has a strong (and remarkably small) circle of 19/16's.
    • 10/9 is approximated well by 3\20 and 14/11 is approximated well by 7\20. Has a flattish approximation of 7/4 and some higher (octave-reduced) harmonics but I don't think I'd use it to approximate those higher harmonics.
This gives it the (additional) remarkable property that all its flavours of seconds are arguably consonant other than 1\20, which is arguably an augmented unison anyway.
  • 22: The first EDO that melodically approximates the 11-limit, and very tone efficient for that purpose. Sounds harmonically complex. Superpyth + Orwell tuning. Really not a fan of porcupine; it's an exotemperament (AKA "troll temperament") IMO. Use echidna if you want 22ish structure with harmonic approximations of the 11-limit (not just melodic).
  • 24: I think neutral intervals and semifourths are kinda cool as an addition and unexpected root movement is cool, so acts as a nice stepping stone into microtonality with a strong base of familiarity to build off of if what you are looking for is the microtonal. But I wouldn't recommend it to a beginner as there are more approachable systems that offer a healthier introduction to microtonality and (especially) xenharmony, such as (especially?) 31 EDO. I also include it because I like highly composite EDOs, and this is very clearly one. Represents the 2.3.11.17.19.31.37 subgroup particularly well.
  • 26: Neat for having very good 8/7's and 10/9's, both flavours of major second that I very much appreciate (while 9/8 can get pretty bland). Basically the only tuning of flattone that I'd consider using as its about as big as a flattone system should be. Note that while 19 EDO is technically also flattone, it represents the border between sharper meantones and flattone, so I do not consider it a proper (in the sense of typical/representative) example of such (for example 19 EDO trying to combine the mappings of 7 as augmented sixth and diminished seventh results in S7 being tempered, which is to me harmonically almost as implausible as tempering 25/24, so definitely an exotemperament/"troll temperament"). Furthermore, in this tuning of flattone the minor seconds are 13/12's, thus representing a near-equal diatonic such that the minor seconds are subneutral seconds. Has the benefit of extending 13 EDO into a larger and more complete colour set and conceptual framework, creating some truly xenharmonic and xenmelodic opportunities with flattone acting as a rough roadmap back to the more familiar things. Very nice model of the 2.7.11 subgroup; in my eye, it is to the 2.7.11 subgroup as 31 EDO is to the 2.5.7 subgroup.
  • 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.
  • 34: The first good approximation of the 5-prime-limit due to being the first reasonably accurate tuning of Hanson AKA kleismic. 19 is also a tuning for kleismic but feels like it doesn't do justice to the accuracy and pristineness of kleismic to me. 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)n / (20000/19683).
  • 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.
  • 37: Truly an excellent no-3's 13-limit system. Ridiculously overlooked just because of its not so great approximation of prime 3 (which is at least diatonic and sounds convincing enough especially in context). A logical system for building on it is 111edo which keeps this 13-limit mapping (but improves the 3).
  • 50: The last meantone EDO that should ever be considered because it is the last EDO to consistently map 9/8 and 10/9 to the same step and because 81/80 is a rather large comma to temper at this scale and thus costs you a lot of accuracy. It is surprisingly consistent in the higher limits, and that it is quite composite is appealing to me, especially given that it is a superset of 10 EDO.
  • 53: Catakleismic Pythagorean Orwell Buzzard. If that description doesn't sound epic I don't really know what will. Very colourful EDO. Near-perfect 5-limit JI with good 7-limit, passable 11-limit through Orwell and good no-17's 19-limit. Normally I wouldn't like large prime EDOs but this is a rare exception as in this case it's a practically perfect representation of the 2.3 subgroup.
  • 58: Weirdly consistent tuning with a nice selection of colours. Also supports the important 53&58 temperament Buzzard. Record in Pepper ambiguity in the 13- and 15-odd-limit. The first EDO to be consistent in the 17-odd-limit. I haven't looked at this EDO very closely but suspect it may have some surprisingly accurate/good approximations hiding under its slightly meh prime error profile. It supports hemipyth thru 16/13 = 11/9, a questionable equivalence but arguably 58 is the only EDO to make it work/make sense. I like its organisation of intervals/colours a lot.
  • 65: Very cool. Underappreciated. Good nestoria + wurschmidt tuning, but more importantly, it is in some surprisingly exact sense the "dual" to what 53 EDO's schismic offers harmonically, including the fact that its subgroup is larger and involves larger primes at the cost of some accuracy (depending on how strict you wanna be about which primes you consider approximated for the purposes of interpreting harmony). Also has a very cool superset, 130edo, but I implore people to explore what 65 EDO has to offer first, being 5 * 13 with lots of cool xen stuff deriving from the implications of that.
  • 68: Superset of 34 that enables the 7-prime-limit. Not too remarkable for that reason alone, however my interest in this EDO was increased when I deduced that it has a step size that is close to half the size of 49/48 meaning a 7/6, an 8/7 and a semifourth can all be distinguished with accuracy. For that reason, this EDO is important as an EDO around which other EDOs have the potential for a good selection of colours which approximate these 3 intervals of interest. It also performs well as a no-11's no-29's 31-limit temperament, although it shares the idiosyncracy of 80 EDO of splitting the syntonic comma into two 64/63's.
  • 72: catakleismic miracle octopus (among other things). If you want the 11-limit in a finite number of pitches, look no further, but it even does well in the no-13's 17-limit, the full 17-limit and the full 19-limit (with a few inconsistencies in the lattermost case). Added convenience of being a superset of 12 EDO and a very composite EDO. The first true EDO to represent ennealimmal (as 27edo only makes sense if you want to use superpyth and 45edo is a trollish flattone tuning). The page for the keenanisma, 385/384, has some explanation for why this is a theoretically interesting comma for extending the 7-limit to the 11-limit, for which 72 does very logically (among the many very logical things it does).
  • 77: Very good (and elegant) high-limit system. See #Important 23-limit EDOs. I overlooked this one. Supports the rudely-named absurdity temperament, which is ironically very reasonable if not in some senses ideal for modelling higher-limit harmony.
  • 80: My former favourite EDO. In the past, my favourite was 53 EDO. Now I am leaning again to 53 EDO as my favourite but honestly there are so many seriously good EDOs that it feels unfair to single one out. 80 EDO may be a surprising choice for former favourite at first but there are a lot of reasons feeding into it which means if you don't get it it means you're probably underestimating it and haven't looked closely enough (I don't mean this as some way of trying to impose my opinion; there is a lot of exceptional properties that 80 EDO is hiding of many natures). Tunes 17-limit Tolermic, a strange temperament which tempers many commas I'm interested in tempering; in fact there is a strange intuitiveness to 80 EDO's tempering. The highly complex 80&311 temperament superlimmal is of note as being essentially a no-31's 37- or 41-limit temperament.
  • 87: A good approximation of the 13-limit, with the 5-limit also good, and an alternative Tolermic tuning, so it's closely related to 80 EDO but with better fifths and harmonic sevenths. Compared to 80 EDO, 7/4 is still the worst prime but lower in both absolute and relative error, and it is tuned flatly instead of sharply. My former "final and most colourful EDO", but 80 EDO is more than enough colours for me in that it covers all the colours I'd want from 87 and in ways I prefer and find more intuitive. Has an interesting conceptualisation as 29 EDO representing an approximate 2.3 subgroup with 5, 7, 11 and 13 all being 1\87 flat of the 29 EDO circle, providing an elegant model of navigation. 29 EDO is itself not bad as something that sounds like a brighter 12 EDO, but it feels more elegantly and interestingly conceptualised in this superset.
  • 111: an absurdly elegant system from a tempering perspective in the sheer wealth and intuitiveness of equivalences it affords you; so much so that it sacrifices tuning accuracy for temperamental beauty and efficiency. One of a kind EDO.
  • 311: If you asked God what his favourite EDO was, he would say 311 EDO. It is almost unsettling how much of the harmonic series this EDO approximates well considering its comparatively small size. Very recommendable alternative to cents for low-complexity (in the sense of integer- or odd-limited) JI, as this EDO is not only consistent in the full 41-odd-limit, but many (mainly non-prime) odd harmonics greater than 41 can be added to the set without causing inconsistencies between them and other odd harmonics. I wonder if a precise JI harmonic series singer would implicitly target notes of 311 EDO in both singing and in their conceptualisation of JI. I find describing the prime subgroup interpretation of this EDO rather amusing, so here it is: 2.3.5.7.11.13.17.19.23.29.31.37.41.73.89.109.113. Note that as 89, 109 and 113 aren't as accurate as 73, so they could arguably be omitted because of their combination of complexity and inaccuracy. Fun fact: in Group Theory (a subfield of Abstract Algebra), excepting 37, all the primes up to and including 41 appear in the prime factorisation of the order of the Monster Group. The largest prime to appear in its factorisation is 71, the prime just before 73, which is the first prime after 41 that 311 EDO approximates well.

Philosophy

Firstly, this will obviously be heavily influenced by my opinions, so I may state subjective things or theories in a rather matter-of-fact way, but feel free to disagree with me/provide criticisms on my user page. Having said that... I think while measuring intervals relative to 12 EDO is useful initially, this should not be the final way of measuring them. Instead, I believe different intervals should be considered like "colours" or "flavours", of which 12 EDO's intervals are (approximately) one type (corresponding generally to novaminor and novamajor), and that these new terms (or whatever terms you prefer) should eventually be more natural a musical language than comparison to 12 EDO, which often causes a variety of intervals to be used similarly rather than distinguishing them as unique categories not subservient to (but related to) other categories. This is primarily due to a stark lack of theory in the musical and especially emotional meanings of these new intervals. This also makes me more open to the idea of using larger EDOs - which provide more distinctions - as frameworks in which many colours are possible, however, I have relatively high standards for large EDOs, as a large number of tones is something that needs to be quite seriously justified. For example, I generally dislike prime or not-very-composite EDOs unless they have exceedingly pure intervals and/or are very remarkable for other reasons. Sub-EDOs or extremely pure intervals in an EDO generally help to give it a feeling of griddedness and thus orientation in. I think the most important takeaway from 12 EDO is the effectiveness of simplicity and that you shouldn't underestimate how much can be built and how much nuance can be achieved by carefully combining simple things, and that the most objective possible interpretations of music come from the aggregation and combination of many patterns. This also leads me to believe scales are interpreted in terms of their pieces more than vice versa, as aforementioned.
Secondly, I think in new and unfamiliar territory, it will be helpful to use symbolism and analogies in order to get a rough initial understanding for potential emotional/symbolic meanings as the usual music theory isn't enough.

Number symbolism

1 : Unity, simplicity & wholeness (obviously). The ultimate source/root. The Formless. The first Form.
2 : Duality (obviously), contrast, distinction; the first prime number, and thus the first pure essence (assuming we don't count 1 as a pure essence). The source of the most basic/primitive/trivial kinds of order. As such, it is a fundamental organising order from which more complex orders can be judged through (imperfect and careful) assumptions of octave equivalence, and to a lesser extent, octave complements, both of which specifically make sense in the context of approximating simple ratios in EDOs (or vice versa). 2 notes is a dyad and specifies an interval.
3 : Trinity (obviously) and stability - due to each element helping define and stabilise the other two in relation to each-other; think of how the triangle is the strongest shape for scaffolding. Thus represents a place of completeness and strength, and thus sometimes also of rest due to its stability. (It is a pure essence due to being prime.) 3 notes is a triad - the most primitive kind of chord - with 3 inversions that maximise closeness of notes all having at least subtly different but generally similar meanings. (See the subsection on inversions.)
4 : The first composite number. The tetrahedron (AKA 3-simplex) with its 6 edges is an important 3D and general mathematical shape (a musically notable instance is its relation to the structure of tetrads AKA 4-note chords). Thus represents the 3 spatial dimensions or the 3 dimensions of space paired with 1 dimension of time. The tetrahedron is a strong shape due to being made of 4 triangles which all share a unique 3 point subset of a set of 4 points, thus 4 similarly represents a place of completeness, strength and sometimes rest, but can be sharp/hazardous partly due to containing repeated duality (this applies (at least to small extent) to all numbers with powers of 2 in their prime factorisations) and thus pitfalls/negations of the ideal (as duality creates negation). Thus represents the Earth/its likeness (AKA the material world) and thus also represents the limitations of the Earth. Interestingly, melody seems to only be registered by the human mind (or at least mine) in (approximately) 2 octaves of pitch space, as beyond that, melody doesn't have as much meaning and the timbre starts to take over. Because of its limiting nature (being the primitive order of the Earth), it can also be thought of as a number of death.
5 : The number of sides of a square-based pyramid, which has 4 triangular (but not necessarily regular/fully symmetric) faces/sides and thus similar stability but with far more orientedness, both because of the shape and because it is a pure essence due to being prime, and can strongly suggest a harmonic fundamental to the point of encouraging a sense of transparency and orientation. Also the number of vertices/corners of a square-based pyramid. 6 square based pyramids form a cube which tiles 3D space, and where squares themselves tile 2D space. 2 square-based pyramids form an octahedron - the dual of a cube. Additionally, pentagons are the last 2D regular convex polygon for which you can join 3 mutually to each-other and get something 3D, and thus 5 is also related to the dodecahedron, and thus the number 12. Also the 4 points of a tetrahedron with an added/unifying centre (which could be interpreted to be at a distance in a 4th dimension thus forming a 4-simplex, in which case it is projected like a shadow into the Earth), thus representing Earthly dualities that have had their pitfalls tempered through unification through an ideal (which is perhaps not fully of the Earth), thus representing construction upwards to something better, using what is available. Thus represents the pure essence of The Transcendent's grace, benevolence and revitalisation on the Earth, AKA the first/simplest form of transcendence from/beyond the Earth (as it is 4+1), although not a complete/sufficient one for transcendence on its own. Also of course strongly related to pentads (AKA 5-note chords), and due to the combinatorics therein (which relate to the 4-simplex), to the number 10.
6 : As 6 is 2*3, it represents a mixture of their meanings. Furthermore, as it is the number of sides a cube has or the number of corners an octahedron has, it represents the outside shell of an object with no core, and thus represents purposelessness and indifference as well as constant conflict caused by the stability of widespread self-opposing duality, and thus represents the metaphysical engines of war. This is further evidenced by it being 4+2, thus representing duality (2) on earth (4) or the combination of death (4) and duality (2). Furthermore, it also represents the potential for growth due to the lack of a core.
7 : The last "small prime". Represents purity (partly due to being the last "small prime" and thus the last obvious pure essence), wateriness (due to being at the limit of what is immediately intuitively comprehendible to the human mind in terms of identifying numbers of objects as well as making numbers of distinctions on a scale or in general from a symbolic perspective; due to being expressible as many sums; due to many other mathematical properties it has) and perfection (due to many cultural associations). It derives much of its meaning through being expressible as 6+1, 5+2 and 4+3. 6+1 means that it is unity applied to the duality of earth, thus providing a core, while simultaneously transcending the symbolic evils associated with the number 6. 5+2 means that it is duality applied to grace, offering multiplicity/variety in grace and thus representing many forms of grace unified by their purity of form. 4+3 means it is the stabilisation and completion of the earth and of death, hence can be seen as purifying, also because of meaning derived from being 6+1.
8 : Represents power. 2^3 means stability achieved through duality. 2*4 means duality of the earth - and thus the division of it into the abstract patterns of power and its negation; this also induces a sort of death (4) due to the imbalance. 2+6 means duality of the shell; the dual nature of conflict comes from power imbalance.
9 : As it is 3^2, it represents duality achieved through stability and repeated completeness, which is in a sense dual to the meaning of 8. Note that this number has a very different meaning to 8 as duality achieved in this way is a well-formed duality absent of conflict, and so represents a sort of finality of existential balance. Being 5+4, it is also grace on earth, and being 8+1 it is also the transcension of and progression above power vs powerlessness.
10 : As it is 5*2, and as much of human organisation revolves around this number, this is the number of man and human governments. Fittingly, it contains both grace (5) and duality (2), thus representing a corrupted grace and thus implicitly containing the sin of man, also due to being 4+6 and 3+7 (thus being a sort of pure and heartless metastability without regard for anything else). This number is important in connection to the numbers 11 and 12 whose meanings are related.
11 : The simplest "complex" prime essence, and the end of the prime gap starting at 7, this number symbolises divine judgement and chaos. This is partly because it is between 10, the number of the order of man, and 12, discussed next. It is also because it is:

  • The purification (7) of the order of the earth (4), being 7+4.
  • Where power (8) and stability (3) mix, being 8+3, thus representing that appropriate judgement is needed for the stabilisation of power.
  • The duality (2) of finality (9), given that judgement requires an axis along which to judge.
  • The mix of 5 and 6, where 5 represents a stable foundation and 6 represents corelessness, and where 5 represents grace and harmony and 6 represents sin and disharmony. Thus, it takes on a "neutral" nature imbetween the two, necessary for judgement, which is fitting considering that the lowest complexity intervals involving this harmonic are neutral. Even 11/8 is a mixture of an antitonic and a serviant, thus representing an ambiguous region which is nonetheless rooted (contrasting it with 4/3).

12 : The number of divine, perfect order, as it is 7+5 and 4*3. Means pure grace/graceful purity stabilised on the earth and is the stabilisation of death such that it becomes under control for good. This is also partly because it has a large number of factors, thus integrating many simpler orders into a perfect (7) and graceful (5) combined one.
13 : Represents rebellion against the first divine order (12+1), but also represents the attempted progression upwards/further and thus also represents new beginnings. Thus represents the smokeless flame in its raw and partially sinful (in the sense of imperfect) desire to blaze new trails and fly higher. It is 7+6 and thus can also be a number representing pure evil, although using it for this meaning only would be shallow, as mistakes (sin; 6) give further opportunity for enhanced perfection (7). It is 8+5 and thus can be the graceful side of power, again representing a sinful counterpart to divine order, hence the potential association with evil. It is 9+4 and thus is supreme/complete stability applied to the laws of earth and applied to death without necessarily giving sufficient concern to other principles/values, hence again fitting all the themes mentioned prior. Finally, it is 10+3, representing the stabilisation of the order of man, and thus the attempted solidification of imperfection into an essence. Thus is associated with fire, as misguided transcendence may have steep consequences and as fire can be used for good for burning off imperfection by first drawing strong attention to it by illuminating it. Note that 13/8 is a rooted rational approximation of phi (the golden ratio) which is mathematically in some sense "the most irrational number" due to having a continued fraction consisting only of 1's, thus 13/8 takes on some of this mysterious quality while being perfectly just; a sort of ambiguous smoothness emerges in at least some of the low complexity intervals involving this prime essence, analogous to the beauty and smoothness of a smokeless flame.

Diatonic functions

TODO: Expand & revise this section. Topics to expand on:

  • Defining mediant and contramediant in different cases following on from analysis of the cases.
  • Defining emergent ambiguous areas between the 10 basic functions.
  • Defining generalised functions that apply across all MOSSes following on from the analysis of the cases; the lead and contralead are a simple example of this as they are mostly the same in all reasonable cases.
  • (Maybe?) How to create natural progressions - both in terms of melody and chords, following on from all previous points, as well as from the perspective of harmony and an analysis of its meaning. This implies an analysis of various #Intervals and JI #Scales is needed in conjunction with this step. This is the goal.

This section is largely inspired by Aura's Ideas on Functional Harmony combined with my own observations, intuitions and simplifications, as I admittedly do not understand nearly as much as Aura on this subject, but I do intuitively understand my musical sense, which I wish to approximately translate here. This will also focus on generalisation from the diatonic MOSS to other MOSSes.

We begin with a rooted generator, meaning an interval of the form a/2^n where 2^(n+1) > a > 2^n. This is taken as the most important interval, other than the period, which we will assume and define to be an octave as I believe this is the most natural period for a MOSS, especially in the case where the generator or its octave-complement is rooted. We also begin with a tonic, meaning a 'centre' of the scale from which the scale is constructed by stacking periods and generators up or down. The position of the tonic depends on the mode of the MOSS used. Note that we will start with the Pythagorean tuning of the diatonic MOSS and generalise from there. Now we are ready to start defining the most fundamental functions:

  • The tonic, notated 0. The number 0 is meant to represent an origin/reference point. This is always 1/1 - and due to assuming octave-equivalence throughout this analysis of function - is also always equivalent to 2/1, 4/1, etc.
  • The dominant, notated 6. The number 6 looks visually somewhat like a reversed delta/d symbol, for "dominant". The 6th harmonic up to octave-equivalence is 3/2, the perfect fifth. The dominant should ideally always be a(n approximation of a) rooted generator.
  • The serviant, notated 4. The number 4 should remind you that the perfect fourth is 4/3. The serviant is always the octave-complement (AKA period-complement) of the dominant, with the only distinguishing feature between the two being which is a (up to octave-equivalence) a (rooted) harmonic and which is a subharmonic. The serviant should (ideally) always be a subharmonic of the tonic.
  • The supertonic, notated 2, is a stack of two dominants upwards (and octave-reduced if needed). The 2 should remind you that it is two dominants. It should also be clear from 6 * 2 = 12, as 10 is the tonic and so 12 is a supertonic above the tonic, hence "super"-tonic.
  • The subtonic, notated 8, is the octave-complement of the supertonic, and is thus a stack of two serviants upwards (and octave-reduced if needed). The 8 should remind you that it is two (2) serviants (4) as 2 * 4 = 8.
  • The antitonic, notated 5, is approximately half of an octave, being important due to both being a less strong consonance or a dissonance and due to being the mirror line when performing octave-complementing.

Note that in the system, there is always the rule that the octave-complement (AKA period-complement) of a function's number is 10 minus that number. This is because in the final system, 10 functions are derived for diatonic. These functions' numbers are picked in the order they appear in diatonic, as this notation is intended for diatonic. For other MOSSes, the names of the functions remain the same but some of the notation may change. However, the numbers for the functions 0, 1, 5, 9 are generalisable to all octave-period MOSSes. This leads us to our next pair of functions: 1 and 9.

  • The lead, notated 9, is a small step (s) or chroma (L-s) below the tonic. The number 9 represents being 1 step (chroma or small step) below the tonic (10 - 1 = 9). The name is derived from the term "leading tone". Diatonically, this is a semitone.
  • The contralead, notated 1, is a small step (s) or chroma (L-s) above the tonic. The name is derived from contra+lead. The number 1 represents being 1 step (chroma or small step) above the tonic.

Finally, there are a pair of functions which act as 'gap fillers' in the MOSS, which can be assigned to the result of stacking 3 to 5 generators (whichever amounts avoid the regions corresponding to other functions and fit the restrictions). These are the mediant and the contramediant. Before defining the mediant, an analysis of the order of the functions so far is required. The basic order is:

tonic < contralead < antitonic < lead < tonic (or in numeric notation: 0 < 1 < 5 < 9 < 10)

Then the position of the dominant, serviant, supertonic and subtonic depend on whether the dominant is above or below the antitonic. If it is above the antitonic, then it is also below the lead. This means that two dominants exceed an octave and therefore that the supertonic is always strictly between the contralead and the lead. Thus the subtonic is also strictly between the contralead and the lead. We will examine this first as there are more low-complexity rooted generators in this case, namely 3/2, 7/4, 13/8 and 15/8, with 5/4 and 11/8 being the main exceptions, and with 9/8 contained in the MOSS for 3/2 and 5/4 reachable indirectly through meantone or schismic. It is 3/2, 5/4 and [[7/4] that are of most interest though, as in those cases the generator is simple enough that stacking it twice still constitutes a reasonable consonance, while 11/8 requires sometihng as complex as 121/64. Finally, 15/8 stacked twice is 225/128 (up to octave-equivalence) which can be equated with 7/4 if you temper 225/224. The result is:

tonic < contralead < serviant < antitonic < dominant < lead < tonic AND contralead < supertonic/subtonic < lead

Can we deduce more than this? We can answer this question by considering the extreme cases. The lowest dominant above the antitonic would result in a supertonic just above the contralead (such as 5L 2s in 12edo) and therefore below the dominant. The highest dominant above the antitonic would result in a supertonic just below the contralead, but also below the dominant, as by stacking the dominant twice, the distance from the tonic is doubled. Therefore, the supertonic must be below the dominant and the subtonic must be above the serviant. Therefore:

contralead < supertonic < dominant < lead AND contralead < serviant < subtonic < lead

This justifies the following numeric notation for when dominant > antitonic:

0 (tonic) < 1 (contralead) < 2 (supertonic) ? 4 (serviant) < 5 (antitonic) < 6 (dominant) ? 8 (subtonic) < 9 (lead) < (1)0 (tonic)

Note however that the supertonic may in some cases be above the antitonic (and correspondingly the subtonic may in some cases be below the antitonic). This leaves 3 (the mediant) and 7 (the contramediant) as gap-fillers for below and above 5 (the antitonic) respectively. This does however point to the next place to investigate: when is the supertonic above the antitonic? This would imply that twice the dominant is above 1.5 octaves, or that the dominant is above 900 cents while being below the contralead, thus implying that an EDO approximation must be 9 or above to support such a MOSS (as there must be space for the contralead to be distinct).

Another interesting observation is that this system suggests an at-least-5-note-per-period MOSS because in order for all of the functions 0, 1, 2, 4, 6, 8, 9 to be present, there must be at least 5 notes. That way, there exists at least one mode where the dominant, serviant, supertonic and subtonic are all present relative to the tonic, with the (contra)lead being - at minimum - a reality of the (chromatic) extension of the MOSS, which is always important as it acts as melodic background. Note that the digits whose functions are potentially absent from the minimum MOSS are 1, 3, 5, 7, 9 which is to say all the odd digits, and where 3 and 7 are context-dependent gap-filler functions while 5 is a function unrelated to the generator and instead related to the period-complement symmetry of the functions suggesting a midpoint between adjacent instances of the tonic. Also note that for 5-note MOSSes, the mediant and contramediant necessarily cannot exist in the same mode where the dominant, serviant, supertonic and subtonic are simultaneously present. Yet more interestingly, the desire to include the functions 1, 3, 7, 9 suggests using the child MOSS as the basis for analysing a 5-note and sometimes 6-note (and even 7-note) MOSS in cases where the small step is too large to serve as a true (contra)lead. Meanwhile, if the small step is small enough to serve as a (contra)lead, it will necessarily be confused with either a supertonic or a subtonic, implying an interesting musical reality for such MOSSes. This system thus also suggests that modes which have at least two generators from the tonic in each direction have a unique musical capability and thus modes that do not satisfy this restriction have emergent musical properties based on their very lack of some of those functions.

Examples of functions

  • If 5/4 is taken as the generator and dominant, then 25/16 is a supertonic. Thus 8/5 is a serviant and 32/25 is a subtonic. if 32/25 is equated with 9/7 by tempering 225/224, then 9/7 is a subtonic and 14/9 is a supertonic. This forms a sephiroid (3L7s) scale.
  • if 13/8 is taken as the generator and dominant, then 169/128 is a supertonic, which seems rather complex, but this can neatly be equated with 21/16 by tempering 169/168, which is convenient as we want to preserve the rootedness of the generator when stacking it. Thus 16/13 is a serviant and 32/21 is a subtonic. This also forms a sephiroid (3L7s) scale, but the generator is now above the antitonic rather than below.
  • If 3/2 is taken as the generator and dominant, then 9/8 is a supertonic. Thus 4/3 is a serviant and 16/9 is a subtonic. This is a uniquely very low complexity instance. This forms a diatonic (5L2s) scale.
  • If 7/4 is taken as the generator and dominant, then 49/32 is a supertonic. Thus 8/7 is a serviant and 64/49 is a subtonic. This forms a machinoid (5L1s) scale which extends to a 5L 6s chromatic/extended scale.
  • If 11/8 is taken as the generator and dominant, then 121/64 is a supertonic. Thus 16/11 is a serviant and 128/121 is a subtonic. This is a peculiar case where the supertonic can reasonably be equated with the lead (and correspondingly the subtonic can reasonably be equated with the contralead). Given this, it doesn't seem necessary to introduce any temperings, as leads and contraleads are a more dissonant type of function. This forms an antidiatonic scale which extends to a 2L 7s and 2L 9s chromatic/extended scale, or 11L 2s for very accurately tuned 11/8's.
  • If 15/8 is taken as the generator and dominant, then 225/128 is a supertonic. Thus 16/15 is a serviant and 256/225 is a subtonic. This is a peculiar case where the dominant can reasonably be equated with the lead (and correspondingly the serviant can reasonably be equated with the contralead). Furthermore, if we temper 225/224 then the supertonic is 7/4 and the subtonic is 8/7, meaning the supertonic and subtonic are noticeably more consonant and stable both harmonically and functionally than the dominant and serviant. Due to the close proximity of the generator to the tonic, the MOSSes formed by this get quite big before the generator changes from being the small step to being the large step, with 1L 9s being the last MOSS where 16/15 is the small step, and the MOSS after that (assuming near-just tuning of 16/15) is 10L 1s. However, in the case of 10L 1s, the large and small steps are so close in size that you may want to use 11edo as a simplified tuning. This does come at the cost of tempering 225/224 being suboptimal however.

Intervals

For now, I will leave tables for the interval/colour/type namings of the seconds and thirds of my two favourite highly-chromatic EDOs, both of which related to 17-limit Tolermic due to this family tempering many commas I am interested in tempering, and due to the resulting intervals providing a good framework for thinking about interval colours:

Generalised colours of supertonics, subtonics, leads and contraleads

inframinor (AKA 'ultraminor') second aka one quarter-tone: (many things)
subminor second: 30/29 or 29/28 or 28/27 or 27/26 or 26/25
neominor second: 25/24 or 24/23 or 23/22 or 22/21
novaminor (AKA pythminor) second: 21/20 or 20/19 or 19/18
minor second: 18/17 or 17/16 or 16/15
supraminor second: 15/14 or 14/13
subneutral second: 13/12
neutral second AKA three quarter-tones: 12/11
superneutral second: 11/10
submajor second: 10/9 or 21/19
major second: 19/17 or 28/25
novamajor (AKA pythmajor) second: 9/8
neomajor second: 17/15 or flat 25/22
supermajor second: 25/22 or flat-to-just 8/7
ultramajor second: sharp 8/7 or 23/20
semifourth AKA five quarter-tones: 15/13

80 EDO interval colours/types (seconds, thirds, fourths and antitonics/tritones)

 45c inframinor (AKA ~quarter-tone)
 60c subminor (AKA ~third-tone)
 75c neominor
 90c novaminor
105c minor
120c supraminor
135c subneutral (AKA minor neutral)
150c superneutral (AKA major neutral)
165c submajor
180c major
195c novamajor
210c neomajor
225c supermajor
240c ultramajor
255c inframinor (AKA ~semifourth)
270c subminor
285c neominor
300c novaminor
315c minor
330c supraminor
345c subneutral
360c superneutral
375c submajor
390c major
405c novamajor
420c neomajor
435c supermajor
450c ultramajor (AKA ~semisixth)
465c oneiro
480c pentatonic
495c perfect
510c flattone/semiquartal
525c acute/hendrix/mavila
540c ?
555c inframinor
570c neominor
585c minor
600c neutral
615c major
630c neomajor
645c ultramajor
660c ?
675c grave/hendrix/mavila
690c flattone/semiquartal
705c perfect
720c pentatonic
735c oneiro
...
80 EDO MOSS gens table:

(1\10: 120c)
(~1\9: 135c (~~8L1s))
(1\8: 150c)
7L1s: 165c
6L1s: 180c (7L6s) (note 195c (6L7s) is extreme with L/s=6.5)
5L1s: 210c (6L5s), 225c (5L6s)
(1\5: 240c)
5L4s: 255c
4L5s: 270c, 285c
(1\4: 300c)
4L7s: 315c
(~~1\11: 330c (~7L4s))
7L3s: 345c
3L4s: 360c
3L7s: 375c, 390c
3L8s: 405c, 420c, 435c (all also ofc 3L5s; biased to 435c)
(3\8: 450c)
5L3s: 465c
(2\5: 480c)
5L2s: 495c, 510c
7L2s: 525c
2L7s: 540c, 555c, 570c, 585c (all also ofc 2L5s; biased to 540c)

RINGER 80

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.

mode 63 of the harmonic series (corresponding to 125-odd-limit) with added odds from mode 63*2=126 in square brackets:
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):
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

87 EDO interval colours/types (seconds and thirds)

 41.4c fifth-tone
 55.2c ultraminor (AKA quarter-tone)
 69.0c subminor (AKA third-tone)
 82.8c neominor
 96.6c novaminor
110.3c minor
124.1c supraminor
137.9c subneutral (AKA minor neutral)
151.7c superneutral (AKA major neutral)
165.5c submajor
179.3c major
193.1c novamajor
206.9c neomajor
220.7c supermajor
234.5c ultramajor
248.3c semifourth
262.1c ultraminor
275.9c subminor
289.7c neominor
303.4c novaminor
317.2c minor
331.0c supraminor
344.8c subneutral
358.6c superneutral
372.4c submajor
386.2c major
400.0c novamajor
413.8c neomajor
427.6c supermajor
441.4c ultramajor
455.2c semisixth

MOSS names i think are pretty

Note: I am only considering octave-period MOSSes here. Table is an edited version of the standardised version at TAMNAMS#Mos_pattern_names. I made a few small changes of personal preference:

  • 4L 1s I call "pentoid" after the 11-limit 4&5 exotemperament for which practically the entire tuning range of the MOSS is valid due to its impractically low accuracy/high damage (this is evidenced by it also being supported by patent val in EDOs 4+5=9 (the basic tuning) and 4+5+4=14 (the hard tuning), and by the 13b val (the soft tuning) where you take the (only barely) second-best fifth).
  • 1L 6s I find significant as a 7-note scale underlying all nL1s scales for n>=7. I have named it "onyx", which has a variety of aesthetic reasonings for it: "1Ln-ic's" and "nL1-ic's (like, the -ic suffix applied to MOSS names, collectivised for 1Lns and nL1s) sounds like "one-el-en-ics" or "en-el-one-ics" which abbreviated sort of sounds like "one-ics" => "onyx". Then "onyx" sounds sort of like "one-six". Furthermore the onyx mineral comes in many colours and types, which seems fitting given this is the parent scale for a wide variety of MOSSes; specifically of interest being 7L 1s (pine), 8L 1s (subneutralic) and 9L 1s (sinatonic). Finally, the name "onyx" is also supposed to be vaguely reminiscent of "anti-archaeotonic" as "chi" (the greek letter) is written like an "x" (this is related to why "christmas" is abbreviated sometimes as "X-mas") and other than that, the letters "o" and "n" and their sounds are also present in "archaeotonic", and "x" is vaguely reminiscent of negation and multiplication. There is also something like a "y" sound in "archaeotonic" in the "aeo" part (depending partially on your pronounciation).
  • 4L 3s I noticed is unique in that out of 1L 6s, 2L 5s, 3L 4s, 4L 3s and 5L 2s it is the only MOSS pattern that doesn't have both of its child MOSSes named and included. Note that it makes sense to make an exception for 6L 1s as the large number of large steps and small number of small steps makes the range of valid tunings for the generator(s) the most strict of all the 7-note MOSSes. Anyway, as generators close to 6/5 corresponding to kleismic temperament seem to be of especial importance for this MOSS, I consider the 4L 7s pattern important enough to be named, and it had a proposed name of "kleistonic", a name I think is fitting. As a result, 7L 4s could be named "anti-kleistonic", but I think its actually sort of the more natural extension of the 4L 3s scale pattern which has sharper minor thirds in less complex tunings, hence I've opted for naming it "suprasmitonic" instead after its (and to a lesser extent smitonic's) supraminor third generator AKA "sharp minor third" - the origin of the name "smitonic". Note that another reason I think 4L 7s is worth distinguishing and naming is because kleismic temperaments tend to need both more generators and very hard tunings for 4L 3s, as evidenced by the basic tuning of 11edo being way too sharp for kleismic and the hard tuning of 15edo being the sharpest kleismic that could ever make some sense (as it requires, for instance, accepting 720c as an approximation of 3/2 and 400c as an approximation of 5/4). The interval prefixes/abbreviations for 7L 4s are "ssmi" to show preference in comparing it to the underlying 4L 3s scale and as a shortenage of "sharp supraminor third" and "suprasmitonic" correspondingly.
  • I gave names for the interval prefixes and interval name abbreviations for 5L 7s (p-chromatic) and 7L 5s (m-chromatic). For 7L 5s I picked "chrome" as it is strongly befitting of meantone as meantone does the 7-limit efficiently, elegantly and colourfully, and because its a contraction of "chromatic" and the "me" in "meantone". Pronounced like the word. For 5L 7s, in contrast with 5L 7s which is mellow and harmonic, 5L 7s is sharp and active, perhaps like fire, hence I picked "pyr-" from "pyro" and from "[py]thago[r]ean", with the letters "pyr" of course also being for "[p]a[r]a[py]th", "su[p]e[rpy]th" and "ult[r]a[py]yth".
  • Similarly to how I made an exception for the 1Lns scale form of 1L 6s, I'm also making an exception to include the multiMOSS of 6L 2s as echidna (from which the MOSS derives its name) is a strong and important temperament to me with a period of half an octave and where one generator is both 11/10 and 9/7 depending on from which direction you view it, as 11/10 * 9/7 is extremely close to half an octave, the period for this MOSS scale. ("MultiMOSS" stands for "multi-period Moment Of Symmetry Scale".) However, this would not be included if I were to have this as "the semi-official TAMNAMS minimal MOSS name list", because this is a true exception to a lot of the design principles behind choosing this set of MOSSes to name and recognise as important.
TAMNAMS mos names
5-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
2L 3s pentic pent- pent Shortening of pentatonic.
3L 2s antipentic apent- apent Sister MOSS of pentic.
4L 1s pentoid man- man The 11-lim[4&5] exotemperament "pentoid" is practically equivalent.
6-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
5L 1s machinoid mech- mech Named after the 2.9.7.11 5&6 temperament machine.
7-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
1L 6s onyx on- on My name for this scale. It is important as the parent of 7L1s, 8L1s, 9L1s, etc.
2L 5s antidiatonic pel- pel Established name. pel comes from pelog.
3L 4s mosh mosh- mosh Graham Breed's name, from mohajira-ish.
4L 3s smitonic smi- smi From sharp minor third.
5L 2s diatonic none none
6L 1s arch(a)eotonic arch(a)eo- arch A name originally given to 13edo's 6L 1s.
8-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
3L 5s sensoid sen- sen From sensi temperament.
5L 3s oneirotonic oneiro- on A name originally given to 13edo's 5L 3s.
6L 2s echinoid ech- ech From hedgehog and echidna temperaments.
7L 1s pine pine- pine Named after the 11-limit 7&8 temperament porcupine.
9-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
2L 7s joanatonic jo- jo From joan temperament.
4L 5s orwelloid or- or From orwell temperament.
5L 4s semiquartal sequar- seq From half-fourth.
7L 2s superdiatonic arm- arm Established name. arm- comes from armodue theory.
8L 1s subneutralic blu- blu From subneutral 2nd generator. blu comes from bleu temperament.
10-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
3L 7s sephiroid sephi- seph Named after the 2.5.11.13.17 3&10 temperament sephiroth.
7L 3s dicotonic dico- dico Named after the 11-limit 7&10 temperament dichotic.
9L 1s sinatonic sina- si Named after the sinaic that generates the pattern, which in turn is named after Ibn Sina.
11-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
4L 7s kleistonic klei- klei- Named after kleismic and its extensions.
7L 4s suprasmitonic ssmi- ssmi- Generated by (sharp) supraminor thirds.
12-note mosses
Pattern Name Interval prefix[1] Abbreviation[2] Notes
5L 7s p-chromatic pyr- pyr- p- is for "pure or sharp (para-/super-)pyth(agorean).
7L 5s m-chromatic chrome- chrome- m- is for "(maybe-mellow) meantone chromatic".
  1. 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 used in interval names, e.g. perfect 3-oneirostep
  2. 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 used in abbreviations of interval names, e.g. P3ons

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:

>>> sg = [2, 3, 7, 11, 13, 17, 19]
>>> [unfact(x,sg) for x in inconsistent_ivs_by_val( [fact(x,sg) for x in odd_lim(21,[5,15],complements=False)], val(sg,ed(104)) )]
[(11, 7), (19, 14), (21, 16), (19, 17), (13, 7), (21, 13), (21, 19), (17, 9), (17, 12), (7, 6), (19, 12), (19, 18), (11, 8), (21, 11), (13, 11), (21, 17), (13, 8), (7, 4), (17, 16), (17, 13), (11, 9), (19, 16), (11, 6), (19, 13), (13, 9), (13, 12), (17, 11), (9, 7), (17, 14), (19, 11)]

This code is licensed under the AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html), a version of the AGPL corresponding to the GPLv3.

# this code is licensed under the AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>

import math
from math import log2
import functools

# prod() computes the product of a list of things.
# It can be thought of as the multiplicative counterpart to sum().
# EG: `prod([3,2,4])` evaluates to (3*2)*4 = 24.
prod = functools.partial(functools.reduce, lambda x,y: x*y)

# IMPORTANT NOTE ABOUT USING SUBGROUPS:
# if you're using a subgroup in the form of a list of integers > 1,
# define it as a short temporary variable and pass it in to every function
# that asks for a subgroup parameter "p"; the "p" in the gen_prime(p) function below
# is sort of an exception because it expects a full-prime-limit primes-only subgroup.
# ALSO note that the only type of subgroup currently supported is, as aforementioned,
# a list of integers > 1, as otherwise factorisation gets complicated.

# p is a complete list of (at least 2) primes up to and including some prime.
# gen_prime(p) appends the next prime to p and returns that prime.
def gen_prime(p):
	c = p[-1] + 2
	while True:
		i = 0
		found_divisor = False
		while c >= p[i]*p[i]:
			if c % p[i] == 0:
				found_divisor = True
				break
			i+=1
		if not found_divisor: # aka if c is prime
			p.append(c)
			return c
		c += 2
primes = [2,3]

# Cannot give index error (unless you run out of memory) and
# allows implicit convenient access to global variable `primes`
def prime_idx(i):
	global primes
	while i >= len(primes):
		gen_prime(primes)
	return primes[i]
# Returns a list of primes up to and (if applicable) including the limit,
# representing the corresponding prime limit.
def prime_lim(limit): # includes limit (if applicable)
	l = []
	i = 0
	while prime_idx(i) <= limit:
		l.append(prime_idx(i))
		i += 1
	return l
lim = prime_lim
prime_limit = prime_lim
# Can be thought of as the conceptual opposite of strip_list_of_right().
def right_justify_list(l, n, justify_with=0):
	while len(l) < n:
		l.append(justify_with)
	return l
# Can be thought of as the conceptual opposite of right_justify_list().
# Note that what_to_strip is a list of elements eligible for stripping (removal).
def strip_list_of_right(l, what_to_strip=[0]):
	while len(l)>0 and (l[-1] in what_to_strip):
		l.pop()
	return l

# If p is None (default):
#    Factorises a positive integer into a list of the exponents/powers
#    of corresponding primes starting with the exponent/power of 2.
#    Note that any non-positive integer will give an empty list/factorisation
#    and that this is the same factorisation that the number 1 gets.
# Otherwise:
#    Attempts factorisation based on a list of positive integers p
#    which can be used to try to divide n down to 1.
#    If n cannot be divided down to 1, an empty list is returned.
#    Note that two versions of each function dealing with factorisations exist;
#    one of which specifying such a list of positive integers p.
#    Also note that the positive integers are tried in the order they are listed.
def fact_int(n, p=None):
	if p==None:
		f = []
		while n > 1:
			f.append(0)
			while n % prime_idx(len(f)-1) == 0:
				f[-1] += 1
				n //= prime_idx(len(f)-1)
		return f
	else:
		f = [0]*len(p)
		i = 0
		while n > 1 and i < len(p):
			while n % p[i] == 0:
				n //= p[i]
				f[i] += 1
			i += 1
		return f if n==1 else []
# Note that this works for negative exponents of primes too but produces
# a floating point value rather than an exact rational representation.
# Also note that this failing when p!=None and f==[] is intentional
# because [] represents a failed subgroup factorisation in the subgroup case.
def unfact_int(f, p=None):
	return (
		1 if f==[] and p==None
		else prod([ prime_idx(i)**f[i] for i in range(len(f)) ]) if p==None
		else prod([ p[i]**f[i] for i in range(len(f)) ])
		)

# Note: "fact" stands for "factorise(d form (of))/factorisation".
# Note: when combining two factorisations in some way,
#       both must either be normal or subgroup-based.
def mul_fact(*fs):
	max_len = max([ len(f) for f in fs ]) # to convert list to tuple next line
	fs = (*[ right_justify_list(f.copy(),max_len) for f in fs ],) # conversion
	result = [ sum([ fs[n][i] for n in range(len(fs)) ]) for i in range(max_len)]
	return result
def mul_iv(*rs):
	return iv( prod([ r[0] for r in rs ]), prod([ r[1] for r in rs ]) )
def recip_fact(f, p=None): # useless parameter p used for niceness
	return [-i for i in f]
def recip_iv(r):
	return (r[1], r[0])
def div_fact(f1, f2):
	return mul_fact(f1, recip_fact(f2))
def div_iv(r1, r2):
	return mul_iv(r1, recip_iv(r2))
def add_iv(r1, r2):
	return iv( r1[0]*r2[1] + r2[0]*r1[1], r1[1]*r2[1] )
def sub_iv(r1, r2):
	return iv( r1[0]*r2[1] - r2[0]*r1[1], r1[1]*r2[1] )
def as_float(x, p=None):
	if type(x)==list: # monzo (factored)
		uf = unfact(x, p)
		return uf[0] / uf[1]
	elif type(x)==tuple: # ratio (unfactored)
		return x[0] / x[1]
	else: # assumed to already be a float (or integer)
		return x
def S(k):
	return (k*k, k*k-1)
s = S
def convert(x, totype, p=None, subharmonic=False):
	if totype==float:
		return as_float(x,p)
	if totype==int:
		if type(x)==int:
			return x
		interval = 0
		if type(x)==tuple:
			interval = iv(x[0],x[1])
		if type(x)==list:
			interval = unfact(x,p)
		if len(fact_int(interval[1]))==1 or interval[1]==1: # if harmonic (denominator in 2-lim)
			return interval[0]
		if subharmonic and (len(fact_int(interval[0]))==1 or interval[0]==1): # if subharmonic (numerator in 2-lim)
			return interval[1]
		elif subharmonic:
			raise TypeError("interval is not harmonic (/2^n) or subharmonic (2^n/)")
		else:
			raise TypeError("interval is not harmonic (/2^n); note 2^n/ conversion to int is off by default (subharmonic=False)")
	if totype==tuple:
		if type(x)==int:
			return (x,1)
		if type(x)==tuple:
			return x
		if type(x)==list:
			return unfact(x,p)
	if totype==list:
		if type(x)==int:
			return fact_int(x,p)
		if type(x)==tuple:
			return fact(x,p)
		if type(x)==list:
			return x
def fact(r, p=None):
	if p==None:
		return strip_list_of_right(div_fact( fact_int(r[0]), fact_int(r[1]) ))
	else:
		numer = fact_int(r[0],p)
		denom = fact_int(r[1],p)
		if len(numer)>0 and len(denom)>0 and r[0]!=r[1]:
			return div_fact(numer,denom)
		else:
			return []
def unfact(f, p=None):
	return unfact_int([max(n,0) for n in f],p), unfact_int([-min(d,0) for d in f],p)
# to simplify an existing rational "x" expressed as a (pythonic) 2-tuple, use: "iv(*x)"
def iv(n, d, p=None): # provides automatic reduction to simplest form
	if n==0:
		return (0, 1)
	r = unfact(fact( (abs(n),abs(d)),p ),p)
	return (sgn(n*d)*r[0], r[1])
def striv(n, d=None): # accepted formats: [(n, d, s)], [(n, d), s], [n, d], [n=h]
	if d==None and type(n)==tuple and type(n[0])==int and type(n[1])==int:
		if len(n)==2:
			return str(n[0])+'/'+str(n[1])
		elif len(n)==3 and type(n[2])==int: # (future expansion)
			return str(n[0])+'/'+str(n[1])+': '+str(n[2])
	if type(d)==int and type(n)==tuple and type(n[0])==int and type(n[1])==int:
		return str(n[0])+'/'+str(n[1])+': '+str(d) # (future expansion)
	if type(n)==int:
		if d==None:
			return str(n)+'/1'
		elif type(d)==int:
			return str(n)+'/'+str(d)
	return '[not interval]'
# use str.ljust(width,space) and str.rjust(width,space) for left- and right-padding and
# use str.center(width,space) if you dont want to centre around a specific character (e.g '/')
def pad(what,width,centre='/',space=' '):
	if len(what) >= width:
		return what
	lspaces = (width+1)//2
	rspaces = width - lspaces
	c = what.find(centre)
	if c != -1: # if centre present in what
		c += (len(centre)+1)//2
	else:
		c = (len(what)+1)//2
	lspaces -= c
	rspaces -= len(what)-c
	if lspaces >= 0 and rspaces >= 0:
		return lspaces*space + what + rspaces*space
	if rspaces < 0 and lspaces >= 0:
		return (lspaces+rspaces)*space + what
	if lspaces < 0 and rspaces >= 0:
		return what + (lspaces+rspaces)*space
	return what
# Converts a prime factorisation into a prettier and potentially
# more readable (simple) string representation. (Not for subgroup-factorisations.)
# redundancy is either 0, 1 or 2 and has the following effects:
# redundancy=0 (default):
#    Only includes primes with nonzero exponent and omits the exponent if
#    if the exponent is 1 (AKA if the prime would be followed by '^1').
# redundancy=1:
#    Same as redundancy=0 except exponents are always explicit ('^1' is included).
# redundancy=2:
#    Shows all primes up to the largest prime with nonzero exponent.
def fact_to_str(f, redundancy=0):
	sl = []
	for i in range(len(f)):
		if redundancy==2 or f[i]!=0:
			sl.append( str(prime_idx(i))
				+( '^'+str(f[i]) if redundancy>0 or f[i]!=1 else '' )
				)
	if len(sl) == 0:
		return '1'
	return ' * '.join(sl)

# The closest approximation of r in (d)ED(n) is `round(in_ed(r,d,n))`, or
# more commonly, the closest approximation of r in n-EDO is `round(in_ed(r,n))`.
def steps(r, et2):
	return math.log(convert(r,float),2) / et2
# Note that error is measured as deviation from the exact value of r in
# (divisions)ED(octave_equivalent). The unsigned AKA absolute error of the
# closest approximation of r in n-EDO would be written `abs(err_in_ed(r,n))`.
def step_err(r, et2):
	exact = steps(r, et2)
	return round(exact) - exact
# For use with steps(), step_err(), etc. as the et2 argument
def ed(equal_divisions, of_n=2):
	return math.log(of_n,2)/equal_divisions
def pval(f, et2, p=None):
	f = convert(f,list)
	if p==None:
		p = prime_lim(prime_idx( len(f)-1 ))
	return sum([ f[i]*round(steps(p[i],et2)) for i in range(len(f)) ])
def map_iv(val_map, x, sg=None): # assumes same harmonic subgroup
	if not sg:
		x = convert(x,list)
	elif type(x)!=list:
		x = fact(x,sg)
	return sum([ x[i]*val_map[i] for i in range(len(x)) ])
# returns the signed error of an interval according to a val map, afterwards optionally specifying a specific subgroup and/or equave;
# note that the equave can be used to compare to a tempered-octave/tritave tuning as it doesn't affect the mapping by map_iv
# (which under normal usage should always output an integer, though you can make it output floats too but the codebase isn't designed for handling that)
def iv_map_err(x, m, sg_or_equave_1=None, sg_or_equave_2=None): # assumes m[0] = period/equave
	sg = sg_or_equave_1 if type(sg_or_equave_1)==list else sg_or_equave_2 if type(sg_or_equave_2)==list else None
	equave = sg_or_equave_1 if type(sg_or_equave_1) in [int,float] else sg_or_equave_2 if type(sg_or_equave_2) in [int,float] else 2
	return map_iv(m,x,sg) - steps(x,ed(m[0],equave))
def sgn(x):
	if x > 0:
		return 1
	elif x < 0:
		return -1
	else:
		return 0
def idx_alpha(c):
	n = ord(c)
	if n>=ord('a') and n<=ord('z'):
		return n-ord('a')
	elif n>=ord('A') and n<=ord('Z'):
		return n-ord('A')+26
	elif c=='#': # the hash/octothorpe character is
		return 52 # the beginning of a record prime gap
	else: # prime_idx(53) = 251 is the highest prime reachable
		return 53 # with extended warts
def alphawarts(alphabet, gens = prime_lim(256)):
	return [gens[idx_alpha(c)] for c in alphabet]
# the wart_tendency determines if a wart's behaviour is:
# 0: to change to second-best mapping (default)
# 1: to change to one-step-sharper mapping always
# -1: to change to one-step-flatter mapping always
# currently only one wart per gen is supported;
# if you want more its easier to specify the val yourself plus note that
# combined with wart_tendency you can sort of specify up to 2 warts per gen
def make_val(gens, et2, warts = [], wart_tendency = 0):
	val = []
	if type(warts)==str:
		warts = alphawarts(warts,gens)
	for g in gens:
		gen_steps = steps(g,et2)
		pval_steps = round(gen_steps)
		if g in warts:
			if wart_tendency != 0:
				val.append(pval_steps + wart_tendency)
			else:
				val.append(pval_steps - sgn(pval_steps - gen_steps))
		else:
			val.append(pval_steps)
	return val
val = make_val
def odd_lim(lim, remove=set(), add=set(), complements=True):
	odds = set([i for i in range(1,lim+1,2) if i not in remove])
	odds |= set(add)
	ivs = set()
	for numer in odds:
		for denom in odds:
			if numer > denom:
				oct_denom = denom
				while oct_denom*2 < numer:
					oct_denom *= 2
				ivs|={iv(numer,oct_denom)}
	if complements:
		ivs |= set([ iv(i[1]*2,i[0]) for i in ivs ])
	return sorted(ivs,key=lambda x: x[0]/x[1])

def subgroup(ivs):
	sg = []
	for i in ivs:
		f = fact(i)
		for j in range(len(f)):
			if f[j] and prime_idx(j) not in sg:
				sg.append(prime_idx(j))
	return sorted(sg)

# if x is a monzo (which is assumed if type(x) not in [int,tuple]), assumed to be full prime limit (or check would be redundant)
def in_subgroup(x, sg):
	if type(x)==int:
		x = (x, 1) # ensure (x, y) = x/y tuple form if not a monzo
	if type(x)==tuple and x[0]==x[1]:
		return True
	return len( fact(x,sg) if type(x)==tuple else fact(unfact(x),sg) )==len(sg)
def powerset(s):
	pset = set()
	for i in range(1 << len(s)):
		pset |= {frozenset([ s[j] for j in range(len(s)) if (i & (1<<j)) ])}
	return pset
def inconsistent_ivs_by_val(ivs, val, threshold=1/2, sg=None, equave=None):
	return [ i for i in ivs if abs(iv_map_err(i,val,sg,equave))>=threshold ]
def deduce_subgroup(r):
	if type(r)==tuple:
		r = fact(r)
	elif type(r)==int:
		r = fact_int(r)
	# else assumed to be monzo
	return [prime_idx(i) for i in range(len(r)) if r[i]!=0]
def get_first_nonpositive(val,lim,start=1):
	if lim % 2 == 1:
		lim += 1
	for i in range(start,lim):
		f = fact( (i+1,i) )
		if not (map_iv(val,f) > 0):
			return (i,f)
	return (lim,[])
def firstnp(val,odd_lim,start=1):
	r = get_first_nonpositive(val,odd_lim,start)
	return (r[0],r[1]) if r[0]%2==0 else (r[0]-1,r[1])
def firstnp_edowarts(edo, warts, tendency = 0, pl = []):
	if pl == []:
		pl = prime_lim(2*edo-1)
	return firstnp( make_val(pl,ed(edo),warts,tendency), 2*edo-1 )
# NOTE: this* condition is a small optimisation and restriction that says if you want prime p fixed then assume full p-odd-limit must be achieved.
def base_ringer(edo, tendency = 0, fixed_subgroup = [2], initial_warts = None):
	if 2 not in fixed_subgroup:
		fixed_subgroup = [2] + fixed_subgroup
	pl = prime_lim(2*edo-1)
	best_warts = initial_warts
	if initial_warts==None:
		best_warts = [ [] ]
	while True:
		last_size = len(best_warts)
		for warts in best_warts:
			(barrier,f) = firstnp_edowarts(edo,warts,tendency,pl)
			wartable = [p for p in deduce_subgroup(f) if p not in fixed_subgroup+warts]
			for new_warts in powerset(wartable):
				if( firstnp_edowarts(edo,warts+list(new_warts),tendency,pl)[0] > max(fixed_subgroup)+1 # *SEE NOTE ABOVE
				and warts+list(new_warts) not in best_warts ):
					best_warts.append(warts+list(new_warts))
		if last_size==len(best_warts): # if no new vals found this time
			best_warts.sort(key=lambda warts: -firstnp_edowarts(edo,warts,tendency,pl)[0])
			best_lim = firstnp_edowarts(edo,best_warts[0],tendency,pl)[0]
			result = (best_lim,[])
			initial_warts = [ [] ]
			i = 0
			while (i<len(best_warts) and
				firstnp_edowarts(edo,best_warts[i],tendency,pl)[0]==best_lim):
				result[1].append(best_warts[i])
				i += 1
			return result
# if the val given splits a 2-step superparticular interval into two 1-step superparticular intervals without modification
# then it will show that, otherwise it will just show what the superparticular interval is mapped to by the val.
# used for completing building Ringer scales. 
def showRinger(edo, warts = None, tendency = 0):
  if warts==None:
    warts = []
  v = val(lim(edo*4),ed(edo),warts,tendency)
  for h in range(v[0],v[0]*2):
    x = (h+1,h)
    edosteps = map_iv(v,x)
    if edosteps==2 and map_iv(v,(2*h+1,2*h))==map_iv(v,(2*h+2,2*h+1))==1:
      print(striv( (2*h+1,2*h  ),1 ))
      print(striv( (2*h+2,2*h+1),1 ))
    else:
      print(striv(x,edosteps))
def rwartable(edo,warts,tendency=0):
	l = firstnp_edowarts(edo,warts,tendency)[0]
	results = []
	for p in lim(l):
		w = []
		if p in warts:
			w = [i for i in warts if i!=p]
		else:
			w = warts+[p]
		if firstnp_edowarts(edo,w,tendency)[0] >= l:
			results.append(p)
		return results

def maxringer(edo):
	return max(base_ringer(edo)[0],base_ringer(edo,1)[0],base_ringer(edo,-1)[0])

# this function is experimental and has a higher chance of bugs/unexpected behaviour
def genRinger(oddlim,edo,warts,tendency=0):
	if oddlim%2==1:
		oddlim+=1
	h=[k for k in range(oddlim//2,oddlim+1)]
	i = 0
	basev=val(lim(oddlim*4),ed(edo),warts,tendency)
	wadd=[]
	while i+1 < len(h):
		x=iv(h[i+1],h[i])
		m = map_iv(basev,x)
		if m==1:
			i+=1 # no filler needed
			continue
		elif m>1:
			y=iv(h[i]*2+1,h[i]*2)
			if in_subgroup(y,lim(oddlim)):
				if map_iv(basev,y) < m:
					h = h[:i+1] + [h[i]*2+1] + h[i+1:]
					i+=2
					continue
				else:
					print(striv(x)+' is mapped to 2 steps but '+striv(y)+' is mapped to '+str(map_iv(basev,y))+' steps!')
					i+=1
					continue
			else:
				f=fact_int(h[i]*2+1)
				wp=prime_idx(len(f)-1)
				if map_iv(basev,y)==1:
					h = h[:i+1] + [h[i]*2+1] + h[i+1:]
					i+=2
					continue
				else:
					m2=map_iv( val(lim(oddlim*4),ed(edo),warts+wadd+[wp],tendency), y )
					if m2 < m:
						h = h[:i+1] + [h[i]*2+1] + h[i+1:]
						wadd.append(wp)
						i+=2
						continue
					else:
						print(striv(y)+'\'s mapping could not trivially be fixed!')
						i+=1
						continue
		else:
			print(striv(x)+' is mapped to '+str(m)+' steps! (bad val)')
			i+=1
			continue
	return ':'.join([ str(k)+('' if k<=oddlim else '/2') for k in h ])

def toneji(strneji,minimalmode=False):
	strhs = strneji.split(':')
	denoms = [ int(h.split('/')[1]) for h in strhs if '/' in h ]
	multiplier = math.lcm(*denoms) # note: math.lcm not present in some older versions of Python 3
	neji = [ int(h.split('/')[0]) * multiplier // int(h.split('/')[1])
		if '/' in h else int(h) * multiplier for h in strhs ]
	if minimalmode:
		neji = neji[:-1] # remove octave-duplication of lowest harmonic
		while neji[-1]%2==0:
			neji = [neji[-1]//2] + neji[:-1]
		neji.append(neji[0]*2) # add back octave-duplication of lowest harmonic
	return neji

def worstneji(neji,n=1,**val_or_warts):
	if type(neji)==str:
		neji = toneji(neji)
	v = val_or_warts.get('val') or val_or_warts.get('v')
	# in the below or expressions, the rightmost value is the default
	warts = val_or_warts.get('warts') or val_or_warts.get('warted') or val_or_warts.get('w') or []
	tends = val_or_warts.get('tendency') or val_or_warts.get('tends') or val_or_warts.get('tend') or val_or_warts.get('t') or 0
	if warts and type(v)==list:
		print('WARNING: can\'t give both a val and warts at the same time! using val...')
	if not v or type(v)==int: # if an explicit full val map (list of ints) was not specified, deduce the val
		v = val( lim(neji[-1]), ed(len(neji)-1) if not v else ed(v), warts if warts else [], tends if tends else 0 )
	ineji = [( iv(x,y), iv_map_err((x,y),v) ) for x in neji for y in neji]
	ineji.sort(key=lambda xerr: -xerr[1]) # (no duplicates; use positive errors)
	for x,err in ineji[:n]:
		print(striv(x)+':',err)
	if not val_or_warts.get('suppress') and not all([ map_iv( v, (neji[i+1],neji[i]) )==1 for i in range(len(neji)-1) ]):
		print('bad val (scale is potentially not CS); see below:')
		for i in range(len(neji)-1):
			x = iv(neji[i+1],neji[i])
			print(striv( x, map_iv(v,x) ))
# reduce with harmonic integer equave "ave" into range [1, ave)
def reduce(x,ave=2,minimum=1): # CURRENTLY DOES NOT SUPPORT NON-INTEGER EQUAVES
	x = convert(x,tuple)
	while x[0] >= x[1] * ave:
		if x[0] % ave == 0:
			x = (x[0]//ave, x[1])
		else:
			x = (x[0], x[1]*ave)
	while x[0] < x[1]:
		if x[1] % ave == 0:
			x = (x[0], x[1]//ave)
		else:
			x = (x[0]*ave, x[1])
	while as_float(x) < minimum:
		x = mul_iv(x,(ave,1))
	return iv(x[0], x[1])


def vector_avg_w(vs,weights=None):
	if not weights:
		weights=[ 1.0 for v in vs ]
	return [ sum([ vs[vi][i]*weights[vi] for vi in range(len(vs)) ])/sum(weights) for i in range(len(vs[0])) ]
def length(v):
	return sum([ c**2 for c in v ])**(1/2)
def turns(v): # assumes v is 2D vector; turns expressed as unit up being 0 turns and unit left being -1/4 turns
	return math.atan2(v[0],v[1]) # assuming 0th/first coordinate (x) is right and 1th/second coordinate (y) is up
# [0,1] (unit up) is exactly in-tune with et2; note the below:
# mean = turns(result)/math.tau, variance = 1 - length(result), badness = length(result)
def circular_mean(et2,harmonics,weights=None):
	if weights==True: # interpret as primelike subgroup; use commonness in harmonic series
		weights = [ 1/math.log(h,2) for h in harmonics ]
	herr=[ (h,step_err(h,et2)) for h in harmonics ]
	return vector_avg_w([ [math.sin(math.tau*e[1]),math.cos(math.tau*e[1])] for e in herr ],weights)
def circular_normalise(e,m): # into range -1/2, +1/2
	return (e-m)%1 if (e-m)%1 < 0.5 else (e-m)%1 - 1
def circular_step_err(x,et2,m):
	return circular_normalise(step_err(x,et2),m)

def orderedapproximator(et2,startingharmonics,maximalharmonics,n=None,weights=None,noisy=True):
	currentharmonics=startingharmonics.copy()
	oldl = 1
	if n==None:
		n = -1
	widest = max([ len(str(reduce(h)[0])) for h in maximalharmonics ])
	while set(currentharmonics)!=set(maximalharmonics) and (
		n!=0 if type(n)==int else not set(n)<set(currentharmonics)
		):
		if type(n)==int:
			n -= 1
		oldl = length(circular_mean(et2,currentharmonics,weights))
		addable = [ h for h in maximalharmonics if h not in currentharmonics ]
		addable.sort(key=lambda i:length( circular_mean(et2,currentharmonics+[i],weights) ))
		currentharmonics.append(addable[-1])
		if noisy:
			cm = circular_mean(et2,currentharmonics,weights)
			print( str.rjust( str(reduce(addable[-1])[0]), widest ), '+',
				str( 100*(oldl-length( cm )) )[:6] + ',',
				str( math.atan2(cm[0],cm[1])/math.tau*360 )[:6],
				'('+str( 100 - length(cm)*100 )[:5]+'% out)'
				)
	return currentharmonics


# below is not well tested! consider yourself warned.
def scalar_mul_fact( s, f ):
	return [ s*entry for entry in f ]

# IMPORTANT: the format is [a, b, ...] for each prime in order, so it expects the transpose() of the mapping as documented on the xen wiki
def temp_coord_iv( tmp, x ):
	monzo = convert(x,list)
	return mul_fact(*[ scalar_mul_fact(monzo[i],tmp[i]) for i in range(len(monzo)) ])
def transpose(M):
	Mt = [ [0]*len(M) for i in range(max([ len(row) for row in M ])) ]
	for j in range(len(Mt)):
		for i in range(len(Mt[0])):
			if len(M[i]) > j:
				Mt[j][i] = M[i][j]
	return Mt

def temp_gen_iv( tmp, gens, x ):
	return sum([ temp_coord_iv(tmp,x)[i]*gens[i] for i in range(len(gens)) ])

def temp_max_err( tmp, gens, ol ):
	worstiv = (1,1)
	maxerr = 0.0
	for x in ol:
		e = temp_gen_iv( tmp, gens, x ) - steps(as_float(x),ed(1200))
		if abs(e) > abs(maxerr):
			maxerr = e
			worstiv = x
	print(striv(worstiv),str(maxerr)+'c')

def temp_err( tmp, gens, ol ):
	worstupperiv = (1,1)
	maxerr = 0.0
	worstloweriv = (1,1)
	minerr = 0.0
	for x in ol:
		e = temp_gen_iv( tmp, gens, x ) - steps(as_float(x),ed(1200))
		if e > maxerr:
			maxerr = e
			worstupperiv = x
		if e < minerr:
			minerr = e
			worstloweriv = x
	print( striv(worstupperiv), str(maxerr)+'c' )
	print( striv(worstloweriv), '-'+str(-minerr)+'c' )


def bright_gen(nL, ns): # credit to inthar for this function
	nL, ns = nL//math.gcd(nL, ns), ns//math.gcd(nL, ns)
	path = []
	while nL > 1 or ns > 1:
		path.append((nL, ns))
		nL, ns = min(nL, ns), abs(nL - ns)
	result = [1, 0]
	for j in range(len(path)-1, -1, -1):
		child_nL, child_ns = path[j][0], path[j][1]
		if child_nL > child_ns:
			result = [child_nL - (result[0] + result[1]), child_ns - result[0]]
		else:
			result = [result[0], result[0] + result[1]]
	return result

def bright_gens(nL, ns):
	r = bright_gen(nL, ns)
	return [iv(r[0], nL), iv(r[0]+r[1], nL+ns)]
def least_gens(nL, ns):
	d = math.gcd(nL,ns)
	r = bright_gens(nL//d, ns//d)
	if as_float(r[0])>=1/2:
		r[0] = sub_iv((1,1), r[0])
	if as_float(r[1])>=1/2:
		r[1] = sub_iv((1,1), r[1])
	if as_float(r[0]) > as_float(r[1]):
		r = [r[1], r[0]]
	return [mul_iv( r[0], (1,d) ), mul_iv( r[1], (1,d) )]
def strmoss(nL, ns, period=1200):
	r = least_gens(nL, ns)
	return( str(nL)+'L '+str(ns)+'s has range '
		+str(r[0][0])+'\\'+str(r[0][1])+' to '+str(r[1][0])+'\\'+str(r[1][1])+' AKA '
		+str( as_float(r[0])*period )[:6]+' - '+str( as_float(r[1])*period )[:6]
		+(' (period='+str(period)+')' if period!=1200 else '') )

def mediant(r1, r2):
	return (r1[0] + r2[0], r1[1] + r2[1])
def mediant_iv(r1, r2): # for convenience; provides simplified form
	return iv(*mediant(r1,r2))
def mulmediant(r, k):
	return (r[0]*k, r[1]*k)

def gs(gs,n=0,ave=2):
	if n==0:
		n = len(gs)*2 + 1
	scale = [(1,1)]
	gi = 0
	while len(scale) < n:
		new_iv = reduce(mul_iv( scale[-1], gs[gi] ),ave)
		gi = (gi+1)%len(gs)
		while new_iv in scale:
			new_iv = reduce(mul_iv( new_iv, gs[gi] ),ave)
			gi = (gi+1)%len(gs)
		scale.append(new_iv)
	scale.sort(key=lambda x: as_float(x))
	scale.append((ave,1))
	return scale[1:]
ags = gs

# 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."
def cs(scale_in):
	stepmap = dict()
	scale = scale_in.copy()
	n = len(scale)
	scale = scale + [ mul_iv(x,scale[-1]) for x in scale ]
	for j in range(1,(n+1)//2):
		for i in range(n):
			interval = div_iv( scale[i], scale[i+j] )
			if interval in stepmap:
				if stepmap[interval] != j:
					return [interval, (i+1)%n, j]
			else:
				stepmap[interval] = j
	return []

def rotate(scale,n,p=(2,1)):
	if n==0:
		return scale.copy()
	rs = scale + [ mul_iv(x,p) for x in scale ]
	return [ div_iv( x, rs[n-1] ) for x in rs[n:len(scale)+n] ]
def equiv_scales(x,y,p=(2,1)):
	if len(x)!=len(y):
		return 0
	x = [ iv(i[0],i[1]) for i in x.copy() ]
	y = [ iv(i[0],i[1]) for i in y.copy() ]
	if x==y:
		return len(x)
	for k in range(len(x)):
		if rotate(x,k,p) == y:
			return k
	return 0

# generates a detempering of any EDO into a minimal-complexity set of octave-reduced prime harmonics;
# only uses patent val primes as else itd not only be nontrivial but lead to more questionable scales
def primer(edo, prime_octave=False):
	primerscale = [placeholder for placeholder in range(0,edo+1)]
	k = 0
	while not all([ type(x)==tuple for x in primerscale ]):
		prime_iv = reduce( (prime_idx(k),1) )
		stepsofedo = round(steps( as_float(prime_iv), ed(edo) ))
		if type(primerscale[stepsofedo])==int:
			primerscale[stepsofedo] = prime_iv
		k += 1
	if prime_octave:
		return primerscale[1:]
	return primerscale[1:-1] + [(2,1)] # replace the prime at the octave with 2/1

# note: it's easy for any pythonista to write the equivalent print statement out manually,
# but i've included it here for brevity/convenience as it's common to want to do to get
# smth compatible with EG ScaleWorkshop or other tuning formats for ease of copypasting
def print_scale(scale):
	print('\n'.join([ striv(x) for x in scale ]))

# NOTE: requires you to manually correct non-monotonic mappings of intervals in outputted table
# IMPORTANT: can be used to generate interval tables for edos for the xen wiki!
# use wiki=[prime limit] to only link intervals of the wiki-prime-limit
def interpret_edo(edo,ol=17,sg=[],no=[],add=[],styler=lambda x,relerr: striv(x) if abs(relerr) < 1/2 else '*'+striv(x)+'*',dec='*',wiki=0):
	considered_bad = styler if type(styler)==int or type(styler)==float else 1/2
	if considered_bad != 1/2 or dec != '*': # if a custom bound was provided instead of a styler or a decoration was provided
		styler=lambda x,relerr: striv(x) if abs(relerr) < considered_bad else dec+striv(x)+dec[::-1]
	# else use default styler if no custom decoration was provided and the styler is not a numeric type
	print(edo,'EDO interpretation')
	if not sg:
		sg = lim(ol)
	odds = [odd for odd in range(1,ol+1,2) if in_subgroup(odd,sg) and odd not in no] + add
	sedo = 0
	if wiki:
		def hyperlink(x):
			return '[['+striv(x)+']]' if prime_idx(len(fact(x))-1) <= wiki else striv(x)
		styler=lambda x,relerr: hyperlink(x) if abs(relerr) < considered_bad else dec+hyperlink(x)+dec[::-1]
	for x in sorted( odd_lim(1,[],odds), key=lambda x: steps(x,ed(edo)) ):
		sedoofx = pval(x,ed(edo))
		relerr = steps(x,ed(edo)) - sedoofx
		if sedoofx!=sedo:
			sedo = sedoofx
			if wiki:
				print('\n|-\n| '+str(sedo)+'\n| '+str( int(1200/edo*sedo * 100 + .5)/100 )+'\n| '+styler(x,relerr),end='')
			else:
				print('\n'+str(sedo)+'\\'+str(edo)+': '+styler(x,relerr),end='')
		else:
			print(', '+styler(x,relerr),end='')
	print()

def bestprodedos(ivs,edos=range(1,201)):
	prodedos = []
	for edo in edos:
		prodivs = prod([ abs( pval(x,ed(edo)) - steps(x,ed(edo)) )*4 for x in ivs if len(fact(x)) > 1 ])
		prodedos.append((edo,prodivs))
	prodedos.sort(key=lambda edo_prod: edo_prod[1])
	return prodedos

# utility function for converting a xen wiki style temperament to a list of lists where each list (row) contained (in the list of lists) represents a generator
def totemp(str_in):
	str_in = ''.join([ c for c in str_in if c.isalnum() or c in ',- ' ])
	rows = str_in.split(',')
	M = []
	for row in rows:
		M.append([ int(x) for x in row.strip().split(' ') ])
	return M

def identity_M(dim): # dim by dim identity matrix (of dimension dim); self-explanatory
	return sum([[ [0]*i + [1] + [0]*(dim-1-i) for i in range(dim) ]],[])

# leave 2nd parameter unspecified for odd-limit, use 1 for tenney height (log2(a*b)), 2 for benedetti (a*b) and 3 for sopfr
# optionally, you can define your own complexity in terms of:
# the interval (x) and the the numerator (n) and denominator (d) of the interval once 2's have been removed.
# for examples:
# * no-2's benedetti height: iv_complexity(x, lambda x,f,n,d: n*d)
# * halving the odd-limit of harmonic intervals: iv_complexity(x, lambda x,f,n,d: max(n,d)/(1 if d>1 else 2))
def iv_complexity(x, func=lambda x,f,n,d: max(n,d)):
	f = convert(x,list) # to monzo
	num2s = f[0] # save
	f[0] = 0
	no2s_x = unfact(f)
	f[0] = num2s # restore
	if func==3:
		return sum([ prime_idx(i)*abs(f[i]) for i in range(len(f)) ])
	return no2s_x[0]*no2s_x[1] if func==2 else math.log(no2s_x[0]*no2s_x[1], 2) if func==1 else func(x, f, no2s_x[0], no2s_x[1])

# sg[A&B] temp with g=gentuning in [0, n-1] gen range,
# interpreted in ol-odd-limit, with:
# * an optional condition cond on inclusion of interpretations
# * an optional precision prec (default 3 decimal places for cents)
# * an optional interval formatter fmt (default is to bold harmonic & subharmonic)
# ONLY SUPPORTS OCTAVE-PERIOD FOR NOW
# (to extend: need to check all period-equivalents and period-reduce instead of octave-reduce)
# (however, n periods must still equal an octave)
def interval_table(sg,A,B,gentuning,n, ol,**args):
	targets = sorted(list(
		odd_lim(1,[],[ odd for odd in range(3,ol+1,2) if in_subgroup(odd,sg) ])
		),key=lambda x: as_float(x))
	cond = args.get('condition') or args.get('cond') or (lambda x,err: True)
	prec = args.get('precision') or args.get('prec') or 3
	fmt = args.get('formatter') or args.get('format') or args.get('fmt') or (lambda x,cent_err:
		"'''[["+striv(x)+"]]'''" if iv_complexity(x,lambda x,f,n,d: min(n,d))==1 else '[['+striv(x)+']]')
	if type(A)==int:
		A=val( lim(max(sg)), ed(A) )
	if type(B)==int:
		B=val( lim(max(sg)), ed(B) )
	gA = math.floor(gentuning/1200*A[0]+.5) # hacky way of getting the generator in A EDO
	gB = math.floor(gentuning/1200*B[0]+.5) # hacky way of getting the generator in B EDO
	for i in range(1,n):
		print('|-')
		print('|',i)
		print('|',int( ( gentuning * i % 1200 ) * 10**prec + .5)/10**prec)
		cent_err=lambda x: abs( steps(x,ed(1200)) - gentuning * i % 1200 )
		print('|',', '.join([ fmt(x,cent_err(x)) for x in targets if
			map_iv(A,x)==gA*i%A[0] and map_iv(B,x)==gB*i%B[0] and cond(x,cent_err(x))
		]))

patent_vals = dict()
ivs_cache = []
ivs_int_cache = 0
ivs_sum_weights_cache = 1
# the default badness is the sum of the squares of the errors with each interval's contribution weighted proportional to its odd-limit complexity,
# in terms of absolute (cent/octave) error (corresponding to the multiplication by et2) and divided by the sum of the weightings of the intervals.
def et_badness(ivs,v,badness=lambda rel_err,x,et2: rel_err**2 * et2,weighting=lambda x: iv_complexity(x),combine='avg',et2=0):
	# if the weighting is unspecified, use the default of:
	# weighting an interval x proportional to its odd-limit complexity iv_complexity(x);
	# this is a compromise between theoretic sensitivity (approx. equal to the square of the odd-limit complexity)
	# 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
	if weighting==1 or weighting in ['unweighted','trivial','basic','CE','CEE']:
		weighting = lambda x: 1 # for use with prime harmonic interval sets
	elif weighting==2 or weighting in ['natural', 'harmonic', 'TE', 'te', 'tenney']:
		weighting = lambda x: 1/math.log2(x[0] * x[1]) # mainly for use with prime harmonic interval sets
	global ivs_cache
	global ivs_int_cache
	global ivs_sum_weights_cache
	# convert the ivs argument into a valid set of intervals before attempting operations with it
	if type(ivs)==int:
		if ivs==ivs_int_cache: # use cached value to avoid regenerating an entire odd-limit every time
			ivs = ivs_cache
		else:
			ivs_int_cache = ivs
			if ivs % 2: # use odd-limit if odd
				ivs = odd_lim(ivs_int_cache)
			else: # use integer-limit if even
				ivs = [x for x in odd_lim(ivs_int_cache) if x[0] <= ivs_int_cache]
			ivs_cache = ivs
	elif (type(ivs)==list or type(ivs)==set) and all([ type(x)==int for x in ivs ]): # specify odd-limit in terms of list/set of odds
		ivs = odd_lim(1,[],ivs)
	# else: it's assumed we have a list or set of 2-tuples of integers (a list or set of intervals)
	
	# deduce if using the default behaviour (mean average of sum of badnesss) and if so note it for later
	using_avg = False
	ivcount = combine
	if combine in ['avg','mean','average','sum','net']: # (note the mean average is equal to a sum up to a scalar factor)
		using_avg = combine not in ['sum','net'] # if a pure sum is requested, do not divide by the sum of the weights
		combine = lambda badnesses: sum(badnesses)
		# specifically, cache the sum of weighting(x), as set of intervals likely to remain constant between calls.
		# this is to allow meaningful comparison of performance between different sets of intervals, as otherwise
		# adding an interval to the set always increases the total badness even if the average per interval decreases,
		# so this is for automatically calculating the average w.r.t. the weighting function given.
		# it is thus redundant if you are not comparing between different sets of JI intervals as targets.
		if ivs_cache!=ivs:
			ivs_cache = ivs
			ivs_sum_weights_cache = sum([ weighting(x) for x in ivs ])
	elif combine in ['max','maximum'] or ivcount==1: # average of top 1 highest badnesses
		combine = lambda badnesses: max(badnesses)
	elif combine in ['min','minimum'] or ivcount==-1: # average of top 1 lowest badnesses
		combine = lambda badnesses: min(badnesses)
	elif ivcount > 0: # (unweighted) average of <combine> highest badnesses AKA "judge by worst <ivcount>"
		combine = lambda badnesses: sum( sorted(badnesses)[-ivcount:] )/ivcount
	elif ivcount < 0: # (unweighted) average of <combine> lowest badnesses AKA "judge by best -<ivcount>"
		combine = lambda badnesses: sum( sorted(badnesses)[:-ivcount] )/-ivcount
	# use the cached val if it is present; otherwise, create a 251-prime-limit (255-odd-limit) patent val and cache it.
	# alternatively, feed the real number of divisions of the octave that generates your val for each val you want to compare.  
	global patent_vals
	if type(v)==int:
		if v in patent_vals:
			v = patent_vals[v]
		else:
			v = val(lim(255),ed(v))
			patent_vals[v[0]] = v
	elif type(v)==float:
		v = val(lim(255),ed(v))
	# 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
	badness3args = lambda rel_err,x,et2: 0
	if num_args==1:
		badness3args = lambda rel_err,x,et2: badness(rel_err)
	elif num_args==2:
		badness3args = lambda rel_err,x,et2: badness(rel_err,x)
	elif num_args==3:
		badness3args = lambda rel_err,x,et2: badness(rel_err,x,et2)
	# deduce the et2 parameter if not given; if it is given, it can be a tempered octave/tritave/etc.
	if not et2:
		et2 = 1/v[0]
	# 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 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,
#   in term of absolute error (cent/octave) error, (redundantly) divided by the sum of the weightings of the intervals
# * for edos in the 2 to 311 range (inclusive)
def optimal_edo_sequence(ivs_or_edo_badness,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg'):
	et_badness_judger = ivs_or_edo_badness
	if type(ivs_or_edo_badness) in [int,set,list]: # user gave intervals (default et_badness)
		ivs = ivs_or_edo_badness
		et_badness_judger = lambda edo: et_badness(ivs,edo,weighting=weighting,combine=combine)
	# else user gave et_badness manually (custom)
	best_edo = et_badness_judger(1)
	best_edos = []
	for edo in edo_set:
		current = et_badness_judger(edo)
		if current < best_edo:
			best_edo = current
			best_edos.append(edo)
	return best_edos
# like optimal_edo_sequence, except:
# it only considers an edo better than a smaller edo if it does better in pure relative error.
# 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).
def strict_optimal_edo_sequence(ivs,edo_set=range(2,311+1),weighting=lambda x: iv_complexity(x),combine='avg'):
	return optimal_edo_sequence(lambda edo: et_badness(ivs,edo,lambda rel_err: rel_err**2,weighting,combine),edo_set)

Important 23-limit EDOs

Produced with the help of my above code and corrected with love. Below are theoretically important 23-limit EDOs in that they do an increasingly better job at representing the 27-odd-limit minus zero or one of composite odds 25 and 27. The exception is that if an EDO (such as 94) achieves less than 2 inconsistencies in the 25-odd-limit or no-25's 27-odd-limit then we don't consider a record to be set to give a chance to larger EDOs that are often interesting for having better approximations (of the intervals mentioned or of other intervals in higher limits). The logical stopping point is 311edo due to its extreme efficiency and elegance as a 23-limit and 41-limit temperament (plus a handful of optional large primes). Please note that there may be one or two important 23-limit EDOs missing here, because I've only checked patent val and occasionally a non-patent-val will perform better for consistency, however, one can argue that a patent val representation should be a requirement for any serious 23-limit EDO.

It should hopefully go without saying that by "serious 23-limit EDO" I mean in terms of archetypal and structural importance; to me it's obvious, with the exception of maybe EDOs 46 and 53 (corresponding to Eros (rank 3) and maybe also Amity (rank 2)), that any 23-limit EDO must have at least 77 notes, to allow for the fitting of a submajor third (21/17 plus various higher-complexity submajor thirds), a superneutral third (16/13), a subneutral third (11/9) and a supraminor third (most notably 17/14 and 23/19; equated in any "small" system) between 5/4 and 6/5, corresponding to splitting 25/24 into 5 equal parts. If you do this with one part equated to 81/80 you get absurdity, explaining its non-absurdity and, to the contrary, intuitiveness, as a 29-limit temperament (given that prime 29 is essentially free for multiples of 7 EDO). If you instead choose to exaggerate 81/80 to 2 parts you get an extension of 5-limit artoneutral (I specify 5-limit as 80edo, one logical tuning for it, uses a different mapping for 7).

10edo gets 13 wrong in the no-25's 27-odd-limit (and 14 wrong in the 25-odd-limit (noticed the code produced a mistake here))

12edo gets 12 wrong in the no-25's 27-odd-limit (and 13 wrong in the 25-odd-limit (noticed the code produced a mistake here))

15edo gets 9 wrong in the 25-odd-limit

16edo gets 8 wrong in the 25-odd-limit

24edo gets 10 wrong in the no-25's 27-odd-limit

29edo gets 9 wrong in the no-25's 27-odd-limit

41edo gets 8 wrong in the no-25's 27-odd-limit

43edo gets 8 wrong in the 25-odd-limit

46edo gets 5 wrong in the no-25's 27-odd-limit and 8 wrong in the 25-odd-limit (noticed the code produced a mistake here)

50edo gets 5 wrong in the 25-odd-limit

53edo gets 5 wrong in the 27-odd-limit

68edo gets 5 wrong in the 25-odd-limit

77edo gets 5 wrong in the no-25's 27-odd-limit

80edo gets 4 wrong in the 25-odd-limit

94edo gets 0 wrong in the no-25's 27-odd-limit

111edo gets 2 wrong in the no-25's 27-odd-limit

113edo gets 2 wrong in the no-25's 27-odd-limit

130edo gets 3 wrong in the no-25's 27-odd-limit

149edo gets 4 wrong in the 25-odd-limit

159edo gets 1 wrong in the 27-odd-limit

183edo gets 3 wrong in the no-25's 27-odd-limit

193edo gets 1 wrong in the 25-odd-limit & no-25's 27-odd-limit (union of these is not the same as 27-odd-limit!)

217edo gets 2 wrong in the 27-odd-limit

282edo gets 0 wrong in the 27-odd-limit

296edo gets 2 wrong in the no-25's 27-odd-limit

311edo gets 0 wrong in the 27-odd-limit

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.

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.

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 AGPL corresponding to the GPLv3.

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 pip install rtmidi 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 pip and python refer to Python 2, so use pip3 and python3 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. rtmidi seems to install but there's issues when trying to use it; installing python-rtmidi 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 import mido.

4. If you are getting a long error where towards the end there is error: Microsoft Visual C++ 14.0 or greater is required. (which might happen if you are using an older version of Windows) then try pip install --only-binary :all: rtmidi 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 pip install --only-binary :all: rtmidi seems to work but then brings an error when trying to use the code, try pip install --only-binary :all: python-rtmidi in case it helps.

5. If you are on Windows, make sure you have 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 (@osmiumic).

Isomorphic keyboard code for launchpads with programmer mode

# this code is licensed under the AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
import math
import mido # pip install mido && pip install python-rtmidi; requires loopMIDI on Windows (using the default name should work; the code searches for a name starting with "loop")
# IMPORTANT: if installation of the dependencies of this code isn't working, try: pip install --only-binary :all: rtmidi

def lpselect(of):
	return 'aunchpad' in of or 'aunchPad' in of or 'AUNCHPAD' in of or 'LP' in of
# IMPORTANT: requires the launchpad to be plugged in with updated firmware and be in programmer mode!
def basic_iso(vel=100): # y=10, x=1
	midi_inputs = mido.get_input_names()
	print('midi inputs detected:')
	for mi in midi_inputs:
		print(mi)
	print('selecting:')
	launchpad = [mi for mi in midi_inputs if lpselect(mi)][0]
	print(launchpad)
	# transform the launchpad's midi input (mi) into custom midi output (mo)
	with mido.open_input(launchpad) as mi, mido.open_output() as mo:
		for msg in mi: # process midi msg from launchpad
			to_send = msg.copy()
			if 'control' in str(msg): # buttons not pressure-sensitive so use set velocity (i choose 100)
				to_send = mido.Message( 'note_on', channel=0,
					note=msg.control, velocity=vel if msg.value else 0
					)
			if not 'clock' in str(to_send):
				print(to_send)
			mo.send(to_send)
# rb for rainbow
rb12 = [5, 9, 13, 17, 21, 25, 77, 37, 45, 81, 53, 57]
rb13 = [5, 9, 13, 17, 21, 25, 77, 37, 41, 45, 81, 53, 57] # add 41 to rb12
cm16 = [4, 5, 6, 7, 12, 13, 14, 15, 24, 25, 26, 27, 44, 45, 46, 47]
rb16 = [120, 60, 9, 109, 13, 98, 122, 25, 29, 37, 41, 45, 49, 81, 53, 57]
rb17 = [120, 60, 84, 96, 109, 13, 98, 122, 25, 29, 37, 41, 45, 49, 81, 53, 57]
rb18 = [120, 60, 84, 96, 109, 13, 98, 122, 25, 29, 37, 41, 45, 49, 81, 94, 82, 57]
rb29old = [120, 5, 60, 84, 96, 61, 62, 97, 13, 74, 98, 75, 122, 21, 25, 29, 33, 37, 41, 79, 67, 80, 49, 81, 94, 53, 95, 57, 58]
rb30old = rb29old[:6] + [126] + rb29old[6:] # insert 126 between 61 and 62
rb31old = rb30old[:4] + [9] + rb30old[4:] # insert 9 between 84 and 96

rb36ry = [120, 5, 60, 84, 96, 61, 126, 62, 99, 97] # len-1=9; 61 & then 126 to be removed for distinctness
rb36yg = [97, 13, 74, 98, 85, 75, 17, 122, 21] # len-1=8; 98 & 75 to be removed for distinctness
rb36gb = [21, 25, 29, 33, 37, 78, 41, 79, 45] # len-1=8; lacking in colours so do not remove any if making rb36 more even
rb36br = [45, 80, 69, 49, 81, 94, 53, 82, 95, 57, 58, 120] # len-1=11 53 or 82 to be removed for smoothness; 80 to be removed for distinctness
rb36 = rb36ry[:-1] + rb36yg[:-1] + rb36gb[:-1] + rb36br[:-1] # maximal, but uneven, rainbow
rb37 = [6] + rb36
rb35 = [c for c in rb36 if c!=126]
rb34 = [c for c in rb36 if c not in [61,62]]
ry5 = [120, 60, 84, 126, 99]
yg5 = [97, 13, 74, 98, 75]
gc5 = [17, 122, 21, 25, 29]
cb5 = [33, 37, 78, 41, 79]
bp5 = [45, 80, 49, 81, 94]
pr5 = [53, 82, 95, 57, 58]
rb30 = ry5 + yg5 + gc5 + cb5 + bp5 + pr5
rb31 = [120, 5] + rb30[1:]
rb32 = [c for c in rb34 if c not in [85,96]]
rb33 = [c for c in rb34 if c!=85]
rb29 = [c for c in rb30 if c!=75]
# 29 colours is a fairly ideal rainbow; we use it for indexing into, so as to derive a 17-colour palette.
# note that i chose the +0.4 experimentally based on the colour palette that seemed most distinct to me.
def cols(cs, n, offset=0.4):
	return [ cs[round( i/n*len(cs)+offset )%len(cs)] for i in range(n) ]
rb17v2 = cols(rb29,17)[-1:] + cols(rb29,17)[:-1] # rotate to match rb17
rb19 = cols(rb29,19)
rb28 = [c for c in rb29 if c!=95]
rb27 = [c for c in rb28 if c!=99] # or 97?
rb26 = [c for c in rb27 if c!=37]
rb25 = [c for c in rb26 if c!=74]

colmaps = { 12: rb12, 13: rb13, 17: rb17v2, 18: rb18, 19: rb19, 25: rb25, 26: rb26, 27: rb27, 28: rb28, 29: rb29, 30: rb30, 31: rb31, 32: rb32, 33: rb33, 34: rb34, 35: rb35, 36: rb36, 37: rb37 }
# IMPORTANT: SUSPECTED DUPLICATE COLOURS: 45 = 67, 97 = 124, (a duplicate red i need to refind)
rb80map = {0: 2, 1: 83, 2: 8, 3: 47, 4: 46, 5: 55, 6: 51, 7: 81, 8: 36, 9: 105, 10: 1, 11: 111, 12: 99, 13: 9, 14: 116, 15: 66, 16: 119, 17: 40, 18: 44, 19: 78, 20: 5, 21: 45, 22: 65, 23: 70, 24: 124, 25: 98, 26: 21, 27: 4, 28: 29, 29: 33, 30: 13, 31: 113, 32: 3, 33: 115, 34: 20, 35: 102, 36: 104, 37: 53, 38: 54, 39: 55, 40: 2}
rb80 = [rb80map[i] for i in range(41)]
rb80 = rb80 + rb80[::-1][1:-1]
colmaps[80] = rb80
colmaps[40] = rb80[::2]
colmaps[20] = rb80[::4]
colmaps[16] = rb80[::5]


# for example, if you're using 34 EDO and you want a 1\2 = 17\34 period
# (so that notes separated by an integer multiple of the period are the same)
# you can use a 17-colour rainbow (either rb17 or rb17v2) with a gen of your choosing
# to define the nearest colours EG iso(6,5,17,g=7) for hemipyth or g=3 for diaschismic/srutal archagall;
# the latter ive made a corresponding colouring for on scaleworkshop:
# https://sevish.com/scaleworkshop/?l=1By&c=lawngreen_blue_red_limegreen_slateblue_orange_springgreen_orchid_gold_aquamarine_fuchsia_yellow_cyan_deeppink_yellowgreen_dodgerblue_mediumvioletred&v=9&w=3&a=1a&s=nz&r=5n&t=bh&b=el&i=0&version=2.4.1
def circle(gen,rb):
	result = [0]*len(rb)
	for i in range(len(rb)):
		result[i*gen%len(rb)] = rb[i]
	return result

# rotate an arbitrary period-repeating scale to start at the nth note of the scale.
# scale and mode can be specified in any order.
def mode(a,b):
	scale = []
	n = 0
	if type(a)==int and type(b)==list:
		n = a
		scale = b
	elif type(a)==list and type(b)==int:
		n = b
		scale = a
	else:
		print('INVALID SCALE AND/OR MODE:')
		print('scale should be a list of positive integers up to and including the period.')
		print('mode should be a nonnegative integer 0 to the number of notes per period inclusive.')
		print('HIGHLIGHTING ALL NOTES DUE TO BAD ARGUMENTS (scale=[1]).')
		return [1]
	period = scale[-1]
	while n<0:
		n += len(scale)
	if n==0:
		n = len(scale)
	while n>len(scale):
		n -= len(scale)
	scalewide = scale + [i+period for i in scale] # assumes (excl., incl.] rng
	mode_of_scale = scalewide[n-1:n+len(scale)]
	return [i-mode_of_scale[0] for i in mode_of_scale][1:]

# read as "i want [notes] notes of [of] (EDO/other background scale) and the generator is [gen] scalesteps of [of]".
# if you want to use the (0-indexed) [tonic]th degree as the tonic specify it as the 4th argument [tonic] > 0.
# if you want to generate downwards by -tonic gens from the tonic, specify it as the 4th argument [tonic] < 0.
def genindices(notes,of,gen=7,tonic=0):
	notesubset = [0]
	while len(notesubset) < notes:
		notesubset.append( (notesubset[-1] + gen) % of )
	tonic_scalesteps = 0
	if tonic < 0:#, then instead of going up by gen\of notes times, we add tonic to the amount
		tonic_scalesteps = notesubset[-tonic] # of times we go up so that we go down -tonic times
	notesubset.sort() # it's important that this line is exactly here
	if tonic_scalesteps:
		tonic = notesubset.index(tonic_scalesteps)
	if tonic > 0: # use degree [tonic] as the new tonic
		tonic = tonic % notes # [notes]th degree = 0th degree = tonic
		notesubset += [i+of for i in notesubset]
		notesubset = [i-notesubset[tonic] for i in notesubset[tonic:tonic+notes]]
	return notesubset[1:] + [of] # exclude 1/1, include period
# TODO: add more colmaps
import platform # to check whether we're running Windows, in which case we want to use loopMIDI
# use the same midi/mido output (mo) for the entire session: (the port is opened when you import the library)
mo = mido.open_output() if platform.system()!='Windows' else mido.open_output([ port for port in mido.get_output_names() if 'loop' in port ][0])

# IMPORTANT: to highlight a scale, use [a,b,c,...,p] where p is the number of MIDI notes corresponding to your period and a>0.
# SEE: genindices() to generate a rank 2 scale and mode() to rotate an arbitrary scale.
def iso(x=5, y=1, colmap_in=[34], highlight=[], **args):
	bg = args.get('bg') # colour to indicate note is not in the highlighted subset (white by default)
	if bg==None: # default
		bg = 3
	relative = args.get('relative') or args.get('rel') or False
	autorelative = args.get('autorelative') or args.get('autorel') or False
	playable_shift = args.get('playable_shift') or False # specific to Launchpad Pro MK3
	last_tuning_note = 0
	global mo
	colmap = colmap_in # make default value immutable
	# select colmap
	global colmaps
	if type(colmap)==int:
		if colmaps.get(colmap):
			colmap = colmaps[colmap]
		elif colmap < 31 or colmap%2==0 and colmap//2 < 31:
			if colmap > 31 and colmap%2==0:
				colmap //= 2
			colmaps[colmap] = [ rb30[round( k/colmap*30+0.4 )%30] for k in range(colmap) ]
			colmap = colmaps[colmap]
		else:
			print('no colmap in colmaps for',colmap,'notes; using default colmap')
			colmap = [4,5,6,7]
	# get arguments with default values
	bottomleftnote = args.get('bottomleftnote') or args.get('blnote') or args.get('bl') or args.get('b') or 0 # WARNING: NOTE: used to be min(x,y) by default!
	vel = args.get('velocity') or args.get('vel') or args.get('v') or 102 # velocity used when lacking sensitivity
	min_vel = ( args.get('minimum_velocity') or args.get('minimum_vel') or args.get('minimum_v')
		or args.get('min_velocity') or args.get('min_vel') or args.get('min_v')
		or args.get('minvelocity') or args.get('minvel') or args.get('minv')
		or 1 ) # minimum velocity required (else ignored)
	half = args.get('half') or args.get('h') or args.get('vol') or args.get('volume') or max(40,min(120,len(colmap))) # how many midi notes to go up from bottomleft before the velocity is halved
	extra = args.get('extra') or args.get('eleven') or False # whether to consider half-buttons as distinct notes of an uneven 10x11 grid on Launchpad Pro MK3 (off by default as weird to play with)
	# these are for if you want or need to keep the colour mapping distinct from the midi notes EG in large EDO subsets
	large = args.get('large') or (not args.get('small') and max(abs(x)*9+abs(y)*8,abs(x)*8+abs(y)*9) > 127) or False
	# the idea of this feature is to reduce by an octave
	# (or more generally some abstract interval of [reducer] MIDI notes)
	# and to do so every [every] notes.
	# reducer,every (equiv. x_reducer,every_x) specify x axis.
	# y_reducer,every_y specify y axis.
	# DOES NOT WORK WITH LARGE (large=True) LAYOUTS CURRENTLY.
	x_reducer = args.get('x_reducer') or args.get('reducer') or 0
	every_x = args.get('every_x') or args.get('every') or 2
	y_reducer = args.get('y_reducer') or 0
	every_y = args.get('every_y') or 2
	
	coverage = 10 # probably shouldn't mess with this unless you know what you're doing
	if not large and (x < 0 or y < 0):
		print('WARNING: using a negative number of MIDI notes per increment of the x or y axis likely requires setting the bottomleftnote bl correspondingly')
	if large:
		print('WARNING: the midi note range of your layout is too large, so x=1, y='+str(coverage)+'=c=coverage is being used.')
		edo = args.get('edo') or args.get('ed2') or args.get('ed') or args.get('et') or len(colmap)
		print('IMPORTANTLY: the below is a scale of',edo,'EDO corresponding to your chosen layout.')
		print('IMPORTANTLY: the base note for frequency MUST be midi note 0 so that the notes are placed correctly!')
		print('the .scl file follows:')
		print('x='+str(x)+'\\'+str(edo)+', y='+str(y)+'\\'+str(edo)+' launchpad mapping')
		print(coverage*(coverage+1)-1) # account for half buttons potentially being used as distinct notes in the future
		for j in range(coverage+1):
			for i in range(coverage):
				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
					print( str(steps)+'\\'+str(edo) if args.get('backslash') or args.get('backslashes') else int(.5 + 10000 * steps/edo*1200)/10000 )
		print()
	# for the launchpad pro MK3 specifically:
	distinct_half_buttons = ( args.get('distinct_half_buttons') or args.get('distinct_halves')
		or args.get('distinct_buttons') or args.get('distinct') or args.get('d') # (d for double)
		or False ) # whether to interpret the half-buttons as being two buttons for the same note or a single button (off by default as weird to play with) 
	# generate circle of nths based on gen
	g = args.get('g') or args.get('gen') or args.get('similar') or 1
	colmap = circle(g,colmap)
	if highlight: # highlight subset scale
                highlight = sorted(list(set(highlight)))
                mul = math.lcm(len(colmap),highlight[-1])
                colmap = [( colmap*mul )[i%len(colmap)] if i%highlight[-1] in [0]+highlight else bg for i in range(mul)]
	
	# find launchpad output (what we send colour (col) data to)
	midi_outputs = mido.get_output_names()
	print('midi outputs detected:')
	for i in midi_outputs:
		print(i)
	print('selecting:')
	launchpad_col = args.get('midi_out_col') or args.get('col')
	if not launchpad_col:
		launchpad_col = [i for i in midi_outputs if lpselect(i)][0]
	print('midi_out_col=\''+launchpad_col+'\'')
	# find launchpad input (what we get midi input (mi) data from)
	midi_inputs = mido.get_input_names()
	print('\nmidi inputs detected:')
	for i in midi_inputs:
		print(i)
	print('selecting:')
	launchpad_mi = args.get('midi_input') or args.get('mi')
	if not launchpad_mi:
		launchpad_mi = [i for i in midi_inputs if lpselect(i)][0]
	print('midi_input=\''+launchpad_mi+'\'')
	
	# define mapping from midi note given by launchpad to midi note in isomorphic layout
	def transform(midi_note):
		if midi_note < 100:
			return midi_note%10 * x - (midi_note%10)//every_x*x_reducer + midi_note//10 * y - (midi_note//10)//every_y*y_reducer + bottomleftnote + (10 if extra else 0)
		else:
			return (midi_note - 100) * x - (midi_note%10)//every_x*x_reducer + bottomleftnote
	def get_midi_note(midi_note):
		if extra and midi_note < 10:
			return midi_note # bottom row of half buttons has correct mapping already
		if midi_note < 100:
			return midi_note%10 + midi_note//10 * coverage + (10 if extra else 0)
		else:
			return midi_note - 100 + (10 if extra else 0)
	
	key_codes = range(1,109) # includes a few key codes not corresponding to anything on launchpads but shouldn't be an issue.
	note_state = dict() # for keeping track of how many equivalent notes are being held at a given time
	for k in key_codes:
		note_state[transform(k)] = 0
	
	shift_held = False
	with mido.open_input(launchpad_mi) as mi, mido.open_output(launchpad_col) as col:
		print('\ncolouring...')
		for k in key_codes:
			col.send(mido.Message( 'note_on', channel=0,
				note=k, velocity=colmap[ transform(k) % len(colmap) ]
				))
		
		print('starting midi event processing...')
		# transform the launchpad's midi input (mi) into custom midi output (mo)
		for msg in mi: # process midi msgs from launchpad
			to_send = msg.copy()
			key = 0 # what key is being pressed
			note_vel = 0
			is_half_button = False
			is_shift = False
			if 'control' in str(msg):
				key = msg.control
				note_vel = vel if msg.value else 0
				is_half_button = msg.control > 100 or msg.control < 10
				is_shift = key==90
				if is_shift:
					shift_held = bool(note_vel)
			elif str(msg).startswith('note'):
				key = msg.note
				note_vel = msg.velocity
			else:
				continue
			note_vel = note_vel if note_vel >= min_vel else 0
			tuning_note = transform(key)
			midi_note = get_midi_note(key) if large else tuning_note
			print('tuning:',tuning_note,'midi:',midi_note)
			if relative and (sum([ note_state[i] for i in note_state ])==0 and autorelative or shift_held):
				last_tuning_note = tuning_note
				# only in the situation where the last_tuning_note is set can the grid possibly be fully recoloured
				if autorelative or shift_held:
					for k in key_codes:
						col.send(mido.Message( 'note_on', channel=0,
							note=k, velocity=colmap[ (transform(k) - tuning_note) % len(colmap) ]
							))
			if note_vel and ((not is_shift or playable_shift) or not relative or relative and autorelative): # if we're pressing a key
				note_vel = min(127,round( note_vel * 2**( -(tuning_note-bottomleftnote)/half ) ))
				if note_state[tuning_note]: # if one or more equivalent keys are already held
					to_send = mido.Message('note_on', channel=0, note=midi_note, velocity=0)
					if not is_half_button or distinct_half_buttons:
						print(to_send)
						mo.send(to_send) # turn off equivalent midi note before repressing
				else: # turn off all equivalent keys to indicate being pressed
					for k in key_codes:
						if transform(k)==tuning_note:
							col.send(mido.Message('note_on', channel=0, note=k, velocity=0))
				to_send = mido.Message('note_on', channel=0, note=midi_note, velocity=note_vel)
				if not is_half_button or note_state[tuning_note]==0 or distinct_half_buttons:
					print(to_send)
					mo.send(to_send) # turn on equivalent midi note
					note_state[tuning_note] += 1
			elif note_state[tuning_note]: # if we're releasing a key and there are equivalent keys held
				note_state[tuning_note] -= 1
				if note_state[tuning_note]==0: # if we turn off all keys as a result... turn off
					to_send = mido.Message('note_on', channel=0, note=midi_note, velocity=0)
					print('col='+str(colmap[ (tuning_note - last_tuning_note) % len(colmap) ]),to_send)
					mo.send(to_send) # turn off equivalent midi note
					for k in key_codes:
						if transform(k)==tuning_note: # if equivalent... set key to unpressed col
							col.send(mido.Message('note_on', channel=0,
								note=k, velocity=colmap[ (transform(k) - last_tuning_note) % len(colmap) ]
								))