pulls out portaudio-specific stuff into separate code file
This commit is contained in:
parent
97dc8025bf
commit
292005e1cd
2 changed files with 170 additions and 165 deletions
168
src/AudioIO.jl
168
src/AudioIO.jl
|
@ -3,13 +3,6 @@ module AudioIO
|
||||||
# export the basic API
|
# export the basic API
|
||||||
export play
|
export play
|
||||||
|
|
||||||
typealias PaTime Cdouble
|
|
||||||
typealias PaError Cint
|
|
||||||
typealias PaSampleFormat Culong
|
|
||||||
typealias PaStream Void
|
|
||||||
|
|
||||||
const PA_NO_ERROR = 0
|
|
||||||
|
|
||||||
# default stream used when none is given
|
# default stream used when none is given
|
||||||
_stream = nothing
|
_stream = nothing
|
||||||
|
|
||||||
|
@ -33,17 +26,7 @@ type DeviceInfo
|
||||||
end
|
end
|
||||||
|
|
||||||
include("nodes.jl")
|
include("nodes.jl")
|
||||||
|
include("portaudio.jl")
|
||||||
type PortAudioStream <: AudioStream
|
|
||||||
mixer::AudioMixer
|
|
||||||
info::DeviceInfo
|
|
||||||
|
|
||||||
function PortAudioStream(sample_rate, buf_size)
|
|
||||||
mixer = AudioMixer()
|
|
||||||
new(mixer, DeviceInfo(sample_rate, buf_size))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
############ Exported Functions #############
|
############ Exported Functions #############
|
||||||
|
|
||||||
|
@ -56,11 +39,11 @@ function play(node::AudioNode, stream::AudioStream)
|
||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the stream is not given, use the default global stream
|
# If the stream is not given, use the default global PortAudio stream
|
||||||
function play(node::AudioNode)
|
function play(node::AudioNode)
|
||||||
global _stream
|
global _stream
|
||||||
if _stream == nothing
|
if _stream == nothing
|
||||||
_stream = open_portaudio_stream()
|
_stream = PortAudioStream()
|
||||||
end
|
end
|
||||||
play(node, _stream)
|
play(node, _stream)
|
||||||
end
|
end
|
||||||
|
@ -93,149 +76,4 @@ function play{T <: Unsigned}(arr::Array{T}, args...)
|
||||||
play(arr, args...)
|
play(arr, args...)
|
||||||
end
|
end
|
||||||
|
|
||||||
############ Internal Functions ############
|
|
||||||
|
|
||||||
function open_portaudio_stream(sample_rate::Int=44100, buf_size::Int=1024)
|
|
||||||
# TODO: handle more streams
|
|
||||||
global _stream
|
|
||||||
if _stream != nothing
|
|
||||||
error("Currently only 1 stream is supported at a time")
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: when we support multiple streams we won't set _stream here.
|
|
||||||
# this is just to ensure that only one stream is ever opened
|
|
||||||
_stream = PortAudioStream(sample_rate, buf_size)
|
|
||||||
|
|
||||||
|
|
||||||
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
|
||||||
|
|
||||||
info("Launching Audio Task...")
|
|
||||||
function task_wrapper()
|
|
||||||
audio_task(fd, _stream)
|
|
||||||
end
|
|
||||||
schedule(Task(task_wrapper))
|
|
||||||
# TODO: test not yielding here
|
|
||||||
yield()
|
|
||||||
info("Audio Task Yielded, starting the stream...")
|
|
||||||
|
|
||||||
err = ccall((:open_stream, libportaudio_shim), PaError,
|
|
||||||
(Cuint, Cuint),
|
|
||||||
sample_rate, buf_size)
|
|
||||||
handle_status(err)
|
|
||||||
info("Portaudio stream started.")
|
|
||||||
|
|
||||||
return _stream
|
|
||||||
end
|
|
||||||
|
|
||||||
function wake_callback_thread(out_array)
|
|
||||||
ccall((:wake_callback_thread, libportaudio_shim), Void,
|
|
||||||
(Ptr{Void}, Cuint),
|
|
||||||
out_array, size(out_array, 1))
|
|
||||||
end
|
|
||||||
|
|
||||||
function audio_task(jl_filedesc::Integer, stream::PortAudioStream)
|
|
||||||
info("Audio Task Launched")
|
|
||||||
in_array = zeros(AudioSample, stream.info.buf_size)
|
|
||||||
desc_bytes = Cchar[0]
|
|
||||||
jl_stream = fdio(jl_filedesc)
|
|
||||||
jl_rawfd = RawFD(jl_filedesc)
|
|
||||||
while true
|
|
||||||
out_array = render(stream.mixer, in_array, stream.info)::AudioBuf
|
|
||||||
# wake the C code so it knows we've given it some more data
|
|
||||||
wake_callback_thread(out_array)
|
|
||||||
# wait for new data to be available from the sound card (and for it to
|
|
||||||
# have processed our last frame of data). At some point we should do
|
|
||||||
# something with the data we get from the callback
|
|
||||||
wait(jl_rawfd, readable=true)
|
|
||||||
# read from the file descriptor so that it's empty. We're using ccall
|
|
||||||
# here because readbytes() was blocking the whole julia thread. This
|
|
||||||
# shouldn't block at all because we just waited on it
|
|
||||||
ccall(:read, Clong, (Cint, Ptr{Void}, Culong), jl_filedesc, desc_bytes, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function handle_status(err::PaError)
|
|
||||||
if err != PA_NO_ERROR
|
|
||||||
msg = ccall((:Pa_GetErrorText, "libportaudio"),
|
|
||||||
Ptr{Cchar}, (PaError,), err)
|
|
||||||
error("libportaudio: " * bytestring(msg))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function init_portaudio()
|
|
||||||
info("Initializing PortAudio. Expect errors as we scan devices")
|
|
||||||
err = ccall((:Pa_Initialize, "libportaudio"), PaError, ())
|
|
||||||
handle_status(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
########### Module Initialization ##############
|
|
||||||
|
|
||||||
const libportaudio_shim = find_library(["libportaudio_shim",],
|
|
||||||
[Pkg.dir("AudioIO", "deps", "usr", "lib"),])
|
|
||||||
|
|
||||||
@assert(libportaudio_shim != "", "Failed to find required library libportaudio_shim. Try re-running the package script using Pkg.build(\"AudioIO\"), then reloading with reload(\"AudioIO\")")
|
|
||||||
|
|
||||||
init_portaudio()
|
|
||||||
|
|
||||||
end # module AudioIO
|
end # module AudioIO
|
||||||
|
|
||||||
|
|
||||||
#type PaStreamCallbackTimeInfo
|
|
||||||
# inputBufferAdcTime::PaTime
|
|
||||||
# currentTime::PaTime
|
|
||||||
# outputBufferDacTime::PaTime
|
|
||||||
#end
|
|
||||||
#
|
|
||||||
#typealias PaStreamCallbackFlags Culong
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#function stream_callback{T}( input_::Ptr{T},
|
|
||||||
# output_::Ptr{T},
|
|
||||||
# frame_count::Culong,
|
|
||||||
# time_info::Ptr{PaStreamCallbackTimeInfo},
|
|
||||||
# status_flags::PaStreamCallbackFlags,
|
|
||||||
# user_data::Ptr{Void})
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# println("stfl:$status_flags \tframe_count:$frame_count")
|
|
||||||
#
|
|
||||||
# ret = 0
|
|
||||||
# return convert(Cint,ret)::Cint #continue stream
|
|
||||||
#
|
|
||||||
#end
|
|
||||||
#
|
|
||||||
#T=Float32
|
|
||||||
#stream_callback_c = cfunction(stream_callback,Cint,
|
|
||||||
#(Ptr{T},Ptr{T},Culong,Ptr{PaStreamCallbackTimeInfo},PaStreamCallbackFlags,Ptr{Void})
|
|
||||||
#)
|
|
||||||
#stream_obj = Array(Ptr{PaStream},1)
|
|
||||||
#
|
|
||||||
#pa_err = ccall(
|
|
||||||
#(:Pa_Initialize,"libportaudio"),
|
|
||||||
#PaError,
|
|
||||||
#(),
|
|
||||||
#)
|
|
||||||
#
|
|
||||||
#println(get_error_text(pa_err))
|
|
||||||
#
|
|
||||||
#pa_err = ccall(
|
|
||||||
#(:Pa_OpenDefaultStream,"libportaudio"),
|
|
||||||
#PaError,
|
|
||||||
#(Ptr{Ptr{PaStream}},Cint,Cint,PaSampleFormat,Cdouble,Culong,Ptr{Void},Any),
|
|
||||||
#stream_obj,0,1,0x1,8000.0,4096,stream_callback_c,None
|
|
||||||
#)
|
|
||||||
#
|
|
||||||
#println(get_error_text(pa_err))
|
|
||||||
#
|
|
||||||
#function start_stream(stream)
|
|
||||||
# pa_err = ccall(
|
|
||||||
# (:Pa_StartStream,"libportaudio"),
|
|
||||||
# PaError,
|
|
||||||
# (Ptr{PaStream},),
|
|
||||||
# stream
|
|
||||||
# )
|
|
||||||
# println(get_error_text(pa_err))
|
|
||||||
#end
|
|
||||||
#
|
|
||||||
#end #module
|
|
||||||
|
|
167
src/portaudio.jl
Normal file
167
src/portaudio.jl
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
typealias PaTime Cdouble
|
||||||
|
typealias PaError Cint
|
||||||
|
typealias PaSampleFormat Culong
|
||||||
|
typealias PaStream Void
|
||||||
|
|
||||||
|
const PA_NO_ERROR = 0
|
||||||
|
const libportaudio_shim = find_library(["libportaudio_shim",],
|
||||||
|
[Pkg.dir("AudioIO", "deps", "usr", "lib"),])
|
||||||
|
|
||||||
|
# track whether we've already inited PortAudio
|
||||||
|
portaudio_inited = false
|
||||||
|
|
||||||
|
################## Types ####################
|
||||||
|
|
||||||
|
type PortAudioStream <: AudioStream
|
||||||
|
mixer::AudioMixer
|
||||||
|
info::DeviceInfo
|
||||||
|
|
||||||
|
function PortAudioStream(sample_rate::Int=44100, buf_size::Int=1024)
|
||||||
|
global portaudio_inited
|
||||||
|
if !portaudio_inited
|
||||||
|
@assert(libportaudio_shim != "", "Failed to find required library libportaudio_shim. Try re-running the package script using Pkg.build(\"AudioIO\"), then reloading with reload(\"AudioIO\")")
|
||||||
|
|
||||||
|
init_portaudio()
|
||||||
|
portaudio_inited = true
|
||||||
|
else
|
||||||
|
error("Currently only 1 stream is supported at a time")
|
||||||
|
end
|
||||||
|
mixer = AudioMixer()
|
||||||
|
stream = new(mixer, DeviceInfo(sample_rate, buf_size))
|
||||||
|
# we need to start up the stream with the portaudio library
|
||||||
|
open_portaudio_stream(stream)
|
||||||
|
return stream
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
############ Internal Functions ############
|
||||||
|
|
||||||
|
function wake_callback_thread(out_array)
|
||||||
|
ccall((:wake_callback_thread, libportaudio_shim), Void,
|
||||||
|
(Ptr{Void}, Cuint),
|
||||||
|
out_array, size(out_array, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function init_portaudio()
|
||||||
|
info("Initializing PortAudio. Expect errors as we scan devices")
|
||||||
|
err = ccall((:Pa_Initialize, "libportaudio"), PaError, ())
|
||||||
|
handle_status(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
function open_portaudio_stream(stream::PortAudioStream)
|
||||||
|
# starts up a stream with the portaudio library and associates it with the
|
||||||
|
# given AudioIO PortAudioStream
|
||||||
|
|
||||||
|
# TODO: handle more streams
|
||||||
|
|
||||||
|
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
||||||
|
|
||||||
|
info("Launching PortAudio Task...")
|
||||||
|
function task_wrapper()
|
||||||
|
portaudio_task(fd, stream)
|
||||||
|
end
|
||||||
|
schedule(Task(task_wrapper))
|
||||||
|
# TODO: test not yielding here
|
||||||
|
yield()
|
||||||
|
info("Audio Task Yielded, starting the stream...")
|
||||||
|
|
||||||
|
err = ccall((:open_stream, libportaudio_shim), PaError,
|
||||||
|
(Cuint, Cuint),
|
||||||
|
stream.info.sample_rate, stream.info.buf_size)
|
||||||
|
handle_status(err)
|
||||||
|
info("Portaudio stream started.")
|
||||||
|
end
|
||||||
|
|
||||||
|
function handle_status(err::PaError)
|
||||||
|
if err != PA_NO_ERROR
|
||||||
|
msg = ccall((:Pa_GetErrorText, "libportaudio"),
|
||||||
|
Ptr{Cchar}, (PaError,), err)
|
||||||
|
error("libportaudio: " * bytestring(msg))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream)
|
||||||
|
info("Audio Task Launched")
|
||||||
|
in_array = zeros(AudioSample, stream.info.buf_size)
|
||||||
|
desc_bytes = Cchar[0]
|
||||||
|
jl_stream = fdio(jl_filedesc)
|
||||||
|
jl_rawfd = RawFD(jl_filedesc)
|
||||||
|
while true
|
||||||
|
out_array = render(stream.mixer, in_array, stream.info)::AudioBuf
|
||||||
|
# wake the C code so it knows we've given it some more data
|
||||||
|
wake_callback_thread(out_array)
|
||||||
|
# wait for new data to be available from the sound card (and for it to
|
||||||
|
# have processed our last frame of data). At some point we should do
|
||||||
|
# something with the data we get from the callback
|
||||||
|
wait(jl_rawfd, readable=true)
|
||||||
|
# read from the file descriptor so that it's empty. We're using ccall
|
||||||
|
# here because readbytes() was blocking the whole julia thread. This
|
||||||
|
# shouldn't block at all because we just waited on it
|
||||||
|
ccall(:read, Clong, (Cint, Ptr{Void}, Culong), jl_filedesc, desc_bytes, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Old code for reference during initial development. We can get rid of this
|
||||||
|
# once the library is a little more mature
|
||||||
|
|
||||||
|
|
||||||
|
#type PaStreamCallbackTimeInfo
|
||||||
|
# inputBufferAdcTime::PaTime
|
||||||
|
# currentTime::PaTime
|
||||||
|
# outputBufferDacTime::PaTime
|
||||||
|
#end
|
||||||
|
#
|
||||||
|
#typealias PaStreamCallbackFlags Culong
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#function stream_callback{T}( input_::Ptr{T},
|
||||||
|
# output_::Ptr{T},
|
||||||
|
# frame_count::Culong,
|
||||||
|
# time_info::Ptr{PaStreamCallbackTimeInfo},
|
||||||
|
# status_flags::PaStreamCallbackFlags,
|
||||||
|
# user_data::Ptr{Void})
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# println("stfl:$status_flags \tframe_count:$frame_count")
|
||||||
|
#
|
||||||
|
# ret = 0
|
||||||
|
# return convert(Cint,ret)::Cint #continue stream
|
||||||
|
#
|
||||||
|
#end
|
||||||
|
#
|
||||||
|
#T=Float32
|
||||||
|
#stream_callback_c = cfunction(stream_callback,Cint,
|
||||||
|
#(Ptr{T},Ptr{T},Culong,Ptr{PaStreamCallbackTimeInfo},PaStreamCallbackFlags,Ptr{Void})
|
||||||
|
#)
|
||||||
|
#stream_obj = Array(Ptr{PaStream},1)
|
||||||
|
#
|
||||||
|
#pa_err = ccall(
|
||||||
|
#(:Pa_Initialize,"libportaudio"),
|
||||||
|
#PaError,
|
||||||
|
#(),
|
||||||
|
#)
|
||||||
|
#
|
||||||
|
#println(get_error_text(pa_err))
|
||||||
|
#
|
||||||
|
#pa_err = ccall(
|
||||||
|
#(:Pa_OpenDefaultStream,"libportaudio"),
|
||||||
|
#PaError,
|
||||||
|
#(Ptr{Ptr{PaStream}},Cint,Cint,PaSampleFormat,Cdouble,Culong,Ptr{Void},Any),
|
||||||
|
#stream_obj,0,1,0x1,8000.0,4096,stream_callback_c,None
|
||||||
|
#)
|
||||||
|
#
|
||||||
|
#println(get_error_text(pa_err))
|
||||||
|
#
|
||||||
|
#function start_stream(stream)
|
||||||
|
# pa_err = ccall(
|
||||||
|
# (:Pa_StartStream,"libportaudio"),
|
||||||
|
# PaError,
|
||||||
|
# (Ptr{PaStream},),
|
||||||
|
# stream
|
||||||
|
# )
|
||||||
|
# println(get_error_text(pa_err))
|
||||||
|
#end
|
||||||
|
#
|
||||||
|
#end #module
|
Loading…
Reference in a new issue