5

'Modes of limited transposition' are modes that have a limited availability of transpositions. Unlike a major scale that has 12 possible unique transpositions, the seven modes of limited transposition have fewer than 12 possibilities of transposition.

French Composer Olivier Messiaen coined this idea, and he wrote, "Their series is closed, it is mathematically impossible to find others, at least in our tempered system of 12 semitones." I want to try and figure out why. Might anyone have any idea how I could use mathematics to prove this statement?

3
  • By 'mathematically', I think he meant 'by using patterns of tones/semitones', rather than pure maths. While the term 'modes' is technically correct, I wish he'd have come up with a different term - there's enough modes, as we know them, without adding to the list...
    – Tim
    Mar 2, 2021 at 13:53
  • @Tim Can you explain a bit more what you mean by your comment?
    – DPJDPJ
    Mar 2, 2021 at 14:28
  • Messiaen used the term mode because that's what a set of notes, with tone/semitone spaces, in particular order, is. It's just that there are many, many modes that already exist, such as church, Ambrosian, modern, all with their own special 'foibles', and differs, so when Mesiaen decided to embark on his thesis, adding his own 'modes' to what already existed muddied things (for me). Even calling them scales may have simplified things. Just my view...
    – Tim
    Mar 2, 2021 at 15:00

3 Answers 3

12

Well, to give a boring answer to how it can be proved – since there are only finitely many subsets of the 12 tones (namely, 4096), you can just list them all and check exhaustively. But we can be a bit more efficient than that.

There's not really such a thing as limited transposition. What characterises these scales is that some of the transpositions will be enharmonically equivalent to the original. In other words, there's a transpositional symmetry in the scale. (Mathematically, you could say the group of transpositions contains nontrivial equivalences classes with respect to action on those scales.)

In effect, this boils down to these scales splitting up into several equal parts. Because 12 has the integer factors 2, 3, 4 and 6, that means you have the following options, omitting scales that are equivalent by global transposition and omitting the scales that have additional finer-grained symmetry:

  • Size-2 blocks: s0=□□, s1=■□, s2=■■
  • Size-3 blocks: □□□≃s0, s3=■□□, s4=■■□, ■■■=s2
  • Size-4 blocks: □□□□≃s0, s5=■□□□, s6=■■□□, ■□■□=s1, s7=■■■□, ■■■■=s2
  • Size-6 blocks: □□□□□□≃s0, s8=■□□□□□, s9=■■□□□□, s10=■□■□□□, ■□□■□□=s3, s11=■■■□□□, s12=■■□■□□, s13=■■□□■□, ■□■□■□≃s1, s14=■■■■□□, s15=■■■□■□, ■■□■■□≃s4, s16=■■■■■□, ■■■■■■≃s1

Putting it back together again, that means we have these 17 note-sets which have a transpositional symmetry:

□□□□□□□□□□□□, ■□□□□□■□□□□□, ■■□□□□■■□□□□, ■□□□■□□□■□□□, ■□■□□□■□■□□□, ■■■□□□■■■□□□, ■□□■□□■□□■□□, ■■□■□□■■□■□□, ■■□□■■□□■■□□, ■□■■□□■□■■□□, ■■■■□□■■■■□□, ■□■□■□■□■□■□, ■■■□■□■■■□■□, ■■□■■□■■□■■□, ■■■□■■■□■■■□, ■■■■■□■■■■■□, ■■■■■■■■■■■■

From these we can manually filter out those that Messiaen considers “modes”, as those that have no steps larger than two semitones and aren't simply the full chromatic scale:

■□■□■□■□■□■□, ■■■□■□■■■□■□, ■■□■■□■■□■■□, ■■■□■■■□■■■□, ■■■■■□■■■■■□

For reasons that are a bit mysterious to me, he also includes ■■■■□□■■■■□□ and ■■■□□□■■■□□□, though these have bigger steps in them.

3
  • 1
    I wonder if using Cayley tables would be clearer. Maybe clearer to the mathematical mind but less clear to the musical one. Mar 2, 2021 at 16:02
  • I know that this answer is incredibly useful, but from a mathematical perspective (I don't have too much of a music theory background) I somewhat struggle to comprehend it!
    – DPJDPJ
    Mar 3, 2021 at 1:06
  • @DPJDPJ so, what is not clear? –I mean, I didn't really use any group theory here since most people on the site don't have too much of a maths background. This could of course be expressed more elegantly by talking about subgroups, but I daresay the stupid manual enumeration approach is clear enough as well? Mar 3, 2021 at 9:11
0

I have made a video with list them all and check exhaustively approach

Here is the code

https://codesandbox.io/s/basicaccordion-material-demo-forked-ggkek?file=/index.js

https://gist.github.com/srghma/3d28ecf2db90edffe66302466c68e5ce

the

      if (R.toPairs(xs).length >= 12) { return "" }

filters non-12 scales

You can use

https://guichaoua.gitlab.io/web-hexachord/

https://cifkao.github.io/tonnetz-viz/

to visualize tonnetz

import * as React from 'react'
import { useState } from 'react'
import ReactDOM from 'react-dom'
import { StyledEngineProvider } from '@mui/material/styles'
import * as R from 'ramda'
import * as RA from 'ramda-adjunct'
import { useMIDI, useMIDIOutput } from '@react-midi/hooks'
// import { Pcset } from "@tonaljs/tonal"
//
// TODO: calculate Forte number
// TODO: is generated by "Ervin Wilson's Hexany" (not cube https://www.youtube.com/watch?v=-GeR8XbFxvI)
// TODO: perfect vs imperfect scale http://allthescales.org/scales.php?n=6

// What is a scale?
// 1. A scale starts on the root tone.
// 1. A scale does not have any leaps greater than n semitones.
// chroma, valid colors https://github.com/tonaljs/tonal/blob/9622ec8fa4031f0c80515d278dfa06424bf159e5/packages/pcset/index.ts#L161

// does scale have refl symm (palyndrome), IF yes THEN is it chiral

function assert(x, y) {
  if (!R.equals(x, y)) {
    console.log({ x, y })
    throw new Error('assert')
  }
}

//////////////////////

const arrRotateLeft1Mutate = a => {
  a.push(a.shift())
}
const arrRotateRight1Mutate = a => {
  a.unshift(a.pop())
}
const arrRotateLeft = (a, n) => {
  const a_ = [...a]
  while (n > 0) {
    arrRotateLeft1Mutate(a_)
    n--
  }
  return a_
}
const arrRotateRight = (a, n) => {
  const a_ = [...a]
  while (n > 0) {
    arrRotateRight1Mutate(a_)
    n--
  }
  return a_
}
//////////////////////

const binaryToDecimal = x => parseInt(x, 2)

assert(binaryToDecimal("010101010101"), 1365)
assert(binaryToDecimal("101010101010"), 2730)

const decimalToBinary = x => x.toString(2)

//////////////////////

// https://en.wikipedia.org/wiki/Interval_(music)
const numberOfNotesForPerfectUnison = 12

const endingBigEndianBinary = R.times(R.always("1"), numberOfNotesForPerfectUnison).join('')
const endingDecimals = binaryToDecimal(endingBigEndianBinary)
assert(endingDecimals, 4095)

let chords = R.range(1, endingDecimals + 1)
chords = chords.map(decimalToBinary)
chords = chords.map(x => RA.padCharsStart("0", numberOfNotesForPerfectUnison, x))

assert(R.head(chords), "000000000001")
assert(R.last(chords), "111111111111")

const rotateUntil = (predicate, a) => {
  const a_ = [...a]
  let n = 0
  while (predicate(a_[0]) !== true) {
    if (n > a.length) {
      throw new Error(`rotateUntil: maxTimes for ${JSON.stringify(a)}`)
    }
    arrRotateLeft1Mutate(a_)
    n++
  }

  if (typeof a === 'string') { return a_.join('') }
  return a_
}

assert(rotateUntil(x => x[0] === "1", "010101010101"), "101010101010")
assert(rotateUntil(x => x[0] === "1", "101010101010"), "101010101010")
assert(rotateUntil(x => x[0] === "1", "000101010101"), "101010101000")

function binaryToIndexesOfEnabledNotes(binary) {
  // if (binary[0] !== "1" || binary.length !== 12) { throw new Error(binary) }

  // console.log(binary)
  return binary.split('').map((x, index) => {
    // console.log({ x, index })
    if (x === "0") { return undefined }
    if (x === "1") { return index }
    throw new Error('binaryToIndexesOfEnabledNotes')
  }).filter(x => x !== undefined)
}

assert(binaryToIndexesOfEnabledNotes("000000000000"), [])
assert(binaryToIndexesOfEnabledNotes("111111111111"), [0,1,2,3,4,5,6,7,8,9,10,11])
assert(binaryToIndexesOfEnabledNotes("010101010101"), [1,3,5,7,9,11])
assert(binaryToIndexesOfEnabledNotes("101010101010"), [0,2,4,6,8,10])
assert(binaryToIndexesOfEnabledNotes("101111111111"), [0,2,3,4,5,6,7,8,9,10,11])

// https://github.com/AtActionPark/Pianissimo/blob/master/lib/theory.js
// https://felixroos.github.io/pitch-class-sets
function indexesOfEnabledNotesToPitchClassSet(indexesOfEnabledNotes) {
  const firstNoteIsEnabled = indexesOfEnabledNotes[0] === 0
  if (!firstNoteIsEnabled) { throw new Error(indexesOfEnabledNotes) }

  // indexesOfEnabledNotes = R.tail(indexesOfEnabledNotes)

  const output = indexesOfEnabledNotes.reduce((accumulator, value, index, array) => {
    let nextIndexOfEnabledNotes = array[index + 1]

    if (nextIndexOfEnabledNotes === undefined) { nextIndexOfEnabledNotes = numberOfNotesForPerfectUnison }

    // console.log({ accumulator, value, index, nextIndexOfEnabledNotes })
    accumulator.push(nextIndexOfEnabledNotes - value)
    return accumulator
  }, [])

  // console.log(output)

  return output
}

assert(indexesOfEnabledNotesToPitchClassSet([0,1,2,3,4,5,6,7,8,9,10,11]), [1,1,1,1,1,1,1,1,1,1,1,1])
assert(indexesOfEnabledNotesToPitchClassSet([0,1,2,3,4,5,6,7,8,9,10,11]), [1,1,1,1,1,1,1,1,1,1,1,1])
assert(indexesOfEnabledNotesToPitchClassSet([0,2,3,4,5,6,7,8,9,10,11]),   [2,1,1,1,1,1,1,1,1,1,1])

const permutationCycles = xs => {
  const buff = []
  for (let index = 0; index < xs.length; index++) {
    const buff_ = []
    for (let plusIndex = 0; plusIndex < xs.length; plusIndex++) {
      const indexSum = plusIndex + index
      const index_ = indexSum % xs.length
      //console.log({ index, plusIndex, indexSum, index_ })
      buff_.push(xs[index_])
    }
    buff.push(buff_)
  }
  if (typeof xs === 'string') { buff = buff.map(xs => xs.join('')) }
  return R.uniq(buff)
}

assert(permutationCycles([1]), [[1]])
assert(permutationCycles([1,2]), [[1,2], [2,1]])
assert(permutationCycles([1,2,3]), [[1,2,3], [2,3,1], [3,1,2]])
assert(permutationCycles([1,1,1,1,1,1,1,1,1,1,1,1]), [[1,1,1,1,1,1,1,1,1,1,1,1]])

const sortByLengthThenByContent = arr => {
  arr = R.groupBy(R.prop('length'), arr)
  arr = R.values(arr).map(x => x.sort()).flat()
  return arr
}

function normalizedPitchClassSet_rotationalSymmetry(pitchClassSet) {
  const scalesThatShareRotationSymmetryWithThisOne = permutationCycles(pitchClassSet)
  // console.log({pitchClassSet, scalesThatShareRotationSymmetryWithThisOne})
  return scalesThatShareRotationSymmetryWithThisOne.sort()[0]
}

assert(normalizedPitchClassSet_rotationalSymmetry([1,1,1,1,1,1,1,1,1,1,1,1]), [1,1,1,1,1,1,1,1,1,1,1,1])
assert(normalizedPitchClassSet_rotationalSymmetry([2,1,1,1,1,1,1,1,1,1,1]),   [1,1,1,1,1,1,1,1,1,1,2])
assert(normalizedPitchClassSet_rotationalSymmetry([12]),   [12])

// b.c scalesThatShareRotationSymmetryWithThisOne = [[11, 1], [1, 11]]
assert(normalizedPitchClassSet_rotationalSymmetry(indexesOfEnabledNotesToPitchClassSet(binaryToIndexesOfEnabledNotes("100000000001"))),   [1,11])
assert(normalizedPitchClassSet_rotationalSymmetry(indexesOfEnabledNotesToPitchClassSet(binaryToIndexesOfEnabledNotes("110000000000"))),   [1,11])

// function normalizedPitchClassSet_transitivity_and_inversion(pitchClassSet) {
//   return sortByLengthThenByContent(permutationCycles(pitchClassSet).map(x => [x, x.reverse()]).flat())[0]
// }

// assert(normalizedPitchClassSet_transitivity_and_inversion(binaryToIndexesOfEnabledNotes("100000000010")),   [2,10])
// assert(normalizedPitchClassSet_transitivity_and_inversion(binaryToIndexesOfEnabledNotes("101000000000")),   [2,10])

function omitUndefinedFields(obj) {
  return Object.keys(obj).reduce((acc, key) => {
    const _acc = acc;
    if (obj[key] !== undefined) _acc[key] = obj[key];
    return _acc;
  }, {})
}

const chordToInfo = chord => {
  const isScaleAndChord = chord[0] === "1"

  const binary = binaryToDecimal(chord)
  const indexesOfEnabledNotes = binaryToIndexesOfEnabledNotes(chord)

  const commonFields = {
    isScaleAndChord,
    binary,
    indexesOfEnabledNotes,
  }

  if (isScaleAndChord) {
    const pitchClassSet = indexesOfEnabledNotesToPitchClassSet(indexesOfEnabledNotes)

    return {
      ...commonFields,
      pitchClassSet,
      normalizedPitchClassSet_rotationalSymmetry: normalizedPitchClassSet_rotationalSymmetry(pitchClassSet),
      // normalizedPitchClassSet_transitivity_and_inversion: normalizedPitchClassSet_transitivity_and_inversion(pitchClassSet),
      // scalesThatShareRotationSymmetryWithThisOne: permutationCycles(pitchClassSet),
    }
  }

  if (chord.includes("1")) {
    const approximated_scale = rotateUntil(chord_ => {
      const firstNoteIsEnabled = chord_[0] === "1"
      return firstNoteIsEnabled
    }, chord)

    return {
      ...commonFields,
      approximated_scale
    }
  }

  return commonFields
}

chords = chords.map(chord => ({ chord, ...chordToInfo(chord) }))

const chordToInfoObject = R.fromPairs(chords.map(x => [x.chord, x]))

// let scales = chords.filter(x => x.isScaleAndChord)
let scales = chords

// console.log(chordToInfoObject)

scales = R.groupBy(x => x.chord.split('').filter(x => x === "1").length, scales)

scales = R.map(
  chords => {
    return R.groupBy(
      x => {
        const info = x.isScaleAndChord ? x : chordToInfoObject[x.approximated_scale]
        // console.log(x)
        // console.log(info)
        return info.normalizedPitchClassSet_rotationalSymmetry
      },
      chords
    )
  },
  scales
)

// console.log(scales)

// scales = R.map(R.map(
//   chords => {
//     return R.groupBy(
//       x => {
//         const info = x.isScaleAndChord ? x : chordToInfoObject[x.approximated_scale]
//         // console.log(x)
//         // console.log(info)
//         return info.normalizedPitchClassSet_rotationalSymmetry
//       },
//       chords
//     )
//   }),
//   scales
// )

// console.log(scales)

// x = R.uniq(chords.map(([chord, info]) => info.pitchClassSet).filter(x => x))
// x = sortByLengthThenByContent(x)
// console.log(x)

// let x = R.uniq(chords.map(([chord, info]) => info.normalizedPitchClassSet).filter(x => x))
// x = sortByLengthThenByContent(x)
// console.log(x)

wikipediaScaleNames = `Acoustic scale   W-W-W-H-W-H-W
1st Messiaen mode   W--W--W--W--W--W
2st Messiaen mode   W-H--W-H--W-H--W-H
3st Messiaen mode   W-H-H--W-H-H--W-H-H
4st Messiaen mode   H-H-H-3H--H-H-H-3H
5st Messiaen mode   H-4H-H--H-4H-H
6st Messiaen mode   W-W-H-H--W-W-H-H
Aeolian mode or natural minor scale W-H-W-W-H-W-W
Algerian scale  W-H-3H-H-H-3H-H-W-H-W
Altered scale or Super Locrian scale    H-W-H-W-W-W-W
Augmented scale 3H-H-3H-H-3H-H
Bebop dominant scale    W-W-H-W-W-H-H-H
Blues scale 3H-W-H-H-3H-W
Chromatic scale H-H-H-H-H-H-H-H-H-H-H-H
Dorian mode W-H-W-W-W-H-W
Double harmonic scale   H-3H-H-W-H-3H-H
Enigmatic scale H-3H-W-W-W-H-H
Flamenco mode   H-3H-H-W-H-3H-H
"Gypsy" scale   W-H-3H-H-H-W-W
Half diminished scale   W-H-W-H-W-W-W
Harmonic major scale    W-W-H-W-H-3H-H
Harmonic minor scale    W-H-W-W-H-3H-H
Hirajoshi scale 2W-W-H-2W-H
Hungarian "Gypsy" scale / Hungarian minor scale W-H-3H-H-H-3H-H
Hungarian major scale   3H-H-W-H-W-H-W
In scale    H-2W-W-H-2W
Insen scale H-2W-W-3H-W
Ionian mode or major scale  W-W-H-W-W-W-H
Istrian scale   H-W-H-W-H-5H
Iwato scale H-2W-H-2W-W
Locrian mode    H-W-W-H-W-W-W
Lydian augmented scale  W-W-W-W-H-W-H
Lydian mode W-W-W-H-W-W-H
Major bebop scale   W-W-H-W-W-W-H
Major Locrian scale W-W-H-H-W-W-W
Major pentatonic scale  W-W-3H-W-3H
Melodic minor scale (descending) OR Mixolydian mode or Adonai malakh mode   W-W-H-W-W-H-W
Melodic minor scale (ascending) W-H-W-W-W-W-H
Minor pentatonic scale, Yo scale    3H-W-W-3H-W
Neapolitan major scale  H-W-W-W-W-W-H
Neapolitan minor scale  H-W-W-W-H-3H-H
Octatonic scale W-H-W-H-W-H-W-W-W-H-W-H-W-H
Persian scale   H-3H-H-H-W-3H-H
Phrygian dominant scale H-3H-H-W-H-W-W
Phrygian mode   H-W-W-W-H-W-W
Prometheus scale    W-W-W-3H-H-W
Scale of harmonics  3H-H-H-W-W-3H
Tritone scale   H-3H-W-H-3H-W
Two-semitone tritone scale  H-H-4H-H-H-4H
Ukrainian Dorian scale  W-H-3H-H-W-H-W
Vietnamese scale of harmonics   5Q-Q-H-H-W
Whole tone scale    W-W-W-W-W-W`.split('\n').map(x => x.split('\t'))
wikipediaScaleNames = wikipediaScaleNames.map(([name, pattern]) => {
  pattern = pattern.split('-').filter(Boolean).map(x => {
    let number = 0
    if (x.endsWith('Q')) { number = Number(x.replace('Q', '')) * 100 }
    if (x.endsWith('H')) { number = Number(x.replace('H', '')) }
    if (x.endsWith('W')) { number = Number(x.replace('W', '')) * 2 }
    if (x === 'H') { number = 1 }
    if (x === 'W') { number = 2 }
    if (x === 'Q') { number = 100 }
    const valid = number > 0
    if (valid) { return number }
    throw new Error(JSON.stringify({ name, pattern, x, number }))
  })
  return { name, pattern }
})
wikipediaScaleNames = R.groupBy(x => x.pattern, wikipediaScaleNames)
wikipediaScaleNames = R.map(x => {
  const valid = R.sum(x[0].pattern) === 12
  const name = x.map(x => x.name).join(' OR ')
  return `${name}${valid ? '' : ' (INVALID)'}`
}, wikipediaScaleNames)

// console.log(wikipediaScaleNames)



function InfoImplementation({ onPlay }) {
  const output = R.toPairs(scales).map(([nOfNotes, xs]) => {

    xs = R.toPairs(xs).map(([normalizedPitchClassSet_rotationalSymmetry, xs]) => {
      // const items = permutationCycles(normalizedPitchClassSet_rotationalSymmetry)
      if (R.toPairs(xs).length >= 12) { return "" }


      xs = xs.map(x => {
        if (x.isScaleAndChord) {
          const name = wikipediaScaleNames[x.pitchClassSet]
          const pitchClassSet_ = x.pitchClassSet.map(x => {
            if (x === 1) { return 'H' }
            if (x === 2) { return 'W' }
            return `${x}H`
          }).join('-')

          return <tr key={x.chord} onClick={(e) => onPlay(x.indexesOfEnabledNotes, e)}>
            <td>{x.chord}</td>
            <td>{x.pitchClassSet}</td>
            <td>{pitchClassSet_}</td>
            <td><a rel="noopener noreferrer" href={`https://ianring.com/musictheory/scales/${x.binary}`} target="_blank">{x.binary}</a></td>
            <td>{name || ''}</td>
          </tr>
        }

        return <tr key={x.chord} onClick={(e) => onPlay(x.indexesOfEnabledNotes,e )}>
          <td>{x.chord}</td>
          <td></td>
          <td></td>
          <td><a rel="noopener noreferrer" href={`https://ianring.com/musictheory/scales/${x.binary}`} target="_blank">{x.binary}</a></td>
          <td></td>
        </tr>
      })

      return <div key={normalizedPitchClassSet_rotationalSymmetry}>
        <h1>rotationalSymmetry: {normalizedPitchClassSet_rotationalSymmetry}, length: {xs.length}</h1>
        <table border="1"><tbody>
        {xs}
        </tbody></table>
      </div>
    })


    return <div key={nOfNotes}>
      <h1>N of Notes: {nOfNotes}, length: {xs.length}</h1>
      <div>{xs}</div>
    </div>
  })

  return <div>{output}</div>
}

const Info = React.memo(InfoImplementation)

function midiSend(onOff, { output, note, velocity, activateAfter }) {
  if (note > 12 || note < 0) { throw new Error('note') }
  const pitch = note + 60
  const timestamp = activateAfter ? window.performance.now() + activateAfter : undefined
  output.send([onOff, pitch, velocity], timestamp)
}

// https://webmidi-examples.glitch.me/
const midiNoteOn = (config) => midiSend(0x90, config)
const midiNoteOff = (config) => midiSend(0x80, config)

function App() {
  const [playingNotes, setPlayingNotes] = useState([])

  const { outputs } = useMIDI();

  if (outputs.length < 1) return <div>No MIDI Outputs</div>;

  const output = R.last(outputs)
  const velocity = 100
  const midiNotesOn = notes => {
    notes.forEach((note, index) => {
      midiNoteOn({ output, note, velocity, activateAfter: index * 500 })
    })
  }
  const midiNotesOff = notes => {
    notes.forEach((note, index) => {
      midiNoteOff({ output, note, velocity, activateAfter: undefined })
    })
  }

  const handlePlay = (notes, e) => {
    const addToExisting = e.shiftKey

    if (addToExisting) {
      midiNotesOn(notes)
      setPlayingNotes(R.uniq(playingNotes.concat(notes)))
      return
    }

    midiNotesOff(playingNotes)
    midiNotesOn(notes)
    setPlayingNotes(notes)
  }

  const handleRemoveAll = () => {
    midiNotesOff([0,1,2,3,4,5,6,7,8,9,10,11,12])
    setPlayingNotes([])
  }

  return <div>
    <div style={
      {"backgroundColor":"#314963","height":"40px","width":"40px","borderRadius":"100%","position":"fixed","bottom":"21px","right":"25px"}
    } onClick={handleRemoveAll}></div>
    <div>Using {output.name}</div>
    <Info onPlay={handlePlay}/>
  </div>
}

ReactDOM.render(
  <StyledEngineProvider injectFirst>
    <App/>
  </StyledEngineProvider>,
  document.querySelector("#root")
)


0
-1

There are several papers on the subject. With any limited set of tones (12 in the Western Case) and choosing a subset (7 for example), there are only a finite number of combinations that fit.

For Western music, we consider a closed 8-tone scale (the last note an octave above the first). There are 12 half-step intervals to select from. There is a constraint that the intervals should span the octave. Two obvious choices are 12 half steps or 6 whole steps which yield the chromatic and whole-tone scale respectively. WIth 7 notes (the most common), one needs 5 whole steps and 2 half steps. Still, there are many arrangements. The major scale and its cyclic rotations are one possibility. While not derived this way historically, these are the most "balanced" inf various way. The link leads to one discussion on the subject. There are more (but I can't remember the exact links).

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.