shorepine / amy

AMY - the Additive Music synthesizer librarY

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

General linear-combination control inputs

dpwe opened this issue · comments

Currently, the way that note, envelope, and lfo inputs affect pitch, envelope, and filter cutoff etc. is fairly complex and irregular.

In the spirit of the voltage-summing nodes of analog synths, I want to introduce a fully orthogonal structure, where each voice parameter is calculated as the sum of the same set of control inputs via a matrix of scale coefficients.

For example, instead of filter_freq=1000 setting the cutoff to a fixed value, followed by bp0_target=FILTER_FREQ and setting up the bp0 envelope to get a sweep, you would do something like:

filter_freq=1000.0,1.0,0,0,0

where the vector of coefficients now indicate the weights for a fixed set of control inputs that are summed together.

The first coefficient is always taken as-is, providing a constant starting point, but the remainder are applied to inputs whose values vary, defined in some fixed order. In the example above, the second value applies to bp0, but we would also include note value (pitch), note velocity, lfo, etc.

Voice parameters include oscillator frequency, output level, filter frequency, PWM duty, and stereo pan.

I have this approach working in branch linear_combo_ctl. Each of amp, freq, filter_freq, duty, and pan take a list of up to 6 parameters, which are applied to CONST, NOTE, VEL, EG0, EG1, and MOD to calculate the actual values used by the oscillator.

All the calculations are linear sums (param = \sum coefficient * control_input) except amplitude. To support scaling the envelope generator by the velocity value, we have to multiply together all the non-zero elements. This is irregular and inelegant. It also means the amp coefficients are essentially just binary, i.e. included or not (since any nonzero value other than 1 simply scales the overall amplitude). The fix here would be to have the amplitude also specified in log-units (e.g. dB, although probably something with a range closer to 0..10), but it's impossible to express 0 amplitude in log units (since log(0) = -inf). However, we could define 0.0 as, e.g., -100 dB (which becomes silence for 16 bit outputs), and 10.0 as 0 dB (nominal full-scale).

To make the behavior sort-of contiguous with how things were before, I also had to introduce some specific defaults:

  • amp_coefs defaults to {0, 0, 1, 1, 0, 0} so that by default the note amplitude is the product of the velocity attribute and the first envelope generator (EG0). EG0 follows the key gate (1 after a nonzero velocity, then 0 after a vel=0 event), so it provides the rectangular envelope proportional to velocity. (Note that the VEL control input does not clear to zero at note-off, so any release provided by the EG will continue at the same scale).
  • freq_coefs defaults to {0, 1, 0, 0, 0, 0}, i.e. the oscillator frequency follows the note input by default. However, a command such as amy.send(osc=0, freq=1000) is interpreted as freq_coefs = {1000, 0, 0, 0, 0, 0}, i.e., the frequency is made independent of the note attribute. You can, of course, have the frequency be offset from the note input with something like amy.send(osc=0, freq='16.36,1'). The constant frequency is represented as log-frequency relative to MIDI 0 (8.18 Hz), so this command makes osc 0 have a frequency one octave above the note value.

This was resolved by #93