💾 Archived View for thrig.me › blog › 2024 › 04 › 05 › relatively-accidental.gmi captured on 2024-08-18 at 18:14:57. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-05-10)

-=-=-=-=-=-=-

Relatively Accidental

LilyPond (a program for music typesetting) supports the notion of relative notes so that one can type

    e8 e e c4

and make that relative to some other note, e.g.

    \relative c'' { e8 e e c4 }
    \relative c,  { e8 e e c4 }

without which one would have to use absolute note names. This obscures the notes, a bit, and can make edits to a new octave more difficult.

    e''8 e'' e'' c''4
    e,8 e, e, c,4

One problem with lilypond on OpenBSD is that it is very slow, at the lowest CPU setting on my laptop a small file takes

    $ echo '\\relative c { c }' > c
    $ time lilypond c
        0m16.95s real     0m16.09s user     0m00.80s system

something like a quarter of a minute to put a note on a staff. Certainly this goes faster if you turn the CPU up, but then you're wasting energy, especially when fiddling and previewing, a lot. A kdump(1) shows that lilypond opens, or tries to open 2040 files. This is the same problem that texlive has: it can do all sorts of things, but it is huge, and thus maybe not so suitable for rapid prototyping.

So one idea is to have something that supports a very small subset of the LilyPond syntax and can quickly convert that to MIDI, or if necessary (and presumably less often) into a full file that LilyPond can render. Therefore, one needs to support relative notes, and resolve such questions as if you have a "bisis" followed by a "feses", should the double-flat F be placed above or below the double-sharp B? If you go by the raw MIDI pitch number, 61 (a B♯♯) and 63 (a nearby F♭♭) are pretty close to one another… or does something else need to happen?

Luckily computers are good at brute forcing this sort of thing. Each note relative to itself going to every other note can be written with a functional program that runs a function for every note and then runs a function for every other note.

    (defun everynote (fn)
      (dolist (note '("a" "b" "c" "d" "e" "f" "g"))
        (dolist (flavor '("" "eses" "es" "is" "isis"))
          (funcall fn (concatenate 'string note flavor)))))

    (everynote
      (lambda (self)
        (everynote
          (lambda (other)
            (format t "\\relative ~a { ~a ~a }~&" self other self)))))

allnotes.ly

This file can be rendered to MIDI and with some scripting the note names and resulting MIDI pitch numbers combined and analyzed. What this appears to show is that the motion is based on the root note involved; the accidentals are just there for flavor, so even though a B-sharp-sharp is "close" to a F-flat-flat, the relative motion for B to F is downwards.

        a   b   c   d    e   f   g
    a   0   2   3   5   -5  -4  -2
    b  -2   0   1   3    5  -6  -4
    c  -3  -1   0   2    4   5  -5
    d  -5  -3  -2   0    2   3   5
    e   5  -5  -4  -2    0   1   3
    f   4   6  -5  -3   -1   0   2
    g   2   4   5  -5   -3  -2   0

So B to F is the "b" row and then over to the "f" column, so down by -6, then adjusted as need be for any accidentals or octave shift indicators on the "f" or "fis," or "feses''" or whatever. Probably. This hasn't been checked much. The good news is that is a very small table and that you can substract 97 from the ASCII number of the root note letter to index into the array.

    $ perl -E 'say for map ord() - 97, "a".."g"'
    0
    1
    2
    3
    4
    5
    6