diff --git a/deps/src/shim.c b/deps/src/shim.c index 335e3a8..7d8d387 100644 --- a/deps/src/shim.c +++ b/deps/src/shim.c @@ -3,11 +3,11 @@ #include #include -static int paCallback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData); +int paCallback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData); static PaStream *AudioStream; static int JuliaPipeReadFD = 0; @@ -61,33 +61,16 @@ 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, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) +int paCallback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) { unsigned int i; unsigned char fd_data = 0; diff --git a/src/AudioIO.jl b/src/AudioIO.jl index a9ee241..d519e8c 100644 --- a/src/AudioIO.jl +++ b/src/AudioIO.jl @@ -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 diff --git a/src/nodes.jl b/src/nodes.jl index ce2cb62..c5d42a8 100644 --- a/src/nodes.jl +++ b/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 diff --git a/src/portaudio.jl b/src/portaudio.jl index b2d4ea2..52a6371 100644 --- a/src/portaudio.jl +++ b/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 + init_portaudio() 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 diff --git a/test/test_AudioIO.jl b/test/test_AudioIO.jl index 64de9d3..0e89108 100644 --- a/test/test_AudioIO.jl +++ b/test/test_AudioIO.jl @@ -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 - write(f, reference) - end - - 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 +af_open(fname, "w") do f + write(f, reference) end + +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 + +info("Testing Audio Device Listing...") +d_list = get_audio_devices() +@test length(d_list) > 0