shorepine / amy

AMY - A high-performance fixed-point Music synthesizer librarY for microcontrollers
https://shorepine.github.io/amy/
MIT License
224 stars 14 forks source link

Buses proposal #114

Open bwhitman opened 8 months ago

bwhitman commented 8 months ago

We have a couple of quality of life issues that a "bus" could help us obviate

Let's add a bus construct, where oscillators are rendered into a bus and effects can be added after summing into that bus.

If this sounds complicated, never fear, we already have two buses! fbl is used on multi core hardware to render a group of oscillators. We then sum those and add effects to them together. What if fbl could be, say, up to 6, instead of two, and you can assign oscillators to a bus number. By default, they would be set up like they are now, into two buses by their osc #, (0-AMY_OSCS/2, AMY_OSCS/2-AMY_OSCS). But a parameter, say, U / bus, can be added to the oscillator to override this bus.

Then, synth wide "effects" like (currently) chorus, reverb, eq can also have U / bus set alongside them (with default being all active buses), e.g. chorus_level=1, bus=0,1,3. And (for example) juno patches can have bus numbers pre-set into ones that need chorus / eq / etc and those that don't. Then dx7 and juno voices could be played simultaneously with chorus only on the appropriate ones.

(Future) a bus can be indicated as non-audible, or have its volume set per bus for mixing. Other C code could peek into a bus to, for example, send the bus output to a non-audio sink, like a i2c DAC on Tulip for modular synth ramps.

bwhitman commented 1 month ago

A more concrete buses spec:

We make a new parameter Y = bus_input. bus_input can be 0-7 (internal rendering buses), or 8-15 (external input buses). bus_input is 0 by default (if not given)

We also make a new parameter z = bus_output. This is 0 by default (first stereo output) but can be up to 7.

Ignoring audio in for now, imagine a Juno-6 patch that has Y1 in it. This would mean that any (eg) chorus, EQ, or reverb command only turns on for that bus. We also make a Y2 for every dx7 patch, so that those effects do not render for those patches. We implement this by having more fbl in our rendering (instead of 2, we can have 8 of them)

Now, I want the audio in to go through an echo. I send AMY Y8M1. This tells AMY that bus 8 (first external input) goes through echo with level 1.0. I can render effects on any input.

Not giving bus_input (which really means bus_input=0) means the rest of the AMY commands in the message render into a default bus (the way it works now). Audio input will not be affected if bus_input is not given with an effect command.

To mix buses, we can use volume. Y9V0.5 will change the volume of the first input. To set pan on buses we use pan. V9Q0,1,1,1 sets the PAN_COEFs for a bus.

To send buses to other outputs, we can use bus_output, so e.g. sending external input 0 to the output bus 1 is Y8z1. bus_output is 0 if not given, the default now, just sending audio to the first output.

Buses are stereo pairs. I could imagine a future bus parameter that breaks apart stereo pairs and allows you to address channels individually but we shouldn’t worry about that now.

Buses can be a list of buses. bus_input = 1,2 should work. So should bus_output=1,2

dpwe commented 1 month ago

I feel like a bus is a bus and an output is an output. Oscs are assigned to buses, each bus has effects parameters. Then outputs are mixed down from buses. I think this is pretty much your syntax, it's just the terminology.

In this view, external audio-in could be another bus as you suggest (instead of an osc). I'm not sure how to pan a mono external input - right now pan is a property of an osc, and I think of buses as all stereo. I'm not crazy about a pan parameter in the final mixdown, but it's possible, then another flag to make a bus mono?

dpwe commented 1 month ago

Oh I think maybe there's a difference in how we specify the final mixdown. Your bus_output=1,2 suggests you set the output as a property of some signal source.

For me, that would be output_1_mix=',,1,,,', output_2_mix=',,1,,,'. Which signals appear on which outputs is determined by independent parameters owned by each output; it's not the source's business.

My approach allows the precise mix level on each output to be set differently, a bit reminiscent of ControlCoefs vs mod_target.