If there's any code showing how it's supposed to work, I'd probably be off and running! What I want to do is apply a volume envelope to a channel when it plays a sample, so it fades in and back out smoothly. But instead, the result is simply silence:
voiceChannel:addSource(voice)
local fadeTime = 1.5 --Attack and release
local env = snd.envelope.new(fadeTime, 0, 1, fadeTime)
voiceChannel:setVolumeMod(env)
env:trigger(1, fadeTime)
voice:play(1, -1)
voiceChannel has a sampleplayer voice which plays just fine (in reverse: rate = -1) as long as I don't add the envelope.
(Omitting the env:trigger or moving it after the voice:play makes no difference. Still silence.)
Looks like I broke this when I added envelope rate scaling (changing the envelope speed based on pitch)--if you trigger the envelope manually instead of using noteOn() the rate isn't set and the envelope doesn't move. I've fixed that by setting the rate to 1 when manually triggering the envelope, and also implemented velocity sensitivity there to match noteOn().
I came up with a workaround by creating a dummy synth, playing and immediately stopping it so the envelope rate gets set, then using that envelope for the channel volume mod, and that exposed another bug in the Lua layer where synth:getEnvelope() messes up the synth->channel reference. But it does work if you do getEnvelope() after play/stop instead of before:
-- hack to create a triggerable envelope
local dummysynth = snd.synth.new()
dummysynth:setADSR(fadeTime, 0, 1, fadeTime)
dummysynth:playNote("C4")
dummysynth:stop()
local env = dummysynth:getEnvelope() -- has to be after play/stop, due to yet another bug
Nailing the trifecta, your demo also unearthed an out of bounds read when you do looping reverse playback. It's only one sample past the end, unlikely anyone would ever notice it, but you should be proud that you managed to find three bugs with one demo.
Thanks for the workarounds, I may need them. For now, my workaround has been that a sine LFO with the proper parameters is very close to the ADSR envelope I wanted anyway: slow rise, slow falloff. So I constructed that LFO and run it for a single cycle at a time.