-
Notifications
You must be signed in to change notification settings - Fork 238
Add rate-limiting for channel gain change messages #2535
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
Conversation
6ebc768 to
f32c241
Compare
|
Tested with single and grouped channels, and the Wireshark trace examined. |
src/client.cpp
Outdated
| if ( newGain[iId] != oldGain[iId] ) | ||
| { | ||
| // send new gain and record as old gain | ||
| Channel.SetRemoteChanGain ( iId, oldGain[iId] = newGain[iId] ); |
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.
When does the assignment happen? Before the call or after? I'd rather see it on a separate line so I don't have to think about it.
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.
The assignment expression happens first, and the assigned value is used as the parameter. To me it's a common C idiom, and in this case it also avoids repeating the array lookup again (which the compiler may possibly optimise out, but I like to write optimal code of my own).
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.
One could do:
fGain = oldGain[iId] = newGain[iId];
Channel.SetRemoteChanGain ( iId, fGain );
but to me the original is perfectly readable.
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 find it readable, but it's easy to miss, IMO.
I would have gone with a simple two-step assignment / method call as I don't think this is in a performance-critical path.
The alternative (reuse fGain) also looks fine to me.
In the end, I don't feel strongly.
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.
OK, I've updated it for clarity to use fGain.
|
Harmless code alerts in Oboe, but Github won't let me dismiss them. |
|
I think waiting for 50 milliseconds would give a more realistic response because 300 ms is too long. |
| // start timer so that elapsed time works | ||
| PreciseTime.start(); | ||
|
|
||
| // set gain delay timer to single-shot and connect handler function |
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.
That comment almost repeats the variable/method names and provides little added value, but I guess it's ok.. :)
| // but just stored in newGain[iId], and the minGainId and maxGainId updated to note the range of | ||
| // IDs that must be checked when the time expires (this will usually be a single channel | ||
| // unless channel grouping is being used). This avoids having to check all possible channels. |
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.
As we are talking about worst case n=200 or something and this is not part of the critical path (sound/network processing) and not part of something which scales exponentially (e.g. server-side per-client-per-client stuff), I think I would have gone with the check-all-channels approach in order to keep the logic simpler and shorter.
PS: Now this whole block comment provides value and is well-written! :)
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.
Yes, but I figured if it is possible at little cost to limit the number of checks needed to the specific one or a few out of the 200 channels, it's worth doing. And of course include an explanation, as I did :)
src/client.cpp
Outdated
| if ( newGain[iId] != oldGain[iId] ) | ||
| { | ||
| // send new gain and record as old gain | ||
| Channel.SetRemoteChanGain ( iId, oldGain[iId] = newGain[iId] ); |
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 find it readable, but it's easy to miss, IMO.
I would have gone with a simple two-step assignment / method call as I don't think this is in a performance-critical path.
The alternative (reuse fGain) also looks fine to me.
In the end, I don't feel strongly.
|
@softins I just tried that build with a server 160ms away and it works much better! MIDI controller changes take effect very quickly. Thank you! Originally posted by @bawbgale in #2492 (comment) |
This avoids a backlog of messages being queued due to ACK latency, particularly if using a MIDI controller that send fine-grained level changes. After sending a gain change message, further gain changes will be updated locally for 300ms and then the latest value sent to the server.
f32c241 to
2db5156
Compare
|
I've tested this PR and it does what it claims to do. Wireshark looks as expected. At the same time, this causes very noticable loudness jumps (similar to artifacts) when changing the fader level (tested via streaming music and listening to the sound coming back from the server while playing with the slider of my channel).
With 50ms, it's much smoother again, but obviously causes more protocol messages. In essence, I think
|
|
I think any server-side smoothing of gain changes might be a worthwhile improvement, but should be a separate feature and PR, as it would only apply to servers running new code. The current PR addresses the root issue at the client, which is where it originates, and works when connected to any version of server. I did wonder about having a smaller default delay period, and then increasing it if necessary based on the ping time of the connected server. Maybe 50ms or 100ms by default, and 1.5x or 2x the ping time if greater (to allow a little extra time)? It sounds like that might be a worthwhile improvement. |
|
The ping time multiplied by a suitable constant could easily be stored in |
|
I've added a second commit to default the timer to |
hoffie
left a comment
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.
Thanks! This behaves much better and looks clean. Nice work :)
PR jamulussoftware#2535 introduced rate limiting for gain change messages. The logic required storing the previously used gain value per channel. This logic had two issues: 1. The previously used gain value defaulted to 0, despite the server-side view of the channel being set to 1 (as the default). Therefore, gain(0) changes during a series of gain changes would be lost. The most common scenario would be the initial connection, which always triggers the rate limit and therefore the faulty logic. This also affected New Client Level = 0. 2. The previously used gain values were not reset upon changing servers. This might have caused losing arbitrary gain change messages, e.g. stored fader values. This commit introduces a gain level memory reset to 1 (100%) on connect to fix both of these issues. Fixes: jamulussoftware#2730
PR jamulussoftware#2535 introduced rate limiting for gain change messages. The logic required storing the previously used gain value per channel. This logic had some flaws: 1. The previously used gain value defaulted to 0, despite the server-side view of the channel being set to 1 (as the default). Therefore, gain(0) changes during a series of gain changes would be lost. The most common scenario would be the initial connection, which always triggers the rate limit and therefore the faulty logic. This also affected New Client Level = 0. 2. The previously used gain values were not reset upon changing servers. This might have caused losing arbitrary gain change messages, e.g. stored fader values. 3. The previously used gain values were not reset upon a channel disconnect. This might have caused missing fader level restores. This commit introduces a gain level memory reset to 1 (100%) on connect as well as on channel disconnects to fix these issues. Fixes: jamulussoftware#2730
PR jamulussoftware#2535 introduced rate limiting for gain change messages. The logic required storing the previously used gain value per channel. This logic had some flaws: 1. The previously used gain value defaulted to 0, despite the server-side view of the channel being set to 1 (as the default). Therefore, gain(0) changes during a series of gain changes would be lost. The most common scenario would be the initial connection, which always triggers the rate limit and therefore the faulty logic. This also affected New Client Level = 0. 2. The previously used gain values were not reset upon changing servers. This might have caused losing arbitrary gain change messages, e.g. stored fader values. 3. The previously used gain values were not reset upon a channel disconnect. This might have caused missing fader level restores. This commit introduces a gain level memory reset to 1 (100%) on connect as well as on channel disconnects to fix these issues. Fixes: jamulussoftware#2730
PR jamulussoftware#2535 introduced rate limiting for gain change messages. The logic required storing the previously used gain value per channel. This logic had some flaws: 1. The previously used gain value defaulted to 0, despite the server-side view of the channel being set to 1 (as the default). Therefore, gain(0) changes during a series of gain changes would be lost. The most common scenario would be the initial connection, which always triggers the rate limit and therefore the faulty logic. This also affected New Client Level = 0. 2. The previously used gain values were not reset upon changing servers. This might have caused losing arbitrary gain change messages, e.g. stored fader values. 3. The previously used gain values were not reset upon a channel disconnect. This might have caused missing fader level restores. This commit introduces a gain level memory reset to 1 (100%) on connect as well as on channel disconnects to fix these issues. Fixes: jamulussoftware#2730 Co-authored-by: ann0see <[email protected]>
PR jamulussoftware#2535 introduced rate limiting for gain change messages. The logic required storing the previously used gain value per channel. This logic had some flaws: 1. The previously used gain value defaulted to 0, despite the server-side view of the channel being set to 1 (as the default). Therefore, gain(0) changes during a series of gain changes would be lost. The most common scenario would be the initial connection, which always triggers the rate limit and therefore the faulty logic. This also affected New Client Level = 0. 2. The previously used gain values were not reset upon changing servers. This might have caused losing arbitrary gain change messages, e.g. stored fader values. 3. The previously used gain values were not reset upon a channel disconnect. This might have caused missing fader level restores. This commit introduces a gain level memory reset to 1 (100%) on connect as well as on channel disconnects to fix these issues. Fixes: jamulussoftware#2730 Co-authored-by: ann0see <[email protected]>
Short description of changes
Adds rate-limiting for channel gain change messages.
This avoids a backlog of messages being queued due to ACK latency,
particularly if using a MIDI controller that send fine-grained
level changes. After sending a gain change message, further gain
changes will be updated locally for
300msa time and then the latestvalue sent to the server. This time will be 50ms by default, or double the
current ping time, whichever is greater.
CHANGELOG: Client: Fix potential long delay in sending fader changes to the server.
Context: Fixes an issue?
Fixes #2492
Does this change need documentation? What needs to be documented and how?
No, bug fix only
Status of this Pull Request
Tested and working
What is missing until this pull request can be merged?
Review and testing on other platforms, and by @bawbgale, who reported the issue
Checklist