adds better source/sink show method and more docs/examples to README

This commit is contained in:
Spencer Russell 2016-03-20 03:23:28 -04:00
parent e51c980f24
commit 64a08bc90f
3 changed files with 93 additions and 12 deletions

View file

@ -1,18 +1,71 @@
PortAudio.jl PortAudio.jl
============ ============
[![Build Status](https://travis-ci.org/JuliaAudio/PortAudio.jl.svg?branch=master)](https://travis-ci.org/JuliaAudio/PortAudio.jl)
[![codecov.io] (http://codecov.io/github/JuliaAudio/PortAudio.jl/coverage.svg?branch=master)] (http://codecov.io/github/JuliaAudio/PortAudio.jl?branch=master)
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), so it provides `PortAudioSink` and `PortAudioSource` types, which can be read from and written to.
## Opening a source or sink
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:
```julia
function PortAudioSink(eltype=Float32, sr=48000Hz, channels=2, bufsize=4096)
```
You can open a specific device by adding it as the first argument, either as a `PortAudioDevice` instance or by name.
```julia
function PortAudioSink(device::PortAudioDevice, args...)
function PortAudioSink(devname::AbstractString, args...)
```
You can get a list of your system's devices with the `PortAudio.devices()` function:
```julia
julia> PortAudio.devices()
6-element Array{PortAudio.PortAudioDevice,1}:
PortAudio.PortAudioDevice("AirPlay","Core Audio",0,2,0)
PortAudio.PortAudioDevice("Built-in Microph","Core Audio",2,0,1)
PortAudio.PortAudioDevice("Built-in Output","Core Audio",0,2,2)
PortAudio.PortAudioDevice("JackRouter","Core Audio",2,2,3)
PortAudio.PortAudioDevice("After Effects 13.5","Core Audio",0,0,4)
PortAudio.PortAudioDevice("Built-In Aggregate","Core Audio",2,2,5)
```
## 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. You can read to a buffer with `buf = read(source)`
## Examples ## Examples
### Set up an audio pass-through from microphone to speaker ### Set up an audio pass-through from microphone to speaker
```julia ```julia
src = PortAudioSource() source = PortAudioSource()
sink = PortAudioSink() sink = PortAudioSink()
write(sink, source) write(sink, source)
end ```
### Open your built-in microphone and speaker by name
```julia
source = PortAudioSource("Built-in Microph")
sink = PortAudioSink("Built-in Output")
write(sink, source)
```
### Record 10 seconds of audio and save to an ogg file
```julia
julia> using FileIO, 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> buf = read(source, 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⁻¹
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁
julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf)
``` ```

View file

@ -35,11 +35,17 @@ function devices()
for (i, d) in enumerate(infos)] for (i, d) in enumerate(infos)]
end end
# paramaterized on the sample type and sampling rate type # not for external use, used in error message printing
devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n")
# Sources and sinks are mostly the same, so define them with this handy bit of
# metaprogramming
for (TypeName, Super) in ((:PortAudioSink, :SampleSink), for (TypeName, Super) in ((:PortAudioSink, :SampleSink),
(:PortAudioSource, :SampleSource)) (:PortAudioSource, :SampleSource))
# paramaterized on the sample type and sampling rate type
@eval type $TypeName{T, U} <: $Super @eval type $TypeName{T, U} <: $Super
stream::PaStream stream::PaStream
name::UTF8String
samplerate::U samplerate::U
jlbuf::Array{T, 2} jlbuf::Array{T, 2}
pabuf::Array{T, 2} pabuf::Array{T, 2}
@ -47,7 +53,7 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink),
busy::Bool busy::Bool
end end
@eval function $TypeName(T, stream, sr, channels, bufsize) @eval function $TypeName(T, stream, sr, channels, bufsize, name)
jlbuf = Array(T, bufsize, channels) jlbuf = Array(T, bufsize, channels)
# as of portaudio 19.20140130 (which is the HomeBrew version as of 20160319) # as of portaudio 19.20140130 (which is the HomeBrew version as of 20160319)
# noninterleaved data is not supported for the read/write interface on OSX # noninterleaved data is not supported for the read/write interface on OSX
@ -57,7 +63,7 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink),
Pa_StartStream(stream) Pa_StartStream(stream)
this = $TypeName(stream, sr, jlbuf, pabuf, waiters, false) this = $TypeName(stream, utf8(name), sr, jlbuf, pabuf, waiters, false)
finalizer(this, close) finalizer(this, close)
this this
@ -66,13 +72,13 @@ end
function PortAudioSink(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE) function PortAudioSink(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE)
stream = Pa_OpenDefaultStream(0, channels, type_to_fmt[eltype], float(sr), bufsize) stream = Pa_OpenDefaultStream(0, channels, type_to_fmt[eltype], float(sr), bufsize)
PortAudioSink(eltype, stream, sr, channels, bufsize) PortAudioSink(eltype, stream, sr, channels, bufsize, "Default Sink")
end end
function PortAudioSink(device::PortAudioDevice, eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE) function PortAudioSink(device::PortAudioDevice, eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE)
params = Pa_StreamParameters(device.idx, channels, type_to_fmt[eltype], 0.0, C_NULL) params = Pa_StreamParameters(device.idx, channels, type_to_fmt[eltype], 0.0, C_NULL)
stream = Pa_OpenStream(C_NULL, pointer_from_objref(params), float(sr), bufsize, paNoFlag) stream = Pa_OpenStream(C_NULL, pointer_from_objref(params), float(sr), bufsize, paNoFlag)
PortAudioSink(eltype, stream, sr, channels, bufsize) PortAudioSink(eltype, stream, sr, channels, bufsize, device.name)
end end
function PortAudioSink(devname::AbstractString, args...) function PortAudioSink(devname::AbstractString, args...)
@ -81,17 +87,18 @@ function PortAudioSink(devname::AbstractString, args...)
return PortAudioSink(d, args...) return PortAudioSink(d, args...)
end end
end end
error("No PortAudio device matching \"$devname\" found.\nAvailable Devices:\n$(devnames())")
end end
function PortAudioSource(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE) function PortAudioSource(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE)
stream = Pa_OpenDefaultStream(channels, 0, type_to_fmt[eltype], float(sr), bufsize) stream = Pa_OpenDefaultStream(channels, 0, type_to_fmt[eltype], float(sr), bufsize)
PortAudioSource(eltype, stream, sr, channels, bufsize) PortAudioSource(eltype, stream, sr, channels, bufsize, "Default Source")
end end
function PortAudioSource(device::PortAudioDevice, eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE) function PortAudioSource(device::PortAudioDevice, eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_BUFSIZE)
params = Pa_StreamParameters(device.idx, channels, type_to_fmt[eltype], 0.0, C_NULL) params = Pa_StreamParameters(device.idx, channels, type_to_fmt[eltype], 0.0, C_NULL)
stream = Pa_OpenStream(pointer_from_objref(params), C_NULL, float(sr), bufsize, paNoFlag) stream = Pa_OpenStream(pointer_from_objref(params), C_NULL, float(sr), bufsize, paNoFlag)
PortAudioSource(eltype, stream, sr, channels, bufsize) PortAudioSource(eltype, stream, sr, channels, bufsize, device.name)
end end
function PortAudioSource(devname::AbstractString, args...) function PortAudioSource(devname::AbstractString, args...)
@ -100,13 +107,20 @@ function PortAudioSource(devname::AbstractString, args...)
return PortAudioSource(d, args...) return PortAudioSource(d, args...)
end end
end end
error("No PortAudio device matching \"$devname\" found.\nAvailable Devices:\n$(devnames())")
end end
# most of these methods are the same for Sources and Sinks, so define them on # most of these methods are the same for Sources and Sinks, so define them on
# the union # the union
typealias PortAudioStream{T, U} Union{PortAudioSink{T, U}, PortAudioSource{T, U}} typealias PortAudioStream{T, U} Union{PortAudioSink{T, U}, PortAudioSource{T, U}}
function Base.show{T <: PortAudioStream}(io::IO, stream::T)
println(io, T, "(\"", stream.name, "\")")
print(io, nchannels(stream), " channels sampled at ", samplerate(stream))
end
function Base.close(stream::PortAudioStream) function Base.close(stream::PortAudioStream)
Pa_StopStream(stream.stream) Pa_StopStream(stream.stream)
Pa_CloseStream(stream.stream) Pa_CloseStream(stream.stream)

View file

@ -121,6 +121,13 @@ type Pa_StreamParameters
hostAPISpecificStreamInfo::Ptr{Void} hostAPISpecificStreamInfo::Ptr{Void}
end end
type PaStreamInfo
structVersion::Cint
inputLatency::PaTime
outputLatency::PaTime
sampleRate::Cdouble
end
function Pa_OpenDefaultStream(inChannels, outChannels, function Pa_OpenDefaultStream(inChannels, outChannels,
sampleFormat::PaSampleFormat, sampleFormat::PaSampleFormat,
sampleRate, framesPerBuffer) sampleRate, framesPerBuffer)
@ -206,6 +213,13 @@ function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf
nothing nothing
end end
function Pa_GetStreamInfo(stream::PaStream)
infoptr = ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo},
(PaStream, ), stream)
unsafe_load(infoptr)
end
# General utility function to handle the status from the Pa_* functions # General utility function to handle the status from the Pa_* functions
function handle_status(err::PaError, show_warnings::Bool=true) function handle_status(err::PaError, show_warnings::Bool=true)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED