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
compliant AudioBufferSourceNode and AudioBuffer #67
compliant AudioBufferSourceNode and AudioBuffer #67
Conversation
…to be consistent with other nodes
Hi @b-ma thanks for all the work. stay tuned, I need some more time |
Hi @b-ma, thanks for the PR. You've surely put a lot of effort in it. As I understand, your PR contains the following:
Regarding 1) the current implementation of AudioBuffer is a Regarding 2) I don't directly see what is the difference is with the current implementation. If we are going to add hundreds of new lines of code, I think there should be a very clear benefit to it. The old AudioBufferSourceNode also takes a buffer, ships it to the render thread, and renders/resamples/loops on the render thread. All in all I don't really see how your implementation differs from the current implementation (although you have added some methods from the spec that are missing in the current implementation). But maybe I am missing something, can you explain what difference in architecture you are trying to achieve? I'm going to add a few comments on the code to help our mutual understanding! All in all this is a good way to discuss the project's internals |
attributes: SharedAttributes, | ||
channel_config: ChannelConfig, | ||
sender: Sender<BufferChannelsMessage>, | ||
detune: AudioParam, // has constraints, no a-rate |
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.
These two params are not actually used in rendering, am I right (also not in the current implementation)
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 they are, the computed_playback_rate
is calculated line 431 and used to update the renderer internal state (mainly playhead position) at each sample (line 576)
ok lot's of things to say, so
Several things there:
const buffer = getSomeOneSecondBufferSomehow();
const now = audioContext.currentTime;
for (let i = 0; i < 100; i++) {
const src = audioContext.createBufferSource();
src.buffer = buffer;
src.start(now + i * 0.01);
} Here, you have 100 source that access the same
const buffer = getSomeOneSecondBufferSomehow();
const now = audioContext.currentTime;
for (let i = 0; i < 100; i++) {
if (i == 50) {
buffer.copyToChannel(someArray, 0);
buffer.copyToChannel(someArray, 1);
}
const src = audioContext.createBufferSource();
src.buffer = buffer;
src.start(now + i * 0.01);
} Here, the 50 first sources (that are still playing) will continue to read the Edit [spec]: This operation returns immutable channel data to the invoker.
The main differences (which are quite important because it's why the
All these features are really important if you want to go beyond simply playing an audio file (for which you'd use |
…tion It is very flawed: - decode_audio_buffer does not exist yet so I need to put it in a media element - this means I need to hardcode the buffer duration (3.0 seconds) - since there's only 1 media element, I need to stop the previous scrub before I can start the next one. Causing clicks and a not nice sound - something is off with the volume, I almost blew my eardrums out!
Hi @b-ma, thanks for your initial replies. Edit: I just now see your reply from 11 minutes ago, I need to read that carefully. Sidenote: I have added a commit with a very rudimentary forward/backward scrubbing example with original implementation It is very flawed:
I will put some more work in it later, I think I can safely port your decode_audio_buffer code and shoehorn it into the current implementation. Then I can work on making my version of the example better! |
These are very valid points, I see now. I will get back |
Hey, for your volume problem, I guess it's probably related to how the decoding is done in Line 504 in af1fe81
which should be I think we kind of fallback here in the discussion of #64. |
To keep stuff a bit organized, I propose to discuss 'acquiring the content' in a later issue. Okay? Let's focus on building a good AudioBufferSourceNode renderer in this PR. |
I think this is going in a great direction! I will try to answer your question inline in the diff |
// | ||
// assert_eq!(buffer.sample_rate().0, 96_000); | ||
// ``` | ||
pub(crate) fn resample(&mut self, sample_rate: SampleRate) { |
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 should definitely replace this version later with the library we are already using
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.
you mean the rubato
lib here?
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!
2 things left I guess to make the
I preferred not to change that since I don't know if |
Let's drop |
Damn, that unfolded another problem I didn't see in let channel_data = ChannelData::from(vec![self.value; RENDER_QUANTUM_SIZE]);
let buffer = AudioBuffer::from_channels(vec![channel_data], self.sample_rate); I guess a good option here could be that the Maybe it's best if you can have a look on that, as I'm not really familiar with these streaming nodes. |
Sure, I'll have a look. Can you push the changes so far, even though the build/tests will fail? |
… + deleted bench.rs
Ok thanks, just pushed my last changes (w/ build failing in
|
and update test. Remove some unused stuff and fix docs
Hey @b-ma I added my changes. Is this ready for final review now or are you still considering new changes? |
Hey, thanks ! No I think I'm good on my side |
src/buffer.rs
Outdated
pub fn is_empty(&self) -> bool { | ||
self.data.is_empty() | ||
} | ||
// never used |
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.
delete or keep that around?
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.
Clippy says we should keep it (as we also provide a len() method)
I'll do a short pass on the comments tomorrow, there are a few typos and irrelevant/outdated notes here and there Edit: that's done |
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.
Almost ready to merge I think. I left some nitpicks
// Then the number of frames copied from buffer to destination is max(0,min(𝑁𝑏−𝑘,𝑁𝑓)). | ||
// If this is less than 𝑁𝑓, then the remaining elements of destination are not modified. | ||
let dest_length = destination.len(); | ||
let max_frame = (self.length() - offset).min(dest_length).max(0); |
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.
if offset is a very large number self.length() - offset
could cause an underflow. We should guard for it as it causes a panic in debug mode
src/buffer.rs
Outdated
pub fn is_empty(&self) -> bool { | ||
self.data.is_empty() | ||
} | ||
// never used |
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.
Clippy says we should keep it (as we also provide a len() method)
src/control.rs
Outdated
@@ -24,6 +26,8 @@ impl Scheduler { | |||
Self { |
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 Scheduler
is the driver of the AudioScheduledSourceNode
trait which corresponds to https://www.w3.org/TR/webaudio/#AudioScheduledSourceNode
We should probably move offset & duration to the Controller
. The Controller (not part of the spec) is also used for MediaElements but I will figure out a way to actually use these fields there later
// | ||
// assert_eq!(buffer.sample_rate().0, 96_000); | ||
// ``` | ||
pub(crate) fn resample(&mut self, sample_rate: SampleRate) { |
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!
- moved `offset` and `duration` in `Controller` - make sure `offset` can't be outside range in `AudioBuffer::copy_from_channel` and `AudioBuffer::copy_to_channel` - reintroduce `ChannelData::is_empty` with `#[allow(dead_code)]`
ok, changes are done! |
Great work! |
Cool ! and thanks for your help and patience on that |
Hey,
Here is a working draft, there are still a number of points that needs to be improved but the thing is working and have a clean user-facing API.
Most of the new code lies in
src/audio_buffer.rs
andsrc/node/audio_buffer_source.rs
, but I had to add some few stuff here and there.I have put a lot of comments in the code but to summarise the things I'm not very happy with:
decodeAudioData
only parse wav files and don't resample, but it's ok for testing and validating the principlesAudioBufferSourceNode
but from the examples I'm quite confident there is no huge problemI have put 2 new examples:
cargo run --release --example trigger_soundfile
which showcases most of the APIcargo run --release --example granular
which shows that it works quite well (stable ~2.4% cpu on my computer, I'm quite happy with this one :)Let me know what you think