a little restructuring and starting to add some tests

This commit is contained in:
Spencer Russell 2013-12-22 18:06:02 -05:00
parent 3a29b40c97
commit 5a2222ccea
3 changed files with 127 additions and 89 deletions

View file

@ -1,9 +1,6 @@
module PortAudio module PortAudio
# simple interface
export play export play
# AudioNodes
export SinOsc, AudioMixer, ArrayPlayer, AudioInput export SinOsc, AudioMixer, ArrayPlayer, AudioInput
typealias PaTime Cdouble typealias PaTime Cdouble
@ -18,13 +15,12 @@ _stream = nothing
################## Types #################### ################## Types ####################
# A node in the render tree
abstract AudioNode
typealias AudioSample Float32 typealias AudioSample Float32
# A frame of audio, possibly multi-channel # A frame of audio, possibly multi-channel
typealias AudioBuf Array{AudioSample} typealias AudioBuf Array{AudioSample}
# A node in the render tree
abstract AudioNode
# Info about the hardware device # Info about the hardware device
type DeviceInfo type DeviceInfo
@ -32,99 +28,24 @@ type DeviceInfo
buf_size::Integer buf_size::Integer
end end
include("nodes.jl")
type AudioStream type AudioStream
# TODO: this union may have performance penalties mixer::AudioMixer
root_node::Union(AudioNode, Nothing)
info::DeviceInfo info::DeviceInfo
function AudioStream(sample_rate, buf_size) function AudioStream(sample_rate, buf_size)
new(nothing, DeviceInfo(sample_rate, buf_size)) mixer = AudioMixer()
new(mixer, DeviceInfo(sample_rate, buf_size))
end end
end end
function render(node::Nothing, device_input::AudioBuf, info::DeviceInfo)
return zeros(AudioSample, info.buf_size)
end
#### SinOsc ####
# Generates a sin tone at the given frequency
type SinOsc <: AudioNode
freq::Real
phase::FloatingPoint
function SinOsc(freq::Real)
new(freq, 0.0)
end
end
function render(node::SinOsc, device_input::AudioBuf, info::DeviceInfo)
phase = AudioSample[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(AudioSample, info.buf_size)
for in_node in node.mix_inputs
mix_buffer += render(in_node, device_input, info)
end
end
#### Array Player ####
# Plays a AudioBuf 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::AudioBuf, info::DeviceInfo)
i = node.arr_index
range_end = min(i + info.buf_size-1, length(node.arr))
output = node.arr[i:range_end]
if length(output) < info.buf_size
output = vcat(output, zeros(AudioSample, 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::AudioBuf, info::DeviceInfo)
@assert size(device_input, 1) == info.buf_size
return device_input[:, node.channel]
end
############ Exported Functions ############# ############ Exported Functions #############
function play(node::AudioNode, stream::AudioStream) function play(node::AudioNode, stream::AudioStream)
stream.root_node = node # TODO: don't break demeter
stream.mixer = vcat(stream.mixer.mix_inputs, [node])
end end
function play(node::AudioNode) function play(node::AudioNode)
@ -196,7 +117,7 @@ function audio_task(jl_filedesc::Integer, stream::AudioStream)
jl_stream = fdio(jl_filedesc) jl_stream = fdio(jl_filedesc)
jl_rawfd = RawFD(jl_filedesc) jl_rawfd = RawFD(jl_filedesc)
while true while true
out_array = render(stream.root_node, in_array, stream.info)::AudioBuf 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 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

82
src/nodes.jl Normal file
View file

@ -0,0 +1,82 @@
#### SinOsc ####
# Generates a sin tone at the given frequency
type SinOsc <: AudioNode
freq::Real
phase::FloatingPoint
function SinOsc(freq::Real)
new(freq, 0.0)
end
end
function render(node::SinOsc, device_input::AudioBuf, info::DeviceInfo)
phase = AudioSample[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}
function AudioMixer{T <: AudioNode}(mix_inputs::Array{T})
new(mix_inputs)
end
function AudioMixer()
new(AudioNode[])
end
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(AudioSample, info.buf_size)
for in_node in node.mix_inputs
mix_buffer += render(in_node, device_input, info)
end
return mix_buffer
end
#### Array Player ####
# Plays a AudioBuf 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::AudioBuf, info::DeviceInfo)
i = node.arr_index
range_end = min(i + info.buf_size-1, length(node.arr))
output = node.arr[i:range_end]
if length(output) < info.buf_size
output = vcat(output, zeros(AudioSample, 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::AudioBuf, info::DeviceInfo)
@assert size(device_input, 1) == info.buf_size
return device_input[:, node.channel]
end

35
test/nodes.jl Normal file
View file

@ -0,0 +1,35 @@
using Base.Test
using PortAudio
info = PortAudio.DeviceInfo(44100, 512)
dev_input = zeros(PortAudio.AudioSample, info.buf_size)
# A TestNode just renders out 1:buf_size each frame
type TestNode <: PortAudio.AudioNode
end
function PortAudio.render(node::TestNode,
device_input::PortAudio.AudioBuf,
info::PortAudio.DeviceInfo)
return PortAudio.AudioSample[1:info.buf_size]
end
#### AudioMixer Tests ####
# TODO: there should be a setup/teardown mechanism and some way to isolate
# tests
mix = AudioMixer()
@test mix.mix_inputs == PortAudio.AudioNode[]
@test PortAudio.render(mix, dev_input, info) == zeros(PortAudio.AudioSample, info.buf_size)
testnode = TestNode()
mix = AudioMixer([testnode])
@test mix.mix_inputs == PortAudio.AudioNode[testnode]
@test PortAudio.render(mix, dev_input, info) == PortAudio.AudioSample[1:info.buf_size]
test1 = TestNode()
test2 = TestNode()
mix = AudioMixer([test1, test2])
@test PortAudio.render(mix, dev_input, info) == 2 * PortAudio.AudioSample[1:info.buf_size]