N voice library (beta, feedback wanted)

continuing discussions from:

I’m sharing a proposal for a community library of synthesis engines based on a shared communication standard.

the goal

to open up a variety of use cases beyond typical poly synth stuff, without complicating the synth engine, + opening up some nice interactions for lua script authors. I’m proposing four basic assumptions around such an engine:

  • there are n voices
  • all the voices are the same (share the same parameters)
  • one parameter must set frequency in hz
  • one parameter must set the peak level of an envelope, or the volume/presence of a sound


what I’ve worked on is a lua syntax for communicating with such an engine, here’s an overview:

nv.name(name) : select engine from the nv lib
nv.init(n) : initialize n voices
nv.vc : list of voices and parameters per voice (e.g. nv.vc[1].hz = 440 )
nv.all : list of global parameters, summed (multiplied if .hz) with voice parameters per voice
.hz : required parameter, frequency in hz
.peak : required prameter, specifies the peak of an envelope or volume of a sound
vc.list : list of additional parameter keys supplied by engine
nv.active : list of active voices, populated via engine polls
nv.free: list of free voices, populated via engine pools
nv.id(id) : voice allocator, assigns an id to a free voice & returns the voice if id does not exist, or returns actice voice if exists
nv.update() : sends table data to supercollider

in the repo I sketched up three example scripts - a regular poly-synth with a couple niche features, two simultaneous, asynchronously-enveloped arps, and a 20 voice MEGADRONE - all using the same engine and syntax

next steps

what do you think ? what haven’t I considered ?

this is the front end - given my limited comfort with supercollider, I’m seeking help with working on the back-end standard, modifying PolySub and coming up with engine params and polls to best suit the spec laid out in this lua lib. (I * assume * it would be best for the engine to receive per-voice values in a batch format, and send free/active states back to lua in a poll)

from there I can fill out the middle !

(aside: I’m not super attached to names of things, though I enjoy super concise character counts for stuff like this)


this is an awesome idea. i would definitely be keen on making an “n voice library” structure for arcologies that would read all these and store the voice for you.

i still say that it is unnecessary to maintain communication about the running voice state.

looking at your sketches, i’m not seeing anything that supports this need.
it seems sufficient for lua to maintain an array of binary values, representing the logical active state of voices. whether a double activation causes a new instance (e.g. one-shot percussive synth) or a retrigger (e.g. sustained mono/uni/poly/parasynth) is up to the engine.

if a certain allocation strategy is to be enforced in the scripting side, the arbitrary nature of the voice ID already supports this.

there is one edge case that i can see:

  • if round-robin (rather than paraphonic) allocation is desired,
  • and if release times are slow
  • and if you run into the limit of the voice count
  • and, you do not want the oldest voice to simply retrigger when reactivated (let’s say you want to ignore the activation request instead.)

then i suppose there is the potential minor issue that logical active state (ON) could be different from the actual runnning state of a voice (OFF, because the engine ignored the activation request and the voice subsequently finished whatever it was busy with.) even then, the functional impact seems minimal.

i think this is quite unlikely to be a problem in practice (retriggering is usually gonna be the desired behavior, and paraphonic allocation is probably a better default) and not worth adding a large amount of class complexity and OSC traffic.

i’m also unclear on what you mean by “batch format” and “send table data.” there is no special procedure at present for sending batches of OSC messages to supercollider.

there is some limit of how many “simultaneous” packets can be sent without dropping any. this is a system-wide issue, but it is mostly theoretical at this point. if we end up running up against that limitation, we can then discuss possible solutions (which could be quite tricky and low-level.)

you’re probably right, if it complicates engine building I’d say it’s worth leaving out. id() could just maintain a FILO buffer by default and the script can adjust voice count to compensate for longer envelopes

yea, had this issue with an R script which is why I mentioned it - this may not be as bad as what was happening in there

so yea ok, dropped packets are a non-hypothetical problem. it was also an issue with softcut at one point before we added a dedicated voice-reset command.

i’m just saying that there isn’t actually a workaround at present (particularly for supercollider, which doesn’t understand any non-networked IPC mechanism out of the box.)

i’m happy to discuss possible workarounds but best to do it in the proper venue since the issue is not specific to this functionality. (there is a GH issue, which may have been closed in the 2020 purge but could be reopened.)

regarding these, and maybe this goes without saying: these should not require special mechanisms from the engine. in norns v3 supercollider WIP, there are helpers to create polyphonic interfaces based on parameter name patterns. (e.g., generates voice_<name> command with [int, float] arguments from a list of synthdef arguments.) but those helpers simply enforce / rely on naming conventions and do not need any new OSC paths or additions to the sc <-> matron protocol.

pragmatically, that kind of additional structure can also be encoded in a lua helper file accompanying the engine, as has become common practice.

yea I misspoke there, by “engine” I meant “synth” which would have an SC engine file and a lua file that populates the vc.list table and maybe other things

I’m back ! I’ve got more or less a working draft of this going with PolySub

(be careful with drone.lua, for some reason the level initializations aren’t working properly)

I decided to store parameter values supercollider rather than lua and updated the spec to match. so there’s an Engine_Nv superclass that other engines inherit from, which means a lua helper file is optional rather than required. it seems like it works … but certainly still getting by bearings with SC

there are some bugs but the examples mostly work !

1 Like