pulls out portaudio-specific stuff into separate code file

This commit is contained in:
Spencer Russell 2014-01-03 11:47:38 -08:00
parent 97dc8025bf
commit 292005e1cd
2 changed files with 170 additions and 165 deletions

View file

@ -3,13 +3,6 @@ module AudioIO
# export the basic API
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
_stream = nothing
@ -33,17 +26,7 @@ type DeviceInfo
end
include("nodes.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
include("portaudio.jl")
############ Exported Functions #############
@ -56,11 +39,11 @@ function play(node::AudioNode, stream::AudioStream)
return nothing
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)
global _stream
if _stream == nothing
_stream = open_portaudio_stream()
_stream = PortAudioStream()
end
play(node, _stream)
end
@ -93,149 +76,4 @@ function play{T <: Unsigned}(arr::Array{T}, args...)
play(arr, args...)
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
#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
View 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