-
Notifications
You must be signed in to change notification settings - Fork 29
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
Conversation
There was a problem hiding this 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–
js/packages/core/src/Reconciler.res
Outdated
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 👍
runtime/elem/builtins/Core.h
Outdated
} | ||
|
||
void activate(FloatType initialGain = FloatType(0)) | ||
void setFades(FloatType fadeInMs, FloatType fadeOutMs) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
runtime/elem/builtins/Core.h
Outdated
{ | ||
setProperty("active", true); | ||
currentGain.store(initialGain); | ||
fadeIn.setFadeTimeMs(GraphNode<FloatType>::getSampleRate(), fadeInMs); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
js/packages/core/index.ts
Outdated
@@ -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 }; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this 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!
runtime/elem/GraphRenderSequence.h
Outdated
@@ -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()) |
There was a problem hiding this comment.
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
runtime/elem/builtins/Core.h
Outdated
if (!val.isNumber()) | ||
return ReturnCode::InvalidPropertyType(); | ||
|
||
fade.setFadeInTimeMs(GraphNode<FloatType>::getSampleRate(), static_cast<float>((js::Number) val)); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we use it a couple places in StereoSampleSeq
, e.g. https://github.com/splice/platform/blob/7bfd56361fcf97ebfd7a0dcd03b2a434c6b478a5/plugins/studio-one-integration/SpliceSampleSeq.h#L59
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactly!
Merged, thank you! |
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.