basic rendering node infrastructure mostly working

This commit is contained in:
Spencer Russell 2013-12-22 16:45:16 -05:00
parent c2d771d952
commit 12760b4096

View file

@ -10,19 +10,126 @@ typealias PaStream Void
const PA_NO_ERROR = 0 const PA_NO_ERROR = 0
# default stream used when none is given # default stream used when none is given
_stream = Nothing _stream = nothing
_process_tasks = Task[]
################## Types ####################
# A node in the render tree
abstract AudioNode
# A frame of audio, possibly multi-channel
typealias AudioBuf Array{Float32}
# Info about the hardware device
type DeviceInfo
sample_rate::Integer
buf_size::Integer
end
type AudioStream
# TODO: this union may have performance penalties
root_node::Union(AudioNode, Nothing)
info::DeviceInfo
function AudioStream(sample_rate, buf_size)
new(nothing, DeviceInfo(sample_rate, buf_size))
end
end
function render(node::Nothing, device_input::AudioBuf, info::DeviceInfo)
return zeros(info.buf_size)
end
#### SinOsc ####
# Generates a sin tone at the given frequency
type SinOsc <: AudioNode
freq::FloatingPoint
phase::FloatingPoint
function SinOsc(freq::FloatingPoint)
new(freq, 0.0)
end
end
function render(node::SinOsc, device_input::AudioBuf, info::DeviceInfo)
phase = Float32[1:info.buf_size] * 2pi * node.freq / info.sample_rate
phase += node.phase
node.phase = phase[end]
return sin(phase)
end
#### AudioMixer ####
# Mixes a set of inputs equally
type AudioMixer <: AudioNode
mix_inputs::Array{AudioNode}
end
function render(node::AudioMixer, device_input::AudioBuf, info::DeviceInfo)
# TODO: we may want to pre-allocate this buffer and share between render
# calls
mix_buffer = zeros(info.buf_size)
for in_node in node.mix_inputs
mix_buffer += render(in_node, device_input, info)
end
end
#### Array Player ####
# Plays a Vector{Float32} by rendering it out piece-by-piece
type ArrayPlayer <: AudioNode
arr::AudioBuf
arr_index::Int
function ArrayPlayer(arr::AudioBuf)
new(arr, 1)
end
end
function render(node::ArrayPlayer, device_input::Vector{Float32}, info::DeviceInfo)
i = node.arr_index
range_end = min(i + info.buf_size, length(node.arr))
output = node.arr[i:range_end]
if length(output) < info.buf_size
output = vcat(output, zeros(info.buf_size - length(output)))
end
node.arr_index = range_end + 1
return output
end
#### AudioInput ####
# Renders incoming audio input from the hardware
type AudioInput <: AudioNode
channel::Int
end
function render(node::AudioInput, device_input::Vector{Float32}, info::DeviceInfo)
@assert size(device_input, 1) == info.buf_size
return device_input[:, node.channel]
end
############ Exported Functions ############# ############ Exported Functions #############
function play(arr::Array{Float32}, stream)
function register(node::AudioNode, stream::AudioStream)
stream.root_node = node
end
function play(arr::AudioBuf, stream::AudioStream)
player = ArrayPlayer(arr)
register(player, stream)
end end
function play(arr) function play(arr)
global _stream global _stream
if _stream == Nothing if _stream == nothing
_stream = open_stream() _stream = open_stream()
end end
play(arr, _stream) play(arr, _stream)
@ -31,16 +138,22 @@ end
############ Internal Functions ############ ############ Internal Functions ############
function open_stream(sample_rate::Int=44100, buf_size::Int=1024) function open_stream(sample_rate::Int=44100, buf_size::Int=1024)
# TODO: handle more streams
global _stream global _stream
if _stream != Nothing if _stream != nothing
error("Currently only 1 stream is supported at a time") error("Currently only 1 stream is supported at a time")
end 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 = AudioStream(sample_rate, buf_size)
fd = ccall((:make_pipe, libportaudio_shim), Cint, ()) fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
info("Launching Audio Task...") info("Launching Audio Task...")
function task_wrapper() function task_wrapper()
audio_task(fd) audio_task(fd, _stream)
end end
schedule(Task(task_wrapper)) schedule(Task(task_wrapper))
# TODO: test not yielding here # TODO: test not yielding here
@ -52,18 +165,8 @@ function open_stream(sample_rate::Int=44100, buf_size::Int=1024)
sample_rate, buf_size) sample_rate, buf_size)
handle_status(err) handle_status(err)
info("Portaudio stream started.") info("Portaudio stream started.")
end
function process!(out_array, in_array) return _stream
global phase
for i in 1:buf_size
out_array[i] = sin(phase)
phase += 2pi * freq / sample_rate
if phase > 2pi
phase -= 2pi
end
end
return buf_size
end end
function wake_callback_thread(out_array) function wake_callback_thread(out_array)
@ -72,15 +175,14 @@ function wake_callback_thread(out_array)
out_array, size(out_array, 1)) out_array, size(out_array, 1))
end end
function audio_task(jl_filedesc) function audio_task(jl_filedesc::Integer, stream::AudioStream)
info("Audio Task Launched") info("Audio Task Launched")
in_array = convert(Array{Float32}, zeros(buf_size)) in_array = convert(AudioBuf, zeros(stream.info.buf_size))
out_array = convert(Array{Float32}, zeros(buf_size))
desc_bytes = Cchar[0] desc_bytes = Cchar[0]
jl_stream = fdio(jl_filedesc) jl_stream = fdio(jl_filedesc)
jl_rawfd = RawFD(jl_filedesc) jl_rawfd = RawFD(jl_filedesc)
while true while true
process!(out_array, in_array) out_array = render(stream.root_node, in_array, stream.info)
# wake the C code so it knows we've given it some more data # wake the C code so it knows we've given it some more data
wake_callback_thread(out_array) wake_callback_thread(out_array)
# wait for new data to be available from the sound card (and for it to # wait for new data to be available from the sound card (and for it to