moves most stream config to keyword args and updates README
This commit is contained in:
parent
096cfd49da
commit
8a7a7d5baa
4 changed files with 110 additions and 97 deletions
40
README.md
40
README.md
|
@ -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⁻¹
|
||||
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁
|
||||
|
|
|
@ -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.
127
test/runtests.jl
127
test/runtests.jl
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue