Adjusting output level & pan for polyphonic SynthDef

So I’m building a polyphonic synth engine for norns and I can’t figure out the output volume: what’s the best (simplest? more efficient?) way to adjust it when multiple notes are playing? I came across this issue when using a noise oscillator, which makes the increase in volume more apparent for every note held. My goal is to keep the output volume relatively “flat” no matter how many notes are playing. I realize there will always be variations depending on harmonics and wave shape etc…

I tried multiplying the output level by the reciprocal of the playing notes count but I’m struggling with the function… linear makes too much of an abrupt change, tried exponential but my ratio is probably wrong. Is there a standard calculation for this or is there another way to do it in SuperCollider I’m not aware of?

Also, just been banging my head on panning: just can’t get it to work… I did exactly as the example by @mimetaur here but all I get is a mono sound with slight amplitude reduction when values are changed towards -1 and +1…

for uncorrelated sources, a good rule of thumb is that each additional source adds about +3db power. (if sources were completely phase-correlated, then summing amplitudes would be appropriate.)

the implementation of level scaling by active voice count in supercollider, is another question. in general i would not actually do this but instead just leave enough headroom to accomodate all voices, and maybe follow up with a limiter. if you do want to manually adjust the output levels, you would do that on a bus, and you would have smoothing and maybe even a lookahead delay. (it’s a compressor, whether you formulate it as such or implement the gain changes directly.)

but it’s an interesting question and i will try and mock up an auto-scaling polyphonic jobbie right now.

re: panning, the example you link is correct, so i’m not sure what’s going wrong with your version; would help to see code. also wonder how you are listening to the output and if L+R are just getting summed after the fact. Pan2 is an equal-power panner, so you will not see a volume dip at zero, but the perceptual smoothness of L+R level would depend on input signal characteristics.

1 Like

here is a gist that aims to help investigate auto-levelling strategies in supercollider.

it consists of:

  • a class file called PolyLevelTest.sc, which is compatible with norns system, and should be installed in e.g. Platform.userExtensionDir,)
  • a script called poly_level_test.scd which should be run line-by-line according to the comments.

the input synth makes rando FM notes with random noise mixture, filtered randomly. so perceptual loudness and spectral distribution varies a lot, although the raw amplitude of each voice should be the same (unless i messed up.)

there are two basic approaches shown here.

  1. use the onFree method when spawning a voice, to define a callback that will fire when that voice is finished; in that callback, calculate and apply appropriate master output bus gain given the current count of active voices. parameters of this calculation can be tuned at runtime. (currently just a single param, estimating added gain per voice.)

limitations: in the very quick and dirty implementation here, this just sounds like a really terrible compressor to me. gain factor is smoothed, but it is still just a series of flat plateaus connected by exponential ramps. overshoot would be a problem if amp smoothing time were longer than the attack time of a voice.

further work for this approach: it would be more robust and flexible to actually use ampltude followers on each voice and/or break out the signals from the voice amplitude envelopes. this is effectively a compressor, but with the gain calculation explicitly broken out and performed on the client side (while keeping per-sample smoothing/filtering calculations on the server side.) like other bus limiters, this would benefit from a lookahead path at the expense of latency.

  1. use Limiter or Normalizer Ugens, performing all gain computation on the server.

Normalizer is just a very aggressive limiter with automatic makeup gain. both of these ugens guarantee that output will never exceed specified gain limit by using a lookahead delay.

limitations: lookahead means latency.

further work: break out limiter parameters for tweaking.