Interactive parameter adjustment - davidpanderson/Numula GitHub Wiki

Much of the work in crafting a nuance specification involves fiddling with parameters: tempi, volume levels, pause durations, etc.

For example, you might have a volume PFT like

sh_vol('p 1/2 f 1/2 p')

where the parameters are

  • volume levels p and f, which are floating-point numbers, namely .67 and 1.33.
  • 1/2, the durations of the crescendo and diminuendo.

Adjusting a parameter and hearing the result involves the following steps:

  • Find the source code window and bring it to the front.
  • Find the old parameter.
  • Enter the new value.
  • Press Ctrl-s to save the file.
  • Press Fn-F5 to run the program.
  • Click on the Pianoteq timeline to go to the desired starting point.

This cycle is cumbersome; it involves lots of keystrokes, mouse movements, and mouse clicks.

To streamline this process, Numula offers a system called Interactive Parameter Adjustment (IPA), which shortens the cycle to:

  • Press the up or down arrow keys to change the parameter value.
  • Press Space to hear the relevant segment of the piece, with the new parameters.

Ideally, Numula would have a GUI and you'd be able to edit nuance visually; IPA is a keyboard-only alternative.

IPA-enabling a Numula program

To use a Numula program with IPA, you'll need make some minor changes. First, import the IPA module:

from numula.ipa import *

Decide what parameters you might want to adjust, and make them into variables. In the example given above, you could make all the parameters adjustable:

sh_vol(f'{v1} {i1} {v2} {i2} {v3}')

Declare these parameters as follows:

var('v1', IPA_VOL_MULT, p)
var('v2', IPA_VOL_MULT, f)
var('v3', IPA_VOL_MULT, p)
var('i1', IPA_DT_SCORE, '1/2')
var('i2', IPA_DT_SCORE, '1/2')

This tells IPA the name, type, and initial value of each variable. IPA supports several types:

IPA type data type purpose default step size range
IPA_VOL float volume level .02 0..1
IPA_VOL_MULT float volume multiplier .04 0..2
IPA_TEMPO float tempo (or multiplier) 2 1..1000
IPA_DT_SCORE N/M score time interval - -
IPA_DT_SEC float real time interval .01 -10..10
IPA_TOGGLE layer/section control:
on: enable, show vars
off: disable, hide vars
hide: enable, hide vars
- -

After declaring the adjustable variables, do

from numula.ipa import *

again. This lets you access the variables.

Your program must define a function main() that returns a Nebula Score object. The program should do other things - such as playing the score - only if invoked as a script; e.g.

if __name__ == '__main__':
    import numula/pianoteq
    numula.pianoteq.play_score(main())

This way the IPA-enabled program works when run both standalone and under IPA.

Running a program under IPA

When you run an IPA-enabled program under IPA, you can adjust variable using the up and down arrow keys, and hear the results immediately by pressing the space bar.

To run the program test_ipa.py under IPA, cd into the test/ directory and type

python ../numula/dipa.py test_ipa

Note: on Windows, dipa.py doesn't work in IDLE, because of how it handles keyboard input. Instead, you must run it from either cmd or PowerShell. (Tip: if you shift-right-click on a folder, you'll get a menu item to open PowerShell in that folder.)

When IPA starts, it shows you a list of the program's adjustable variables and their values. The list is numbered, and in the following commands variables are identified by their number (not their name).

At any point one of the numeric variables is the 'current variable'; initially it's the first one. The up/down arrow keys adjust this variable.

The following variables are pre-defined; you can adjust them using the commands below.

  • start, dur (Score time): the start time and duration of playback when space is pressed. Initially start time is 0 and duration is 1 (i.e. 4 beats).
  • show (Toggle): if 'on', the score is printed in text form on playback.

You can issue the following commands (followed by Enter):

:<i>
    Select variable i as the current variable
    (must be a numeric variable).

:<i> value
    Set the value of the specified variable.
    For Toggle variables, 'value' is on, off, or hide

:s
    Show variable values, playback start time, and duration.

:w
    Write variable values to a file named e.g. `data/ipa_test.vars`.
    If this file exists when you run your program under IPA,
    variable values will be read from it.
    This lets you save your work.

:h or :help or :?
    Show list of commands

:q
    Exit DIPA; prompt if unsaved changes

:q!
    Exit DIPA without prompt

You can issue the following single-character commands:

up arrow / down arrow
    Increment or decrement the current variable by its step size.

<space>
    Render the score using current variable values,
    and play it with the current start time and duration.

Toggles and tags

A piece may contain hundreds of adjustable variables. When you're editing the piece, typically you want to focus on a few of these at a time, and not see the others.

IPA's 'toggle' mechanism lets you do this. A toggle is a variable with values 'on', 'off', and 'hide'. A toggle can be associated with an 'item', which can be either

  • A nuance layer (e.g. long-term volume control)
  • A horizontal section of the piece (a voice, or the RH part of a piano piece)
  • A vertical section of the piece (e.g. a range of measures)

For example, you could declare the following toggles:

var('if_rh', IPA_TOGGLE, 'on')          # toggles the RH part
var('if_lh', IPA_TOGGLE, 'on')          # toggles the LH part
var('if_pauses', IPA_TOGGLE, 'on')      # toggles pauses

and enforce them as follows:

ns = Score()
if if_rh != 'off':
    ns.insert_score(rh_score)
if if_lh != 'off':
    ns.insert_score(lh_score)
...
if if_pauses != 'off':
    ns.tempo_adjust_pft(pauses)

An adjustable variable can be 'tagged' with the names of one or more toggles:

var('p_start', IPA_DT_SEC, .04, ['if_pauses'])

The semantics of a toggle are as follows:

  • 'on': the item is enabled, and the tagged variables are shown in the IPA interface
  • 'off': the item is disabled, and the tagged variables are hidden
  • 'hide': the item is enabled, and the tagged variables are hidden

Example

Here's a complete example (examples/ipa_test.py). It plays notes with a crescendo/diminuendo. The start, middle and end volumes (v1, v2, v3) are adjustable, as are the durations of the crescendo (i1) and the diminuendo (i2).

import numula_path
from numula.ipa import *
from numula.notate_score import *
from numula.notate_nuance import *

var('pvol', IPA_TOGGLE, 'on')

tags = ['pvol']
var('v1', IPA_VOL, .2, tags, 'starting volume')
var('v2', IPA_VOL, .6, tags, 'peak volume')
var('v3', IPA_VOL, .2, tags, 'ending volume')
var('i1', IPA_DT_SCORE, '1/2', tags)
var('i2', IPA_DT_SCORE, '1/2', tags)

if __name__ == '__main__':
    read_vars('test_ipa')

from numula.ipa import *

def main():
    ns = sh_score('1/32 *4 c5 d e f g a b c * 1/4 -c')
    if pvol != 'off':
        ns.vol_adjust_pft(
            sh_vol(f'{v1} {i1} {v2} {i2} {v3}'),
            mode=VOL_SET
        )
    return ns

if __name__ == '__main__':
    import numula.pianoteq
    ns = main()
    print(ns)
    numula.pianoteq.play_score()

pvol is a toggle for the volume adjustment. If pvol is 'off', the volume is not adjusted. v1 and v2 are "tagged" with pvol. If pvol is 'hide' or 'off', v1 and v2 are hidden in the DIPA interface.

You can run this program directly:

python test_ipa.py

If you've edited the parameters and saved the values (see below) it will use the saved values.

Using IPA with long/complex pieces

A piece may have many sections and many nuance layers, potentially leading to hundreds of nuance parameters. It's easiest to focus on a few (maybe 10 to 20) parameters at a time.

A strategy for doing this:

  • Name the nuance layers. For example, ltt could refer to the long-term tempo layer.
  • Name the sections. For example 8x11 could mean measures 8 through 11.
  • Name adjustable variables in a systematic way: for example L_S_n where L is the layer name, S is the section name, and n (1,2,...) identifies the particular variable.

Create toggles for each section and layer, e.g.

var('ltt', IPA_TOGGLE, 'on')
var('m8x11', IPA_TOGGLE, 'on')

Use these as 'tags' for nuance variables:

var('ltt_m8x11_1', IPA_TEMPO, 60, ['ltt', 'm8x11'])

IPA will show a variable only if all the tags are on. So in this case you'll see the variable only if both ltt and m8x11 are 'on'.

When you're done with a section, you can take the final parameter values (from the .vars file) and hardwire them into your program. But you may as well leave them as adjustable variables, so that you can come back to them in the future.

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