tidalcycles / Dirt

Experimental sample playback
GNU General Public License v3.0
85 stars 24 forks source link

Load samples asynchronously in a separate thread #21

Closed munshkr closed 9 years ago

munshkr commented 9 years ago

Whenever Dirt needs to load up a new sample from disk, it now creates a thread for reading and loading sound data into cache. Meanwhile the main thread continues playing, solving the problem with large sample files blocking the main execution thread.

To keep it simple, I made it so that there can only be one thread for file reading (that is, there are no queues), and to enforce that, there is a boolean variable (and its corresponding mutex) that determines if there is actually one running. If there is, skip file loading.

This changeset has a (nice) side effect that makes Dirt play a sample at a specific time only if it's already loaded in the samples cache, meaning that the first time you try to play an unloaded sample, it will not actually play until the next "tick" after it's been loaded in the cache. I think this is actually better than the old behavior, because now it will always play a sample in the right moment, even if it's delayed a bit more than before (because it used to play just after loading it).

munshkr commented 9 years ago

Hmm, I just came across a sample that causes Dirt to segfault, so let me check and test a bit more, just in case.

munshkr commented 9 years ago

OK, there was a small bug, I think it's fixed :)

yaxu commented 9 years ago

Thanks for this I merged it but I don't like the behaviour as it stands.

E.g. if I run this:

d1 $ sound "bd sn:2*2"

then this:

d1 $ sound "bd:2 sn:2*2"

the first time round the bass drum isn't played. It'd be much better if it was late by a few samples in that case.

I guess part of the problem is when it starts loading the sample. It should do that as soon as the OSC message comes in.. I'll have a look into it.

yaxu commented 9 years ago

Sorry I reverted it again, this change of behaviour won't work out in many situations.

I'm surprised this doesn't work out better, there's 0.04 seconds of latency in which dirt can load its samples, and as far as I can tell dirt is using it.

I think what we need though is a way that sample triggers are still queued even if samples aren't loaded yet, and only get dequeued when they are. This could be a runtime option - whether to drop late triggers or not.

munshkr commented 9 years ago

I see what you mean... For small samples, it should take little time and could play them as soon as it's loaded. Do you think preloading as soon as Dirt receives the OSC message would solve this problem in most cases?

I think what we need though is a way that sample triggers are still queued even if samples aren't loaded yet, and only get dequeued when they are. This could be a runtime option - whether to drop late triggers or not.

If I understood correctly, you're saying that when file_get fails to get a cached sample and needs to read from disk, it should queue that audio_play call, and when sample is finally loaded, dequeue and retrigger it?

yaxu commented 9 years ago

Do you think preloading as soon as Dirt receives the OSC message would solve this problem in most cases? Yes although from my brief look, it seems to be doing that already. I could be wrong!

If I understood correctly, you're saying that when file_get fails to get a cached sample and needs to read from disk, it should queue that audio_play call, and when sample is finally loaded, dequeue and retrigger it?

Yes, not sure whether it needs a special queue for this, I guess that would be the ideal situation..

munshkr commented 9 years ago

:+1: I'll try to solve this and let you know. Thanks!

munshkr commented 9 years ago

@yaxu OK, I've been working on this and I think I managed to solve the issue you mentioned...

Basically Dirt now calls audio_play again, with the same arguments, as soon as the sample is loaded. There was no need for a queue because a thread only handles one sample so I just needed to pass a copy of the arguments to the thread start routine as a callback function.

I also added a command-line option to enable or disable late triggering, just in case someone doesn't want late triggers. I left it enabled as default so it behaves as always. Because, actually, most of the time Dirt plays samples really fast :) Especially if you keep them normalized to skip samplerate conversion, for instance.

I'll keep testing it a bit more before submitting a new PR, but you can have a look here if you want.