Replies: 2 comments 7 replies
-
All the Elementary Audio examples I saw online so far, they're usually show how to start the continuous signal with some rate nodes like |
Beta Was this translation helpful? Give feedback.
-
Hey @satelllte, great question, this is one I get a lot. You mentioned Elementary demonstrating continuous signals, and I think that's a good place to start. Every audio graph that you render with Elementary represents a continuous signal, something that will keep playing each time the audio driver needs more information to give to your speakers. So let gate = el.const({key: 'g', value: 1});
let env = el.adsr(1, 1, 1, 1, gate);
// Emits a sine tone that passes through a 1s attack phase of an ADSR envelope
core.render(el.mul(env, el.cycle(440));
// Some time later...
let gate = el.const({key: 'g', value: 0});
let env = el.adsr(1, 1, 1, 1, gate);
// The gate falls to 0, triggering the release phase of the ADSR, eventually muting our sine tone
core.render(el.mul(env, el.cycle(440)); Note in this example that we have to be very intentional about representing the time when the gate is high (envelope is on), and the time when the gate is low (envelope is off). The same principles apply to sample playback, we just have to fit them into the semantics of the // Triggers sample playback immediately because our constant value 1 has a rising edge and never falls
core.render(el.sample({path}, 1, 1);
// Triggers sample playback every 1s, and there's a falling edge halfway through the train period but you don't hear it
// because the sample is in it's default "trigger" mode
core.render(el.sample({path}, el.train(1), 1);
// Triggers sample playback every 1s, and there's a falling edge halfway through the train period and you *do* hear it
// because the sample is in "gate" mode
core.render(el.sample({path, mode: "gate"}, el.train(1), 1); So to your specific questions, stopping the sample requires you to either use "gate" mode and send the falling edge at the time Now of course there are instances where you can't break your "onClick" into two discrete events; here we have to figure out how to map impulses (events which can't be broken into pieces) to the pulse train state. For this, a pattern I like is to use two sampler "voices" (the same way a multi-sampler or synthesizer has many voices). That way, when I get the event impulse, I can send a rising edge to one of my sampler voices and send a falling edge to the other, preparing it for the next event impulse let nextVoice = 0;
let voices = [ {path, value: 0}, {path, value: 0} ];
onImpulseEvent = (e) => {
// We'll send a rising edge to the next available voice
voices[nextVoice].value = 1;
nextVoice = (nextVoice + 1) % voices.length;
// And pre-emptively "reset" the new next voice
voices[nextVoice].value = 0;
core.render(
el.add(
el.sample({path: voices[0].path, mode: "gate"}, el.const({key: 'g1', value: voices[0].value}))),
el.sample({path: voices[1].path, mode: "gate"}, el.const({key: 'g2', value: voices[1].value}))),
)
);
}; It can be confusing trying to get comfortable with this "representing continuous signals" idea, but I think it's worth it once you get it. Let me know if that all makes sense! |
Beta Was this translation helpful? Give feedback.
-
For example, I have
onClick
callback where I'm producing some signal and I want to stop after it's finished playing:So I'm wondered:
Beta Was this translation helpful? Give feedback.
All reactions