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 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
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
PortAudioSink(device::PortAudioDevice, args...)
PortAudioSink(devname::AbstractString, args...)
PortAudioStream(device::PortAudioDevice, args...; kwargs...)
PortAudioStream(devname::AbstractString, args...; kwargs...)
```
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
`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
### Set up an audio pass-through from microphone to speaker
```julia
source = PortAudioSource()
sink = PortAudioSink()
write(sink, source)
stream = PortAudioStream()
write(stream, stream)
```
### Open your built-in microphone and speaker by name
```julia
source = PortAudioSource("Built-in Microph")
sink = PortAudioSink("Built-in Output")
write(sink, source)
stream = PortAudioStream("Built-in Microph", "Built-in Output")
write(stream, stream)
```
### Record 10 seconds of audio and save to an ogg file
```julia
julia> using PortAudio, FileIO, SampleTypes, LibSndFile
julia> using PortAudio, SampleTypes, LibSndFile
julia> source = PortAudioSource("Built-in Microph")
PortAudio.PortAudioSource{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Microph")
2 channels sampled at 48000 s⁻¹
julia> stream = PortAudioStream("Built-in Microph", 1, 0)
PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
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}}
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
# constructor below
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
sr, inchans, outchans, bufsize)
inchans, outchans, sr, bufsize)
inparams = (inchans == 0) ?
Ptr{Pa_StreamParameters}(0) :
Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL))
@ -122,14 +122,14 @@ type PortAudioStream{T, U}
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
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
eltype=Float32, sr=48000Hz, inchans=2, outchans=2, bufsize=DEFAULT_BUFSIZE)
PortAudioStream{eltype, typeof(sr)}(indev, outdev, sr, inchans, outchans, bufsize)
inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, bufsize=DEFAULT_BUFSIZE)
PortAudioStream{eltype, typeof(samplerate)}(indev, outdev, inchans, outchans, samplerate, bufsize)
end
function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...)
function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...; kwargs...)
indev = nothing
outdev = nothing
for d in devices()
@ -147,20 +147,20 @@ function PortAudioStream(indevname::AbstractString, outdevname::AbstractString,
error("No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())")
end
PortAudioStream(indev, outdev, args...)
PortAudioStream(indev, outdev, args...; kwargs...)
end
# if one device is given, use it for input and output
PortAudioStream(device::PortAudioDevice, args...) = PortAudioStream(device, device, args...)
PortAudioStream(device::AbstractString, args...) = PortAudioStream(device, device, args...)
PortAudioStream(device::PortAudioDevice, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
PortAudioStream(device::AbstractString, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
# use the default input and output devices
function PortAudioStream(args...)
function PortAudioStream(args...; kwargs...)
inidx = Pa_GetDefaultInputDevice()
indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx)
outidx = Pa_GetDefaultOutputDevice()
outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx)
PortAudioStream(indevice, outdevice, args...)
PortAudioStream(indevice, outdevice, args...; kwargs...)
end
function Base.close(stream::PortAudioStream)
@ -169,22 +169,32 @@ function Base.close(stream::PortAudioStream)
Pa_CloseStream(stream.stream)
stream.stream = C_NULL
end
nothing
end
Base.isopen(stream::PortAudioStream) = stream.stream != C_NULL
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.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)
println(typeof(stream))
println(" Samplerate: ", samplerate(stream))
println(" Buffer Size: ", stream.bufsize, " frames")
println(" ", nchannels(stream.sink), " channel sink: ", stream.sink.name)
print(" ", nchannels(stream.source), " channel source: ", stream.source.name)
println(io, typeof(stream))
println(io, " Samplerate: ", samplerate(stream))
print(io, " Buffer Size: ", stream.bufsize, " frames")
if nchannels(stream.sink) > 0
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
# 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
@testset "PortAudio Tests" begin
# @testset "Reports version" begin
# 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
devs = PortAudio.devices()
i = findfirst(d -> d.maxinchans > 0, devs)
indev = i > 0 ? devs[i] : nothing
i = findfirst(d -> d.maxoutchans > 0, devs)
outdev = i > 0 ? devs[i] : nothing
i = findfirst(d -> d.maxoutchans > 0 && d.maxinchans > 0, devs)
duplexdev = i > 0 ? devs[i] : nothing
@testset "Reports version" begin
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
inbuf = rand(Float32, 2, 8)
outbuf = Array(Float32, 2, 8)
@ -59,57 +68,49 @@ using SampleTypes
@test alloc == 0
end
# @testset "Open Default Device" begin
# devs = PortAudio.devices()
# source = PortAudioSource()
# sink = PortAudioSink()
# buf = read(source, 0.1s)
# @test size(buf) == (round(Int, 0.1s * samplerate(source)), nchannels(source))
# write(sink, buf)
# close(source)
# close(sink)
# end
# @testset "Open Device by name" begin
# devs = PortAudio.devices()
# source = PortAudioSource("Built-in Microph")
# sink = PortAudioSink("Built-in Output")
# buf = read(source, 0.1s)
# @test size(buf) == (round(Int, 0.1s * samplerate(source)), nchannels(source))
# write(sink, buf)
# io = IOBuffer()
# show(io, source)
# @test takebuf_string(io) ==
# """PortAudio.PortAudioSource{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Microph")
# 2 channels sampled at 48000 s⁻¹"""
# show(io, sink)
# @test takebuf_string(io) ==
# """PortAudio.PortAudioSink{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}("Built-in Output")
# 2 channels sampled at 48000 s⁻¹"""
# close(source)
# close(sink)
# end
# @testset "Error on wrong name" begin
# @test_throws ErrorException PortAudioSource("foobarbaz")
# @test_throws ErrorException PortAudioSink("foobarbaz")
# end
# # no way to check that the right data is actually getting read or written here,
# # but at least it's not crashing.
# @testset "Queued Writing" begin
# sink = PortAudioSink()
# buf = SampleBuf(rand(eltype(sink), 48000, nchannels(sink))*0.1, samplerate(sink))
# t1 = @async write(sink, buf)
# t2 = @async write(sink, buf)
# @test wait(t1) == 48000
# @test wait(t2) == 48000
# close(sink)
# end
# @testset "Queued Reading" begin
# 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
@testset "Open Default Device" begin
stream = PortAudioStream()
buf = read(stream, 0.1s)
@test size(buf) == (round(Int, 0.1s * samplerate(stream)), nchannels(stream.source))
write(stream, buf)
close(stream)
end
@testset "Open Device by name" begin
stream = PortAudioStream("Built-in Microph", "Built-in Output")
buf = read(stream, 0.1s)
@test size(buf) == (round(Int, 0.1s * samplerate(stream)), nchannels(stream.source))
write(stream, buf)
io = IOBuffer()
show(io, stream)
@test takebuf_string(io) == """
PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
Samplerate: 48000 s⁻¹
Buffer Size: 4096 frames
2 channel sink: "Built-in Output"
2 channel source: "Built-in Microph\""""
close(stream)
end
@testset "Error on wrong name" begin
@test_throws ErrorException PortAudioStream("foobarbaz")
end
# no way to check that the right data is actually getting read or written here,
# but at least it's not crashing.
@testset "Queued Writing" begin
stream = PortAudioStream()
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream))
t1 = @async write(stream, buf)
t2 = @async write(stream, buf)
@test wait(t1) == 48000
@test wait(t2) == 48000
close(stream)
end
@testset "Queued Reading" begin
stream = PortAudioStream()
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source)), samplerate(stream))
t1 = @async read!(stream, buf)
t2 = @async read!(stream, buf)
@test wait(t1) == 48000
@test wait(t2) == 48000
close(stream)
end
end