adds tests and support for playing more array types
This commit is contained in:
parent
6c8b99407c
commit
39bae583ed
3 changed files with 100 additions and 16 deletions
|
@ -22,6 +22,10 @@ typealias AudioBuf Array{AudioSample}
|
|||
# A node in the render tree
|
||||
abstract AudioNode
|
||||
|
||||
# A stream of audio (for instance that writes to hardware)
|
||||
# All AudioStream subtypes should have a mixer and info field
|
||||
abstract AudioStream
|
||||
|
||||
# Info about the hardware device
|
||||
type DeviceInfo
|
||||
sample_rate::Integer
|
||||
|
@ -30,11 +34,11 @@ end
|
|||
|
||||
include("nodes.jl")
|
||||
|
||||
type AudioStream
|
||||
type PortAudioStream <: AudioStream
|
||||
mixer::AudioMixer
|
||||
info::DeviceInfo
|
||||
|
||||
function AudioStream(sample_rate, buf_size)
|
||||
function PortAudioStream(sample_rate, buf_size)
|
||||
mixer = AudioMixer()
|
||||
new(mixer, DeviceInfo(sample_rate, buf_size))
|
||||
end
|
||||
|
@ -43,37 +47,55 @@ end
|
|||
|
||||
############ Exported Functions #############
|
||||
|
||||
# TODO: we should have "stop" functions that remove nodes from the render tree
|
||||
|
||||
# Play an AudioNode by adding it as an input to the root mixer node
|
||||
function play(node::AudioNode, stream::AudioStream)
|
||||
# TODO: don't break demeter
|
||||
stream.mixer.mix_inputs = vcat(stream.mixer.mix_inputs, [node])
|
||||
append!(stream.mixer.mix_inputs, [node])
|
||||
return nothing
|
||||
end
|
||||
|
||||
# If the stream is not given, use the default global stream
|
||||
function play(node::AudioNode)
|
||||
global _stream
|
||||
if _stream == nothing
|
||||
_stream = open_stream()
|
||||
_stream = open_portaudio_stream()
|
||||
end
|
||||
play(node, _stream)
|
||||
end
|
||||
|
||||
function play(arr::AudioBuf, stream::AudioStream)
|
||||
# TODO: use a mixer as the root node so multiple playbacks get mixed
|
||||
# Allow users to play a raw array by wrapping it in an ArrayPlayer
|
||||
function play(arr::AudioBuf, args...)
|
||||
player = ArrayPlayer(arr)
|
||||
play(player, stream)
|
||||
play(player, args...)
|
||||
end
|
||||
|
||||
function play(arr::AudioBuf)
|
||||
global _stream
|
||||
if _stream == nothing
|
||||
_stream = open_stream()
|
||||
end
|
||||
play(arr, _stream)
|
||||
# If the array is the wrong floating type, convert it
|
||||
function play{T <: FloatingPoint}(arr::Array{T}, args...)
|
||||
arr = convert(AudioBuf, arr)
|
||||
play(arr, args...)
|
||||
end
|
||||
|
||||
# If the array is an integer type, scale to [-1, 1] floating point
|
||||
|
||||
# integer audio can be slightly (by 1) more negative than positive,
|
||||
# so we just scale so that +/- typemax(T) becomes +/- 1
|
||||
function play{T <: Signed}(arr::Array{T}, args...)
|
||||
arr = arr / typemax(T)
|
||||
play(arr, args...)
|
||||
end
|
||||
|
||||
function play{T <: Unsigned}(arr::Array{T}, args...)
|
||||
zero = (typemax(T) + 1) / 2
|
||||
range = floor(typemax(T) / 2)
|
||||
arr = (arr - zero) / range
|
||||
play(arr, args...)
|
||||
end
|
||||
|
||||
############ Internal Functions ############
|
||||
|
||||
function open_stream(sample_rate::Int=44100, buf_size::Int=1024)
|
||||
function open_portaudio_stream(sample_rate::Int=44100, buf_size::Int=1024)
|
||||
# TODO: handle more streams
|
||||
global _stream
|
||||
if _stream != nothing
|
||||
|
@ -82,7 +104,7 @@ function open_stream(sample_rate::Int=44100, buf_size::Int=1024)
|
|||
|
||||
# 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)
|
||||
_stream = PortAudioStream(sample_rate, buf_size)
|
||||
|
||||
|
||||
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
||||
|
@ -111,7 +133,7 @@ function wake_callback_thread(out_array)
|
|||
out_array, size(out_array, 1))
|
||||
end
|
||||
|
||||
function audio_task(jl_filedesc::Integer, stream::AudioStream)
|
||||
function audio_task(jl_filedesc::Integer, stream::PortAudioStream)
|
||||
info("Audio Task Launched")
|
||||
in_array = zeros(AudioSample, stream.info.buf_size)
|
||||
desc_bytes = Cchar[0]
|
||||
|
|
|
@ -58,6 +58,8 @@ type ArrayPlayer <: AudioNode
|
|||
end
|
||||
|
||||
function render(node::ArrayPlayer, device_input::AudioBuf, info::DeviceInfo)
|
||||
# TODO: this should remove itself from the render tree when playback is
|
||||
# complete
|
||||
i = node.arr_index
|
||||
range_end = min(i + info.buf_size-1, length(node.arr))
|
||||
output = node.arr[i:range_end]
|
||||
|
|
60
test/test_PortAudio.jl
Normal file
60
test/test_PortAudio.jl
Normal file
|
@ -0,0 +1,60 @@
|
|||
using Base.Test
|
||||
using PortAudio
|
||||
|
||||
const TEST_SAMPLERATE = 44100
|
||||
const TEST_BUF_SIZE = 1024
|
||||
|
||||
type TestAudioStream <: PortAudio.AudioStream
|
||||
mixer::AudioMixer
|
||||
info::PortAudio.DeviceInfo
|
||||
|
||||
function TestAudioStream()
|
||||
mixer = AudioMixer()
|
||||
new(mixer, PortAudio.DeviceInfo(TEST_SAMPLERATE, TEST_BUF_SIZE))
|
||||
end
|
||||
end
|
||||
|
||||
# render the stream and return the next block of audio. This is used in testing
|
||||
# to simulate the audio callback that's normally called by the device.
|
||||
function process(stream::TestAudioStream)
|
||||
in_array = zeros(PortAudio.AudioSample, stream.info.buf_size)
|
||||
return PortAudio.render(stream.mixer, in_array, stream.info)
|
||||
end
|
||||
|
||||
|
||||
#### Test playing back various vector types ####
|
||||
|
||||
# data shared between tests, for convenience
|
||||
t = linspace(0, 2, 2 * 44100)
|
||||
phase = 2pi * 100 * t
|
||||
|
||||
## Test Float32 arrays, this is currently the native audio playback format
|
||||
f32 = convert(Array{Float32}, sin(phase))
|
||||
test_stream = TestAudioStream()
|
||||
player = play(f32, test_stream)
|
||||
@test process(test_stream) == f32[1:TEST_BUF_SIZE]
|
||||
#stop(player)
|
||||
#@test process(test_stream) == zeros(PortAudio.AudioSample, TEST_BUF_SIZE)
|
||||
|
||||
|
||||
## Test Float64 arrays
|
||||
f64 = convert(Array{Float64}, sin(phase))
|
||||
test_stream = TestAudioStream()
|
||||
player = play(f64, test_stream)
|
||||
@test process(test_stream) == convert(PortAudio.AudioBuf, f64[1:TEST_BUF_SIZE])
|
||||
|
||||
## Test Int8(Signed) arrays
|
||||
i8 = Int8[-127:127]
|
||||
test_stream = TestAudioStream()
|
||||
player = play(i8, test_stream)
|
||||
@test_approx_eq(process(test_stream)[1:255],
|
||||
convert(PortAudio.AudioBuf, linspace(-1.0, 1.0, 255)))
|
||||
|
||||
## Test Uint8(Unsigned) arrays
|
||||
# for unsigned 8-bit audio silence is represented as 128, so the symmetric range
|
||||
# is 1-255
|
||||
ui8 = Uint8[1:255]
|
||||
test_stream = TestAudioStream()
|
||||
player = play(ui8, test_stream)
|
||||
@test_approx_eq(process(test_stream)[1:255],
|
||||
convert(PortAudio.AudioBuf, linspace(-1.0, 1.0, 255)))
|
Loading…
Reference in a new issue