From 64a08bc90fb22632bc679230f5e9d75abaeea107 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Sun, 20 Mar 2016 03:23:28 -0400 Subject: [PATCH] adds better source/sink show method and more docs/examples to README --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++---- src/PortAudio.jl | 28 +++++++++++++++----- src/libportaudio.jl | 14 ++++++++++ 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0da5414..15f2a8d 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,71 @@ 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. +## 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 ### Set up an audio pass-through from microphone to speaker ```julia -src = PortAudioSource() +source = PortAudioSource() sink = PortAudioSink() 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) ``` diff --git a/src/PortAudio.jl b/src/PortAudio.jl index db91f6a..b13a294 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -35,11 +35,17 @@ function devices() for (i, d) in enumerate(infos)] 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), (:PortAudioSource, :SampleSource)) +# paramaterized on the sample type and sampling rate type @eval type $TypeName{T, U} <: $Super stream::PaStream + name::UTF8String samplerate::U jlbuf::Array{T, 2} pabuf::Array{T, 2} @@ -47,7 +53,7 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink), busy::Bool end - @eval function $TypeName(T, stream, sr, channels, bufsize) + @eval function $TypeName(T, stream, sr, channels, bufsize, name) jlbuf = Array(T, bufsize, channels) # 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 @@ -57,7 +63,7 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink), Pa_StartStream(stream) - this = $TypeName(stream, sr, jlbuf, pabuf, waiters, false) + this = $TypeName(stream, utf8(name), sr, jlbuf, pabuf, waiters, false) finalizer(this, close) this @@ -66,13 +72,13 @@ end function PortAudioSink(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_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 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) 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 function PortAudioSink(devname::AbstractString, args...) @@ -81,17 +87,18 @@ function PortAudioSink(devname::AbstractString, args...) return PortAudioSink(d, args...) end end + error("No PortAudio device matching \"$devname\" found.\nAvailable Devices:\n$(devnames())") end function PortAudioSource(eltype=Float32, sr=48000Hz, channels=2, bufsize=DEFAULT_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 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) 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 function PortAudioSource(devname::AbstractString, args...) @@ -100,13 +107,20 @@ function PortAudioSource(devname::AbstractString, args...) return PortAudioSource(d, args...) end end + error("No PortAudio device matching \"$devname\" found.\nAvailable Devices:\n$(devnames())") end + # most of these methods are the same for Sources and Sinks, so define them on # the union 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) Pa_StopStream(stream.stream) Pa_CloseStream(stream.stream) diff --git a/src/libportaudio.jl b/src/libportaudio.jl index ad1d216..71e9a29 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -121,6 +121,13 @@ type Pa_StreamParameters hostAPISpecificStreamInfo::Ptr{Void} end +type PaStreamInfo + structVersion::Cint + inputLatency::PaTime + outputLatency::PaTime + sampleRate::Cdouble +end + function Pa_OpenDefaultStream(inChannels, outChannels, sampleFormat::PaSampleFormat, sampleRate, framesPerBuffer) @@ -206,6 +213,13 @@ function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf nothing 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 function handle_status(err::PaError, show_warnings::Bool=true) if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED