Compare commits

..

10 commits

Author SHA1 Message Date
Brandon Taylor
06c6fd0495 fix bug, test 2022-07-24 11:54:55 -04:00
bramtayl
fbcd539a76
Allow skipping locks, precompile (#120)
* Allow skipping locks, precompile

* fix tests

* version
2022-07-23 15:42:04 -04:00
Jeff Fessler
3939d47a8d
Add tone with buffer example (#117) 2022-04-05 14:32:13 -04:00
Jeff Fessler
19a49931ad
Merge pull request #116 from JuliaAudio/jf-v1.2
Back to v1.2
2022-04-02 18:34:33 -04:00
Jeff Fessler
d21e1e0363 Back to v1.2 2022-04-02 18:12:53 -04:00
Jeff Fessler
7e0ca0122f
Fix remaining messanger typos, add docstring (#115)
* Fix typo, add docstring

* v1.3.0
2022-04-02 18:04:30 -04:00
bramtayl
156eae0db8
Update readme (#111)
* update readme

* Update README.md

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

* Update src/PortAudio.jl

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

* Update README.md

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>
2022-03-29 13:00:39 -04:00
Abhaya Parthy
497567e329
Update save file example in README.md (#102)
* Update save file example in README.md

* Update README.md

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

* Update README.md

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

* Update README.md

Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>

* Remove extra stream

Co-authored-by: bramtayl <brandon.taylor221@gmail.com>
Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>
2022-03-23 11:36:06 -04:00
Jeff Fessler
24acc0247b
Add octave shift example (#110)
* Add octave shift example

* specify duration

* use for loop
2022-03-22 11:06:41 -04:00
bramtayl
78a0a9918d
work with vector buffers (#109)
* work with vector buffers

* no redundant tests
2022-03-09 17:24:25 -05:00
8 changed files with 272 additions and 78 deletions

View file

@ -17,7 +17,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
version: version:
- '1.3' - '1.6'
- '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.2.0" version = "1.3.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.3" julia = "1.6"
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,10 +10,14 @@ 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, 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. 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.
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=48000Hz, latency=0.1, synced=false) PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000, latency=0.1)
``` ```
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
@ -27,13 +31,16 @@ You can get a list of your system's devices with the `PortAudio.devices()` funct
```julia ```julia
julia> PortAudio.devices() julia> PortAudio.devices()
6-element Array{PortAudio.PortAudioDevice,1}: 14-element Vector{PortAudio.PortAudioDevice}:
PortAudio.PortAudioDevice("AirPlay","Core Audio",0,2,0) "sof-hda-dsp: - (hw:0,0)" 2→2
PortAudio.PortAudioDevice("Built-in Microph","Core Audio",2,0,1) "sof-hda-dsp: - (hw:0,3)" 0→2
PortAudio.PortAudioDevice("Built-in Output","Core Audio",0,2,2) "sof-hda-dsp: - (hw:0,4)" 0→2
PortAudio.PortAudioDevice("JackRouter","Core Audio",2,2,3) "sof-hda-dsp: - (hw:0,5)" 0→2
PortAudio.PortAudioDevice("After Effects 13.5","Core Audio",0,0,4)
PortAudio.PortAudioDevice("Built-In Aggregate","Core Audio",2,2,5) "upmix" 8→8
"vdownmix" 6→6
"dmix" 0→2
"default" 32→32
``` ```
## Reading and Writing ## Reading and Writing
@ -75,7 +82,7 @@ end
### Open your built-in microphone and speaker by name ### Open your built-in microphone and speaker by name
```julia ```julia
PortAudioStream("Built-in Microph", "Built-in Output") do stream PortAudioStream("default", "default") do stream
write(stream, stream) write(stream, stream)
end end
``` ```
@ -83,13 +90,18 @@ 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> using PortAudio, SampledSignals, LibSndFile julia> import LibSndFile # must be in Manifest for FileIO.save to work
julia> stream = PortAudioStream("Built-in Microph", 2, 0) julia> using PortAudio: PortAudioStream
PortAudio.PortAudioStream{Float32,SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
Samplerate: 48000 s⁻¹ julia> using SampledSignals: s
Buffer Size: 4096 frames
2 channel source: "Built-in Microph" julia> using FileIO: save
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}}
@ -108,7 +120,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=Float64(S)) do stream PortAudioStream(0, 2; samplerate=S) do stream
write(stream, x) write(stream, x)
end end
``` ```

89
examples/octave-shift.jl Normal file
View file

@ -0,0 +1,89 @@
#=
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)

21
examples/tone-buffer.jl Normal file
View file

@ -0,0 +1,21 @@
#=
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,6 +48,7 @@ using .LibPortAudio:
Pa_Initialize, Pa_Initialize,
paInputOverflowed, paInputOverflowed,
Pa_IsStreamStopped, Pa_IsStreamStopped,
paNoDevice,
paNoFlag, paNoFlag,
Pa_OpenStream, Pa_OpenStream,
paOutputUnderflowed, paOutputUnderflowed,
@ -195,12 +196,22 @@ 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()
handle_status(Pa_GetDefaultInputDevice()) device_index = Pa_GetDefaultInputDevice()
check_device_exists(device_index, "input")
device_index
end end
function get_default_output_index() function get_default_output_index()
handle_status(Pa_GetDefaultOutputDevice()) device_index = 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
@ -230,19 +241,25 @@ 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) function read_or_write(a_function, buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
pointer_to = buffer.pointer_to
data = buffer.data
handle_status( handle_status(
# because we're calling Pa_ReadStream and Pa_WriteStream from separate threads, if acquire_lock
# we put a lock around these calls # because we're calling Pa_ReadStream and Pa_WriteStream from separate threads,
lock( # we put a lock around these calls
let a_function = a_function, lock(
pointer_to = buffer.pointer_to, let a_function = a_function,
data = buffer.data, pointer_to = pointer_to,
use_frames = use_frames data = data,
() -> a_function(pointer_to, data, use_frames) use_frames = use_frames
end, () -> a_function(pointer_to, data, use_frames)
buffer.stream_lock, end,
), buffer.stream_lock,
)
else
a_function(pointer_to, data, use_frames)
end;
warn_xruns = buffer.warn_xruns, warn_xruns = buffer.warn_xruns,
) )
end end
@ -407,25 +424,39 @@ 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) PortAudio.write_buffer(buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
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) function write_buffer(buffer::Buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
read_or_write(Pa_WriteStream, buffer, use_frames) read_or_write(Pa_WriteStream, buffer, use_frames; acquire_lock = acquire_lock)
end end
""" """
PortAudio.read_buffer!(buffer::Buffer, use_frames = buffer.frames_per_buffer) PortAudio.read_buffer!(buffer::Buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
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) function read_buffer!(buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
read_or_write(Pa_ReadStream, buffer, use_frames) read_or_write(Pa_ReadStream, buffer, use_frames; acquire_lock = acquire_lock)
end end
# the messenger will send tasks to the scribe """
# the scribe will read/write from the buffer Messenger{Sample, Scribe, Input, Output}
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}
@ -497,7 +528,7 @@ function messenger_task(
messenger, task messenger, task
end end
function fetch_messanger(messenger, task) function fetch_messenger(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)
@ -517,9 +548,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_messanger::SinkMessenger sink_messenger::SinkMessenger
sink_task::Task sink_task::Task
source_messanger::SourceMessenger source_messenger::SourceMessenger
source_task::Task source_task::Task
end end
@ -584,10 +615,9 @@ 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 $input_sample_rate for input $(name(input_device)) disagrees with default sample rate $output_sample_rate for output \"$(name(output_device))\".
default sample rate $output_sample_rate for output $(name(output_device)).
Please specify a sample rate. Please specify a sample rate.
""", """,
), ),
@ -730,20 +760,24 @@ function PortAudioStream(
output_device.output_bounds.high_latency, output_device.output_bounds.high_latency,
) )
end end
if samplerate === nothing samplerate = if samplerate === nothing
samplerate = combine_default_sample_rates( 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
if samplerate === nothing samplerate = if samplerate === nothing
samplerate = input_device.default_sample_rate input_device.default_sample_rate
else
float(samplerate)
end end
end end
else else
@ -751,9 +785,11 @@ function PortAudioStream(
if latency === nothing if latency === nothing
latency = output_device.output_bounds.high_latency latency = output_device.output_bounds.high_latency
end end
if samplerate === nothing samplerate = if samplerate === nothing
samplerate = output_device.default_sample_rate output_device.default_sample_rate
end else
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
@ -868,8 +904,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_messanger(stream.source_messanger, stream.source_task) fetch_messenger(stream.source_messenger, stream.source_task)
fetch_messanger(stream.sink_messanger, stream.sink_task) fetch_messenger(stream.sink_messenger, 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)))
@ -891,13 +927,14 @@ 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-sampledsignal scribes # these defaults will error for non-SampledSignals 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...)
@ -911,7 +948,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: ", samplerate(stream), "Hz") print(io, " Samplerate: ", round(Int, 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)
@ -957,10 +994,10 @@ function getproperty(
end end
function nchannels(source_or_sink::PortAudioSource) function nchannels(source_or_sink::PortAudioSource)
nchannels(source_or_sink.stream.source_messanger) nchannels(source_or_sink.stream.source_messenger)
end end
function nchannels(source_or_sink::PortAudioSink) function nchannels(source_or_sink::PortAudioSink)
nchannels(source_or_sink.stream.sink_messanger) nchannels(source_or_sink.stream.sink_messenger)
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)
@ -978,8 +1015,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_messanger) name(source_or_sink::PortAudioSink) = name(source_or_sink.stream.sink_messenger)
name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messanger) name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messenger)
# 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
@ -1008,14 +1045,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_messanger, as_matrix(julia_buffer), already, frame_count) exchange(sink.stream.sink_messenger, as_matrix(julia_buffer), already, frame_count)
end end
function unsafe_read!( function unsafe_read!(
@ -1024,7 +1061,9 @@ function unsafe_read!(
already, already,
frame_count, frame_count,
) )
exchange(source.stream.source_messanger, as_matrix(julia_buffer), already, frame_count) exchange(source.stream.source_messenger, as_matrix(julia_buffer), already, frame_count)
end end
include("precompile.jl")
end # module PortAudio end # module PortAudio

29
src/precompile.jl Normal file
View file

@ -0,0 +1,29 @@
# 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,6 +11,7 @@ using PortAudio:
get_output_type, get_output_type,
handle_status, handle_status,
initialize, initialize,
name,
PortAudioException, PortAudioException,
PortAudio, PortAudio,
PortAudioDevice, PortAudioDevice,
@ -18,7 +19,7 @@ using PortAudio:
safe_load, safe_load,
seek_alsa_conf, seek_alsa_conf,
terminate, terminate,
name write_buffer
using PortAudio.LibPortAudio: using PortAudio.LibPortAudio:
Pa_AbortStream, Pa_AbortStream,
PaError, PaError,
@ -109,7 +110,9 @@ 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
@ -130,15 +133,16 @@ if !isempty(devices())
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: 44100.0Hz Samplerate: 44100Hz
2 channel sink: $(repr(input_name)) 2 channel sink: $(repr(output_name))
2 channel source: $(repr(output_name))""" 2 channel source: $(repr(input_name))"""
@test sprint(show, source) == "2 channel source: $(repr(output_name))" @test sprint(show, source) == "2 channel source: $(repr(input_name))"
@test sprint(show, sink) == "2 channel sink: $(repr(input_name))" @test sprint(show, sink) == "2 channel sink: $(repr(output_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)
@ -209,8 +213,8 @@ if !isempty(devices())
big = typemax(Int) big = typemax(Int)
@test_throws DomainError( @test_throws DomainError(
typemax(Int), typemax(Int),
"$big exceeds maximum input channels for $output_name", "$big exceeds maximum output channels for $output_name",
) PortAudioStream(input_name, output_name, big, 0) ) PortAudioStream(input_name, output_name, 0, big)
@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,
@ -219,8 +223,8 @@ if !isempty(devices())
adjust_channels = true, adjust_channels = true,
) )
@test_throws ArgumentError(""" @test_throws ArgumentError("""
Default sample rate 0 for input $output_name disagrees with Default sample rate 0 for input \"$input_name\" disagrees with
default sample rate 1 for output $input_name. default sample rate 1 for output \"$output_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),