R/intervals_and_scales.R

Defines functions z j maxeven edoo carlos_step meantone_fifth

Documented in carlos_step edoo j maxeven meantone_fifth z

#' Define a tempered fifth for various meantone scales
#' 
#' Creates an interval that approximates a pure 3:2 fifth
#' which has been tempered smaller by some fraction of a syntonic comma,
#' making it easy to construct diatonic meantone scales. The default
#' is to create a quarter-comma meantone fifth (i.e. about 697 cents).
#' 
#' @param
#' frac The fraction of a syntonic comma that the fifth should
#'	be tempered by. Defaults to `1/4`. Numeric.
#' @returns Single numeric value of the tempered fifth
#'	measured in 12edo semitones.
#' @examples
#' zarlino_fifth <- meantone_fifth(2/7)
#' zarlino_diatonic <- sort((0:6 * zarlino_fifth) %% 12)
#' print(zarlino_diatonic)
#' 
#' fifth_in_19edo <- convert(11, 19, 12)
#' meantone_fifth(1/3) - fifth_in_19edo
#' @export
meantone_fifth <- function(frac=1/4) just_p5 - (syntonic_comma * frac)

#' Define a step size for one of Wendy Carlos's scales
#' 
#' For her album *Beauty in the Beast*, Wendy Carlos developed several
#' non-octave scales whose step sizes are calculated to optimize approximations
#' of three intervals: the 3:2 fifth, the 5:4 major third, and the 6:5 minor third.
#' The alpha, beta, gamma, and delta scales differ in terms of how strongly they
#' privilege each of those just intervals. The basic step size for each scale is
#' created by calling this function with the appropriate `name` argument (e.g. "alpha").
#' You can also choose your own weights for the three approximated just intervals, in
#' which case the `name` argument is overridden.
#'
#' @param name Which of Carlos's four scales to create: `"alpha"`, `"beta"`, `"gamma"`,
#'	or `"delta"`. Defaults to `"alpha"`
#' @param weights Numeric vector of length 3 assigning the number of steps that correspond 
#'	to 3:2, 5:4, and 6:5, respectively. Overrides `name` if specified.
#' @param edo Number of unit steps in an octave. Defaults to `12`.
#' @returns Single numeric value containing the step size for the desired scale
#' @examples
#' alpha_scale <- (0:31) * carlos_step()
#' practically_12tet <- (0:24) * carlos_step(weights=c(7, 4, 3))
#' @export
carlos_step <- function(name="alpha", weights=NULL, edo=12) {
  if (is.null(weights)) {
    if (name == "alpha") { weights <- c(9, 5, 4) }
    if (name == "beta") { weights <- c(11, 6, 5) }
    if (name == "gamma") { weights <- c(20, 11, 9) }
    if (name == "delta") { weights <- c(50, 28, 23) }
  }

  just_p5 <- 12 * log2(3/2)
  just_maj3 <- 12 * log2(5/4)
  just_min3 <- 12 * log2(6/5)
  target_intervals <- c(just_p5, just_maj3, just_min3) / edo

  as.numeric((edo/sum(weights^2)) * weights %*% target_intervals)
}

#' Perfectly even scales (the color white)
#'
#' Creates a perfectly even scale that divides the octave into n equal steps.
#' Such scales serve as the origin for the hyperplane arrangements of Modal Color Theory,
#' whence the name `edoo` for "**e**qual **d**ivision of the **o**ctave **o**rigin."
#' 
#' @param card Number of notes in the scale. Numeric.
#' @inheritParams carlos_step
#' @returns Numeric vector of length `card` representing a scale of `card` notes.
#' @examples
#' edoo(5)
#' edoo(5, edo=15)
#' octatonic_scale <- tc(edoo(4), c(0, 1))
#' print(octatonic_scale)
#' @export
edoo <- function(card, edo=12) (0:(card-1))*(edo/card) 

#' Maximally even scales
#'
#' Scales which are "maximally even" divisions of some equal-tempered universe have
#' several musically interesting properties. When a maximally even scale has a number
#' of notes (`card`) that is coprime to the size of the equal-tempered universe, the
#' maximally even scale is called a "non-degenerate well-formed" or "moment of symmetry"
#' scale. When its size divides the equal temperament, it is a perfectly even scale. When
#' it is neither coprime nor a divisor, it produces a scale with a structure like the
#' octatonic (i.e. a union of perfectly even scales, or a well-formed scale with a period
#' smaller than the octave). The scale is generated by quantizing a perfectly even scale
#' to the chosen chromatic cardinality. Two quantization options are offered (rounding down
#' and rounding to the nearest value).
#'
#' @inheritParams edoo
#' @param floor Boolean determining how to quantize. Defaults to `TRUE` causing the 
#'   quantization to round down. If `FALSE` rounds to the nearest value.
#' @returns Numeric vector of length `card` representing a scale of `card` notes.
#' @examples
#' maxeven(7, 12)
#' maxeven(6, 15)
#' maxeven(6, 15, floor=FALSE)
#' 
#' diatonic_in_19 <- maxeven(7, 19)
#' tresillo <- maxeven(3,8)
#' @export
maxeven <- function(card, edo=12, floor=TRUE) {
  if (floor==TRUE) {
    res <- primeform(floor(edoo(card, edo)), edo)
  } else {
    res <- primeform(round(edoo(card, edo), digits=0), edo)
  }

  res
}


#' Convenient just-intonation intervals and scales
#'
#' It's not hard to define a just interval from a frequency ratio: it only
#' requires an input like `12*log2(freq_ratio)`. That gets pretty tiresome
#' if you're doing this a lot, though, so for convenience `musicMCT` includes a `j()`
#' function (not related to [Clough and Douthett's J function](https://www.jstor.org/stable/843811)
#' but it wishes it was). `j()` is designed to behave a lot like base R's [c()]
#' in the way that you'd use it to define a scale (see the examples below).
#' The inputs that this can take are limited and hard-coded, since there's no
#' systematic way to define short hands for every potential just interval.
#' In general, the logic is that individual digits refer to major intervals up from
#' the tonic in the 5-limit just diatonic scale. The prefix "m" to a number (e.g. "m3")
#' gives the equivalent minor version of the interval. If you
#' just want the entire 5-limit diatonic, you can enter `dia`.
#' 
#' @inheritParams tnprime
#' @param ... One or more names that will be matched to just intervals. You can 
#'   enter these as strings, but for convenience sake you needn't. Here are the
#'   currently accepted inputs, their meaning, and their return value:
#'   * `1`: perfect 1th (0 semitones)
#'   * `u`: unison (0 semitones)
#'   * `synt`: syntonic comma (~.215 semitones)
#'   * `pyth`: Pythagorean comma (~.235 semitones)
#'   * `l`: Pythagorean limma (256:243 or ~.9 semitones)
#'   * `s`: 5-limit just semitone (16:15 or ~1.12 semitones)
#'   * `st`: 5-limit just semitone (16:15 or ~1.12 semitones)
#'   * `m2`: 5-limit minor second (16:15 or ~1.12 semitones)
#'   * `h`: half step (16:15 or ~1.12 semitones)
#'   * `a`: Pythagorean apotome (2187:2048 or ~1.14 semitones)
#'   * `mt`: 5-limit minor tone (10:9 or ~1.82 semitones)
#'   * `2`: 3-limit major second (9:8 or ~2.04 semitones)
#'   * `t`: 3-limit whole tone (9:8 or ~2.04 semitones)
#'   * `w`: whole tone (9:8 or ~2.04 semitones)
#'   * `wt`: whole tone (9:8 or ~2.04 semitones)
#'   * `sept`: 7-limit (septimal) whole tone (8:7 or ~2.31 semitones)
#'   * `sdt`: 3-limit semiditone (32/27 or ~2.94 semitones)
#'   * `pm3`: Pythagorean minor third (32/27 or ~2.94 semitones)
#'   * `m3`: 5-limit minor third (6:5 or ~3.16 semitones)
#'   * `3`: 5-limit major third (5:4 or ~3.86 semitones)
#'   * `M3`: 5-limit major third (5:4 or ~3.86 semitones)
#'   * `dt`: 3-limit ditone (81/64 or ~4.08 semitones)
#'   * `4`: 3-limit perfect fourth (4:3 or ~4.98 semitones)
#'   * `utt`: 11-limit tritone (11:8 or ~5.51 semitones)
#'   * `stt`: 7-limit tritone (7:5 or ~5.83 semitones)
#'   * `jtt`: 5-limit tritone (45:32 or ~5.90 semitones)
#'   * `ptt`: 3-limit tritone (729:512 or ~6.12 semitones)
#'   * `pd5`: 3-limit diminished fifth (1024/729 or ~5.88 semitones)
#'   * `5`: 3-limit perfect fifth (3:2 or ~7.02 semitones)
#'   * `m6`: 5-limit minor sixth (8:5 or ~8.14 semitones)
#'   * `6`: 5-limit major sixth (5:3 or ~8.84 semitones)
#'   * `pm7`: Pythagorean minor seventh (16:9 or ~9.96 semitones)
#'   * `m7`: 5-limit minor seventh (9:5 or ~10.18 semitones)
#'   * `7`: 5-limit major seventh (15:8 or ~10.88 semitones)
#'   * `8`: 2-limit perfect octave (2:1 or 12 semitones)
#'   * `dia`: the complete 5-limit diatonic scale
#'
#' @returns Numeric vector representing the input just intervals converted to `edo` unit steps per octave
#' @examples
#' major_triad <- j(1,3,5)
#' isTRUE(all.equal(major_triad, j(u, M3, "5")))
#'
#' isTRUE(all.equal(j(dia), j(1,2,3,4,5,6,7)))
#'
#' # How far is the twelve-equal major scale from the 5-limit just diatonic?
#' dist(rbind(c(0,2,4,5,7,9,11), j(dia)))
#'
#' # Is 53-equal temperament a good approximation of the 5-limit just diatonic?
#' j(dia, edo=53)
#'
#' @seealso
#' [z()] as a shortcut for 12*log2(x) when a just interval you need isn't
#'   defined for `j()`.
#' 
#' @export
j <- function(..., edo=12) {
  input_values <- substitute(...())
  input_values <- unlist(lapply(input_values, toString))
  if (length(input_values) == 0) { 
    return(NULL) 
  }

  values_1        <- c(1,   1,   531441/524288, 81/80, 256/243, 2187/2048, 16/15, 16/15, 16/15, 16/15)
  names(values_1) <- c("1", "u", "pyth",        "synt", "l",    "a",       "h",    "s",  "st",  "m2")

  values_2        <- c(10/9, 9/8, 9/8, 9/8, 9/8,  8/7,    32/27, 32/27, 6/5,  5/4, 5/4,  81/64)
  names(values_2) <- c("mt", "2", "t", "w", "wt", "sept", "sdt", "pm3", "m3", "3", "M3", "dt")

  values_3        <- c(4/3, 7/5,   11/8,  45/32, 729/512, 1024/729, 3/2)
  names(values_3) <- c("4", "stt", "utt", "jtt", "ptt",   "pd5",    "5")

  values_4        <- c(8/5,  5/3, 16/9,  9/5,  15/8, 2, NA)
  names(values_4) <- c("m6", "6", "pm7", "m7", "7", "8", "dia")

  all_values <- c(values_1, values_2, values_3, values_4)
  freq_to_cents <- function(x) 12 * log2(x)
  all_values <- sapply(all_values, freq_to_cents)

  input_values <- match.arg(arg=input_values, choices=names(all_values), several.ok=TRUE)
  
  res <- all_values[input_values]
  names(res) <- NULL

  just_dia <- freq_to_cents(c(1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8))
  insert_dia_at_na <- function(x) {
    if (is.na(x)) { 
      return(just_dia) 
    } else {
      return(x)
    }          
  }
  res <- as.list(res)
  res <- lapply(res, insert_dia_at_na)
  res <- unlist(res)

  convert(res, 12, edo)
}

#' Frequency ratios to logarithmic pitch intervals (e.g. semitones)
#'
#' Simple convenience function for converting frequency ratios to
#' semitones. Useful to have in addition to [j()] because [j()] is only
#' defined for specific common values. Defaults to 12-tone equal temperament 
#' but `edo` parameter allows other units.
#'
#' The name `z()` doesn't make a lot of sense but has the virtue of being
#' a letter that isn't otherwise very common. `r` (for ratio) and `q` (for
#' the rationals) were both avoided because they're already used for other
#' functions.
#'
#' @param ... One or more numerics values that represent frequency ratios.
#' @inheritParams tnprime
#'
#' @returns Numeric vector representing the input ratios converted to
#'   `edo` unit steps per octave
#'
#' @seealso
#' [j()] is a more convenient input method for the most common frequency
#'   ratios.
#'
#' @examples
#' z(81/80) == j(synt)
#'
#' mod_jdia <- z(1, 10/9, 5/4, 4/3, 3/2, 5/3, 15/8)
#' minimize_vl(j(dia), mod_jdia)
#'
#' z(1, 5/4, 3/2, edo=53)
#'
#' @export
z <- function(..., edo=12) {
  ratios <- c(...)
  if (any(ratios <= 0)) {
    stop("All frequency ratios must be positive")
  }   

  edo * log2(ratios)
}

Try the musicMCT package in your browser

Any scripts or data that you put into this service are public.

musicMCT documentation built on June 21, 2026, 9:06 a.m.