Compare commits

..

2 commits

Author SHA1 Message Date
Brandon Taylor
1fa1dd03cb no redundant tests 2022-03-09 17:05:09 -05:00
Brandon Taylor
78621620c9 work with vector buffers 2022-03-09 16:52:50 -05:00
8 changed files with 78 additions and 272 deletions

View file

@ -17,7 +17,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
version: version:
- '1.6' - '1.3'
- '1' - '1'
- 'nightly' - 'nightly'
os: os:

View file

@ -1,7 +1,7 @@
name = "PortAudio" name = "PortAudio"
uuid = "80ea8bcb-4634-5cb3-8ee8-a132660d1d2d" uuid = "80ea8bcb-4634-5cb3-8ee8-a132660d1d2d"
repo = "https://github.com/JuliaAudio/PortAudio.jl.git" repo = "https://github.com/JuliaAudio/PortAudio.jl.git"
version = "1.3.0" version = "1.2.0"
[deps] [deps]
alsa_plugins_jll = "5ac2f6bb-493e-5871-9171-112d4c21a6e7" alsa_plugins_jll = "5ac2f6bb-493e-5871-9171-112d4c21a6e7"
@ -11,7 +11,7 @@ SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
[compat] [compat]
julia = "1.6" julia = "1.3"
alsa_plugins_jll = "1.2.2" alsa_plugins_jll = "1.2.2"
libportaudio_jll = "19.6.0" libportaudio_jll = "19.6.0"
SampledSignals = "2.1.1" SampledSignals = "2.1.1"

View file

@ -10,14 +10,10 @@ PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which g
## Opening a stream ## Opening a stream
The easiest way to open a source or sink is with the default `PortAudioStream()` constructor, 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.
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.
If named keyword arguments `latency` or `samplerate` are unspecified, then PortAudio will use device defaults.
```julia ```julia
PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000, latency=0.1) PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, latency=0.1, synced=false)
``` ```
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 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
@ -31,16 +27,13 @@ You can get a list of your system's devices with the `PortAudio.devices()` funct
```julia ```julia
julia> PortAudio.devices() julia> PortAudio.devices()
14-element Vector{PortAudio.PortAudioDevice}: 6-element Array{PortAudio.PortAudioDevice,1}:
"sof-hda-dsp: - (hw:0,0)" 2→2 PortAudio.PortAudioDevice("AirPlay","Core Audio",0,2,0)
"sof-hda-dsp: - (hw:0,3)" 0→2 PortAudio.PortAudioDevice("Built-in Microph","Core Audio",2,0,1)
"sof-hda-dsp: - (hw:0,4)" 0→2 PortAudio.PortAudioDevice("Built-in Output","Core Audio",0,2,2)
"sof-hda-dsp: - (hw:0,5)" 0→2 PortAudio.PortAudioDevice("JackRouter","Core Audio",2,2,3)
PortAudio.PortAudioDevice("After Effects 13.5","Core Audio",0,0,4)
"upmix" 8→8 PortAudio.PortAudioDevice("Built-In Aggregate","Core Audio",2,2,5)
"vdownmix" 6→6
"dmix" 0→2
"default" 32→32
``` ```
## Reading and Writing ## Reading and Writing
@ -82,7 +75,7 @@ end
### Open your built-in microphone and speaker by name ### Open your built-in microphone and speaker by name
```julia ```julia
PortAudioStream("default", "default") do stream PortAudioStream("Built-in Microph", "Built-in Output") do stream
write(stream, stream) write(stream, stream)
end end
``` ```
@ -90,18 +83,13 @@ end
### 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> import LibSndFile # must be in Manifest for FileIO.save to work julia> using PortAudio, SampledSignals, LibSndFile
julia> using PortAudio: PortAudioStream julia> stream = PortAudioStream("Built-in Microph", 2, 0)
PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
julia> using SampledSignals: s Samplerate: 48000 s⁻¹
Buffer Size: 4096 frames
julia> using FileIO: save 2 channel source: "Built-in Microph"
julia> stream = PortAudioStream(1, 0) # default input (e.g., built-in microphone)
PortAudioStream{Float32}
Samplerate: 44100.0Hz
2 channel source: "default"
julia> buf = read(stream, 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}}
@ -120,7 +108,7 @@ julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf)
using PortAudio, SampledSignals using PortAudio, SampledSignals
S = 8192 # sampling rate (samples / second) S = 8192 # sampling rate (samples / second)
x = cos.(2pi*(1:2S)*440/S) # A440 tone for 2 seconds x = cos.(2pi*(1:2S)*440/S) # A440 tone for 2 seconds
PortAudioStream(0, 2; samplerate=S) do stream PortAudioStream(0, 2; samplerate=Float64(S)) do stream
write(stream, x) write(stream, x)
end end
``` ```

View file

@ -1,89 +0,0 @@
#=
This code illustrates real-time octave down shift
using a crude FFT-based method.
It also plots the input and output signals and their spectra.
This code uses the system defaults for the audio input and output devices.
If you use the built-in speakers and built-in microphone,
you will likely get undesirable audio feedback.
It works "best" if you play the audio output through headphones
so that the output does not feed back into the input.
The spectrum plotting came from the example in
https://github.com/JuliaAudio/PortAudio.jl/blob/master/examples
=#
using PortAudio: PortAudioStream
using SampledSignals: Hz, domain
using SampledSignals: (..) # see EllipsisNotation.jl and IntervalSets.jl
using FFTW: fft, ifft
using Plots: plot, gui, default; default(label="")
function pitch_halver(x) # decrease pitch by one octave via FFT
N = length(x)
mod(N,2) == 0 || throw("N must be multiple of 2")
F = fft(x) # original spectrum
Fnew = [F[1:N÷2]; zeros(N+1); F[(N÷2+2):N]]
out = 2 * real(ifft(Fnew))[1:N]
out.samplerate /= 2 # trick!
return out
end
# Plot input and output signals and their spectra.
# Quantize the vertical axis limits to reduce plot jitter.
function plotter(buf, out, N, fmin, fmax, fs; quant::Number = 0.1)
bmax = quant * ceil(maximum(abs, buf) / quant)
xticks = [1, N]; ylims = (-1,1) .* bmax; yticks = (-1:1)*bmax
p1 = plot(buf; xticks, ylims, yticks, title="input")
p3 = plot(out; xticks, ylims, yticks, title="output")
X = (2/N) * abs.(fft(buf)[fmin..fmax]) # spectrum
Xmax = quant * ceil(maximum(X) / quant)
xlims = (fs[1], fs[end]); ylims = (0, Xmax); yticks = [0,Xmax]
p2 = plot(fs, X; xlims, ylims, yticks)
Y = (2/N) * abs.(fft(out)[fmin..fmax])
p4 = plot(fs, Y; xlims, ylims, yticks)
plot(p1, p2, p3, p4)
end
"""
octave_shift(seconds; N, ...)
Shift audio down by one octave.
# Input
* `seconds` : how long to run in seconds; defaults to 300 (5 minutes)
# Options
* `N` : buffer size; default 1024 samples
* `fmin`,`fmax` : range of frequencies to display; default 0Hz to 4000Hz
"""
function octave_shift(
seconds::Number = 300;
N::Int = 1024,
fmin::Number = 0Hz,
fmax::Number = 4000Hz,
# undocumented options below here that are unlikely to be modified
in_stream = PortAudioStream(1, 0), # default input device
out_stream = PortAudioStream(0, 1), # default output device
buf::AbstractArray = read(in_stream, N), # warm-up
fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])],
Niters::Int = ceil(Int, seconds * in_stream.sample_rate / N),
)
for _ in 1:Niters
read!(in_stream, buf)
out = pitch_halver(buf) # decrease pitch by one octave
write(out_stream, out)
plotter(buf, out, N, fmin, fmax, fs); gui()
end
nothing
end
octave_shift(5)

View file

@ -1,21 +0,0 @@
#=
This example illustrates synthesizing a long tone in small pieces
and routing it to the default audio output device using `write()`.
=#
using PortAudio: PortAudioStream, write
stream = PortAudioStream(0, 1; warn_xruns=false)
function play_tone(stream, freq::Real, duration::Real; buf_size::Int = 1024)
S = stream.sample_rate
current = 1
while current < duration*S
x = 0.7 * sin.(2π * (current .+ (1:buf_size)) * freq / S)
write(stream, x)
current += buf_size
end
nothing
end
play_tone(stream, 440, 2)

View file

@ -48,7 +48,6 @@ using .LibPortAudio:
Pa_Initialize, Pa_Initialize,
paInputOverflowed, paInputOverflowed,
Pa_IsStreamStopped, Pa_IsStreamStopped,
paNoDevice,
paNoFlag, paNoFlag,
Pa_OpenStream, Pa_OpenStream,
paOutputUnderflowed, paOutputUnderflowed,
@ -196,22 +195,12 @@ function show(io::IO, device::PortAudioDevice)
print(io, device.output_bounds.max_channels) print(io, device.output_bounds.max_channels)
end end
function check_device_exists(device_index, device_type)
if device_index == paNoDevice
throw(ArgumentError("No $device_type device available"))
end
end
function get_default_input_index() function get_default_input_index()
device_index = Pa_GetDefaultInputDevice() handle_status(Pa_GetDefaultInputDevice())
check_device_exists(device_index, "input")
device_index
end end
function get_default_output_index() function get_default_output_index()
device_index = Pa_GetDefaultOutputDevice() handle_status(Pa_GetDefaultOutputDevice())
check_device_exists(device_index, "output")
device_index
end end
# we can look up devices by index or name # we can look up devices by index or name
@ -241,25 +230,19 @@ function devices()
end end
# we can handle reading and writing from buffers in a similar way # we can handle reading and writing from buffers in a similar way
function read_or_write(a_function, buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true) function read_or_write(a_function, buffer, use_frames = buffer.frames_per_buffer)
pointer_to = buffer.pointer_to
data = buffer.data
handle_status( handle_status(
if acquire_lock # because we're calling Pa_ReadStream and Pa_WriteStream from separate threads,
# because we're calling Pa_ReadStream and Pa_WriteStream from separate threads, # we put a lock around these calls
# we put a lock around these calls lock(
lock( let a_function = a_function,
let a_function = a_function, pointer_to = buffer.pointer_to,
pointer_to = pointer_to, data = buffer.data,
data = data, use_frames = use_frames
use_frames = use_frames () -> a_function(pointer_to, data, use_frames)
() -> a_function(pointer_to, data, use_frames) end,
end, buffer.stream_lock,
buffer.stream_lock, ),
)
else
a_function(pointer_to, data, use_frames)
end;
warn_xruns = buffer.warn_xruns, warn_xruns = buffer.warn_xruns,
) )
end end
@ -424,39 +407,25 @@ eltype(::Type{Buffer{Sample}}) where {Sample} = Sample
nchannels(buffer::Buffer) = buffer.number_of_channels nchannels(buffer::Buffer) = buffer.number_of_channels
""" """
PortAudio.write_buffer(buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true) PortAudio.write_buffer(buffer, use_frames = buffer.frames_per_buffer)
Write a number of frames (`use_frames`) from a [`PortAudio.Buffer`](@ref) to PortAudio. Write a number of frames (`use_frames`) from a [`PortAudio.Buffer`](@ref) to PortAudio.
Set `acquire_lock = false` to skip acquiring the lock.
""" """
function write_buffer(buffer::Buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true) function write_buffer(buffer::Buffer, use_frames = buffer.frames_per_buffer)
read_or_write(Pa_WriteStream, buffer, use_frames; acquire_lock = acquire_lock) read_or_write(Pa_WriteStream, buffer, use_frames)
end end
""" """
PortAudio.read_buffer!(buffer::Buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true) PortAudio.read_buffer!(buffer::Buffer, use_frames = buffer.frames_per_buffer)
Read a number of frames (`use_frames`) from PortAudio to a [`PortAudio.Buffer`](@ref). Read a number of frames (`use_frames`) from PortAudio to a [`PortAudio.Buffer`](@ref).
Set `acquire_lock = false` to skip acquiring the acquire_lock.
""" """
function read_buffer!(buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true) function read_buffer!(buffer, use_frames = buffer.frames_per_buffer)
read_or_write(Pa_ReadStream, buffer, use_frames; acquire_lock = acquire_lock) read_or_write(Pa_ReadStream, buffer, use_frames)
end end
""" # the messenger will send tasks to the scribe
Messenger{Sample, Scribe, Input, Output} # the scribe will read/write from the buffer
A `struct` with entries
* `device_name::String`
* `buffer::Buffer{Sample}`
* `scribe::Scribe`
* `input_channel::Channel{Input}`
* `output_channel::Channel{Output}`
The messenger will send tasks to the scribe;
the scribe will read/write from the buffer.
"""
struct Messenger{Sample, Scribe, Input, Output} struct Messenger{Sample, Scribe, Input, Output}
device_name::String device_name::String
buffer::Buffer{Sample} buffer::Buffer{Sample}
@ -528,7 +497,7 @@ function messenger_task(
messenger, task messenger, task
end end
function fetch_messenger(messenger, task) function fetch_messanger(messenger, task)
if has_channels(messenger) if has_channels(messenger)
# this will shut down the channels, which will shut down the thread # this will shut down the channels, which will shut down the thread
close(messenger.input_channel) close(messenger.input_channel)
@ -548,9 +517,9 @@ struct PortAudioStream{SinkMessenger, SourceMessenger}
sample_rate::Float64 sample_rate::Float64
# pointer to the c object # pointer to the c object
pointer_to::Ptr{PaStream} pointer_to::Ptr{PaStream}
sink_messenger::SinkMessenger sink_messanger::SinkMessenger
sink_task::Task sink_task::Task
source_messenger::SourceMessenger source_messanger::SourceMessenger
source_task::Task source_task::Task
end end
@ -615,9 +584,10 @@ function combine_default_sample_rates(
) )
if input_sample_rate != output_sample_rate if input_sample_rate != output_sample_rate
throw( throw(
ArgumentError(""" ArgumentError(
Default sample rate $input_sample_rate for input \"$(name(input_device))\" disagrees with """
default sample rate $output_sample_rate for output \"$(name(output_device))\". Default sample rate $input_sample_rate for input $(name(input_device)) disagrees with
default sample rate $output_sample_rate for output $(name(output_device)).
Please specify a sample rate. Please specify a sample rate.
""", """,
), ),
@ -760,24 +730,20 @@ function PortAudioStream(
output_device.output_bounds.high_latency, output_device.output_bounds.high_latency,
) )
end end
samplerate = if samplerate === nothing if samplerate === nothing
combine_default_sample_rates( samplerate = combine_default_sample_rates(
input_device, input_device,
input_device.default_sample_rate, input_device.default_sample_rate,
output_device, output_device,
output_device.default_sample_rate, output_device.default_sample_rate,
) )
else
float(samplerate)
end end
else else
if latency === nothing if latency === nothing
latency = input_device.input_bounds.high_latency latency = input_device.input_bounds.high_latency
end end
samplerate = if samplerate === nothing if samplerate === nothing
input_device.default_sample_rate samplerate = input_device.default_sample_rate
else
float(samplerate)
end end
end end
else else
@ -785,11 +751,9 @@ function PortAudioStream(
if latency === nothing if latency === nothing
latency = output_device.output_bounds.high_latency latency = output_device.output_bounds.high_latency
end end
samplerate = if samplerate === nothing if samplerate === nothing
output_device.default_sample_rate samplerate = output_device.default_sample_rate
else end
float(samplerate)
end
else else
throw(ArgumentError("Input or output must have at least 1 channel")) throw(ArgumentError("Input or output must have at least 1 channel"))
end end
@ -904,8 +868,8 @@ function close(stream::PortAudioStream)
# closing is tricky, because we want to make sure we've read exactly as much as we've written # closing is tricky, because we want to make sure we've read exactly as much as we've written
# but we have don't know exactly what the tasks are doing # but we have don't know exactly what the tasks are doing
# for now, just close one and then the other # for now, just close one and then the other
fetch_messenger(stream.source_messenger, stream.source_task) fetch_messanger(stream.source_messanger, stream.source_task)
fetch_messenger(stream.sink_messenger, stream.sink_task) fetch_messanger(stream.sink_messanger, stream.sink_task)
pointer_to = stream.pointer_to pointer_to = stream.pointer_to
# only stop if it's not already stopped # only stop if it's not already stopped
if !Bool(handle_status(Pa_IsStreamStopped(pointer_to))) if !Bool(handle_status(Pa_IsStreamStopped(pointer_to)))
@ -927,14 +891,13 @@ end
isopen(stream::PortAudioStream) = isopen(stream.pointer_to) isopen(stream::PortAudioStream) = isopen(stream.pointer_to)
samplerate(stream::PortAudioStream) = stream.sample_rate samplerate(stream::PortAudioStream) = stream.sample_rate
function eltype( function eltype(
::Type{<:PortAudioStream{<:Messenger{Sample}, <:Messenger{Sample}}}, ::Type{<:PortAudioStream{<:Messenger{Sample}, <:Messenger{Sample}}},
) where {Sample} ) where {Sample}
Sample Sample
end end
# these defaults will error for non-SampledSignals scribes # these defaults will error for non-sampledsignal scribes
# which is probably ok; we want these users to define new methods # which is probably ok; we want these users to define new methods
read(stream::PortAudioStream, arguments...) = read(stream.source, arguments...) read(stream::PortAudioStream, arguments...) = read(stream.source, arguments...)
read!(stream::PortAudioStream, arguments...) = read!(stream.source, arguments...) read!(stream::PortAudioStream, arguments...) = read!(stream.source, arguments...)
@ -948,7 +911,7 @@ function show(io::IO, stream::PortAudioStream)
print(io, "PortAudioStream{") print(io, "PortAudioStream{")
print(io, eltype(stream)) print(io, eltype(stream))
println(io, "}") println(io, "}")
print(io, " Samplerate: ", round(Int, samplerate(stream)), "Hz") print(io, " Samplerate: ", samplerate(stream), "Hz")
# show source or sink if there's any channels # show source or sink if there's any channels
sink = stream.sink sink = stream.sink
if has_channels(sink) if has_channels(sink)
@ -994,10 +957,10 @@ function getproperty(
end end
function nchannels(source_or_sink::PortAudioSource) function nchannels(source_or_sink::PortAudioSource)
nchannels(source_or_sink.stream.source_messenger) nchannels(source_or_sink.stream.source_messanger)
end end
function nchannels(source_or_sink::PortAudioSink) function nchannels(source_or_sink::PortAudioSink)
nchannels(source_or_sink.stream.sink_messenger) nchannels(source_or_sink.stream.sink_messanger)
end end
function samplerate(source_or_sink::Union{PortAudioSink, PortAudioSource}) function samplerate(source_or_sink::Union{PortAudioSink, PortAudioSource})
samplerate(source_or_sink.stream) samplerate(source_or_sink.stream)
@ -1015,8 +978,8 @@ end
function isopen(source_or_sink::Union{PortAudioSink, PortAudioSource}) function isopen(source_or_sink::Union{PortAudioSink, PortAudioSource})
isopen(source_or_sink.stream) isopen(source_or_sink.stream)
end end
name(source_or_sink::PortAudioSink) = name(source_or_sink.stream.sink_messenger) name(source_or_sink::PortAudioSink) = name(source_or_sink.stream.sink_messanger)
name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messenger) name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messanger)
# could show full type name, but the PortAudio part is probably redundant # could show full type name, but the PortAudio part is probably redundant
# because these will usually only get printed as part of show for PortAudioStream # because these will usually only get printed as part of show for PortAudioStream
@ -1045,14 +1008,14 @@ end
as_matrix(matrix::Matrix) = matrix as_matrix(matrix::Matrix) = matrix
as_matrix(vector::Vector) = reshape(vector, length(vector), 1) as_matrix(vector::Vector) = reshape(vector, length(vector), 1)
# these will only work with SampledSignals scribes # these will only work with sampledsignals scribes
function unsafe_write( function unsafe_write(
sink::PortAudioSink{<:Messenger{<:Any, <:SampledSignalsWriter}}, sink::PortAudioSink{<:Messenger{<:Any, <:SampledSignalsWriter}},
julia_buffer::Array, julia_buffer::Array,
already, already,
frame_count, frame_count,
) )
exchange(sink.stream.sink_messenger, as_matrix(julia_buffer), already, frame_count) exchange(sink.stream.sink_messanger, as_matrix(julia_buffer), already, frame_count)
end end
function unsafe_read!( function unsafe_read!(
@ -1061,9 +1024,7 @@ function unsafe_read!(
already, already,
frame_count, frame_count,
) )
exchange(source.stream.source_messenger, as_matrix(julia_buffer), already, frame_count) exchange(source.stream.source_messanger, as_matrix(julia_buffer), already, frame_count)
end end
include("precompile.jl")
end # module PortAudio end # module PortAudio

View file

@ -1,29 +0,0 @@
# precompile some important functions
const DEFAULT_SINK_MESSENGER_TYPE = Messenger{Float32, SampledSignalsWriter, Tuple{Matrix{Float32}, Int64, Int64}, Int64}
const DEFAULT_SOURCE_MESSENGER_TYPE = Messenger{Float32, SampledSignalsReader, Tuple{Matrix{Float32}, Int64, Int64}, Int64}
const DEFAULT_STREAM_TYPE = PortAudioStream{DEFAULT_SINK_MESSENGER_TYPE, DEFAULT_SOURCE_MESSENGER_TYPE}
const DEFAULT_SINK_TYPE = PortAudioSink{DEFAULT_SINK_MESSENGER_TYPE, DEFAULT_SOURCE_MESSENGER_TYPE}
const DEFAULT_SOURCE_TYPE = PortAudioSource{DEFAULT_SINK_MESSENGER_TYPE, DEFAULT_SOURCE_MESSENGER_TYPE}
precompile(close, (DEFAULT_STREAM_TYPE,))
precompile(devices, ())
precompile(__init__, ())
precompile(isopen, (DEFAULT_STREAM_TYPE,))
precompile(nchannels, (DEFAULT_SINK_TYPE,))
precompile(nchannels, (DEFAULT_SOURCE_TYPE,))
precompile(PortAudioStream, (Int, Int))
precompile(PortAudioStream, (String, Int, Int))
precompile(PortAudioStream, (String, String, Int, Int))
precompile(samplerate, (DEFAULT_STREAM_TYPE,))
precompile(send, (DEFAULT_SINK_MESSENGER_TYPE,))
precompile(send, (DEFAULT_SOURCE_MESSENGER_TYPE,))
precompile(unsafe_read!, (DEFAULT_SOURCE_TYPE, Vector{Float32}, Int, Int))
precompile(unsafe_read!, (DEFAULT_SOURCE_TYPE, Matrix{Float32}, Int, Int))
precompile(unsafe_write, (DEFAULT_SINK_TYPE, Vector{Float32}, Int, Int))
precompile(unsafe_write, (DEFAULT_SINK_TYPE, Matrix{Float32}, Int, Int))

View file

@ -11,7 +11,6 @@ using PortAudio:
get_output_type, get_output_type,
handle_status, handle_status,
initialize, initialize,
name,
PortAudioException, PortAudioException,
PortAudio, PortAudio,
PortAudioDevice, PortAudioDevice,
@ -19,7 +18,7 @@ using PortAudio:
safe_load, safe_load,
seek_alsa_conf, seek_alsa_conf,
terminate, terminate,
write_buffer name
using PortAudio.LibPortAudio: using PortAudio.LibPortAudio:
Pa_AbortStream, Pa_AbortStream,
PaError, PaError,
@ -110,9 +109,7 @@ using Test: @test, @test_logs, @test_nowarn, @testset, @test_throws
initialize() initialize()
end end
if isempty(devices()) if !isempty(devices())
@test_throws ArgumentError("No input device available") get_default_input_index()
else
@testset "Tests with sound" begin @testset "Tests with sound" begin
# these default values are specific to local machines # these default values are specific to local machines
input_name = get_device(get_default_input_index()).name input_name = get_device(get_default_input_index()).name
@ -133,16 +130,15 @@ else
sleep(1) sleep(1)
println("Testing pass-through") println("Testing pass-through")
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true) stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
write_buffer(stream.sink_messenger.buffer, acquire_lock = false)
sink = stream.sink sink = stream.sink
source = stream.source source = stream.source
@test sprint(show, stream) == """ @test sprint(show, stream) == """
PortAudioStream{Float32} PortAudioStream{Float32}
Samplerate: 44100Hz Samplerate: 44100.0Hz
2 channel sink: $(repr(output_name)) 2 channel sink: $(repr(input_name))
2 channel source: $(repr(input_name))""" 2 channel source: $(repr(output_name))"""
@test sprint(show, source) == "2 channel source: $(repr(input_name))" @test sprint(show, source) == "2 channel source: $(repr(output_name))"
@test sprint(show, sink) == "2 channel sink: $(repr(output_name))" @test sprint(show, sink) == "2 channel sink: $(repr(input_name))"
write(stream, stream, 5s) write(stream, stream, 5s)
@test PaErrorCode(handle_status(Pa_StopStream(stream.pointer_to))) == paNoError @test PaErrorCode(handle_status(Pa_StopStream(stream.pointer_to))) == paNoError
@test isopen(stream) @test isopen(stream)
@ -213,8 +209,8 @@ else
big = typemax(Int) big = typemax(Int)
@test_throws DomainError( @test_throws DomainError(
typemax(Int), typemax(Int),
"$big exceeds maximum output channels for $output_name", "$big exceeds maximum input channels for $output_name",
) PortAudioStream(input_name, output_name, 0, big) ) PortAudioStream(input_name, output_name, big, 0)
@test_throws ArgumentError("Input or output must have at least 1 channel") PortAudioStream( @test_throws ArgumentError("Input or output must have at least 1 channel") PortAudioStream(
input_name, input_name,
output_name, output_name,
@ -223,8 +219,8 @@ else
adjust_channels = true, adjust_channels = true,
) )
@test_throws ArgumentError(""" @test_throws ArgumentError("""
Default sample rate 0 for input \"$input_name\" disagrees with Default sample rate 0 for input $output_name disagrees with
default sample rate 1 for output \"$output_name\". default sample rate 1 for output $input_name.
Please specify a sample rate. Please specify a sample rate.
""") combine_default_sample_rates( """) combine_default_sample_rates(
get_device(input_name), get_device(input_name),