Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expose fade times for root activation/deactivation #52

Merged
merged 12 commits into from
Jul 25, 2024

Conversation

awood314
Copy link
Contributor

Instead of the baked-in fades that occur with root node activation and deactivation, this exposes individual fade-in and fade-out times (in ms), which can help in cases where we want to preserve transients.

Copy link
Contributor

@nick-thompson nick-thompson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you! Couple suggestions inline–

let visitSet = Set.make()
let roots = Belt.List.mapWithIndex(Belt.List.fromArray(graphs), (i, g) => {
NodeRepr.create("root", {"channel": i}, [g])
})

visit(delegate, visitSet, roots)

RenderDelegate.activateRoots(delegate, Belt.List.toArray(Belt.List.map(roots, r => r.hash)))
RenderDelegate.activateRoots(delegate, Belt.List.toArray(Belt.List.map(roots, r => r.hash)), rootFadeInMs, rootFadeOutMs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep the custom fade times out of the activateRoots chain, just because we already have a suitable pattern for propagating properties like this to the nodes that need them 3 lines up–

NodeRepr.create("root", {"channel": i, "fadeInMs": rootFadeInMs, "fadeOutMs": rootFadeOutMs}, [g])

With just that change we can keep activateRoots totally ignorant to the business of root node fades, which I think is valuable (any part of the code that doesn't need to know something, shouldn't know it). Plus this way we don't change anything about the instruction format

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nice! didn't realize you could pass properties into create. I like this much better 👍

}

void activate(FloatType initialGain = FloatType(0))
void setFades(FloatType fadeInMs, FloatType fadeOutMs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new method goes away too if we use the setProperty chain, which I find cleaner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

{
setProperty("active", true);
currentGain.store(initialGain);
fadeIn.setFadeTimeMs(GraphNode<FloatType>::getSampleRate(), fadeInMs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be cleaner to still have 1 fade but parameterize the GainFade class to accept different values for up/down steps

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that thought as well, but thought it might get weird if you activate and deactivate quickly. i'll think through that again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@@ -152,6 +152,9 @@ class Renderer {
private _sendMessage: Function;
private _nextRefId: number;

// Fades applied to roots when they are activated and deactivated as a result of render
rootFades: { inMs: number, outMs: number} = { inMs: 20, outMs: 20 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these public with the intention of a user writing property updates after constructing the renderer if they want to customize fade times?

let x = new Renderer((batch) => something(batch));
x.rootFades.inMs = 8;
x.rootFades.outMs = 8;
x.render(...);

Personally I'm not a big fan of the imperative style there; I'd prefer that we either:

  • Make these things constant for the lifetime of the Renderer instance, in which case we take these as constructor args that you don't get to change later
  • Make these arguments to the render() function so that it's clear (and more declarative) with each invocation what your fade expectations are

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha, I thought you might not like this, but I agree it does feel a little gross. I think then I'll go with adding a separate renderWithOptions so we don't break the existing interface. sound good?

@awood314 awood314 requested a review from nick-thompson July 22, 2024 15:53
Copy link
Contributor

@nick-thompson nick-thompson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, this looks great! Thanks for covering all the earlier feedback. I just left a few more small nitpicks, if you don't mind. Then happy to merge asap!

@@ -178,7 +178,7 @@ namespace elem
{
// Don't promote if our RootRenderSequence represents a RootNode that has become
// inactive, even if it's still fading out
if (rootPtr->getTargetGain() < FloatType(0.5))
if (!rootPtr->active() && rootPtr->stillRunning())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticing this– I think this changes the semantics of the conditional. We want to return early if the root node is inactive, even if it's still fading out.

I have a ticket in my todo-list to revisit here to smoothly fade out the feedback taps here, but until we get to that I'd say let's preserve the existing behavior here

if (!val.isNumber())
return ReturnCode::InvalidPropertyType();

fade.setFadeInTimeMs(GraphNode<FloatType>::getSampleRate(), static_cast<float>((js::Number) val));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nitpick but you don't need the static_cast<float> because (js::Number) is just a double, and the setFadeInTimeMs and equivalent methods below accept double

updateCurrentStep();
}

void setCurrentGain(FloatType gain) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ty!

targetGain = g;
void process (const FloatType* input, FloatType* output, int numSamples) {
auto const _currentGain = currentGain.load();
if (_currentGain == targetGain.load()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the fpEqual helper here, and inside the block multiply against the target gain (so that we mul against the target even when the current is just slightly off but still satisfies fpEqual)

@@ -354,7 +354,7 @@ namespace elem
auto ptr = std::dynamic_pointer_cast<RootNode<FloatType>>(it->second);
if (ptr)
{
ptr->activate(currentRoots.empty() ? FloatType(1) : FloatType(0));
ptr->setProperty("active", true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I'm following here, even though this was maybe my idea anyways from our early convos– this line does change the default behavior of the initial render(), right? But our intention is that a user who wants to disable the fade on the first render should make their first render call with renderWithOptions({rootFadeInMs: 0}, ...graph);

Just want to be clear on that so I can update the docs correctly when we push v4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly!

@nick-thompson nick-thompson merged commit ced5db5 into elemaudio:develop Jul 25, 2024
4 checks passed
@nick-thompson
Copy link
Contributor

Merged, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants