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
andf
, 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.
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.
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.
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
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.
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
whereL
is the layer name,S
is the section name, andn
(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.