basic rendering node infrastructure mostly working
This commit is contained in:
parent
c2d771d952
commit
12760b4096
1 changed files with 123 additions and 21 deletions
144
src/PortAudio.jl
144
src/PortAudio.jl
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue