Compare commits

..

4 commits

Author SHA1 Message Date
bramtayl
71616b1bdd
Update README.md
Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>
2022-03-29 13:00:10 -04:00
bramtayl
056e88032d
Update src/PortAudio.jl
Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>
2022-03-28 10:21:29 -04:00
bramtayl
e6a9e7e070
Update README.md
Co-authored-by: Jeff Fessler <JeffFessler@users.noreply.github.com>
2022-03-28 10:20:12 -04:00
Brandon Taylor
896a82fe79 update readme 2022-03-27 10:24:31 -04:00
6 changed files with 54 additions and 141 deletions

View file

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

View file

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

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,
paInputOverflowed,
Pa_IsStreamStopped,
paNoDevice,
paNoFlag,
Pa_OpenStream,
paOutputUnderflowed,
@ -196,22 +195,12 @@ function show(io::IO, device::PortAudioDevice)
print(io, device.output_bounds.max_channels)
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()
device_index = Pa_GetDefaultInputDevice()
check_device_exists(device_index, "input")
device_index
handle_status(Pa_GetDefaultInputDevice())
end
function get_default_output_index()
device_index = Pa_GetDefaultOutputDevice()
check_device_exists(device_index, "output")
device_index
handle_status(Pa_GetDefaultOutputDevice())
end
# we can look up devices by index or name
@ -241,25 +230,19 @@ function devices()
end
# 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)
pointer_to = buffer.pointer_to
data = buffer.data
function read_or_write(a_function, buffer, use_frames = buffer.frames_per_buffer)
handle_status(
if acquire_lock
# because we're calling Pa_ReadStream and Pa_WriteStream from separate threads,
# we put a lock around these calls
lock(
let a_function = a_function,
pointer_to = pointer_to,
data = data,
use_frames = use_frames
() -> a_function(pointer_to, data, use_frames)
end,
buffer.stream_lock,
)
else
a_function(pointer_to, data, use_frames)
end;
# because we're calling Pa_ReadStream and Pa_WriteStream from separate threads,
# we put a lock around these calls
lock(
let a_function = a_function,
pointer_to = buffer.pointer_to,
data = buffer.data,
use_frames = use_frames
() -> a_function(pointer_to, data, use_frames)
end,
buffer.stream_lock,
),
warn_xruns = buffer.warn_xruns,
)
end
@ -424,39 +407,25 @@ eltype(::Type{Buffer{Sample}}) where {Sample} = Sample
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.
Set `acquire_lock = false` to skip acquiring the lock.
"""
function write_buffer(buffer::Buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
read_or_write(Pa_WriteStream, buffer, use_frames; acquire_lock = acquire_lock)
function write_buffer(buffer::Buffer, use_frames = buffer.frames_per_buffer)
read_or_write(Pa_WriteStream, buffer, use_frames)
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).
Set `acquire_lock = false` to skip acquiring the acquire_lock.
"""
function read_buffer!(buffer, use_frames = buffer.frames_per_buffer; acquire_lock = true)
read_or_write(Pa_ReadStream, buffer, use_frames; acquire_lock = acquire_lock)
function read_buffer!(buffer, use_frames = buffer.frames_per_buffer)
read_or_write(Pa_ReadStream, buffer, use_frames)
end
"""
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.
"""
# the messenger will send tasks to the scribe
# the scribe will read/write from the buffer
struct Messenger{Sample, Scribe, Input, Output}
device_name::String
buffer::Buffer{Sample}
@ -528,7 +497,7 @@ function messenger_task(
messenger, task
end
function fetch_messenger(messenger, task)
function fetch_messanger(messenger, task)
if has_channels(messenger)
# this will shut down the channels, which will shut down the thread
close(messenger.input_channel)
@ -548,9 +517,9 @@ struct PortAudioStream{SinkMessenger, SourceMessenger}
sample_rate::Float64
# pointer to the c object
pointer_to::Ptr{PaStream}
sink_messenger::SinkMessenger
sink_messanger::SinkMessenger
sink_task::Task
source_messenger::SourceMessenger
source_messanger::SourceMessenger
source_task::Task
end
@ -615,9 +584,10 @@ function combine_default_sample_rates(
)
if input_sample_rate != output_sample_rate
throw(
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))\".
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)).
Please specify a sample rate.
""",
),
@ -904,8 +874,8 @@ function close(stream::PortAudioStream)
# 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
# for now, just close one and then the other
fetch_messenger(stream.source_messenger, stream.source_task)
fetch_messenger(stream.sink_messenger, stream.sink_task)
fetch_messanger(stream.source_messanger, stream.source_task)
fetch_messanger(stream.sink_messanger, stream.sink_task)
pointer_to = stream.pointer_to
# only stop if it's not already stopped
if !Bool(handle_status(Pa_IsStreamStopped(pointer_to)))
@ -927,14 +897,13 @@ end
isopen(stream::PortAudioStream) = isopen(stream.pointer_to)
samplerate(stream::PortAudioStream) = stream.sample_rate
function eltype(
::Type{<:PortAudioStream{<:Messenger{Sample}, <:Messenger{Sample}}},
) where {Sample}
Sample
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
read(stream::PortAudioStream, arguments...) = read(stream.source, arguments...)
read!(stream::PortAudioStream, arguments...) = read!(stream.source, arguments...)
@ -948,7 +917,7 @@ function show(io::IO, stream::PortAudioStream)
print(io, "PortAudioStream{")
print(io, eltype(stream))
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
sink = stream.sink
if has_channels(sink)
@ -994,10 +963,10 @@ function getproperty(
end
function nchannels(source_or_sink::PortAudioSource)
nchannels(source_or_sink.stream.source_messenger)
nchannels(source_or_sink.stream.source_messanger)
end
function nchannels(source_or_sink::PortAudioSink)
nchannels(source_or_sink.stream.sink_messenger)
nchannels(source_or_sink.stream.sink_messanger)
end
function samplerate(source_or_sink::Union{PortAudioSink, PortAudioSource})
samplerate(source_or_sink.stream)
@ -1015,8 +984,8 @@ end
function isopen(source_or_sink::Union{PortAudioSink, PortAudioSource})
isopen(source_or_sink.stream)
end
name(source_or_sink::PortAudioSink) = name(source_or_sink.stream.sink_messenger)
name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messenger)
name(source_or_sink::PortAudioSink) = name(source_or_sink.stream.sink_messanger)
name(source_or_sink::PortAudioSource) = name(source_or_sink.stream.source_messanger)
# 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
@ -1045,14 +1014,14 @@ end
as_matrix(matrix::Matrix) = matrix
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(
sink::PortAudioSink{<:Messenger{<:Any, <:SampledSignalsWriter}},
julia_buffer::Array,
already,
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
function unsafe_read!(
@ -1061,9 +1030,7 @@ function unsafe_read!(
already,
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
include("precompile.jl")
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,
handle_status,
initialize,
name,
PortAudioException,
PortAudio,
PortAudioDevice,
@ -19,7 +18,7 @@ using PortAudio:
safe_load,
seek_alsa_conf,
terminate,
write_buffer
name
using PortAudio.LibPortAudio:
Pa_AbortStream,
PaError,
@ -110,9 +109,7 @@ using Test: @test, @test_logs, @test_nowarn, @testset, @test_throws
initialize()
end
if isempty(devices())
@test_throws ArgumentError("No input device available") get_default_input_index()
else
if !isempty(devices())
@testset "Tests with sound" begin
# these default values are specific to local machines
input_name = get_device(get_default_input_index()).name
@ -133,16 +130,15 @@ else
sleep(1)
println("Testing pass-through")
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
write_buffer(stream.sink_messenger.buffer, acquire_lock = false)
sink = stream.sink
source = stream.source
@test sprint(show, stream) == """
PortAudioStream{Float32}
Samplerate: 44100Hz
2 channel sink: $(repr(output_name))
2 channel source: $(repr(input_name))"""
@test sprint(show, source) == "2 channel source: $(repr(input_name))"
@test sprint(show, sink) == "2 channel sink: $(repr(output_name))"
Samplerate: 44100.0Hz
2 channel sink: $(repr(input_name))
2 channel source: $(repr(output_name))"""
@test sprint(show, source) == "2 channel source: $(repr(output_name))"
@test sprint(show, sink) == "2 channel sink: $(repr(input_name))"
write(stream, stream, 5s)
@test PaErrorCode(handle_status(Pa_StopStream(stream.pointer_to))) == paNoError
@test isopen(stream)
@ -213,8 +209,8 @@ else
big = typemax(Int)
@test_throws DomainError(
typemax(Int),
"$big exceeds maximum output channels for $output_name",
) PortAudioStream(input_name, output_name, 0, big)
"$big exceeds maximum input channels for $output_name",
) PortAudioStream(input_name, output_name, big, 0)
@test_throws ArgumentError("Input or output must have at least 1 channel") PortAudioStream(
input_name,
output_name,
@ -223,8 +219,8 @@ else
adjust_channels = true,
)
@test_throws ArgumentError("""
Default sample rate 0 for input \"$input_name\" disagrees with
default sample rate 1 for output \"$output_name\".
Default sample rate 0 for input $output_name disagrees with
default sample rate 1 for output $input_name.
Please specify a sample rate.
""") combine_default_sample_rates(
get_device(input_name),