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

Support call-back functions? #56

Open
bramtayl opened this issue Apr 25, 2021 · 14 comments
Open

Support call-back functions? #56

bramtayl opened this issue Apr 25, 2021 · 14 comments

Comments

@bramtayl
Copy link
Member

At least according to the PortAudio documentation, using a call-back function to access buffers seems like it would be more performant

@bramtayl
Copy link
Member Author

bramtayl commented Apr 29, 2021

I've been playing around with this on my fork of PortAudio here: https://github.com/bramtayl/PortAudio.jl

I'm totally new at interfacing with C from Julia, and I get immediate segfaults as soon as I try anything:

PortAudioStream(input(), output()) do input_buffer, output_buffer, framecount, time_info, callback_flags, userdata
    return paComplete
end

I'll see what I can figure out about what went wrong, but if anyone has some tips that could be helpful.

@bramtayl
Copy link
Member Author

bramtayl commented May 4, 2021

well, I'm pretty sure this won't be feasible. see https://discourse.julialang.org/t/cfunction-help/60263 for more info.

@bramtayl bramtayl closed this as completed May 4, 2021
@rob-luke
Copy link
Member

rob-luke commented May 4, 2021

Thanks for taking the time to document your efforts. It's very informative.

@bramtayl bramtayl reopened this May 12, 2021
@ssfrr
Copy link
Contributor

ssfrr commented May 13, 2021

OK - buckle up.

So the main issue with using portaudio's callback interface is that the callback is called from a separate thread. In the pre-threading days Julia's runtime was not at all thread-safe, so for instance you might try to allocate memory in the callback at the same time as the main Julia thread, and everything would crash. Now that Julia has multithreading support I think they've made most of those functions threadsafe, but I think there's still some bookkeeping that's supposed to be done for each thread that Julia knows about, and it's still not kosher to have some other random thread running Julia code.

Over time I've tried a couple different approaches to this. In the beginning I wrote the callback in Julia and was just very careful not to do anything that would allocate, and made sure everything was type-stable so there was no dynamic dispatch. This actually worked, but was very finicky. Often I'd make some seemingly-innocuous change and it would start segfaulting.
The second approach I tried was writing the callback in C and passing the data to Julia using a lock-free ringbuffer (that's where RingBuffers.jl came from). That worked pretty well, but it was still a hassle to distribute the binaries for the C shim code (this was before BinaryBuilder et al. made things much easier). The C interface and juggling the ringbuffers ended up being somewhat of a hassle to work on and maintain also, so I eventually switched to the current solution of using portaudio's blocking IO interface. It simplified the code substantially.

So that's a brief history of portaudio interface attempts. :)

@bramtayl
Copy link
Member Author

Aha! I wonder if there would be a way to get the thread "bookkeeping" done correctly, especially now with multi-threading support?

@bramtayl
Copy link
Member Author

@Gnimuc was helping me here and might be interested

@bramtayl
Copy link
Member Author

@Gnimuc
Copy link

Gnimuc commented May 15, 2021

Interesting! The original post that gives rise to that doc is also portaudio-related: https://groups.google.com/g/julia-users/c/ztN-UgS9N8c/m/01bM77Vw6UgJ?pli=1

@bramtayl
Copy link
Member Author

Should have known that @ssfrr was behind this! I can't understand documentation about AsyncCondition. This issue seems helpful though, and PortAudio is mentioned again at the bottom: JuliaLang/julia#17573

@ssfrr
Copy link
Contributor

ssfrr commented May 16, 2021

In general the idea is that a task can wait on a condition, and the task will be asleep until someone notifys the condition. The idea with AsyncCondition is that the notification can happen from a totally different thread. It’s basically the only thing that’s safe to do from a different thread.

So rather than processing data within the callback, you have a Julia task with a loop that processes some data, then waits on the condition, over and over. The condition is notified every time new data is available (every audio frame in this case). The notification happens in the callback.

@bramtayl
Copy link
Member Author

Right so then how would you pass inputs and outputs back and forth between the callback and the task?

@ssfrr
Copy link
Contributor

ssfrr commented May 17, 2021

Yeah, that’s tricky. RingBuffers.jl has both a Julia interface and a C interface, so libportaudio called a callback written in C that exchanged data with the ring buffer and then notified the Julia task with an AsyncCondition.

@bramtayl bramtayl changed the title Use call-back functions? Support call-back functions? May 25, 2021
@Gnimuc
Copy link

Gnimuc commented Oct 15, 2022

Julia now supports foreign threads. JuliaLang/julia#17573

@bramtayl
Copy link
Member Author

Yeah I saw that too! I might try it out on nightly in a few days unless someone gets to it first!

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

No branches or pull requests

4 participants