removes flac test as it was broken on osx, adds get_audio_devices
This commit is contained in:
parent
81a8503c1d
commit
0d84cb409b
5 changed files with 147 additions and 94 deletions
21
deps/src/shim.c
vendored
21
deps/src/shim.c
vendored
|
@ -3,7 +3,7 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
|
@ -61,29 +61,12 @@ PaError open_stream(unsigned int sampleRate, unsigned int bufSize)
|
|||
return paNoError;
|
||||
}
|
||||
|
||||
//PaError stop_sin(void)
|
||||
//{
|
||||
// PaError err;
|
||||
// err = Pa_StopStream(sin_stream);
|
||||
// if(err != paNoError)
|
||||
// {
|
||||
// return err;
|
||||
// }
|
||||
//
|
||||
// err = Pa_CloseStream(sin_stream);
|
||||
// if( err != paNoError )
|
||||
// {
|
||||
// return err;
|
||||
// }
|
||||
// return paNoError;
|
||||
//}
|
||||
|
||||
/*
|
||||
* This routine will be called by the PortAudio engine when audio is needed.
|
||||
* It may called at interrupt level on some machines so don't do anything that
|
||||
* could mess up the system like calling malloc() or free().
|
||||
*/
|
||||
static int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module AudioIO
|
||||
|
||||
# export the basic API
|
||||
export play, stop
|
||||
export play, stop, get_audio_devices
|
||||
|
||||
# default stream used when none is given
|
||||
_stream = nothing
|
||||
|
@ -47,34 +47,6 @@ function play(node::AudioNode)
|
|||
play(node, _stream)
|
||||
end
|
||||
|
||||
# Allow users to play a raw array by wrapping it in an ArrayPlayer
|
||||
function play(arr::AudioBuf, args...)
|
||||
player = ArrayPlayer(arr)
|
||||
play(player, args...)
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
function stop(node::AudioNode)
|
||||
deactivate(node)
|
||||
node
|
||||
|
@ -99,4 +71,8 @@ function Base.wait(node::AudioNode)
|
|||
end
|
||||
end
|
||||
|
||||
function get_audio_devices()
|
||||
return get_portaudio_devices()
|
||||
end
|
||||
|
||||
end # module AudioIO
|
||||
|
|
30
src/nodes.jl
30
src/nodes.jl
|
@ -17,7 +17,7 @@ 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
|
||||
phase .+= node.phase
|
||||
node.phase = phase[end]
|
||||
return sin(phase), is_active(node)
|
||||
end
|
||||
|
@ -122,6 +122,34 @@ function render(node::ArrayPlayer, device_input::AudioBuf, info::DeviceInfo)
|
|||
return output, is_active(node)
|
||||
end
|
||||
|
||||
# Allow users to play a raw array by wrapping it in an ArrayPlayer
|
||||
function play(arr::AudioBuf, args...)
|
||||
player = ArrayPlayer(arr)
|
||||
play(player, args...)
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
#### AudioInput ####
|
||||
|
||||
# Renders incoming audio input from the hardware
|
||||
|
|
100
src/portaudio.jl
100
src/portaudio.jl
|
@ -2,11 +2,33 @@ typealias PaTime Cdouble
|
|||
typealias PaError Cint
|
||||
typealias PaSampleFormat Culong
|
||||
typealias PaStream Void
|
||||
typealias PaDeviceIndex Cint
|
||||
typealias PaHostApiIndex Cint
|
||||
typealias PaTime Cdouble
|
||||
typealias PaHostApiTypeId Cint
|
||||
|
||||
const PA_NO_ERROR = 0
|
||||
const libportaudio_shim = find_library(["libportaudio_shim",],
|
||||
[Pkg.dir("AudioIO", "deps", "usr", "lib"),])
|
||||
|
||||
# PaHostApiTypeId values
|
||||
const pa_host_api_names = {
|
||||
0 => "In Development", # use while developing support for a new host API
|
||||
1 => "Direct Sound",
|
||||
2 => "MME",
|
||||
3 => "ASIO",
|
||||
4 => "Sound Manager",
|
||||
5 => "Core Audio",
|
||||
7 => "OSS",
|
||||
8 => "ALSA",
|
||||
9 => "AL",
|
||||
10 => "BeOS",
|
||||
11 => "WDMKS",
|
||||
12 => "Jack",
|
||||
13 => "WASAPI",
|
||||
14 => "AudioScience HPI"
|
||||
}
|
||||
|
||||
# track whether we've already inited PortAudio
|
||||
portaudio_inited = false
|
||||
|
||||
|
@ -17,15 +39,7 @@ type PortAudioStream <: AudioStream
|
|||
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
|
||||
|
@ -40,12 +54,6 @@ function synchronize_buffer(buffer)
|
|||
ccall((:synchronize_buffer, libportaudio_shim), Void, (Ptr{Void},), buffer)
|
||||
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
|
||||
|
@ -55,10 +63,7 @@ function open_portaudio_stream(stream::PortAudioStream)
|
|||
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
||||
|
||||
info("Launching PortAudio Task...")
|
||||
function task_wrapper()
|
||||
portaudio_task(fd, stream)
|
||||
end
|
||||
schedule(Task(task_wrapper))
|
||||
schedule(Task(() -> portaudio_task(fd, stream)))
|
||||
# TODO: test not yielding here
|
||||
yield()
|
||||
info("Audio Task Yielded, starting the stream...")
|
||||
|
@ -108,6 +113,65 @@ function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream)
|
|||
end
|
||||
end
|
||||
|
||||
type PaDeviceInfo
|
||||
struct_version::Cint
|
||||
name::Ptr{Cchar}
|
||||
host_api::PaHostApiIndex
|
||||
max_input_channels::Cint
|
||||
max_output_channels::Cint
|
||||
default_low_input_latency::PaTime
|
||||
default_low_output_latency::PaTime
|
||||
default_high_input_latency::PaTime
|
||||
default_high_output_latency::PaTime
|
||||
default_sample_rate::Cdouble
|
||||
end
|
||||
|
||||
type PaHostApiInfo
|
||||
struct_version::Cint
|
||||
api_type::PaHostApiTypeId
|
||||
name::Ptr{Cchar}
|
||||
deviceCount::Cint
|
||||
defaultInputDevice::PaDeviceIndex
|
||||
defaultOutputDevice::PaDeviceIndex
|
||||
end
|
||||
|
||||
type AudioDevice
|
||||
name::String
|
||||
host_api::String
|
||||
max_input_channels::Int
|
||||
max_output_channels::Int
|
||||
end
|
||||
|
||||
# some thin wrappers to portaudio calls
|
||||
get_device_info(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, "libportaudio"),
|
||||
Ptr{PaDeviceInfo}, (PaDeviceIndex,), i))
|
||||
get_host_api_info(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, "libportaudio"),
|
||||
Ptr{PaHostApiInfo}, (PaHostApiIndex,), i))
|
||||
|
||||
function get_portaudio_devices()
|
||||
init_portaudio()
|
||||
device_count = ccall((:Pa_GetDeviceCount, "libportaudio"), PaDeviceIndex, ())
|
||||
pa_devices = [get_device_info(i) for i in 0:(device_count - 1)]
|
||||
[AudioDevice(bytestring(d.name),
|
||||
bytestring(get_host_api_info(d.host_api).name),
|
||||
d.max_input_channels,
|
||||
d.max_output_channels)
|
||||
for d in pa_devices]
|
||||
end
|
||||
|
||||
function init_portaudio()
|
||||
# can be called multiple times with no effect
|
||||
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\")")
|
||||
|
||||
info("Initializing PortAudio. Expect errors as we scan devices")
|
||||
err = ccall((:Pa_Initialize, "libportaudio"), PaError, ())
|
||||
handle_status(err)
|
||||
portaudio_inited = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Old code for reference during initial development. We can get rid of this
|
||||
# once the library is a little more mature
|
||||
|
|
|
@ -73,25 +73,27 @@ stop(node)
|
|||
process(test_stream)
|
||||
@test process(test_stream) == zeros(AudioIO.AudioSample, TEST_BUF_SIZE)
|
||||
|
||||
for ext in ["wav", "flac"]
|
||||
info("Testing $ext file write/read")
|
||||
info("Testing wav file write/read")
|
||||
|
||||
fname = "test/sinwave.$ext"
|
||||
fname = "test/sinwave.wav"
|
||||
|
||||
samplerate = 44100
|
||||
freq = 440
|
||||
t = [0 : 2 * samplerate - 1] / samplerate
|
||||
phase = 2 * pi * freq * t
|
||||
reference = int16((2 ^ 15 - 1) * sin(phase))
|
||||
samplerate = 44100
|
||||
freq = 440
|
||||
t = [0 : 2 * samplerate - 1] / samplerate
|
||||
phase = 2 * pi * freq * t
|
||||
reference = int16((2 ^ 15 - 1) * sin(phase))
|
||||
|
||||
af_open(fname, "w") do f
|
||||
af_open(fname, "w") do f
|
||||
write(f, reference)
|
||||
end
|
||||
end
|
||||
|
||||
af_open(fname) do f
|
||||
af_open(fname) do f
|
||||
@test f.sfinfo.channels == 1
|
||||
@test f.sfinfo.frames == 2 * samplerate
|
||||
actual = read(f, 2 * samplerate)
|
||||
@test_approx_eq(reference, actual)
|
||||
end
|
||||
end
|
||||
|
||||
info("Testing Audio Device Listing...")
|
||||
d_list = get_audio_devices()
|
||||
@test length(d_list) > 0
|
||||
|
|
Loading…
Add table
Reference in a new issue