Shorthand notations - davidpanderson/Numula GitHub Wiki

Describing a substantial piece of music - the score and the nuance - can involve lots of information. Numula lets you represent this information as Python function calls and data structure declarations, but this can involve huge amounts of typing.

To reduce typing, Numula lets you encode the same information in several "shorthand" notations - character strings that are parsed and converted to data structures.

For example, instead of

ns = Score()
ns.append_note(Note(60, 0, .25))
ns.append_note(Note(62, .25, .25))
ns.append_note(Note(64, .5, .5))

you can write

ns = sh_score('c d 1/2 e')

These produce identical Score objects: quarter note C5 and D5, followed by half note E5.

Numula provides shorthand notations for:

The notation is different in each case. However, all the notations share the following features:

Iteration

*4 ... *

Repeats the given section 4 times (or whatever number is given).

This construct can be nested.

Substitution

Python has several ways to do substitution in strings. These are handy for shorthand strings; they let you "factor out" parts of shorthand strings that occur repeatedly.

For example, consider

sh_vol('pp 2/4 mf 2/4 pp 4/4 mf 4/4 pp')

This defines a pair of swells from pp to mf and back, over 4 and 8 beats. Suppose you want to start from ppp instead of pp. You'd have to manually change the 3 occurrences of 'pp'.

Instead, you can use Python's f-string mechanism:

v0 = 'pp'
v1 = 'mf'
sh_vol(f'{v0} 2/4 {v1} 2/4 {v0} 4/4 {v0} 4/4 {v1}')

This lets you change the limits of the swells all at once.

The text within {} is interpreted as Python code. It can have expressions, function calls, etc. as well as variables.

You can also use substitution to reduce typing, e.g.

cmaj = '[c5 e g]'
ns = sh_score(
    f'{cmaj} f e {cmaj} a b {cmaj}'
)

denotes 3 C major chords, separated by single notes.

Measure numbers

For long shorthand strings, it's handy to include measure numbers, which are denoted

|n

For example:

ns = sh_score(' \
    |1 1/8 c d e f e d c e \
    |2 a g f e d e f g \
    |3 ... \
')

When you want to fix or change something, this makes it easy to find it in the string.

Measure number checking

Measure numbers can also be used to check timing correctness. Consider the following shorthand description of a volume function:

rhv = sh_vol(' meas2/4 \
    |20 *2 *3 ppp 3/4 p 1/4 ppp * 2/4 p 2/4 ppp * \
    |36 *2 [ pp 8/4 p * \
    |44 [ pp 6/4 p 2/4 pp 4/4 pp \
    |50 \
')

This involves lots of time intervals (denoted 3/4, 1/4 etc.). If one of them is wrong or missing, the function will be out of phase from that point on. This type of error can be difficult to track down.

To help locate such errors, Numula keeps track of the sum of the time intervals. Each time it sees a measure number it checks that the sum of times matches the number of measures. To do this it needs to know the duration of a measure; this is specified by the meas2/4.

Timing errors are reported as:

Traceback (most recent call last):
  ...
  File "C:\Users\davea\Documents\Personal\Music\Numula\notate_nuance.py", line 91, in vol
    comment(t, dt, mdur)
  File "C:\Users\davea\Documents\Personal\Music\Numula\notate.py", line 105, in comment
    raise Exception('Inconsistent measure number: %f != %f'%(m1, m2))
Exception: Inconsistent measure number: 44.000000 != 44.250000

Measure duration can change. For example:

ns = sh_score(' meas4/4 \
    |1 1/4 c d e f \
    |2 meas3/4 c d e \
    |3 meas4/4 c d e f \
')

This represents a score with a 3/4 measure between two 4/4 measures. Note: each new measure duration (e.g. meas3/4) must be immediately preceded by a |n.

A shorthand can have items before the first measure boundary, e.g. upbeats. For example:

ns = sh_score(' meas4/4 \
    1/4 b
    |1 c d e f \
')

In this case duration-checking begins at the first measure boundary.

If a piece has fixed measure duration, you can indicate it with

measure_duration(4/4)

This duration will be used for subsequent shorthands (and you can omit the meas items from them).

To error-check time intervals in the last measure, include a final measure number, following the last time interval.

Comments

If a pipe character is followed by non-numeric text, or no measure duration is specified, it is treated as a comment and is ignored.

Syntax error reporting

Each shorthand notation is like a mini programming language, and there can be syntax errors. Such an error produces output like

error in shorthand: - 1/4 + 1/8 + * 1/4 - 4/4 
Traceback (most recent call last):
  File "C:\Users\davea\Documents\Personal\Music\Numula\test_nuance.py", line 127, in <module>
    test_ped()
  File "C:\Users\davea\Documents\Personal\Music\Numula\test_nuance.py", line 123, in test_ped
    ns.pedal_pft(sh_pedal('- 1/4 + 1/8 + * 1/4 - 4/4'))
  File "C:\Users\davea\Documents\Personal\Music\Numula\notate_nuance.py", line 241, in pedal
    items = expand_iter(items)
  File "C:\Users\davea\Documents\Personal\Music\Numula\notate.py", line 52, in expand_iter
    raise Exception('unmatched *')
Exception: unmatched *

The first line shows the token where the error happened (in Idle this is shown in red) and 5 tokens on either side. This is hopefully sufficient for locating it in the notation string. The stack trace shows where the error happened in the code.

⚠️ **GitHub.com Fallback** ⚠️