moves most stream config to keyword args and updates README

This commit is contained in:
Spencer Russell 2016-03-23 23:25:03 -04:00
parent 096cfd49da
commit 8a7a7d5baa
4 changed files with 110 additions and 97 deletions

View file

@ -1,21 +1,21 @@
PortAudio.jl PortAudio.jl
============ ============
PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which gives cross-platform access to audio devices. It is compatible with the types defined in [SampleTypes.jl](https://github.com/JuliaAudio/SampleTypes.jl), so it provides `PortAudioSink` and `PortAudioSource` types, which can be read from and written to. PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which gives cross-platform access to audio devices. It is compatible with the types defined in [SampleTypes.jl](https://github.com/JuliaAudio/SampleTypes.jl). It provides a `PortAudioStream` type, which can be read from and written to.
## Opening a source or sink ## Opening a stream
The easiest way to open a source or sink is with the default `PortAudioSource()` and `PortAudioSink()` constructors, which will open a 2-channel stream to your system's default devices. The constructors also take a variety of positional arguments: The easiest way to open a source or sink is with the default `PortAudioStream()` constructor, which will open a 2-in, 2-out stream to your system's default device(s). The constructor can also take the input and output channel counts as positional arguments, or a variety of other keyword arguments.
```julia ```julia
PortAudioSink(eltype=Float32, sr=48000Hz, channels=2, bufsize=4096) PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, bufsize=4096)
``` ```
You can open a specific device by adding it as the first argument, either as a `PortAudioDevice` instance or by name. You can open a specific device by adding it as the first argument, either as a `PortAudioDevice` instance or by name. You can also give separate names or devices if you want different input and output devices
```julia ```julia
PortAudioSink(device::PortAudioDevice, args...) PortAudioStream(device::PortAudioDevice, args...; kwargs...)
PortAudioSink(devname::AbstractString, args...) PortAudioStream(devname::AbstractString, args...; kwargs...)
``` ```
You can get a list of your system's devices with the `PortAudio.devices()` function: You can get a list of your system's devices with the `PortAudio.devices()` function:
@ -33,35 +33,37 @@ julia> PortAudio.devices()
## Reading and Writing ## Reading and Writing
`PortAudioSource` and `PortAudioSink` are subtypes of `SampleSource` and `SampleSink`, respectively (from [SampleTypes.jl](https://github.com/JuliaAudio/SampleTypes.jl)). This means they support all the stream and buffer features defined there. For example, if you load SampleTypes with `using SampleTypes` you can read 5 seconds to a buffer with `buf = read(source, 5s)`, regardless of the sample rate of the device. The `PortAudioStream` type has `source` and `sink` fields which are of type `PortAudioSource <: SampleSource` and `PortAudioSink <: SampleSink`, respectively. are subtypes of `SampleSource` and `SampleSink`, respectively (from [SampleTypes.jl](https://github.com/JuliaAudio/SampleTypes.jl)). This means they support all the stream and buffer features defined there. For example, if you load SampleTypes with `using SampleTypes` you can read 5 seconds to a buffer with `buf = read(stream.source, 5s)`, regardless of the sample rate of the device.
PortAudio.jl also provides convenience wrappers around the `PortAudioStream` type so you can read and write to it directly, e.g. `write(stream, stream)` will set up a loopback that will read from the input and play it back on the output.
## Examples ## Examples
### Set up an audio pass-through from microphone to speaker ### Set up an audio pass-through from microphone to speaker
```julia ```julia
source = PortAudioSource() stream = PortAudioStream()
sink = PortAudioSink() write(stream, stream)
write(sink, source)
``` ```
### Open your built-in microphone and speaker by name ### Open your built-in microphone and speaker by name
```julia ```julia
source = PortAudioSource("Built-in Microph") stream = PortAudioStream("Built-in Microph", "Built-in Output")
sink = PortAudioSink("Built-in Output") write(stream, stream)
write(sink, source)
``` ```
### Record 10 seconds of audio and save to an ogg file ### Record 10 seconds of audio and save to an ogg file
```julia ```julia
julia> using PortAudio, FileIO, SampleTypes, LibSndFile julia> using PortAudio, SampleTypes, LibSndFile
julia> source = PortAudioSource("Built-in Microph") julia> stream = PortAudioStream("Built-in Microph", 1, 0)
PortAudio.PortAudioSource{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Microph") PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
2 channels sampled at 48000 s⁻¹ Samplerate: 48000 s⁻¹
Buffer Size: 4096 frames
1 channel source: "Built-in Microph"
julia> buf = read(source, 10s) julia> buf = read(stream, 10s)
480000-frame, 2-channel SampleBuf{Float32, 2, SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}} 480000-frame, 2-channel SampleBuf{Float32, 2, SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
10.0 s at 48000 s⁻¹ 10.0 s at 48000 s⁻¹
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁ ▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁

View file

@ -93,7 +93,7 @@ type PortAudioStream{T, U}
# this inner constructor is generally called via the top-level outer # this inner constructor is generally called via the top-level outer
# constructor below # constructor below
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
sr, inchans, outchans, bufsize) inchans, outchans, sr, bufsize)
inparams = (inchans == 0) ? inparams = (inchans == 0) ?
Ptr{Pa_StreamParameters}(0) : Ptr{Pa_StreamParameters}(0) :
Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL)) Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL))
@ -122,14 +122,14 @@ type PortAudioStream{T, U}
end end
# this is the to-level outer constructor that all the other outer constructors # this is the top-level outer constructor that all the other outer constructors
# end up calling # end up calling
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
eltype=Float32, sr=48000Hz, inchans=2, outchans=2, bufsize=DEFAULT_BUFSIZE) inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, bufsize=DEFAULT_BUFSIZE)
PortAudioStream{eltype, typeof(sr)}(indev, outdev, sr, inchans, outchans, bufsize) PortAudioStream{eltype, typeof(samplerate)}(indev, outdev, inchans, outchans, samplerate, bufsize)
end end
function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...) function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...; kwargs...)
indev = nothing indev = nothing
outdev = nothing outdev = nothing
for d in devices() for d in devices()
@ -147,20 +147,20 @@ function PortAudioStream(indevname::AbstractString, outdevname::AbstractString,
error("No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())") error("No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())")
end end
PortAudioStream(indev, outdev, args...) PortAudioStream(indev, outdev, args...; kwargs...)
end end
# if one device is given, use it for input and output # if one device is given, use it for input and output
PortAudioStream(device::PortAudioDevice, args...) = PortAudioStream(device, device, args...) PortAudioStream(device::PortAudioDevice, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
PortAudioStream(device::AbstractString, args...) = PortAudioStream(device, device, args...) PortAudioStream(device::AbstractString, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
# use the default input and output devices # use the default input and output devices
function PortAudioStream(args...) function PortAudioStream(args...; kwargs...)
inidx = Pa_GetDefaultInputDevice() inidx = Pa_GetDefaultInputDevice()
indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx) indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx)
outidx = Pa_GetDefaultOutputDevice() outidx = Pa_GetDefaultOutputDevice()
outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx) outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx)
PortAudioStream(indevice, outdevice, args...) PortAudioStream(indevice, outdevice, args...; kwargs...)
end end
function Base.close(stream::PortAudioStream) function Base.close(stream::PortAudioStream)
@ -169,22 +169,32 @@ function Base.close(stream::PortAudioStream)
Pa_CloseStream(stream.stream) Pa_CloseStream(stream.stream)
stream.stream = C_NULL stream.stream = C_NULL
end end
nothing
end end
Base.isopen(stream::PortAudioStream) = stream.stream != C_NULL Base.isopen(stream::PortAudioStream) = stream.stream != C_NULL
SampleTypes.samplerate(stream::PortAudioStream) = stream.samplerate SampleTypes.samplerate(stream::PortAudioStream) = stream.samplerate
Base.eltype{T, U}(stream::PortAudioStream{T, U}) = T
Base.read(stream::PortAudioStream, args...) = read(stream.source, args...) Base.read(stream::PortAudioStream, args...) = read(stream.source, args...)
Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...) Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...) Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...)
Base.write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...)
function Base.show(io::IO, stream::PortAudioStream) function Base.show(io::IO, stream::PortAudioStream)
println(typeof(stream)) println(io, typeof(stream))
println(" Samplerate: ", samplerate(stream)) println(io, " Samplerate: ", samplerate(stream))
println(" Buffer Size: ", stream.bufsize, " frames") print(io, " Buffer Size: ", stream.bufsize, " frames")
println(" ", nchannels(stream.sink), " channel sink: ", stream.sink.name) if nchannels(stream.sink) > 0
print(" ", nchannels(stream.source), " channel source: ", stream.source.name) println()
print(io, " ", nchannels(stream.sink), " channel sink: \"", stream.sink.name, "\"")
end
if nchannels(stream.source) > 0
println()
print(io, " ", nchannels(stream.source), " channel source: \"", stream.source.name, "\"")
end
end end
# Define our source and sink types # Define our source and sink types

Binary file not shown.

View file

@ -7,16 +7,25 @@ using SampleTypes
# these test are currently set up to run on OSX # these test are currently set up to run on OSX
@testset "PortAudio Tests" begin @testset "PortAudio Tests" begin
# @testset "Reports version" begin devs = PortAudio.devices()
# io = IOBuffer() i = findfirst(d -> d.maxinchans > 0, devs)
# PortAudio.versioninfo(io) indev = i > 0 ? devs[i] : nothing
# result = takebuf_string(io) i = findfirst(d -> d.maxoutchans > 0, devs)
# # make sure this is the same version I tested with outdev = i > 0 ? devs[i] : nothing
# @test result == i = findfirst(d -> d.maxoutchans > 0 && d.maxinchans > 0, devs)
# """PortAudio V19-devel (built Aug 6 2014 17:54:39) duplexdev = i > 0 ? devs[i] : nothing
# Version Number: 1899
# """ @testset "Reports version" begin
# end io = IOBuffer()
PortAudio.versioninfo(io)
result = takebuf_string(io)
# make sure this is the same version I tested with
@test result ==
"""PortAudio V19-devel (built Aug 6 2014 17:54:39)
Version Number: 1899
"""
end
@testset "PortAudio Callback works and doesn't allocate" begin @testset "PortAudio Callback works and doesn't allocate" begin
inbuf = rand(Float32, 2, 8) inbuf = rand(Float32, 2, 8)
outbuf = Array(Float32, 2, 8) outbuf = Array(Float32, 2, 8)
@ -59,57 +68,49 @@ using SampleTypes
@test alloc == 0 @test alloc == 0
end end
# @testset "Open Default Device" begin @testset "Open Default Device" begin
# devs = PortAudio.devices() stream = PortAudioStream()
# source = PortAudioSource() buf = read(stream, 0.1s)
# sink = PortAudioSink() @test size(buf) == (round(Int, 0.1s * samplerate(stream)), nchannels(stream.source))
# buf = read(source, 0.1s) write(stream, buf)
# @test size(buf) == (round(Int, 0.1s * samplerate(source)), nchannels(source)) close(stream)
# write(sink, buf) end
# close(source) @testset "Open Device by name" begin
# close(sink) stream = PortAudioStream("Built-in Microph", "Built-in Output")
# end buf = read(stream, 0.1s)
# @testset "Open Device by name" begin @test size(buf) == (round(Int, 0.1s * samplerate(stream)), nchannels(stream.source))
# devs = PortAudio.devices() write(stream, buf)
# source = PortAudioSource("Built-in Microph") io = IOBuffer()
# sink = PortAudioSink("Built-in Output") show(io, stream)
# buf = read(source, 0.1s) @test takebuf_string(io) == """
# @test size(buf) == (round(Int, 0.1s * samplerate(source)), nchannels(source)) PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
# write(sink, buf) Samplerate: 48000 s⁻¹
# io = IOBuffer() Buffer Size: 4096 frames
# show(io, source) 2 channel sink: "Built-in Output"
# @test takebuf_string(io) == 2 channel source: "Built-in Microph\""""
# """PortAudio.PortAudioSource{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Microph") close(stream)
# 2 channels sampled at 48000 s⁻¹""" end
# show(io, sink) @testset "Error on wrong name" begin
# @test takebuf_string(io) == @test_throws ErrorException PortAudioStream("foobarbaz")
# """PortAudio.PortAudioSink{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Output") end
# 2 channels sampled at 48000 s⁻¹""" # no way to check that the right data is actually getting read or written here,
# close(source) # but at least it's not crashing.
# close(sink) @testset "Queued Writing" begin
# end stream = PortAudioStream()
# @testset "Error on wrong name" begin buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream))
# @test_throws ErrorException PortAudioSource("foobarbaz") t1 = @async write(stream, buf)
# @test_throws ErrorException PortAudioSink("foobarbaz") t2 = @async write(stream, buf)
# end @test wait(t1) == 48000
# # no way to check that the right data is actually getting read or written here, @test wait(t2) == 48000
# # but at least it's not crashing. close(stream)
# @testset "Queued Writing" begin end
# sink = PortAudioSink() @testset "Queued Reading" begin
# buf = SampleBuf(rand(eltype(sink), 48000, nchannels(sink))*0.1, samplerate(sink)) stream = PortAudioStream()
# t1 = @async write(sink, buf) buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source)), samplerate(stream))
# t2 = @async write(sink, buf) t1 = @async read!(stream, buf)
# @test wait(t1) == 48000 t2 = @async read!(stream, buf)
# @test wait(t2) == 48000 @test wait(t1) == 48000
# close(sink) @test wait(t2) == 48000
# end close(stream)
# @testset "Queued Reading" begin end
# source = PortAudioSource()
# buf = SampleBuf(rand(eltype(source), 48000, nchannels(source)), samplerate(source))
# t1 = @async read!(source, buf)
# t2 = @async read!(source, buf)
# @test wait(t1) == 48000
# @test wait(t2) == 48000
# close(source)
# end
end end