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 <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
static int paCallback(const void *inputBuffer, void *outputBuffer,
|
int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
unsigned long framesPerBuffer,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo* timeInfo,
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags,
|
PaStreamCallbackFlags statusFlags,
|
||||||
|
@ -61,29 +61,12 @@ PaError open_stream(unsigned int sampleRate, unsigned int bufSize)
|
||||||
return paNoError;
|
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.
|
* 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
|
* 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().
|
* 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,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo* timeInfo,
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags,
|
PaStreamCallbackFlags statusFlags,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module AudioIO
|
module AudioIO
|
||||||
|
|
||||||
# export the basic API
|
# export the basic API
|
||||||
export play, stop
|
export play, stop, get_audio_devices
|
||||||
|
|
||||||
# default stream used when none is given
|
# default stream used when none is given
|
||||||
_stream = nothing
|
_stream = nothing
|
||||||
|
@ -47,34 +47,6 @@ function play(node::AudioNode)
|
||||||
play(node, _stream)
|
play(node, _stream)
|
||||||
end
|
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)
|
function stop(node::AudioNode)
|
||||||
deactivate(node)
|
deactivate(node)
|
||||||
node
|
node
|
||||||
|
@ -99,4 +71,8 @@ function Base.wait(node::AudioNode)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function get_audio_devices()
|
||||||
|
return get_portaudio_devices()
|
||||||
|
end
|
||||||
|
|
||||||
end # module AudioIO
|
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)
|
function render(node::SinOsc, device_input::AudioBuf, info::DeviceInfo)
|
||||||
phase = AudioSample[1:info.buf_size] * 2pi * node.freq / info.sample_rate
|
phase = AudioSample[1:info.buf_size] * 2pi * node.freq / info.sample_rate
|
||||||
phase += node.phase
|
phase .+= node.phase
|
||||||
node.phase = phase[end]
|
node.phase = phase[end]
|
||||||
return sin(phase), is_active(node)
|
return sin(phase), is_active(node)
|
||||||
end
|
end
|
||||||
|
@ -122,6 +122,34 @@ function render(node::ArrayPlayer, device_input::AudioBuf, info::DeviceInfo)
|
||||||
return output, is_active(node)
|
return output, is_active(node)
|
||||||
end
|
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 ####
|
#### AudioInput ####
|
||||||
|
|
||||||
# Renders incoming audio input from the hardware
|
# 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 PaError Cint
|
||||||
typealias PaSampleFormat Culong
|
typealias PaSampleFormat Culong
|
||||||
typealias PaStream Void
|
typealias PaStream Void
|
||||||
|
typealias PaDeviceIndex Cint
|
||||||
|
typealias PaHostApiIndex Cint
|
||||||
|
typealias PaTime Cdouble
|
||||||
|
typealias PaHostApiTypeId Cint
|
||||||
|
|
||||||
const PA_NO_ERROR = 0
|
const PA_NO_ERROR = 0
|
||||||
const libportaudio_shim = find_library(["libportaudio_shim",],
|
const libportaudio_shim = find_library(["libportaudio_shim",],
|
||||||
[Pkg.dir("AudioIO", "deps", "usr", "lib"),])
|
[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
|
# track whether we've already inited PortAudio
|
||||||
portaudio_inited = false
|
portaudio_inited = false
|
||||||
|
|
||||||
|
@ -17,15 +39,7 @@ type PortAudioStream <: AudioStream
|
||||||
info::DeviceInfo
|
info::DeviceInfo
|
||||||
|
|
||||||
function PortAudioStream(sample_rate::Int=44100, buf_size::Int=1024)
|
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()
|
init_portaudio()
|
||||||
portaudio_inited = true
|
|
||||||
else
|
|
||||||
error("Currently only 1 stream is supported at a time")
|
|
||||||
end
|
|
||||||
mixer = AudioMixer()
|
mixer = AudioMixer()
|
||||||
stream = new(mixer, DeviceInfo(sample_rate, buf_size))
|
stream = new(mixer, DeviceInfo(sample_rate, buf_size))
|
||||||
# we need to start up the stream with the portaudio library
|
# 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)
|
ccall((:synchronize_buffer, libportaudio_shim), Void, (Ptr{Void},), buffer)
|
||||||
end
|
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)
|
function open_portaudio_stream(stream::PortAudioStream)
|
||||||
# starts up a stream with the portaudio library and associates it with the
|
# starts up a stream with the portaudio library and associates it with the
|
||||||
# given AudioIO PortAudioStream
|
# given AudioIO PortAudioStream
|
||||||
|
@ -55,10 +63,7 @@ function open_portaudio_stream(stream::PortAudioStream)
|
||||||
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
|
||||||
|
|
||||||
info("Launching PortAudio Task...")
|
info("Launching PortAudio Task...")
|
||||||
function task_wrapper()
|
schedule(Task(() -> portaudio_task(fd, stream)))
|
||||||
portaudio_task(fd, stream)
|
|
||||||
end
|
|
||||||
schedule(Task(task_wrapper))
|
|
||||||
# TODO: test not yielding here
|
# TODO: test not yielding here
|
||||||
yield()
|
yield()
|
||||||
info("Audio Task Yielded, starting the stream...")
|
info("Audio Task Yielded, starting the stream...")
|
||||||
|
@ -108,6 +113,65 @@ function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream)
|
||||||
end
|
end
|
||||||
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
|
# Old code for reference during initial development. We can get rid of this
|
||||||
# once the library is a little more mature
|
# once the library is a little more mature
|
||||||
|
|
|
@ -73,10 +73,9 @@ stop(node)
|
||||||
process(test_stream)
|
process(test_stream)
|
||||||
@test process(test_stream) == zeros(AudioIO.AudioSample, TEST_BUF_SIZE)
|
@test process(test_stream) == zeros(AudioIO.AudioSample, TEST_BUF_SIZE)
|
||||||
|
|
||||||
for ext in ["wav", "flac"]
|
info("Testing wav file write/read")
|
||||||
info("Testing $ext file write/read")
|
|
||||||
|
|
||||||
fname = "test/sinwave.$ext"
|
fname = "test/sinwave.wav"
|
||||||
|
|
||||||
samplerate = 44100
|
samplerate = 44100
|
||||||
freq = 440
|
freq = 440
|
||||||
|
@ -94,4 +93,7 @@ for ext in ["wav", "flac"]
|
||||||
actual = read(f, 2 * samplerate)
|
actual = read(f, 2 * samplerate)
|
||||||
@test_approx_eq(reference, actual)
|
@test_approx_eq(reference, actual)
|
||||||
end
|
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