From 5c40329df6ba3820a36c57aa1499edbdc8aff751 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 9 May 2017 11:39:42 -0400 Subject: [PATCH 01/15] new C-based ringbuffer handling seems to be mostly working --- .gitignore | 1 + README.md | 11 ++ REQUIRE | 5 +- deps/src/Makefile | 66 +++++++++++ deps/src/pa_ringbuffer.h | 236 +++++++++++++++++++++++++++++++++++++++ deps/src/pa_shim.c | 94 ++++++++++++++++ src/PortAudio.jl | 222 +++++++++++++++++------------------- src/libportaudio.jl | 22 ++-- src/pa_shim.jl | 33 ++++++ 9 files changed, 555 insertions(+), 135 deletions(-) create mode 100644 deps/src/Makefile create mode 100644 deps/src/pa_ringbuffer.h create mode 100644 deps/src/pa_shim.c create mode 100644 src/pa_shim.jl diff --git a/.gitignore b/.gitignore index d86d68c..74e5532 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ deps/deps.jl *.flac *.cov coverage +deps/usr diff --git a/README.md b/README.md index a9139dc..7b2cc9a 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,14 @@ julia> buf = read(stream, 10s) julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf) ``` + +## Building the shim library + +Because PortAudio calls its callback from a separate audio thread, we can't handle it in Julia directly. To work around this we've included a small shim library written in C that uses ring buffers to pass audio data between the callback context and the main Julia context. To build the shim you'll need a few prerequisites: + +* libportaudio +* make +* a C compiler (gcc on linux/macOS, mingw64 on Windows) +* The `RingBuffers` julia package, installed in a folder next to this one. The portaudio shim links against the `pa_ringbuffer` library that comes with `RingBuffers`. + +To build the shim, go into the `deps/src` directory and type `make`. diff --git a/REQUIRE b/REQUIRE index 38ee8b4..f44162a 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,8 +1,7 @@ -julia 0.4 -Compat 0.8.8 +julia 0.6 BinDeps -Devectorize SampledSignals 0.3.0 RingBuffers 0.1.0 +Suppressor @osx Homebrew @windows WinRPM diff --git a/deps/src/Makefile b/deps/src/Makefile new file mode 100644 index 0000000..27aac58 --- /dev/null +++ b/deps/src/Makefile @@ -0,0 +1,66 @@ +# Makefile lifted from Clang.jl +# Copyright (c) 2012-: Isaiah Norton and [contributors](https://github.com/ihnorton/Clang.jl/graphs/contributors) + +ifeq (exists, $(shell [ -e Make.user ] && echo exists )) +include Make.user +endif + +#check-env: +#ifndef JULIA_INC +# $(error Environment variable JULIA_INC is not set.) +#endif + +#INC =-I"$(JULIA_INC)" +CFLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -g +LDFLAGS =-L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer +# LINUX_LIBS =-lrt +# LINUX_LDFLAGS =-rdynamic +# add the Homebrew.jl tree to the include dirs in case we used it for +# portaudio and libsndfile +# DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib +# DARWIN_INC =-I../../../Homebrew/deps/usr/include +TARGETDIR=../usr/lib + +OBJS = pa_shim.o + +# Figure out OS and architecture +OS = $(shell uname) +ifneq (,$(findstring MINGW,$(OS))) + OS=WINNT +endif + +# file extensions and platform-specific libs +ifeq ($(OS), WINNT) + LIBS += $(WINNT_LIBS) + LDFLAGS += $(WINNT_LDFLAGS) + INC += $(WINNT_INC) + SHLIB_EXT = dll +else ifeq ($(OS), Darwin) + INC += $(DARWIN_INC) + LDFLAGS += $(DARWIN_LDFLAGS) + SHLIB_EXT = dylib +else + LIBS += $(LINUX_LIBS) + LDFLAGS += $(LINUX_LDFLAGS) + INC += $(LINUX_INC) + SHLIB_EXT = so +endif + +TARGET=$(TARGETDIR)/pa_shim.$(SHLIB_EXT) + +.PHONY: clean default + +default: $(TARGET) + +%.o: %.c Makefile + $(CC) $< -fPIC -c -o $@ $(INC) $(CFLAGS) + +$(TARGETDIR): + mkdir -p $@ + +$(TARGET): $(OBJS) $(TARGETDIR) Makefile + $(CC) $(OBJS) -shared -o $@ $(LDFLAGS) $(LIBS) + +clean: + rm -f $(OBJS) + rm -f $(TARGET) diff --git a/deps/src/pa_ringbuffer.h b/deps/src/pa_ringbuffer.h new file mode 100644 index 0000000..9edba0d --- /dev/null +++ b/deps/src/pa_ringbuffer.h @@ -0,0 +1,236 @@ +#ifndef PA_RINGBUFFER_H +#define PA_RINGBUFFER_H +/* + * $Id$ + * Portable Audio I/O Library + * Ring Buffer utility. + * + * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on OS X by Bjorn Roche. + * also allowed for const where possible. + * modified for multiple-byte-sized data elements by Sven Fischer + * + * Note that this is safe only for a single-thread reader + * and a single-thread writer. + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + @brief Single-reader single-writer lock-free ring buffer + + PaUtilRingBuffer is a ring buffer used to transport samples between + different execution contexts (threads, OS callbacks, interrupt handlers) + without requiring the use of any locks. This only works when there is + a single reader and a single writer (ie. one thread or callback writes + to the ring buffer, another thread or callback reads from it). + + The PaUtilRingBuffer structure manages a ring buffer containing N + elements, where N must be a power of two. An element may be any size + (specified in bytes). + + The memory area used to store the buffer elements must be allocated by + the client prior to calling PaUtil_InitializeRingBuffer() and must outlive + the use of the ring buffer. + + @note The ring buffer functions are not normally exposed in the PortAudio libraries. + If you want to call them then you will need to add pa_ringbuffer.c to your application source code. +*/ + +#if defined(__APPLE__) +#include +typedef int32_t ring_buffer_size_t; +#elif defined( __GNUC__ ) +typedef long ring_buffer_size_t; +#elif (_MSC_VER >= 1400) +typedef long ring_buffer_size_t; +#elif defined(_MSC_VER) || defined(__BORLANDC__) +typedef long ring_buffer_size_t; +#else +typedef long ring_buffer_size_t; +#endif + + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +typedef struct PaUtilRingBuffer +{ + ring_buffer_size_t bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */ + volatile ring_buffer_size_t writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */ + volatile ring_buffer_size_t readIndex; /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */ + ring_buffer_size_t bigMask; /**< Used for wrapping indices with extra bit to distinguish full/empty. */ + ring_buffer_size_t smallMask; /**< Used for fitting indices to buffer. */ + ring_buffer_size_t elementSizeBytes; /**< Number of bytes per element. */ + char *buffer; /**< Pointer to the buffer containing the actual data. */ +}PaUtilRingBuffer; + +/** Initialize Ring Buffer to empty state ready to have elements written to it. + + @param rbuf The ring buffer. + + @param elementSizeBytes The size of a single data element in bytes. + + @param elementCount The number of elements in the buffer (must be a power of 2). + + @param dataPtr A pointer to a previously allocated area where the data + will be maintained. It must be elementCount*elementSizeBytes long. + + @return -1 if elementCount is not a power of 2, otherwise 0. +*/ +ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr ); + +/** Reset buffer to empty. Should only be called when buffer is NOT being read or written. + + @param rbuf The ring buffer. +*/ +void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ); + +/** Retrieve the number of elements available in the ring buffer for writing. + + @param rbuf The ring buffer. + + @return The number of elements available for writing. +*/ +ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf ); + +/** Retrieve the number of elements available in the ring buffer for reading. + + @param rbuf The ring buffer. + + @return The number of elements available for reading. +*/ +ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf ); + +/** Write data to the ring buffer. + + @param rbuf The ring buffer. + + @param data The address of new data to write to the buffer. + + @param elementCount The number of elements to be written. + + @return The number of elements written. +*/ +ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount ); + +/** Read data from the ring buffer. + + @param rbuf The ring buffer. + + @param data The address where the data should be stored. + + @param elementCount The number of elements to be read. + + @return The number of elements read. +*/ +ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount ); + +/** Get address of region(s) to which we can write data. + + @param rbuf The ring buffer. + + @param elementCount The number of elements desired. + + @param dataPtr1 The address where the first (or only) region pointer will be + stored. + + @param sizePtr1 The address where the first (or only) region length will be + stored. + + @param dataPtr2 The address where the second region pointer will be stored if + the first region is too small to satisfy elementCount. + + @param sizePtr2 The address where the second region length will be stored if + the first region is too small to satisfy elementCount. + + @return The room available to be written or elementCount, whichever is smaller. +*/ +ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ); + +/** Advance the write index to the next location to be written. + + @param rbuf The ring buffer. + + @param elementCount The number of elements to advance. + + @return The new position. +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); + +/** Get address of region(s) from which we can read data. + + @param rbuf The ring buffer. + + @param elementCount The number of elements desired. + + @param dataPtr1 The address where the first (or only) region pointer will be + stored. + + @param sizePtr1 The address where the first (or only) region length will be + stored. + + @param dataPtr2 The address where the second region pointer will be stored if + the first region is too small to satisfy elementCount. + + @param sizePtr2 The address where the second region length will be stored if + the first region is too small to satisfy elementCount. + + @return The number of elements available for reading. +*/ +ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ); + +/** Advance the read index to the next location to be read. + + @param rbuf The ring buffer. + + @param elementCount The number of elements to advance. + + @return The new position. +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_RINGBUFFER_H */ diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c new file mode 100644 index 0000000..1b79708 --- /dev/null +++ b/deps/src/pa_shim.c @@ -0,0 +1,94 @@ +#include +#include "pa_ringbuffer.h" +#include +#include + +#define SHIM_VERSION 3 +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +typedef enum { + PA_SHIM_ERRMSG_OVERFLOW, // input overflow + PA_SHIM_ERRMSG_UNDERFLOW, // output underflow + PA_SHIM_ERRMSG_ERR_OVERFLOW, // error buffer overflowed +} pa_shim_errmsg_t; + +// this callback type is used to notify the Julia side that the portaudio +// callback has run +typedef void (*pa_shim_notifycb_t)(void *userdata); + +// This struct is shared between the Julia side and C +typedef struct { + PaUtilRingBuffer *inputbuf; // ringbuffer for input + PaUtilRingBuffer *outputbuf; // ringbuffer for output + PaUtilRingBuffer *errorbuf; // ringbuffer to send error notifications + int sync; // keep input/output ring buffers synchronized (0/1) + pa_shim_notifycb_t notifycb; // Julia callback to notify conditions + void *inputhandle; // condition to notify on new input + void *outputhandle; // condition to notify when ready for output + void *errorhandle; // condition to notify on new error +} pa_shim_info_t; + +void senderr(pa_shim_info_t *info, pa_shim_errmsg_t msg) { + if(PaUtil_GetRingBufferWriteAvailable(info->errorbuf) < 2) { + // we've overflowed our error buffer! notify the host. + msg = PA_SHIM_ERRMSG_ERR_OVERFLOW; + } + PaUtil_WriteRingBuffer(info->errorbuf, &msg, 1); + if(info->notifycb) { + info->notifycb(info->errorhandle); + } +} + +// return the version of the shim so we can make sure things are in sync +int pa_shim_getversion(void) +{ + return SHIM_VERSION; +} + +/* + * This routine will be called by the PortAudio engine when audio is needed. + * It may called at interrupt level on some machines so don't do anything that + * could mess up the system like calling malloc() or free(). + */ +int pa_shim_processcb(const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) +{ + pa_shim_info_t *info = userData; + if(info->notifycb == NULL) { + fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n"); + } + + int nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf); + int nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf); + nwrite = MIN(frameCount, nwrite); + nread = MIN(frameCount, nread); + if(info->sync) { + // to keep the buffers synchronized, set readable and writable to + // their minimum value + nread = MIN(nread, nwrite); + nwrite = nread; + } + // read/write from the ringbuffers + PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite); + if(info->notifycb) { + info->notifycb(info->inputhandle); + } + PaUtil_ReadRingBuffer(info->outputbuf, output, nread); + if(info->notifycb) { + info->notifycb(info->outputhandle); + } + if(nwrite < frameCount) { + senderr(info, PA_SHIM_ERRMSG_OVERFLOW); + } + if(nread < frameCount) { + senderr(info, PA_SHIM_ERRMSG_UNDERFLOW); + // we didn't fill the whole output buffer, so zero it out + memset(output+nread*info->outputbuf->elementSizeBytes, 0, + (frameCount - nread)*info->outputbuf->elementSizeBytes); + } + + return paContinue; +} diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 48a21db..d81d462 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -3,51 +3,50 @@ __precompile__() module PortAudio using SampledSignals -using Devectorize using RingBuffers -using Compat -import Compat: UTF8String, view +using Suppressor + +using Base: AsyncCondition # Get binary dependencies loaded from BinDeps -include( "../deps/deps.jl") +include("../deps/deps.jl") +include("pa_shim.jl") include("libportaudio.jl") +function __init__() + init_pa_shim() + global const notifycb_c = cfunction(notifycb, Cint, (Ptr{Void}, )) + # initialize PortAudio on module load + @suppress_err Pa_Initialize() +end + + export PortAudioStream # These sizes are all in frames + # the block size is what we request from portaudio if no blocksize is given. # The ringbuffer and pre-fill will be twice the blocksize const DEFAULT_BLOCKSIZE=4096 + # data is passed to and from the ringbuffer in chunks with this many frames # it should be at most the ringbuffer size, and must evenly divide into the # the underlying portaudio buffer size. E.g. if PortAudio is running with a # 2048-frame buffer period, the chunk size can be 2048, 1024, 512, 256, etc. const CHUNKSIZE=128 -function __init__() - # initialize PortAudio on module load - swallow_stderr() do - Pa_Initialize() - end - - # the portaudio callbacks are parametric on the sample type - global const pa_callbacks = Dict{Type, Ptr{Void}}() - - for T in (Float32, Int32, Int16, Int8, UInt8) - pa_callbacks[T] = cfunction(portaudio_callback, Cint, - (Ptr{T}, Ptr{T}, Culong, Ptr{Void}, Culong, - Ptr{CallbackInfo{T}})) - end -end +# ringbuffer to receive errors from the audio processing thread +const ERR_BUFSIZE=512 function versioninfo(io::IO=STDOUT) println(io, Pa_GetVersionText()) - println(io, "Version Number: ", Pa_GetVersion()) + println(io, "Version: ", Pa_GetVersion()) + println(io, "Shim Version: ", shimversion()) end type PortAudioDevice - name::UTF8String - hostapi::UTF8String + name::String + hostapi::String maxinchans::Int maxoutchans::Int defaultsamplerate::Float64 @@ -71,23 +70,9 @@ end # not for external use, used in error message printing devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n") -"""Give a pointer to the given field within a Julia object""" -function fieldptr{T}(obj::T, field::Symbol) - fieldnum = findfirst(fieldnames(T), field) - offset = fieldoffset(T, fieldnum) - FT = fieldtype(T, field) - - Ptr{FT}(pointer_from_objref(obj) + offset) -end - -# we want this to be immutable so we can stack allocate it -immutable CallbackInfo{T} - inchannels::Int - inbuf::LockFreeRingBuffer{T} - outchannels::Int - outbuf::LockFreeRingBuffer{T} - synced::Bool -end +################## +# PortAudioStream +################## type PortAudioStream{T} samplerate::Float64 @@ -95,12 +80,13 @@ type PortAudioStream{T} stream::PaStream sink # untyped because of circular type definition source # untyped because of circular type definition - bufinfo::CallbackInfo{T} # immutable data used in the portaudio callback + errbuf::RingBuffer{pa_shim_errmsg_t} # used to send errors from the portaudio callback + bufinfo::pa_shim_info_t # data used in the portaudio callback # this inner constructor is generally called via the top-level outer # constructor below - function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans, outchans, sr, blocksize, synced) + function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, + inchans, outchans, sr, blocksize, synced) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans inparams = (inchans == 0) ? @@ -113,18 +99,25 @@ type PortAudioStream{T} finalizer(this, close) this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) + this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE) if synced && inchans > 0 && outchans > 0 # we've got a synchronized duplex stream. initialize with the output buffer full write(this.sink, SampleBuf(zeros(T, blocksize*2, outchans), sr)) end - this.bufinfo = CallbackInfo(inchans, this.source.ringbuf, - outchans, this.sink.ringbuf, synced) - this.stream = swallow_stderr() do - Pa_OpenStream(inparams, outparams, float(sr), blocksize, - paNoFlag, pa_callbacks[T], fieldptr(this, :bufinfo)) - end + this.bufinfo = pa_shim_info_t(bufpointer(this.source), + bufpointer(this.sink), + pointer(this.errbuf), + synced, notifycb_c, + getnotifyhandle(this.sink), + getnotifyhandle(this.source), + getnotifyhandle(this.errbuf)) + this.stream = @suppress_err Pa_OpenStream(inparams, outparams, + float(sr), blocksize, + paNoFlag, shim_processcb_c, + this.bufinfo) Pa_StartStream(this.stream) + @async handle_errors(this) this end @@ -219,28 +212,58 @@ function Base.show(io::IO, stream::PortAudioStream) println(io, " Samplerate: ", samplerate(stream), "Hz") print(io, " Buffer Size: ", stream.blocksize, " frames") if nchannels(stream.sink) > 0 - print(io, "\n ", nchannels(stream.sink), " channel sink: \"", stream.sink.name, "\"") + print(io, "\n ", nchannels(stream.sink), " channel sink: \"", name(stream.sink), "\"") end if nchannels(stream.source) > 0 - print(io, "\n ", nchannels(stream.source), " channel source: \"", stream.source.name, "\"") + print(io, "\n ", nchannels(stream.source), " channel source: \"", name(stream.source), "\"") end end +""" + handle_errors(stream::PortAudioStream) + +Handle errors coming over the error stream from PortAudio. This is run as an +independent task while the stream is active. +""" +function handle_errors(stream::PortAudioStream) + err = Vector{pa_shim_errmsg_t}(1) + while true + nread = read!(stream.errbuf, err) + nread == 1 || break + if err[1] == PA_SHIM_ERRMSG_ERR_OVERFLOW + warn("Error buffer overflowed on stream $(stream.name)") + elseif err[1] == PA_SHIM_ERRMSG_OVERFLOW + # warn("Input overflowed from $(name(stream.source))") + elseif err[1] == PA_SHIM_ERRMSG_UNDERFLOW + # warn("Output underflowed to $(name(stream.sink))") + else + error(""" + Got unrecognized error code $(err[1]) from audio thread for + stream "$(stream.name)". Please file an issue at + https://github.com/juliaaudio/portaudio.jl/issues""") + end + end +end + +################################## +# PortAudioSink & PortAudioSource +################################## + # Define our source and sink types for (TypeName, Super) in ((:PortAudioSink, :SampleSink), (:PortAudioSource, :SampleSource)) @eval type $TypeName{T} <: $Super - name::UTF8String + name::String stream::PortAudioStream{T} chunkbuf::Array{T, 2} - ringbuf::LockFreeRingBuffer{T} + ringbuf::RingBuffer{T} nchannels::Int - function $TypeName(name, stream, channels, ringbufsize) + function $TypeName{T}(name, stream, channels, ringbufsize) where {T} # portaudio data comes in interleaved, so we'll end up transposing # it back and forth to julia column-major chunkbuf = zeros(T, channels, CHUNKSIZE) - ringbuf = LockFreeRingBuffer(T, ringbufsize * channels) + ringbuf = RingBuffer{T}(channels, ringbufsize) new(name, stream, chunkbuf, ringbuf, channels) end end @@ -249,35 +272,35 @@ end SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream) SampledSignals.blocksize(s::Union{PortAudioSink, PortAudioSource}) = s.stream.blocksize -Base.eltype{T}(::Union{PortAudioSink{T}, PortAudioSource{T}}) = T +Base.eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T Base.close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf) +Base.isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf) +RingBuffers.getnotifyhandle(s::Union{PortAudioSink, PortAudioSource}) = getnotifyhandle(s.ringbuf) +bufpointer(s::Union{PortAudioSink, PortAudioSource}) = pointer(s.ringbuf) +name(s::Union{PortAudioSink, PortAudioSource}) = s.name -function Base.show{T <: Union{PortAudioSink, PortAudioSource}}(io::IO, stream::T) +function Base.show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} println(io, T, "(\"", stream.name, "\")") print(io, nchannels(stream), " channels") end -function Base.flush(sink::PortAudioSink) - while nwritable(sink.ringbuf) < length(sink.ringbuf) - wait(sink.ringbuf) - end -end +# function Base.flush(sink::PortAudioSink) +# while nwritable(sink.ringbuf) < length(sink.ringbuf) +# wait(sink.ringbuf) +# end +# end function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount) nwritten = 0 while nwritten < framecount - while nwritable(sink.ringbuf) == 0 - wait(sink.ringbuf) - end - # in 0.4 transpose! throws an error if the range is a UInt - writable = div(nwritable(sink.ringbuf), nchannels(sink)) - towrite = Int(min(writable, CHUNKSIZE, framecount-nwritten)) + towrite = min(framecount-nwritten, CHUNKSIZE) # make a buffer of interleaved samples transpose!(view(sink.chunkbuf, :, 1:towrite), view(buf, (1:towrite)+nwritten+frameoffset, :)) - write(sink.ringbuf, sink.chunkbuf, towrite*nchannels(sink)) - - nwritten += towrite + n = write(sink.ringbuf, sink.chunkbuf, towrite) + nwritten += n + # break early if the stream is closed + n < towrite && break end nwritten @@ -286,66 +309,23 @@ end function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameoffset, framecount) nread = 0 while nread < framecount - while nreadable(source.ringbuf) == 0 - wait(source.ringbuf) - end - # in 0.4 transpose! throws an error if the range is a UInt - readable = div(nreadable(source.ringbuf), nchannels(source)) - toread = Int(min(readable, CHUNKSIZE, framecount-nread)) - read!(source.ringbuf, source.chunkbuf, toread*nchannels(source)) + toread = min(framecount-nread, CHUNKSIZE) + n = read!(source.ringbuf, source.chunkbuf, toread) # de-interleave the samples transpose!(view(buf, (1:toread)+nread+frameoffset, :), view(source.chunkbuf, :, 1:toread)) nread += toread + # break early if the stream is closed + n < toread && break end nread end -# This is the callback function that gets called directly in the PortAudio -# audio thread, so it's critical that it not interact with the Julia GC -function portaudio_callback{T}(inptr::Ptr{T}, outptr::Ptr{T}, - nframes, timeinfo, flags, userdata::Ptr{CallbackInfo{T}}) - info = unsafe_load(userdata) - # if there are no channels, treat it as if we can write as many 0-frame channels as we want - framesreadable = info.outchannels > 0 ? div(nreadable(info.outbuf), info.outchannels) : nframes - frameswritable = info.inchannels > 0 ? div(nwritable(info.inbuf), info.inchannels) : nframes - if info.synced - framesreadable = min(framesreadable, frameswritable) - frameswritable = framesreadable - end - towrite = min(frameswritable, nframes) * info.inchannels - toread = min(framesreadable, nframes) * info.outchannels - - read!(info.outbuf, outptr, toread) - write(info.inbuf, inptr, towrite) - - if framesreadable < nframes - outsamples = nframes * info.outchannels - # xrun, copy zeros to outbuffer - # TODO: send a notification to an error msg ringbuf - memset(outptr+sizeof(T)*toread, 0, sizeof(T)*(outsamples-toread)) - end - - paContinue -end - - -"""Call the given function and discard stdout and stderr""" -function swallow_stderr(f) - origerr = STDERR - (errread, errwrite) = redirect_stderr() - result = f() - redirect_stderr(origerr) - close(errwrite) - close(errread) - - result -end - -memset(buf, val, count) = ccall(:memset, Ptr{Void}, - (Ptr{Void}, Cint, Csize_t), - buf, val, count) +# this is called by the shim process callback to notify that there is new data. +# it's run in the audio context so don't do anything besides wake up the +# AsyncCondition +notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Void}, ), handle) end # module PortAudio diff --git a/src/libportaudio.jl b/src/libportaudio.jl index ab4a618..2ce996c 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -1,17 +1,17 @@ # Low-level wrappers for Portaudio calls # General type aliases -typealias PaTime Cdouble -typealias PaError Cint -typealias PaSampleFormat Culong -typealias PaDeviceIndex Cint -typealias PaHostApiIndex Cint -typealias PaHostApiTypeId Cint +const PaTime = Cdouble +const PaError = Cint +const PaSampleFormat = Culong +const PaDeviceIndex = Cint +const PaHostApiIndex = Cint +const PaHostApiTypeId = Cint # PaStream is always used as an opaque type, so we're always dealing # with the pointer -typealias PaStream Ptr{Void} -typealias PaStreamCallback Void -typealias PaStreamFlags Culong +const PaStream = Ptr{Void} +const PaStreamCallback = Void +const PaStreamFlags = Culong const paNoFlag = PaStreamFlags(0x00) @@ -37,7 +37,7 @@ const type_to_fmt = Dict{Type, PaSampleFormat}( UInt8 => 3 ) -typealias PaStreamCallbackResult Cint +const PaStreamCallbackResult = Cint # Callback return values const paContinue = PaStreamCallbackResult(0) const paComplete = PaStreamCallbackResult(1) @@ -69,7 +69,7 @@ end # all the host APIs on the system by iterating through those values. # PaHostApiTypeId values -const pa_host_api_names = Dict{PaHostApiTypeId, UTF8String}( +const pa_host_api_names = Dict{PaHostApiTypeId, String}( 0 => "In Development", # use while developing support for a new host API 1 => "Direct Sound", 2 => "MME", diff --git a/src/pa_shim.jl b/src/pa_shim.jl new file mode 100644 index 0000000..9a97065 --- /dev/null +++ b/src/pa_shim.jl @@ -0,0 +1,33 @@ +const libpa_shim = Libdl.find_library( + ["pa_shim"], + [joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")]) + +function init_pa_shim() + shim_dlib = Libdl.dlopen(libpa_shim) + # pointer to the shim's process callback + global const shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) + if shim_processcb_c == C_NULL + error("Got NULL pointer loading `pa_shim_processcb`") + end +end + +const pa_shim_errmsg_t = Cint +const PA_SHIM_ERRMSG_OVERFLOW = Cint(0) # input overflow +const PA_SHIM_ERRMSG_UNDERFLOW = Cint(1) # output underflow +const PA_SHIM_ERRMSG_ERR_OVERFLOW = Cint(2) # error buffer overflowed + + +# This struct is shared with pa_shim.c +mutable struct pa_shim_info_t + inputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for input + outputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for output + errorbuf::Ptr{PaUtilRingBuffer} # ringbuffer to send error notifications + sync::Cint # keep input/output ring buffers synchronized (0/1) + notifycb::Ptr{Void} # Julia callback to notify on updates (called from audio thread) + inputhandle::Ptr{Void} # condition to notify on new input data + outputhandle::Ptr{Void} # condition to notify when ready for output + errorhandle::Ptr{Void} # condition to notify on new errors +end + +shimversion() = ccall((:pa_shim_getversion, libpa_shim), Cint, ()) +Base.unsafe_convert(::Type{Ptr{Void}}, info::pa_shim_info_t) = pointer_from_objref(info) From 9e3e66d37aec942137c3fb241b1983abdbcece7e Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 9 May 2017 13:08:00 -0400 Subject: [PATCH 02/15] now reports hash of source file used to build shim --- deps/src/Makefile | 10 +++++----- deps/src/pa_shim.c | 8 ++++---- src/pa_shim.jl | 10 +++++++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/deps/src/Makefile b/deps/src/Makefile index 27aac58..bae0abc 100644 --- a/deps/src/Makefile +++ b/deps/src/Makefile @@ -5,13 +5,17 @@ ifeq (exists, $(shell [ -e Make.user ] && echo exists )) include Make.user endif +TARGETDIR=../usr/lib +OBJS = pa_shim.o +SOURCEHASH=$(shell sha256sum pa_shim.c | awk '{print $$1}') + #check-env: #ifndef JULIA_INC # $(error Environment variable JULIA_INC is not set.) #endif #INC =-I"$(JULIA_INC)" -CFLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -g +CFLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -DSOURCEHASH=\"$(SOURCEHASH)\" LDFLAGS =-L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer # LINUX_LIBS =-lrt # LINUX_LDFLAGS =-rdynamic @@ -19,10 +23,6 @@ LDFLAGS =-L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer # portaudio and libsndfile # DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib # DARWIN_INC =-I../../../Homebrew/deps/usr/include -TARGETDIR=../usr/lib - -OBJS = pa_shim.o - # Figure out OS and architecture OS = $(shell uname) ifneq (,$(findstring MINGW,$(OS))) diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c index 1b79708..4317568 100644 --- a/deps/src/pa_shim.c +++ b/deps/src/pa_shim.c @@ -3,7 +3,6 @@ #include #include -#define SHIM_VERSION 3 #define MIN(x, y) ((x) < (y) ? (x) : (y)) typedef enum { @@ -39,10 +38,11 @@ void senderr(pa_shim_info_t *info, pa_shim_errmsg_t msg) { } } -// return the version of the shim so we can make sure things are in sync -int pa_shim_getversion(void) +// return the sha256 hash of the shim source so we can make sure things are in sync +const char *pa_shim_getsourcehash(void) { - return SHIM_VERSION; + // defined on the command-line at build-time + return SOURCEHASH; } /* diff --git a/src/pa_shim.jl b/src/pa_shim.jl index 9a97065..040b896 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -29,5 +29,13 @@ mutable struct pa_shim_info_t errorhandle::Ptr{Void} # condition to notify on new errors end -shimversion() = ccall((:pa_shim_getversion, libpa_shim), Cint, ()) +""" + PortAudio.shimhash() + +Return the sha256 hash(as a string) of the source file used to build the shim. +We may use this sometime to verify that the distributed binary stays in sync +with the rest of the package. +""" +shimhash() = unsafe_string( + ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ())) Base.unsafe_convert(::Type{Ptr{Void}}, info::pa_shim_info_t) = pointer_from_objref(info) From 0c36e1eec5e2a86e2510e400b4854000b79f2d2b Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 11 May 2017 00:58:49 -0400 Subject: [PATCH 03/15] mostly adding tests and fixing bugs. passing tests now --- deps/src/pa_shim.c | 51 +++++--- src/PortAudio.jl | 57 ++++---- src/libportaudio.jl | 4 +- test/runtests.jl | 313 ++++++++++++++++++++++++++++++++------------ 4 files changed, 293 insertions(+), 132 deletions(-) diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c index 4317568..4cdecdc 100644 --- a/deps/src/pa_shim.c +++ b/deps/src/pa_shim.c @@ -60,34 +60,43 @@ int pa_shim_processcb(const void *input, void *output, if(info->notifycb == NULL) { fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n"); } - - int nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf); - int nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf); - nwrite = MIN(frameCount, nwrite); - nread = MIN(frameCount, nread); - if(info->sync) { + int nwrite; + if(info->inputbuf) { + nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf); + nwrite = MIN(frameCount, nwrite); + } + int nread; + if(info->outputbuf) { + nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf); + nread = MIN(frameCount, nread); + } + if(info->inputbuf && info->outputbuf && info->sync) { // to keep the buffers synchronized, set readable and writable to // their minimum value nread = MIN(nread, nwrite); nwrite = nread; } // read/write from the ringbuffers - PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite); - if(info->notifycb) { - info->notifycb(info->inputhandle); + if(info->inputbuf) { + PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite); + if(info->notifycb) { + info->notifycb(info->inputhandle); + } + if(nwrite < frameCount) { + senderr(info, PA_SHIM_ERRMSG_OVERFLOW); + } } - PaUtil_ReadRingBuffer(info->outputbuf, output, nread); - if(info->notifycb) { - info->notifycb(info->outputhandle); - } - if(nwrite < frameCount) { - senderr(info, PA_SHIM_ERRMSG_OVERFLOW); - } - if(nread < frameCount) { - senderr(info, PA_SHIM_ERRMSG_UNDERFLOW); - // we didn't fill the whole output buffer, so zero it out - memset(output+nread*info->outputbuf->elementSizeBytes, 0, - (frameCount - nread)*info->outputbuf->elementSizeBytes); + if(info->outputbuf) { + PaUtil_ReadRingBuffer(info->outputbuf, output, nread); + if(info->notifycb) { + info->notifycb(info->outputhandle); + } + if(nread < frameCount) { + senderr(info, PA_SHIM_ERRMSG_UNDERFLOW); + // we didn't fill the whole output buffer, so zero it out + memset(output+nread*info->outputbuf->elementSizeBytes, 0, + (frameCount - nread)*info->outputbuf->elementSizeBytes); + } } return paContinue; diff --git a/src/PortAudio.jl b/src/PortAudio.jl index d81d462..46d3daf 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -8,6 +8,10 @@ using Suppressor using Base: AsyncCondition +import Base: eltype, show +import Base: close, isopen +import Base: read, read!, write, flush + # Get binary dependencies loaded from BinDeps include("../deps/deps.jl") include("pa_shim.jl") @@ -20,7 +24,6 @@ function __init__() @suppress_err Pa_Initialize() end - export PortAudioStream # These sizes are all in frames @@ -41,7 +44,7 @@ const ERR_BUFSIZE=512 function versioninfo(io::IO=STDOUT) println(io, Pa_GetVersionText()) println(io, "Version: ", Pa_GetVersion()) - println(io, "Shim Version: ", shimversion()) + println(io, "Shim Source Hash: ", shimhash()[1:10]) end type PortAudioDevice @@ -104,13 +107,15 @@ type PortAudioStream{T} # we've got a synchronized duplex stream. initialize with the output buffer full write(this.sink, SampleBuf(zeros(T, blocksize*2, outchans), sr)) end - this.bufinfo = pa_shim_info_t(bufpointer(this.source), - bufpointer(this.sink), - pointer(this.errbuf), - synced, notifycb_c, - getnotifyhandle(this.sink), - getnotifyhandle(this.source), - getnotifyhandle(this.errbuf)) + # pass NULL for input/output we're not using + this.bufinfo = pa_shim_info_t( + inchans > 0 ? bufpointer(this.source) : C_NULL, + outchans > 0 ? bufpointer(this.sink) : C_NULL, + pointer(this.errbuf), + synced, notifycb_c, + inchans > 0 ? notifyhandle(this.source) : C_NULL, + outchans > 0 ? notifyhandle(this.sink) : C_NULL, + notifyhandle(this.errbuf)) this.stream = @suppress_err Pa_OpenStream(inparams, outparams, float(sr), blocksize, paNoFlag, shim_processcb_c, @@ -184,7 +189,7 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...) end -function Base.close(stream::PortAudioStream) +function close(stream::PortAudioStream) if stream.stream != C_NULL Pa_StopStream(stream.stream) Pa_CloseStream(stream.stream) @@ -196,18 +201,18 @@ function Base.close(stream::PortAudioStream) nothing end -Base.isopen(stream::PortAudioStream) = stream.stream != C_NULL +isopen(stream::PortAudioStream) = stream.stream != C_NULL SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate -Base.eltype{T}(stream::PortAudioStream{T}) = T +eltype{T}(stream::PortAudioStream{T}) = T -Base.read(stream::PortAudioStream, args...) = read(stream.source, args...) -Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...) -Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...) -Base.write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...) -Base.flush(stream::PortAudioStream) = flush(stream.sink) +read(stream::PortAudioStream, args...) = read(stream.source, args...) +read!(stream::PortAudioStream, args...) = read!(stream.source, args...) +write(stream::PortAudioStream, args...) = write(stream.sink, args...) +write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...) +flush(stream::PortAudioStream) = flush(stream.sink) -function Base.show(io::IO, stream::PortAudioStream) +function show(io::IO, stream::PortAudioStream) println(io, typeof(stream)) println(io, " Samplerate: ", samplerate(stream), "Hz") print(io, " Buffer Size: ", stream.blocksize, " frames") @@ -272,23 +277,19 @@ end SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream) SampledSignals.blocksize(s::Union{PortAudioSink, PortAudioSource}) = s.stream.blocksize -Base.eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T -Base.close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf) -Base.isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf) -RingBuffers.getnotifyhandle(s::Union{PortAudioSink, PortAudioSource}) = getnotifyhandle(s.ringbuf) +eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T +close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf) +isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf) +RingBuffers.notifyhandle(s::Union{PortAudioSink, PortAudioSource}) = notifyhandle(s.ringbuf) bufpointer(s::Union{PortAudioSink, PortAudioSource}) = pointer(s.ringbuf) name(s::Union{PortAudioSink, PortAudioSource}) = s.name -function Base.show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} +function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} println(io, T, "(\"", stream.name, "\")") print(io, nchannels(stream), " channels") end -# function Base.flush(sink::PortAudioSink) -# while nwritable(sink.ringbuf) < length(sink.ringbuf) -# wait(sink.ringbuf) -# end -# end +flush(sink::PortAudioSink) = flush(sink.ringbuf) function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount) nwritten = 0 diff --git a/src/libportaudio.jl b/src/libportaudio.jl index 2ce996c..ffae4ea 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -240,11 +240,11 @@ function handle_status(err::PaError, show_warnings::Bool=true) if show_warnings msg = ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - warn("libportaudio: " * bytestring(msg)) + warn("libportaudio: " * unsafe_string(msg)) end elseif err != PA_NO_ERROR msg = ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - error("libportaudio: " * bytestring(msg)) + error("libportaudio: " * unsafe_string(msg)) end end diff --git a/test/runtests.jl b/test/runtests.jl index c208e42..e38f869 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,86 +1,193 @@ #!/usr/bin/env julia -if VERSION >= v"0.5.0-dev+7720" - using Base.Test -else - using BaseTestNext -end +using Base.Test +using TestSetExtensions using PortAudio using SampledSignals using RingBuffers -function test_callback(inchans, outchans) - nframes = Culong(8) +# pull in some extra stuff we need to test the callback directly +using PortAudio: notifyhandle, notifycb_c, shim_processcb_c +using PortAudio: pa_shim_errmsg_t, pa_shim_info_t +using PortAudio: PA_SHIM_ERRMSG_ERR_OVERFLOW, PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW - cb = PortAudio.pa_callbacks[Float32] - inbuf = rand(Float32, inchans*nframes) # simulate microphone input - sourcebuf = LockFreeRingBuffer(Float32, inchans*nframes*8) # the microphone input should end up here +"Setup buffers to test callback behavior" +function setup_callback(inchans, outchans, nframes, synced) + sourcebuf = RingBuffer{Float32}(inchans, nframes*2) # the microphone input should end up here + sinkbuf = RingBuffer{Float32}(outchans, nframes*2) # the callback should copy this to cb_output + errbuf = RingBuffer{pa_shim_errmsg_t}(1, 8) - outbuf = zeros(Float32, outchans*nframes) # this is where the output should go - sinkbuf = LockFreeRingBuffer(Float32, outchans*nframes*8) # the callback should copy this to outbuf - - # 2 input channels, 3 output channels - info = PortAudio.CallbackInfo(inchans, sourcebuf, outchans, sinkbuf, true) - - # handle any conversions here so they don't mess with the allocation - # the seemingly-redundant type specifiers avoid some allocation during the ccall. - # might be due to https://github.com/JuliaLang/julia/issues/15276 - inptr::Ptr{Float32} = Ptr{Float32}(pointer(inbuf)) - outptr::Ptr{Float32} = Ptr{Float32}(pointer(outbuf)) + # pass NULL for i/o we're not using + info = pa_shim_info_t( + inchans > 0 ? pointer(sourcebuf) : C_NULL, + outchans > 0 ? pointer(sinkbuf) : C_NULL, + pointer(errbuf), + synced, notifycb_c, + inchans > 0 ? notifyhandle(sourcebuf) : C_NULL, + outchans > 0 ? notifyhandle(sinkbuf) : C_NULL, + notifyhandle(errbuf) + ) flags = Culong(0) - infoptr::Ptr{PortAudio.CallbackInfo{Float32}} = Ptr{PortAudio.CallbackInfo{Float32}}(pointer_from_objref(info)) - testin = zeros(Float32, inchans*nframes) - testout = rand(Float32, outchans*nframes) - write(sinkbuf, testout) # fill the output ringbuffer - ret = ccall(cb, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}), - inptr, outptr, nframes, C_NULL, flags, infoptr) - @test ret === PortAudio.paContinue - @test outbuf == testout - read!(sourcebuf, testin) - @test inbuf == testin + cb_input = rand(Float32, inchans, nframes) # simulate microphone input + cb_output = rand(Float32, outchans, nframes) # this is where the output should go + function processfunc() + ccall(shim_processcb_c, Cint, + (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{Void}), + cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info)) + end + + (sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc) +end + +function test_callback(inchans, outchans, synced) + nframes = 8 + (sourcebuf, sinkbuf, errbuf, + cb_input, cb_output, process) = setup_callback(inchans, outchans, + nframes, synced) if outchans > 0 - underfill = 3 # should be less than nframes - testout = rand(Float32, outchans*underfill) - write(sinkbuf, testout) # underfill the output ringbuffer - # call again (partial underrun) - ret = ccall(cb, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}), - inptr, outptr, nframes, C_NULL, flags, infoptr) - @test ret === PortAudio.paContinue - @test outbuf[1:outchans*underfill] == testout - @test outbuf[outchans*underfill+1:outchans*nframes] == zeros(Float32, (nframes-underfill)*outchans) - @test nreadable(sourcebuf) == inchans*underfill - @test read!(sourcebuf, testin) == inchans*underfill - @test testin[1:inchans*underfill] == inbuf[1:inchans*underfill] - - # call again (total underrun) - ret = ccall(cb, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}), - inptr, outptr, nframes, C_NULL, flags, infoptr) - @test ret === PortAudio.paContinue - @test outbuf == zeros(Float32, outchans*nframes) - @test nreadable(sourcebuf) == 0 - + testout = rand(Float32, outchans, nframes) # generate some test data to play write(sinkbuf, testout) # fill the output ringbuffer - # test allocation - alloc = @allocated ccall(cb, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}), - inptr, outptr, nframes, C_NULL, flags, infoptr) - @test alloc == 0 - # now test allocation in underrun state - alloc = @allocated ccall(cb, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}), - inptr, outptr, nframes, C_NULL, flags, infoptr) - @test alloc == 0 + end + @test process() == PortAudio.paContinue + if outchans > 0 + # testout -> sinkbuf -> cb_output + @test cb_output == testout + end + if inchans > 0 + # cb_input -> sourcebuf + @test read(sourcebuf, nframes) == cb_input + end + @test framesreadable(errbuf) == 0 +end + +""" + test_callback_underflow(inchans, outchans; nframes=8, underfill=3, synced=false) + +Test that the callback works on underflow conditions. underfill is the numer of +frames we feed in, which should be less than nframes. +""" +function test_callback_underflow(inchans, outchans, synced) + nframes = 8 + underfill = 3 # must be less than nframes + (sourcebuf, sinkbuf, errbuf, + cb_input, cb_output, process) = setup_callback(inchans, outchans, + nframes, synced) + outchans > 0 || error("Can't test underflow with no output") + testout = rand(Float32, outchans, underfill) + write(sinkbuf, testout) # underfill the output ringbuffer + # call callback (partial underflow) + @test process() == PortAudio.paContinue + @test cb_output[:, 1:underfill] == testout + @test cb_output[:, (underfill+1):nframes] == zeros(Float32, outchans, (nframes-underfill)) + errs = readavailable(errbuf) + if inchans > 0 + received = readavailable(sourcebuf) + if synced + @test size(received, 2) == underfill + @test received == cb_input[:, 1:underfill] + @test length(errs) == 2 + @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) + else + @test size(received, 2) == nframes + @test received == cb_input + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW + end + else + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW + end + + # call again (total underflow) + @test process() == PortAudio.paContinue + @test cb_output == zeros(Float32, outchans, nframes) + errs = readavailable(errbuf) + if inchans > 0 + received = readavailable(sourcebuf) + if synced + @test size(received, 2) == 0 + @test length(errs) == 2 + @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) + else + @test size(received, 2) == nframes + @test received == cb_input + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW + end + else + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW + end +end + +function test_callback_overflow(inchans, outchans, synced) + nframes = 8 + (sourcebuf, sinkbuf, errbuf, + cb_input, cb_output, process) = setup_callback(inchans, outchans, + nframes, synced) + inchans > 0 || error("Can't test overflow with no input") + @test frameswritable(sinkbuf) == nframes*2 + + # the first time it should half-fill the input ring buffer + if outchans > 0 + testout = rand(Float32, outchans, nframes) + write(sinkbuf, testout) + end + @test framesreadable(sourcebuf) == 0 + outchans > 0 && @test frameswritable(sinkbuf) == nframes + @test process() == PortAudio.paContinue + @test framesreadable(errbuf) == 0 + @test framesreadable(sourcebuf) == nframes + outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 + + # now run the process func again to completely fill the input ring buffer + outchans > 0 && write(sinkbuf, testout) + @test framesreadable(sourcebuf) == nframes + outchans > 0 && @test frameswritable(sinkbuf) == nframes + @test process() == PortAudio.paContinue + @test framesreadable(errbuf) == 0 + @test framesreadable(sourcebuf) == nframes*2 + outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 + + # now this time the process func should overflow the input buffer + outchans > 0 && write(sinkbuf, testout) + @test framesreadable(sourcebuf) == nframes*2 + outchans > 0 && @test frameswritable(sinkbuf) == nframes + @test process() == PortAudio.paContinue + @test framesreadable(sourcebuf) == nframes*2 + errs = readavailable(errbuf) + if outchans > 0 + if synced + # if input and output are synced, thec callback didn't pull from + # the output ringbuf + @test frameswritable(sinkbuf) == nframes + @test cb_output == zeros(Float32, outchans, nframes) + @test length(errs) == 2 + @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) + else + @test frameswritable(sinkbuf) == nframes*2 + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_OVERFLOW + end + else + @test length(errs) == 1 + @test errs[1] == PA_SHIM_ERRMSG_OVERFLOW end end # these test are currently set up to run on OSX -@testset "PortAudio Tests" begin +@testset DottedTestSet "PortAudio Tests" begin + if is_windows() + default_indev = "Microphone Array (Realtek High " + default_outdev = "Speaker/Headphone (Realtek High" + elseif is_apple() + default_indev = "Built-in Microph" + default_outdev = "Built-in Output" + end + devs = PortAudio.devices() i = findfirst(d -> d.maxinchans > 0, devs) indev = i > 0 ? devs[i] : nothing @@ -92,24 +199,68 @@ end @testset "Reports version" begin io = IOBuffer() PortAudio.versioninfo(io) - result = takebuf_string(io) + result = split(String(take!((io))), "\n") # make sure this is the same version I tested with - @test result == - """PortAudio V19-devel (built Aug 6 2014 17:54:39) - Version Number: 1899 - """ + @test startswith(result[1], "PortAudio V19-devel") + @test result[2] == "Version: 1899" + @test result[3] == "Shim Source Hash: 4bfafb6888" end - @testset "PortAudio Callback works for duplex stream" begin - test_callback(2, 3) + @testset "Basic callback functionality" begin + @testset "basic duplex (no sync)" begin + test_callback(2, 3, false) + end + @testset "basic input-only (no sync)" begin + test_callback(2, 0, false) + end + @testset "basic output-only (no sync)" begin + test_callback(0, 2, false) + end + @testset "basic no input or output (no sync)" begin + test_callback(0, 0, false) + end + @testset "basic duplex (sync)" begin + test_callback(2, 3, true) + end + @testset "basic input-only (sync)" begin + test_callback(2, 0, true) + end + @testset "basic output-only (sync)" begin + test_callback(0, 2, true) + end + @testset "basic no input or output (sync)" begin + test_callback(0, 0, true) + end end - @testset "Callback works with input-only stream" begin - test_callback(2, 0) + @testset "Ouput underflow" begin + @testset "underflow duplex (nosync)" begin + test_callback_underflow(2, 3, false) + end + @testset "underflow output-only (nosync)" begin + test_callback_underflow(0, 3, false) + end + @testset "underflow duplex (sync)" begin + test_callback_underflow(2, 3, true) + end + @testset "underflow output-only (sync)" begin + test_callback_underflow(0, 3, true) + end end - @testset "Callback works with output-only stream" begin - test_callback(0, 2) + @testset "Input overflow" begin + @testset "overflow duplex (nosync)" begin + test_callback_overflow(2, 3, false) + end + @testset "overflow input-only (nosync)" begin + test_callback_overflow(2, 0, false) + end + @testset "overflow duplex (sync)" begin + test_callback_overflow(2, 3, true) + end + @testset "overflow input-only (sync)" begin + test_callback_overflow(2, 0, true) + end end @testset "Open Default Device" begin @@ -139,18 +290,18 @@ end close(stream) end @testset "Open Device by name" begin - stream = PortAudioStream("Built-in Microph", "Built-in Output") + stream = PortAudioStream(default_indev, default_outdev) buf = read(stream, 0.001s) @test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) write(stream, buf) io = IOBuffer() show(io, stream) - @test takebuf_string(io) == """ + @test String(take!(io)) == """ PortAudio.PortAudioStream{Float32} - Samplerate: 48000.0Hz + Samplerate: 44100.0Hz Buffer Size: 4096 frames - 2 channel sink: "Built-in Output" - 2 channel source: "Built-in Microph\"""" + 2 channel sink: "$default_outdev" + 2 channel source: "$default_indev\"""" close(stream) end @testset "Error on wrong name" begin From 85c34d39068d9e11de839073c3f7218b7b5b705d Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Mon, 15 May 2017 22:57:18 -0400 Subject: [PATCH 04/15] adds ps_shim lib build for OSX --- .gitignore | 1 - deps/src/Makefile | 54 +++++---- deps/src/pa_ringbuffer.h | 236 ------------------------------------- deps/src/pa_shim.c | 3 +- deps/usr/lib/pa_shim.dylib | Bin 0 -> 8972 bytes test/runtests.jl | 2 +- 6 files changed, 31 insertions(+), 265 deletions(-) delete mode 100644 deps/src/pa_ringbuffer.h create mode 100755 deps/usr/lib/pa_shim.dylib diff --git a/.gitignore b/.gitignore index 74e5532..d86d68c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ deps/deps.jl *.flac *.cov coverage -deps/usr diff --git a/deps/src/Makefile b/deps/src/Makefile index bae0abc..160c4bd 100644 --- a/deps/src/Makefile +++ b/deps/src/Makefile @@ -5,48 +5,50 @@ ifeq (exists, $(shell [ -e Make.user ] && echo exists )) include Make.user endif -TARGETDIR=../usr/lib +TARGETDIR = ../usr/lib OBJS = pa_shim.o -SOURCEHASH=$(shell sha256sum pa_shim.c | awk '{print $$1}') -#check-env: -#ifndef JULIA_INC -# $(error Environment variable JULIA_INC is not set.) -#endif - -#INC =-I"$(JULIA_INC)" -CFLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -DSOURCEHASH=\"$(SOURCEHASH)\" -LDFLAGS =-L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer -# LINUX_LIBS =-lrt -# LINUX_LDFLAGS =-rdynamic -# add the Homebrew.jl tree to the include dirs in case we used it for -# portaudio and libsndfile -# DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib -# DARWIN_INC =-I../../../Homebrew/deps/usr/include # Figure out OS and architecture -OS = $(shell uname) +OS=$(shell uname) ifneq (,$(findstring MINGW,$(OS))) OS=WINNT endif # file extensions and platform-specific libs ifeq ($(OS), WINNT) - LIBS += $(WINNT_LIBS) - LDFLAGS += $(WINNT_LDFLAGS) - INC += $(WINNT_INC) + LIBS += + LDFLAGS += -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer + INC += + SHACMD = sha256sum SHLIB_EXT = dll else ifeq ($(OS), Darwin) - INC += $(DARWIN_INC) - LDFLAGS += $(DARWIN_LDFLAGS) + LIBS += + INC += +# we'll rely on Julia to load RingBuffers.jl, which will in turn load the C +# library that we depend on for these symbols + LDFLAGS += -dynamiclib -Wl,-undefined,dynamic_lookup SHLIB_EXT = dylib + SHACMD = shasum -a256 else - LIBS += $(LINUX_LIBS) - LDFLAGS += $(LINUX_LDFLAGS) - INC += $(LINUX_INC) + LIBS += + INC += + LDFLAGS += -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer SHLIB_EXT = so + SHACMD = sha256sum endif -TARGET=$(TARGETDIR)/pa_shim.$(SHLIB_EXT) +SOURCEHASH = $(shell $(SHACMD) pa_shim.c | awk '{print $$1}') + +CFLAGS += -I../../../RingBuffers/deps/src -Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -DSOURCEHASH=\"$(SOURCEHASH)\" +LDFLAGS += +# LINUX_LIBS =-lrt +# LINUX_LDFLAGS =-rdynamic +# add the Homebrew.jl tree to the include dirs in case we used it for +# portaudio and libsndfile +# DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib +# DARWIN_INC =-I../../../Homebrew/deps/usr/include + +TARGET = $(TARGETDIR)/pa_shim.$(SHLIB_EXT) .PHONY: clean default diff --git a/deps/src/pa_ringbuffer.h b/deps/src/pa_ringbuffer.h deleted file mode 100644 index 9edba0d..0000000 --- a/deps/src/pa_ringbuffer.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef PA_RINGBUFFER_H -#define PA_RINGBUFFER_H -/* - * $Id$ - * Portable Audio I/O Library - * Ring Buffer utility. - * - * Author: Phil Burk, http://www.softsynth.com - * modified for SMP safety on OS X by Bjorn Roche. - * also allowed for const where possible. - * modified for multiple-byte-sized data elements by Sven Fischer - * - * Note that this is safe only for a single-thread reader - * and a single-thread writer. - * - * This program is distributed with the PortAudio Portable Audio Library. - * For more information see: http://www.portaudio.com - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * The text above constitutes the entire PortAudio license; however, - * the PortAudio community also makes the following non-binding requests: - * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the - * license above. - */ - -/** @file - @ingroup common_src - @brief Single-reader single-writer lock-free ring buffer - - PaUtilRingBuffer is a ring buffer used to transport samples between - different execution contexts (threads, OS callbacks, interrupt handlers) - without requiring the use of any locks. This only works when there is - a single reader and a single writer (ie. one thread or callback writes - to the ring buffer, another thread or callback reads from it). - - The PaUtilRingBuffer structure manages a ring buffer containing N - elements, where N must be a power of two. An element may be any size - (specified in bytes). - - The memory area used to store the buffer elements must be allocated by - the client prior to calling PaUtil_InitializeRingBuffer() and must outlive - the use of the ring buffer. - - @note The ring buffer functions are not normally exposed in the PortAudio libraries. - If you want to call them then you will need to add pa_ringbuffer.c to your application source code. -*/ - -#if defined(__APPLE__) -#include -typedef int32_t ring_buffer_size_t; -#elif defined( __GNUC__ ) -typedef long ring_buffer_size_t; -#elif (_MSC_VER >= 1400) -typedef long ring_buffer_size_t; -#elif defined(_MSC_VER) || defined(__BORLANDC__) -typedef long ring_buffer_size_t; -#else -typedef long ring_buffer_size_t; -#endif - - - -#ifdef __cplusplus -extern "C" -{ -#endif /* __cplusplus */ - -typedef struct PaUtilRingBuffer -{ - ring_buffer_size_t bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */ - volatile ring_buffer_size_t writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */ - volatile ring_buffer_size_t readIndex; /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */ - ring_buffer_size_t bigMask; /**< Used for wrapping indices with extra bit to distinguish full/empty. */ - ring_buffer_size_t smallMask; /**< Used for fitting indices to buffer. */ - ring_buffer_size_t elementSizeBytes; /**< Number of bytes per element. */ - char *buffer; /**< Pointer to the buffer containing the actual data. */ -}PaUtilRingBuffer; - -/** Initialize Ring Buffer to empty state ready to have elements written to it. - - @param rbuf The ring buffer. - - @param elementSizeBytes The size of a single data element in bytes. - - @param elementCount The number of elements in the buffer (must be a power of 2). - - @param dataPtr A pointer to a previously allocated area where the data - will be maintained. It must be elementCount*elementSizeBytes long. - - @return -1 if elementCount is not a power of 2, otherwise 0. -*/ -ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr ); - -/** Reset buffer to empty. Should only be called when buffer is NOT being read or written. - - @param rbuf The ring buffer. -*/ -void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ); - -/** Retrieve the number of elements available in the ring buffer for writing. - - @param rbuf The ring buffer. - - @return The number of elements available for writing. -*/ -ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf ); - -/** Retrieve the number of elements available in the ring buffer for reading. - - @param rbuf The ring buffer. - - @return The number of elements available for reading. -*/ -ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf ); - -/** Write data to the ring buffer. - - @param rbuf The ring buffer. - - @param data The address of new data to write to the buffer. - - @param elementCount The number of elements to be written. - - @return The number of elements written. -*/ -ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount ); - -/** Read data from the ring buffer. - - @param rbuf The ring buffer. - - @param data The address where the data should be stored. - - @param elementCount The number of elements to be read. - - @return The number of elements read. -*/ -ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount ); - -/** Get address of region(s) to which we can write data. - - @param rbuf The ring buffer. - - @param elementCount The number of elements desired. - - @param dataPtr1 The address where the first (or only) region pointer will be - stored. - - @param sizePtr1 The address where the first (or only) region length will be - stored. - - @param dataPtr2 The address where the second region pointer will be stored if - the first region is too small to satisfy elementCount. - - @param sizePtr2 The address where the second region length will be stored if - the first region is too small to satisfy elementCount. - - @return The room available to be written or elementCount, whichever is smaller. -*/ -ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, - void **dataPtr1, ring_buffer_size_t *sizePtr1, - void **dataPtr2, ring_buffer_size_t *sizePtr2 ); - -/** Advance the write index to the next location to be written. - - @param rbuf The ring buffer. - - @param elementCount The number of elements to advance. - - @return The new position. -*/ -ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); - -/** Get address of region(s) from which we can read data. - - @param rbuf The ring buffer. - - @param elementCount The number of elements desired. - - @param dataPtr1 The address where the first (or only) region pointer will be - stored. - - @param sizePtr1 The address where the first (or only) region length will be - stored. - - @param dataPtr2 The address where the second region pointer will be stored if - the first region is too small to satisfy elementCount. - - @param sizePtr2 The address where the second region length will be stored if - the first region is too small to satisfy elementCount. - - @return The number of elements available for reading. -*/ -ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, - void **dataPtr1, ring_buffer_size_t *sizePtr1, - void **dataPtr2, ring_buffer_size_t *sizePtr2 ); - -/** Advance the read index to the next location to be read. - - @param rbuf The ring buffer. - - @param elementCount The number of elements to advance. - - @return The new position. -*/ -ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* PA_RINGBUFFER_H */ diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c index 4cdecdc..6976290 100644 --- a/deps/src/pa_shim.c +++ b/deps/src/pa_shim.c @@ -1,7 +1,8 @@ #include -#include "pa_ringbuffer.h" +#include #include #include +#include #define MIN(x, y) ((x) < (y) ? (x) : (y)) diff --git a/deps/usr/lib/pa_shim.dylib b/deps/usr/lib/pa_shim.dylib new file mode 100755 index 0000000000000000000000000000000000000000..42b02735d85325fcd109e2a5c4fc7e2e320e6c61 GIT binary patch literal 8972 zcmeHNU1(fI6rN36-L}!)mVoiE-HMe~N}9h-Zd$FI&}(n$rq;A;3sQ#tx!YabB-{Pb ztXK_f`*2xP0^*xsDFq7(&0`gjtzB#drSU~u@u3LH4JhnGks#RhJF|25=cX+_`fvwk z&YYPu-#PQ$bII(T-1z&?yALzwTE!T92=x)v%c~hX!@TG*_BiTI7h{|sQI8!FL+wr7 ztT`riV6ru`(j@0OeI`e}dRc+jSs%aEAq}7-jAmKQW}6D(WpnvRwlQSK`}xuO)U(op zbZnzh+!BxWOr?&;6M8DGXM}FsuSb}%U|lc<-3nCjdUC#T}mLT`_6LE>o=fOI>Cg-w#4t0JE3-q9nYEi=~hypN>kO1VOn z3g=TPo-Is9rc*qf%Rs+3o&`r~Wv!+4#J}|nNay^-bW577!1GEVHyNXlZpX9cXrA*_ z_(Ab?OT1n(Mj^cwkM_G(%+wp=jX35!xc}e*b?<)bo$%G?%6_Vnl93UTo zEsU*)+yQ{wkkmhf+FB@7s;&IB&Qre%9GPluQ%^!8Kkt8c^XOyeU)=cNw$IicI{SK@ z-ak;vSm!!XpSN@xL!sARL8Y}6D`P+6wRf=w3)uVNLN=#Q_KdYM@f<7(S3x`!?{=xv zKAqw);4t7Y;4t7Y;4tuD211(gn^ye8s~M-3jr<+0bZS{Ez2`M6_+wmL?(odf!R4BI zhyFHy;XF+ahK(2E5hGt{c*^hUBOsjvw6q@_|@)QT1F@vHN~-`tuX zuUJX50XKsR1_QHqQyPvP3J}(JoqOWuRRI$VEbfxfR*V7FbbW4@KT*!BER$SVl;@4M`B}Rx9O~OQDLn9$}&nI^Lyc zbFT4hK#6!ZkcDk{Hni{Es_7|`zrMe)uj}To|I}(rP>CWSh(JHK)Y6O_z|~TyY@QQi z4JRMn(@C#zQm#9pBlA}xm&niC@B^3EuGCQAxr&Wxr4T}!aC&Gi#T%wSY4 z2e*G6L_FiAfO|X|=-ThOv}n?q5=767X#2{@v@|lUwv~S0)VA&t?F*vaBievy$IvpC zpk`?k^@#VSF6teo9?q*Qk)|Fh3(h@#3u5$eZR@v?^NF))mwrdciSyL^1-(H%+#go_ z{X>x(a1yoIw`xP+$5zU<(QN!);j|Z0}cZY0}cZY0}cZY0}cZY0}cZY0}cZY z0}ccK8wNIP>3)rZ*%07#1s>BgnKXO#q<21mYsZ+lWakmwJL6J()}${qR9y4yWcQu` zKNJq-k}1AV&rRT_dn_N1>zN5X92CJ(iJdl)8bl_zPotvQ6??_e}N`RpOyL! zsf&wF^!=!qz6&ABF|@7vJ2w3ToBnBC$C7&a+@@D-`u8@Sz6IfeRs3%TeNnXTx9Qsw zN`O9t-p|IzxJ6exOO4?sTijy!KP1ULO>F65+1L`p8n&kdlDQ=rT2i}_5i8Q0v3~*f Cx{HAT literal 0 HcmV?d00001 diff --git a/test/runtests.jl b/test/runtests.jl index e38f869..5cade49 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -203,7 +203,7 @@ end # make sure this is the same version I tested with @test startswith(result[1], "PortAudio V19-devel") @test result[2] == "Version: 1899" - @test result[3] == "Shim Source Hash: 4bfafb6888" + @test result[3] == "Shim Source Hash: 4ea2a8526b" end @testset "Basic callback functionality" begin From c53ac388e979655f4d8ecdb04156cdeadab5b8c6 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Mon, 15 May 2017 23:02:43 -0400 Subject: [PATCH 05/15] allow 0.6 prerelease --- REQUIRE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index f44162a..49a1d8f 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ -julia 0.6 +julia 0.6- BinDeps SampledSignals 0.3.0 RingBuffers 0.1.0 From c58143404fd9c3890762f8f9dcf221a0ea23376d Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 16 May 2017 23:26:02 -0400 Subject: [PATCH 06/15] some CI and REQUIRE tweaks --- .travis.yml | 3 +-- REQUIRE | 2 +- appveyor.yml | 5 +---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc66025..ca62952 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,7 @@ os: - osx sudo: required julia: - - 0.4 - - 0.5 + - 0.6 notifications: email: false script: diff --git a/REQUIRE b/REQUIRE index 49a1d8f..297f03f 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,7 +1,7 @@ julia 0.6- BinDeps SampledSignals 0.3.0 -RingBuffers 0.1.0 +RingBuffers 1.0.0 Suppressor @osx Homebrew @windows WinRPM diff --git a/appveyor.yml b/appveyor.yml index 85a3bdc..7a1873b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,6 @@ environment: matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" - - JULIAVERSION: "julialang/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" + - JULIAVERSION: "julialang/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" notifications: - provider: Email From acaa305dfaa75fb95ddf614ebb5c05ecb2387fb6 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 17 May 2017 00:32:19 -0400 Subject: [PATCH 07/15] adds linux build of pa_shim, removes Suppressor dependency --- .gitignore | 1 - LICENSE | 6 ++++++ REQUIRE | 1 - deps/src/Makefile | 4 ++-- deps/usr/lib/pa_shim.so | Bin 0 -> 8536 bytes src/PortAudio.jl | 3 ++- src/pa_shim.jl | 7 +++---- src/suppressor.jl | 20 ++++++++++++++++++++ test/runtests.jl | 3 +-- 9 files changed, 34 insertions(+), 11 deletions(-) create mode 100755 deps/usr/lib/pa_shim.so create mode 100644 src/suppressor.jl diff --git a/.gitignore b/.gitignore index d86d68c..d7bc517 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.swp -*.so *.o deps/deps.jl *.wav diff --git a/LICENSE b/LICENSE index 0f797a9..04c37c2 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +suppressor.jl includes code from the Suppressor.jl package, licensed under the +MIT "Expat" License: + +Copyright (c) 2016: Ismael Venegas Castelló. diff --git a/REQUIRE b/REQUIRE index 297f03f..3a091fe 100644 --- a/REQUIRE +++ b/REQUIRE @@ -2,6 +2,5 @@ julia 0.6- BinDeps SampledSignals 0.3.0 RingBuffers 1.0.0 -Suppressor @osx Homebrew @windows WinRPM diff --git a/deps/src/Makefile b/deps/src/Makefile index 160c4bd..7c87b0a 100644 --- a/deps/src/Makefile +++ b/deps/src/Makefile @@ -17,7 +17,7 @@ endif # file extensions and platform-specific libs ifeq ($(OS), WINNT) LIBS += - LDFLAGS += -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer + LDFLAGS += -shared -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer INC += SHACMD = sha256sum SHLIB_EXT = dll @@ -32,7 +32,7 @@ else ifeq ($(OS), Darwin) else LIBS += INC += - LDFLAGS += -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer + LDFLAGS += -shared SHLIB_EXT = so SHACMD = sha256sum endif diff --git a/deps/usr/lib/pa_shim.so b/deps/usr/lib/pa_shim.so new file mode 100755 index 0000000000000000000000000000000000000000..228a44d1f3a98412d8a8a3c3238139445d4e1b11 GIT binary patch literal 8536 zcmd^EZE##w89tkBnv|y5w1C7GWyMws!pj z0OLNo#rG_OZ$bm9J>Ej?4tqiHAqf2ZKLME^ZU7JC@s=|H{Ni>34||otC-7YDgZ~!T zS|7Yaw(|;cpebA-ErMGou*g=~a8^loOZ{eX(4+rTrGHfEuUG5hy9&=LoaU8ExANnS zOJlPRK@ZePHRtC85?>=8UhP13Mc0S3xiP(98F@?Bg`OG9SVB*O2z~edem!O8&EZVJ zGV}ZUdq;C)=6+*n)U;*(5D5~!t&w(TnAJprr&x*&bXG3A>kM=UB8Oncg%&mUlq-Tt5;5X(U0s-w{88em ziwn<7K2JP#ZQ%*Y-$6W0$-=bchlr=HEF6>kKkC9FJq=zKZL0egC}>l)L7K<>@tigpU(_aJ zU{B58d9hr^NCfV!I~6}i6W|D^`p>;cGo+P%Mv4!RqHP$a(Za|{FJWzR0NxUvTItv2 z+3D9#%3xEqo8fHGW+vW_A@aOdDxTI#19Mubf3~j_f3>gFKLaN-S}A@O6iv|9pwIc*=aZJ~Q={;L3oWc<9AoCQ9ob)C>c zJ5K;p5$dV!*EkGpbZN6lz46$fThuBU<>QVC>_1G*WULw0OJUa)KOfo?KVOQI6DO23 zyWMGubTrwIQINygg{aCUby^N5R4@XQ1E7h`ayZHQQSGMqTwyIKzl@=f{e1JhV@(|j zeeUZJI6WXgcrW`;t5`2BY5T^Cs#uFE)}V}K&;6;jx^RNslLo!kI#N&p=!$uf;UCc0@^%M@0ZJcpnn9lK+mChUj#KU zIo|*s1N{joofoMDjwVFlNK;@#LtXte#%CiiItwMrw)Hn% z*S7I$g2{ddo)PG|zZYctDLe-eSDuBoH#Xg0)4OJ6%?KjczVC+oS;*xXhvk|S%h-=Y z{$*`@p9lE+&qF~=n+lyRs5IEbIUn+QlFx-7RB}FVY*0MUrE_gm z=MI`zDlGro#d41Lvnn2+C88?`weNaFJ81((;j_c-N}taR&9+$HZ)8<{mz9}#TFH66 z@;Ceng+IU2t}s5Q8p7?IQ+lfu<#^a|W&2-H`uLQ~Rp8xpUccglw#OH;KD z|04CP^C1@{UcDYJL&w69c}ogAh8Oo$|m4` z`rD*_gYf2;27q5(AaCO^;9vN9fygFlPRtWRdtw_P28E--XGWFdv;JVOg5P7sX|U4F~(Aq{n~Ncp6&g+ zd!d0(q8`_D+f3V$0E9#tJJ{8?w`Y5wzIW%&TjTrn{o8x`;&A&f@@jr5vC0&c%02lh zdU9uW7SB#z#bWN$(ez`w873XOV4Tx_tkwaDH z`SgkuqsNy_VA^@VN{NUR#I&FpUlYcSTQnG%#5aP$fShiWL7bf8XtqD zkkrcClEY@ckjafzFgj#;bJQS3RXa3liHK}b1l7oJ4ygK1X{#$M_GG5Ofq`d%iy~UBrG!h2m0`?fG0E z14h2tp3jj?`ThYFS(H0wg@zj41=ya?n@q1$n%sWYV>$pi-7T2sb1BoXviG)M!6JnU z$=IIHuMSm;9N*jjF=gMQ^!S|1^l6tpzS33WcLW%XiMm!f=VAUo1K+!p8`)N_6D{Cz z+w-}Z=`m7r75?@!{VZf|dp=(?<^Hk@FaHIPz0*OZ_%IKga=>xf-;~Fm&+AP29?AMN zSMKs9Fw}mYKR(wp&A63O_qP8rkNtB0w`4co_|JIk`JTY^`)sJFH~zPQQ(m2iiSHBq ze@(AG^GtsL+mOrNxp$CL6xOr3C7$sgLqOxl@%esI|8MNs-z+M$cG;fqG5mk$8FG%o zSQg9PCcKgBoE`2eB(`@8pG)obBC|*S_1u5f#5#Z7lSixd9KipZ9Ni izqsvg`)S&&P$oUjIj$|W&!&F%8`RbEJyulIYyVHmTFGeu literal 0 HcmV?d00001 diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 46d3daf..61841e6 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -4,7 +4,7 @@ module PortAudio using SampledSignals using RingBuffers -using Suppressor +#using Suppressor using Base: AsyncCondition @@ -14,6 +14,7 @@ import Base: read, read!, write, flush # Get binary dependencies loaded from BinDeps include("../deps/deps.jl") +include("suppressor.jl") include("pa_shim.jl") include("libportaudio.jl") diff --git a/src/pa_shim.jl b/src/pa_shim.jl index 040b896..acd81fa 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -1,8 +1,7 @@ -const libpa_shim = Libdl.find_library( - ["pa_shim"], - [joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")]) - function init_pa_shim() + global const libpa_shim = Libdl.find_library( + ["pa_shim"], + [joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")]) shim_dlib = Libdl.dlopen(libpa_shim) # pointer to the shim's process callback global const shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) diff --git a/src/suppressor.jl b/src/suppressor.jl new file mode 100644 index 0000000..ab297bc --- /dev/null +++ b/src/suppressor.jl @@ -0,0 +1,20 @@ +# while waiting for this PR to get merged: https://github.com/Ismael-VC/Suppressor.jl/pull/12 +# we'll just include the relevant code here + +macro suppress_err(block) + quote + if ccall(:jl_generating_output, Cint, ()) == 0 + ORIGINAL_STDERR = STDERR + err_rd, err_wr = redirect_stderr() + err_reader = @async readstring(err_rd) + end + + value = $(esc(block)) + + if ccall(:jl_generating_output, Cint, ()) == 0 + redirect_stderr(ORIGINAL_STDERR) + close(err_wr) + end + value + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 5cade49..f6df19d 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -201,8 +201,7 @@ end PortAudio.versioninfo(io) result = split(String(take!((io))), "\n") # make sure this is the same version I tested with - @test startswith(result[1], "PortAudio V19-devel") - @test result[2] == "Version: 1899" + @test startswith(result[1], "PortAudio V19") @test result[3] == "Shim Source Hash: 4ea2a8526b" end From 3e7c6b5c1b76e70b41e5468f7c67d1b9a6318c1f Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 17 May 2017 00:34:40 -0400 Subject: [PATCH 08/15] fixes test opening default devices --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index f6df19d..0c26a30 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -186,6 +186,9 @@ end elseif is_apple() default_indev = "Built-in Microph" default_outdev = "Built-in Output" + elseif is_linux() + default_indev = "default" + default_outdev = "default" end devs = PortAudio.devices() From efd70272ab7a253a9187c8ba3c6409004f1343b4 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 18 May 2017 12:38:13 -0400 Subject: [PATCH 09/15] adds cross-compiled multiplatform builds and infrastructure to load them --- .gitignore | 3 + deps/src/Makefile | 90 +- deps/src/build.sh | 54 + deps/src/dockerbuild_cb.sh | 8 + deps/src/dockerbuild_hbb.sh | 13 + deps/src/pa_shim.c | 2 +- deps/src/portaudio.h | 1225 +++++++++++++++++ deps/usr/lib/pa_shim.so | Bin 8536 -> 0 bytes deps/usr/lib/pa_shim_arm-linux-gnueabihf.so | Bin 0 -> 6376 bytes deps/usr/lib/pa_shim_i686-linux-gnu.so | Bin 0 -> 6373 bytes deps/usr/lib/pa_shim_i686-w64-mingw32.dll | Bin 0 -> 81524 bytes deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so | Bin 0 -> 9320 bytes ...ib => pa_shim_x86_64-apple-darwin14.dylib} | Bin 8972 -> 8972 bytes deps/usr/lib/pa_shim_x86_64-linux-gnu.so | Bin 0 -> 8340 bytes deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll | Bin 0 -> 110424 bytes src/pa_shim.jl | 27 +- test/runtests.jl | 10 +- 17 files changed, 1385 insertions(+), 47 deletions(-) create mode 100755 deps/src/build.sh create mode 100755 deps/src/dockerbuild_cb.sh create mode 100755 deps/src/dockerbuild_hbb.sh create mode 100644 deps/src/portaudio.h delete mode 100755 deps/usr/lib/pa_shim.so create mode 100755 deps/usr/lib/pa_shim_arm-linux-gnueabihf.so create mode 100755 deps/usr/lib/pa_shim_i686-linux-gnu.so create mode 100755 deps/usr/lib/pa_shim_i686-w64-mingw32.dll create mode 100755 deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so rename deps/usr/lib/{pa_shim.dylib => pa_shim_x86_64-apple-darwin14.dylib} (63%) create mode 100755 deps/usr/lib/pa_shim_x86_64-linux-gnu.so create mode 100755 deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll diff --git a/.gitignore b/.gitignore index d7bc517..dd2812d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ deps/deps.jl *.flac *.cov coverage +deps/usr/lib/pa_shim.so +deps/usr/lib/pa_shim.dylib +deps/usr/lib/pa_shim.dll diff --git a/deps/src/Makefile b/deps/src/Makefile index 7c87b0a..c711226 100644 --- a/deps/src/Makefile +++ b/deps/src/Makefile @@ -1,68 +1,80 @@ -# Makefile lifted from Clang.jl +# Makefile originally lifted from Clang.jl # Copyright (c) 2012-: Isaiah Norton and [contributors](https://github.com/ihnorton/Clang.jl/graphs/contributors) ifeq (exists, $(shell [ -e Make.user ] && echo exists )) include Make.user endif -TARGETDIR = ../usr/lib +TARGETDIR=../usr/lib +TARGETBASENAME=pa_shim OBJS = pa_shim.o -# Figure out OS and architecture -OS=$(shell uname) -ifneq (,$(findstring MINGW,$(OS))) - OS=WINNT +# check to see if the user passed in a HOST variable for cross-compiling +ifeq ($(HOST),) + # Figure out OS and architecture + OS=$(shell uname) + ifneq ($(findstring MINGW,$(OS)),) + OS=WINNT + endif +else + HOSTSUFFIX=_$(HOST) + ifneq ($(findstring linux,$(HOST)),) + OS=Linux + else ifneq ($(findstring darwin,$(HOST)),) + OS=Darwin + else ifneq ($(findstring mingw,$(HOST)),) + OS=WINNT + endif endif -# file extensions and platform-specific libs +CFLAGS = -Wall -Wno-strict-aliasing -fno-omit-frame-pointer -I../../../RingBuffers/deps/src +LDFLAGS += + ifeq ($(OS), WINNT) - LIBS += - LDFLAGS += -shared -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer - INC += - SHACMD = sha256sum - SHLIB_EXT = dll + LIBS += + LDFLAGS += -shared -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer$(HOSTSUFFIX) + INC += + SHACMD = sha256sum + SHLIB_EXT = dll else ifeq ($(OS), Darwin) - LIBS += - INC += -# we'll rely on Julia to load RingBuffers.jl, which will in turn load the C -# library that we depend on for these symbols - LDFLAGS += -dynamiclib -Wl,-undefined,dynamic_lookup - SHLIB_EXT = dylib - SHACMD = shasum -a256 + LIBS += + INC += + # we'll rely on Julia to load RingBuffers.jl, which will in turn load the C + # library that we depend on for these symbols + LDFLAGS += -dynamiclib -Wl,-undefined,dynamic_lookup + SHLIB_EXT = dylib + SHACMD = shasum -a256 else - LIBS += - INC += - LDFLAGS += -shared - SHLIB_EXT = so - SHACMD = sha256sum + CFLAGS += -fPIC + LIBS += + INC += + LDFLAGS += -shared + SHLIB_EXT = so + SHACMD = sha256sum endif SOURCEHASH = $(shell $(SHACMD) pa_shim.c | awk '{print $$1}') +CFLAGS += -DSOURCEHASH=\"$(SOURCEHASH)\" -CFLAGS += -I../../../RingBuffers/deps/src -Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -DSOURCEHASH=\"$(SOURCEHASH)\" -LDFLAGS += -# LINUX_LIBS =-lrt -# LINUX_LDFLAGS =-rdynamic -# add the Homebrew.jl tree to the include dirs in case we used it for -# portaudio and libsndfile -# DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib -# DARWIN_INC =-I../../../Homebrew/deps/usr/include +TARGET=$(TARGETDIR)/$(TARGETBASENAME)$(HOSTSUFFIX).$(SHLIB_EXT) -TARGET = $(TARGETDIR)/pa_shim.$(SHLIB_EXT) - -.PHONY: clean default +.PHONY: clean cleantemp default default: $(TARGET) %.o: %.c Makefile - $(CC) $< -fPIC -c -o $@ $(INC) $(CFLAGS) + $(CC) $< -c -o $@ $(INC) $(CFLAGS) $(TARGETDIR): mkdir -p $@ $(TARGET): $(OBJS) $(TARGETDIR) Makefile - $(CC) $(OBJS) -shared -o $@ $(LDFLAGS) $(LIBS) + $(CC) $(OBJS) $(LDFLAGS) -o $@ $(LIBS) -clean: +cleantemp: rm -f $(OBJS) - rm -f $(TARGET) + +clean: cleantemp + rm -f $(TARGETDIR)/$(TARGETBASENAME)*.so + rm -f $(TARGETDIR)/$(TARGETBASENAME)*.dylib + rm -f $(TARGETDIR)/$(TARGETBASENAME)*.dll diff --git a/deps/src/build.sh b/deps/src/build.sh new file mode 100755 index 0000000..4e210e1 --- /dev/null +++ b/deps/src/build.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# User docker to build pa_shim library for all supported platforms. + +set -e + +make clean + +echo "" +# NOTE: the darwin build ends up actually being x86_64-apple-darwin14. It gets +# mapped within the docker machine +for platform in \ + arm-linux-gnueabihf \ + powerpc64le-linux-gnu \ + x86_64-apple-darwin \ + x86_64-w64-mingw32 \ + i686-w64-mingw32; do + echo "================================" + echo "building for $platform..." + docker run --rm \ + -v $(pwd)/../..:/workdir \ + -v $(pwd)/../../../RingBuffers:/RingBuffers \ + -w /workdir/deps/src \ + -e CROSS_TRIPLE=$platform \ + multiarch/crossbuild \ + ./dockerbuild_cb.sh + echo "================================" + echo "" +done + +# we use holy-build-box for the x86 linux builds because it uses an older +# glibc so it should be compatible with more user environments +echo "================================" +echo "building for x86_64-linux-gnu..." +docker run --rm \ + -v $(pwd)/../..:/workdir \ + -v $(pwd)/../../../RingBuffers:/RingBuffers \ + -w /workdir/deps/src \ + -e HOST=x86_64-linux-gnu \ + phusion/holy-build-box-64 \ + ./dockerbuild_hbb.sh +echo "================================" +echo "" + +echo "================================" +echo "building for i686-linux-gnu..." +docker run --rm \ + -v $(pwd)/../..:/workdir \ + -v $(pwd)/../../../RingBuffers:/RingBuffers \ + -w /workdir/deps/src \ + -e HOST=i686-linux-gnu \ + phusion/holy-build-box-32 \ + ./dockerbuild_hbb.sh +echo "================================" diff --git a/deps/src/dockerbuild_cb.sh b/deps/src/dockerbuild_cb.sh new file mode 100755 index 0000000..7b04f2b --- /dev/null +++ b/deps/src/dockerbuild_cb.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# this script is run by build.sh within each docker instance to do the build. + +set -e + +make HOST=$CROSS_TRIPLE +make cleantemp diff --git a/deps/src/dockerbuild_hbb.sh b/deps/src/dockerbuild_hbb.sh new file mode 100755 index 0000000..090611a --- /dev/null +++ b/deps/src/dockerbuild_hbb.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# this script is run by build.sh within each docker instance to do the build. +# it's meant to be run from within a "Holy Build Box" docker instance. + +set -e + +# Activate Holy Build Box environment. +source /hbb_exe/activate + +set -x +make HOST=$HOST +make cleantemp diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c index 6976290..c6d67cc 100644 --- a/deps/src/pa_shim.c +++ b/deps/src/pa_shim.c @@ -1,4 +1,4 @@ -#include +#include "portaudio.h" #include #include #include diff --git a/deps/src/portaudio.h b/deps/src/portaudio.h new file mode 100644 index 0000000..8a94aaf --- /dev/null +++ b/deps/src/portaudio.h @@ -0,0 +1,1225 @@ +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup public_header + @brief The portable PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** Retrieve the release number of the currently running PortAudio build. + For example, for version "19.5.1" this will return 0x00130501. + + @see paMakeVersionNumber +*/ +int Pa_GetVersion( void ); + +/** Retrieve a textual description of the current PortAudio build, + e.g. "PortAudio V19.5.0-devel, revision 1952M". + The format of the text may change in the future. Do not try to parse the + returned string. + + @deprecated As of 19.5.0, use Pa_GetVersionInfo()->versionText instead. +*/ +const char* Pa_GetVersionText( void ); + +/** + Generate a packed integer version number in the same format used + by Pa_GetVersion(). Use this to compare a specified version number with + the currently running version. For example: + + @code + if( Pa_GetVersion() < paMakeVersionNumber(19,5,1) ) {} + @endcode + + @see Pa_GetVersion, Pa_GetVersionInfo + @version Available as of 19.5.0. +*/ +#define paMakeVersionNumber(major, minor, subminor) \ + (((major)&0xFF)<<16 | ((minor)&0xFF)<<8 | ((subminor)&0xFF)) + + +/** + A structure containing PortAudio API version information. + @see Pa_GetVersionInfo, paMakeVersionNumber + @version Available as of 19.5.0. +*/ +typedef struct PaVersionInfo { + int versionMajor; + int versionMinor; + int versionSubMinor; + /** + This is currently the Git revision hash but may change in the future. + The versionControlRevision is updated by running a script before compiling the library. + If the update does not occur, this value may refer to an earlier revision. + */ + const char *versionControlRevision; + /** Version as a string, for example "PortAudio V19.5.0-devel, revision 1952M" */ + const char *versionText; +} PaVersionInfo; + +/** Retrieve version information for the currently running PortAudio build. + @return A pointer to an immutable PaVersionInfo structure. + + @note This function can be called at any time. It does not require PortAudio + to be initialized. The structure pointed to is statically allocated. Do not + attempt to free it or modify it. + + @see PaVersionInfo, paMakeVersionNumber + @version Available as of 19.5.0. +*/ +const PaVersionInfo* Pa_GetVersionInfo(); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, + paCanNotWriteToACallbackStream, + paCanNotReadFromAnOutputOnlyStream, + paCanNotWriteToAnInputOnlyStream, + paIncompatibleStreamHostApi, + paBadBufferPtr +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initializes internal data structures and prepares underlying + host APIs for use. With the exception of Pa_GetVersion(), Pa_GetVersionText(), + and Pa_GetErrorText(), this function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initialized by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_GetDeviceCount()-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_GetDeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12, + paWASAPI=13, + paAudioScienceHPI=14 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asynchronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constraining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the default host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds. PaTime is + used for the fields of the PaStreamCallbackTimeInfo argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + PaTime values have unspecified origin. + + @see PaStreamCallback, PaStreamCallbackTimeInfo, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that audio data is passed as an array + of pointers to separate buffers, one buffer for each channel. Usually, + when this flag is not used, audio data is passed as a single buffer with + all channels interleaved. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000) /**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */ + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /** Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /** Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + should round the suggestedLatency up to the next practical value - ie to + provide an equal or higher latency than suggestedLatency wherever possible. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. + + Time values are expressed in seconds and are synchronised with the time base used by Pa_GetStreamTime() for the associated stream. + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; /**< The time when the first sample of the input buffer was captured at the ADC input */ + PaTime currentTime; /**< The time when the stream callback was invoked */ + PaTime outputBufferDacTime; /**< The time when the first sample of the output buffer will output the DAC */ +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, /**< Signal that the stream should continue invoking the callback and processing audio. */ + paComplete=1, /**< Signal that the stream should stop invoking the callback and finish once all output samples have played. */ + paAbort=2 /**< Signal that the stream should stop invoking the callback and finish as soon as possible. */ +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + When a stream is running, PortAudio calls the stream callback periodically. + The callback function is responsible for processing buffers of audio samples + passed via the input and output parameters. + + The PortAudio stream callback runs at very high or real-time priority. + It is required to consistently meet its time deadlines. Do not allocate + memory, access the file system, call library functions or call other functions + from the stream callback that may block or take an unpredictable amount of + time to complete. + + In order for a stream to maintain glitch-free operation the callback + must consume and return audio data faster than it is recorded and/or + played. PortAudio anticipates that each callback invocation may execute for + a duration approaching the duration of frameCount audio frames at the stream + sample rate. It is reasonable to expect to be able to utilise 70% or more of + the available CPU time in the PortAudio callback. However, due to buffer size + adaption and other factors, not all host APIs are able to guarantee audio + stability under heavy CPU load with arbitrary fixed callback buffer sizes. + When high callback CPU utilisation is required the most robust behavior + can be achieved by using paFramesPerBufferUnspecified as the + Pa_OpenStream() framesPerBuffer parameter. + + @param input and @param output are either arrays of interleaved samples or; + if non-interleaved samples were requested using the paNonInterleaved sample + format flag, an array of buffer pointers, one non-interleaved buffer for + each channel. + + The format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo Timestamps indicating the ADC capture time of the first sample + in the input buffer, the DAC output time of the first sample in the output buffer + and the time the callback was invoked. + See PaStreamCallbackTimeInfo and Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + ::PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilize this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissible to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will receive an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behavior of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output device. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream() is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter is invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Returns the current time in seconds for a stream according to the same clock used + to generate callback PaStreamCallbackTimeInfo timestamps. The time values are + monotonically increasing and have unspecified origin. + + Pa_GetStreamTime returns valid time values for the entire life of the stream, + from when the stream is opened until it is closed. Starting and stopping the stream + does not affect the passage of time returned by Pa_GetStreamTime. + + This time may be used for synchronizing other events to the audio stream, for + example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback, PaStreamCallbackTimeInfo +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been written - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/deps/usr/lib/pa_shim.so b/deps/usr/lib/pa_shim.so deleted file mode 100755 index 228a44d1f3a98412d8a8a3c3238139445d4e1b11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8536 zcmd^EZE##w89tkBnv|y5w1C7GWyMws!pj z0OLNo#rG_OZ$bm9J>Ej?4tqiHAqf2ZKLME^ZU7JC@s=|H{Ni>34||otC-7YDgZ~!T zS|7Yaw(|;cpebA-ErMGou*g=~a8^loOZ{eX(4+rTrGHfEuUG5hy9&=LoaU8ExANnS zOJlPRK@ZePHRtC85?>=8UhP13Mc0S3xiP(98F@?Bg`OG9SVB*O2z~edem!O8&EZVJ zGV}ZUdq;C)=6+*n)U;*(5D5~!t&w(TnAJprr&x*&bXG3A>kM=UB8Oncg%&mUlq-Tt5;5X(U0s-w{88em ziwn<7K2JP#ZQ%*Y-$6W0$-=bchlr=HEF6>kKkC9FJq=zKZL0egC}>l)L7K<>@tigpU(_aJ zU{B58d9hr^NCfV!I~6}i6W|D^`p>;cGo+P%Mv4!RqHP$a(Za|{FJWzR0NxUvTItv2 z+3D9#%3xEqo8fHGW+vW_A@aOdDxTI#19Mubf3~j_f3>gFKLaN-S}A@O6iv|9pwIc*=aZJ~Q={;L3oWc<9AoCQ9ob)C>c zJ5K;p5$dV!*EkGpbZN6lz46$fThuBU<>QVC>_1G*WULw0OJUa)KOfo?KVOQI6DO23 zyWMGubTrwIQINygg{aCUby^N5R4@XQ1E7h`ayZHQQSGMqTwyIKzl@=f{e1JhV@(|j zeeUZJI6WXgcrW`;t5`2BY5T^Cs#uFE)}V}K&;6;jx^RNslLo!kI#N&p=!$uf;UCc0@^%M@0ZJcpnn9lK+mChUj#KU zIo|*s1N{joofoMDjwVFlNK;@#LtXte#%CiiItwMrw)Hn% z*S7I$g2{ddo)PG|zZYctDLe-eSDuBoH#Xg0)4OJ6%?KjczVC+oS;*xXhvk|S%h-=Y z{$*`@p9lE+&qF~=n+lyRs5IEbIUn+QlFx-7RB}FVY*0MUrE_gm z=MI`zDlGro#d41Lvnn2+C88?`weNaFJ81((;j_c-N}taR&9+$HZ)8<{mz9}#TFH66 z@;Ceng+IU2t}s5Q8p7?IQ+lfu<#^a|W&2-H`uLQ~Rp8xpUccglw#OH;KD z|04CP^C1@{UcDYJL&w69c}ogAh8Oo$|m4` z`rD*_gYf2;27q5(AaCO^;9vN9fygFlPRtWRdtw_P28E--XGWFdv;JVOg5P7sX|U4F~(Aq{n~Ncp6&g+ zd!d0(q8`_D+f3V$0E9#tJJ{8?w`Y5wzIW%&TjTrn{o8x`;&A&f@@jr5vC0&c%02lh zdU9uW7SB#z#bWN$(ez`w873XOV4Tx_tkwaDH z`SgkuqsNy_VA^@VN{NUR#I&FpUlYcSTQnG%#5aP$fShiWL7bf8XtqD zkkrcClEY@ckjafzFgj#;bJQS3RXa3liHK}b1l7oJ4ygK1X{#$M_GG5Ofq`d%iy~UBrG!h2m0`?fG0E z14h2tp3jj?`ThYFS(H0wg@zj41=ya?n@q1$n%sWYV>$pi-7T2sb1BoXviG)M!6JnU z$=IIHuMSm;9N*jjF=gMQ^!S|1^l6tpzS33WcLW%XiMm!f=VAUo1K+!p8`)N_6D{Cz z+w-}Z=`m7r75?@!{VZf|dp=(?<^Hk@FaHIPz0*OZ_%IKga=>xf-;~Fm&+AP29?AMN zSMKs9Fw}mYKR(wp&A63O_qP8rkNtB0w`4co_|JIk`JTY^`)sJFH~zPQQ(m2iiSHBq ze@(AG^GtsL+mOrNxp$CL6xOr3C7$sgLqOxl@%esI|8MNs-z+M$cG;fqG5mk$8FG%o zSQg9PCcKgBoE`2eB(`@8pG)obBC|*S_1u5f#5#Z7lSixd9KipZ9Ni izqsvg`)S&&P$oUjIj$|W&!&F%8`RbEJyulIYyVHmTFGeu diff --git a/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so b/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so new file mode 100755 index 0000000000000000000000000000000000000000..fc997729717e619fcb4fce3deabb7ee690b9b04a GIT binary patch literal 6376 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 literal 0 HcmV?d00001 diff --git a/deps/usr/lib/pa_shim_i686-w64-mingw32.dll b/deps/usr/lib/pa_shim_i686-w64-mingw32.dll new file mode 100755 index 0000000000000000000000000000000000000000..956032baf7284bb152631a84c6d37aaafd2140c7 GIT binary patch literal 81524 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 delta 893 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 C&;dFC literal 0 HcmV?d00001 diff --git a/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll b/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll new file mode 100755 index 0000000000000000000000000000000000000000..1a39c4434e26670c415dba8639f3b4cec8b4eb5f GIT binary patch literal 110424 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 B`&j@0 literal 0 HcmV?d00001 diff --git a/src/pa_shim.jl b/src/pa_shim.jl index acd81fa..c1100be 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -1,7 +1,28 @@ function init_pa_shim() - global const libpa_shim = Libdl.find_library( - ["pa_shim"], - [joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")]) + libdir = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib") + libsuffix = "" + basename = "pa_shim" + @static if is_linux() && Sys.ARCH == :x86_64 + libsuffix = "x86_64-linux-gnu" + elseif is_linux() && Sys.ARCH == :i686 + libsuffix = "i686-linux-gnu" + elseif is_apple() && Sys.ARCH == :x86_64 + libsuffix = "x86_64-apple-darwin14" + elseif is_windows() && Sys.ARCH == :x86_64 + libsuffix = "x86_64-w64-mingw32" + elseif is_windows() && Sys.ARCH == :i686 + libsuffix = "i686-w64-mingw32" + elseif !any( + (sfx) -> isfile(joinpath(libdir, "$basename.$sfx")), + ("so", "dll", "dylib")) + error("Unsupported platform $(Sys.MACHINE). You can build your own library by running `make` from $(joinpath(@__FILE__, "..", "deps", "src"))") + end + # if there's a suffix-less library, it was built natively on this machine, + # so load that one first, otherwise load the pre-built one + global const libpa_shim = Base.Libdl.find_library( + [basename, "$(basename)_$libsuffix"], + [libdir]) + libpa_shim == "" && error("Could not load $basename library, please file an issue at https://github.com/JuliaAudio/RingBuffers.jl/issues with your `versioninfo()` output") shim_dlib = Libdl.dlopen(libpa_shim) # pointer to the shim's process callback global const shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) diff --git a/test/runtests.jl b/test/runtests.jl index 0c26a30..2a40ac0 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -177,9 +177,8 @@ function test_callback_overflow(inchans, outchans, synced) end end -# these test are currently set up to run on OSX - -@testset DottedTestSet "PortAudio Tests" begin +# these default values are specific to my machines +@testset ExtendedTestSet "PortAudio Tests" begin if is_windows() default_indev = "Microphone Array (Realtek High " default_outdev = "Speaker/Headphone (Realtek High" @@ -205,7 +204,10 @@ end result = split(String(take!((io))), "\n") # make sure this is the same version I tested with @test startswith(result[1], "PortAudio V19") - @test result[3] == "Shim Source Hash: 4ea2a8526b" + end + + @testset "using correct shim version" begin + @test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" end @testset "Basic callback functionality" begin From e7b67133b363602accd8e0eb23119c69e4e646ad Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 18 May 2017 21:19:03 -0400 Subject: [PATCH 10/15] tweaks one of the write tests --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 2a40ac0..f43efa5 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -287,7 +287,7 @@ end println("done") end @testset "Samplerate-converting writing" begin - stream = PortAudioStream() + stream = PortAudioStream(0, 2) write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220, 330]), 3s) write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220, 330]), 3s) flush(stream) From e45ca2e0b6cadd2b2ccee4722d6abb46728f61e8 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 18 May 2017 21:23:14 -0400 Subject: [PATCH 11/15] adds 32-bit windows testing for appveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 7a1873b..0908f4f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ environment: matrix: - JULIAVERSION: "julialang/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" + - JULIAVERSION: "julialang/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" notifications: - provider: Email From a7cc0672a572c08fa0cd6d2f9d3ba932dc237a2c Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 18 May 2017 21:36:01 -0400 Subject: [PATCH 12/15] splits tests so we can run as much as possible during CI --- test/runtests.jl | 86 ------------------------------------------ test/runtests_local.jl | 85 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 86 deletions(-) create mode 100644 test/runtests_local.jl diff --git a/test/runtests.jl b/test/runtests.jl index f43efa5..bd77615 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -177,27 +177,7 @@ function test_callback_overflow(inchans, outchans, synced) end end -# these default values are specific to my machines @testset ExtendedTestSet "PortAudio Tests" begin - if is_windows() - default_indev = "Microphone Array (Realtek High " - default_outdev = "Speaker/Headphone (Realtek High" - elseif is_apple() - default_indev = "Built-in Microph" - default_outdev = "Built-in Output" - elseif is_linux() - default_indev = "default" - default_outdev = "default" - end - - devs = PortAudio.devices() - i = findfirst(d -> d.maxinchans > 0, devs) - indev = i > 0 ? devs[i] : nothing - i = findfirst(d -> d.maxoutchans > 0, devs) - outdev = i > 0 ? devs[i] : nothing - i = findfirst(d -> d.maxoutchans > 0 && d.maxinchans > 0, devs) - duplexdev = i > 0 ? devs[i] : nothing - @testset "Reports version" begin io = IOBuffer() PortAudio.versioninfo(io) @@ -266,70 +246,4 @@ end test_callback_overflow(2, 0, true) end end - - @testset "Open Default Device" begin - println("Recording...") - stream = PortAudioStream(2, 0) - buf = read(stream, 5s) - close(stream) - @test size(buf) == (round(Int, 5 * samplerate(stream)), nchannels(stream.source)) - println("Playing back recording...") - stream = PortAudioStream(0, 2) - write(stream, buf) - println("flushing...") - flush(stream) - close(stream) - println("Testing pass-through") - stream = PortAudioStream(2, 2) - write(stream, stream, 5s) - flush(stream) - close(stream) - println("done") - end - @testset "Samplerate-converting writing" begin - stream = PortAudioStream(0, 2) - write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220, 330]), 3s) - write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220, 330]), 3s) - flush(stream) - close(stream) - end - @testset "Open Device by name" begin - stream = PortAudioStream(default_indev, default_outdev) - buf = read(stream, 0.001s) - @test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) - write(stream, buf) - io = IOBuffer() - show(io, stream) - @test String(take!(io)) == """ - PortAudio.PortAudioStream{Float32} - Samplerate: 44100.0Hz - Buffer Size: 4096 frames - 2 channel sink: "$default_outdev" - 2 channel source: "$default_indev\"""" - close(stream) - end - @testset "Error on wrong name" begin - @test_throws ErrorException PortAudioStream("foobarbaz") - end - # no way to check that the right data is actually getting read or written here, - # but at least it's not crashing. - @testset "Queued Writing" begin - stream = PortAudioStream(0, 2) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream)) - t1 = @async write(stream, buf) - t2 = @async write(stream, buf) - @test wait(t1) == 48000 - @test wait(t2) == 48000 - flush(stream) - close(stream) - end - @testset "Queued Reading" begin - stream = PortAudioStream(2, 0) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream)) - t1 = @async read!(stream, buf) - t2 = @async read!(stream, buf) - @test wait(t1) == 48000 - @test wait(t2) == 48000 - close(stream) - end end diff --git a/test/runtests_local.jl b/test/runtests_local.jl new file mode 100644 index 0000000..76356d9 --- /dev/null +++ b/test/runtests_local.jl @@ -0,0 +1,85 @@ +# This file has runs the normal tests and also adds tests that can only be run +# locally on a machine with a sound card. It's mostly to put the library through +# its paces assuming a human is listening. + +include("runtests.jl") + +# these default values are specific to my machines +if is_windows() + default_indev = "Microphone Array (Realtek High " + default_outdev = "Speaker/Headphone (Realtek High" +elseif is_apple() + default_indev = "Built-in Microph" + default_outdev = "Built-in Output" +elseif is_linux() + default_indev = "default" + default_outdev = "default" +end + +@testset ExtendedTestSet "Local Tests" begin + @testset "Open Default Device" begin + println("Recording...") + stream = PortAudioStream(2, 0) + buf = read(stream, 5s) + close(stream) + @test size(buf) == (round(Int, 5 * samplerate(stream)), nchannels(stream.source)) + println("Playing back recording...") + stream = PortAudioStream(0, 2) + write(stream, buf) + println("flushing...") + flush(stream) + close(stream) + println("Testing pass-through") + stream = PortAudioStream(2, 2) + write(stream, stream, 5s) + flush(stream) + close(stream) + println("done") + end + @testset "Samplerate-converting writing" begin + stream = PortAudioStream(0, 2) + write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220, 330]), 3s) + write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220, 330]), 3s) + flush(stream) + close(stream) + end + @testset "Open Device by name" begin + stream = PortAudioStream(default_indev, default_outdev) + buf = read(stream, 0.001s) + @test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) + write(stream, buf) + io = IOBuffer() + show(io, stream) + @test String(take!(io)) == """ + PortAudio.PortAudioStream{Float32} + Samplerate: 44100.0Hz + Buffer Size: 4096 frames + 2 channel sink: "$default_outdev" + 2 channel source: "$default_indev\"""" + close(stream) + end + @testset "Error on wrong name" begin + @test_throws ErrorException PortAudioStream("foobarbaz") + end + # no way to check that the right data is actually getting read or written here, + # but at least it's not crashing. + @testset "Queued Writing" begin + stream = PortAudioStream(0, 2) + buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream)) + t1 = @async write(stream, buf) + t2 = @async write(stream, buf) + @test wait(t1) == 48000 + @test wait(t2) == 48000 + flush(stream) + close(stream) + end + @testset "Queued Reading" begin + stream = PortAudioStream(2, 0) + buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream)) + t1 = @async read!(stream, buf) + t2 = @async read!(stream, buf) + @test wait(t1) == 48000 + @test wait(t2) == 48000 + close(stream) + end +end From 860b54ade0fb40ff089866ba3dc1ff3cfc8bdc92 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 18 May 2017 21:57:01 -0400 Subject: [PATCH 13/15] now actuall test on CI --- .travis.yml | 3 +-- appveyor.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ca62952..5d1b762 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,5 @@ julia: notifications: email: false script: - # we can't actually run on travis, so just make sure it's installable - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("PortAudio"); using PortAudio' + - julia -e 'Pkg.clone(pwd()); Pkg.build("PortAudio"); Pkg.test("PortAudio")' diff --git a/appveyor.yml b/appveyor.yml index 0908f4f..64390f9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,5 +24,4 @@ build_script: Pkg.clone(pwd(), \"PortAudio\"); Pkg.build(\"PortAudio\")" test_script: - # can't actually run the test, so just make sure it's installable - - C:\projects\julia\bin\julia -e "using PortAudio" + - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"PortAudio\")" From de0dd1054f28101aa752ea48fc19f64603b5f001 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Fri, 19 May 2017 01:03:43 -0400 Subject: [PATCH 14/15] adds docstring for PortAudioStream --- src/PortAudio.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 61841e6..cb3974b 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -129,6 +129,29 @@ type PortAudioStream{T} end end +""" + PortAudioStream(inchannels=2, outchannels=2; options...) + PortAudioStream(duplexdevice, inchannels=2, outchannels=2; options...) + PortAudioStream(indevice, outdevice, inchannels=2, outchannels=2; options...) + +Audio devices can either be `PortAudioDevice` instances as returned +by `PortAudio.devices()`, or strings with the device name as reported by the +operating system. If a single `duplexdevice` is given it will be used for both +input and output. If no devices are given the system default devices will be +used. + +Options: + +* `eltype`: Sample type of the audio stream (defaults to Float32) +* `samplerate`: Sample rate (defaults to device sample rate) +* `blocksize`: Size of the blocks that are written to and read from the audio + device. (Defaults to $DEFAULT_BLOCKSIZE) +* `synced`: Determines whether the input and output streams are kept in + sync. If `true`, you must read and write an equal number of + frames, and the round-trip latency is guaranteed constant. If + `false`, you are free to read and write separately, but + overflow or underflow can affect the round-trip latency. +""" # this is the top-level outer constructor that all the other outer constructors # end up calling function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, From 47ea6a0c3016d045488a32e918b1f2adee1524fb Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Fri, 19 May 2017 01:10:19 -0400 Subject: [PATCH 15/15] adds TestSetExtensions to test/REQUIRE --- src/PortAudio.jl | 4 +--- test/REQUIRE | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index cb3974b..c301b77 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -6,8 +6,6 @@ using SampledSignals using RingBuffers #using Suppressor -using Base: AsyncCondition - import Base: eltype, show import Base: close, isopen import Base: read, read!, write, flush @@ -350,7 +348,7 @@ end # this is called by the shim process callback to notify that there is new data. # it's run in the audio context so don't do anything besides wake up the -# AsyncCondition +# AsyncCondition handle associated with that ring buffer notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Void}, ), handle) end # module PortAudio diff --git a/test/REQUIRE b/test/REQUIRE index 94e516f..557b023 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -1 +1 @@ -BaseTestNext +TestSetExtensions