diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..d1fd5c9 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,31 @@ +# Documentation: https://github.com/JuliaCI/Appveyor.jl +environment: + matrix: + - julia_version: 1 + - julia_version: nightly +platform: + - x86 + - x64 +matrix: + allow_failures: + - julia_version: nightly +branches: + only: + - master + - /release-.*/ +notifications: + - provider: Email + on_build_success: false + on_build_failure: false + on_build_status_changed: true +install: + - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) +build_script: + - echo "%JL_BUILD_SCRIPT%" + - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" +test_script: + - echo "%JL_TEST_SCRIPT%" + - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" +on_success: + - echo "%JL_CODECOV_SCRIPT%" + - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" diff --git a/.gitignore b/.gitignore index dd2812d..5362a77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.swp *.o deps/deps.jl +deps/build.log *.wav *.flac *.cov diff --git a/.travis.yml b/.travis.yml index 5d1b762..6025d00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,14 @@ language: julia os: - linux - osx -sudo: required julia: - - 0.6 + - 1 + - nightly +matrix: + allow_failures: + - julia: nightly + fast_finish: true notifications: - email: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("PortAudio"); Pkg.test("PortAudio")' + email: true +after_success: + - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..c70700c --- /dev/null +++ b/Project.toml @@ -0,0 +1,20 @@ +name = "PortAudio" +uuid = "80ea8bcb-4634-5cb3-8ee8-a132660d1d2d" +repo = "https://github.com/JuliaAudio/PortAudio.jl.git" +version = "1.1.0" + +[deps] +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167" +libportaudio_jll = "2d7b7beb-0762-5160-978e-1ab83a1e8a31" + +[compat] +julia = "1.3" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" + +[targets] +test = ["Test", "TestSetExtensions"] diff --git a/README.md b/README.md index 42d3790..7d9ee9b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ PortAudio.jl [![Build Status](https://travis-ci.org/JuliaAudio/PortAudio.jl.svg?branch=master)](https://travis-ci.org/JuliaAudio/PortAudio.jl) [![Build status](https://ci.appveyor.com/api/projects/status/6x1ha7uvrnel060g/branch/master?svg=true)](https://ci.appveyor.com/project/ssfrr/portaudio-jl/branch/master) - PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which gives cross-platform access to audio devices. It is compatible with the types defined in [SampledSignals.jl](https://github.com/JuliaAudio/SampledSignals.jl). It provides a `PortAudioStream` type, which can be read from and written to. ## Opening a stream @@ -52,13 +51,26 @@ PortAudio.jl also provides convenience wrappers around the `PortAudioStream` typ ```julia stream = PortAudioStream(2, 2) -write(stream, stream) +try + # cancel with Ctrl-C + write(stream, stream) +finally + close(stream) +end +``` + +### Use `do` syntax to auto-close the stream +```julia +PortAudioStream(2, 2) do stream + write(stream, stream) +end ``` ### Open your built-in microphone and speaker by name ```julia -stream = PortAudioStream("Built-in Microph", "Built-in Output") -write(stream, stream) +PortAudioStream("Built-in Microph", "Built-in Output") do stream + write(stream, stream) +end ``` ### Record 10 seconds of audio and save to an ogg file @@ -78,6 +90,8 @@ julia> buf = read(stream, 10s) ▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁ ▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁ +julia> close(stream) + julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf) ``` diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index ad4f72f..0000000 --- a/REQUIRE +++ /dev/null @@ -1,6 +0,0 @@ -julia 0.6.0-dev.2746 -BinDeps -SampledSignals 0.3.0 -RingBuffers 1.0.0 -@osx Homebrew -@windows WinRPM diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a18e182..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,28 +0,0 @@ -environment: - matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: - - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" -# Download most recent Julia Windows binary - - ps: (new-object net.webclient).DownloadFile( - $env:JULIA_URL, - "C:\projects\julia-binary.exe") -# Run installer silently, output to C:\projects\julia - - C:\projects\julia-binary.exe /S /D=C:\projects\julia - -build_script: -# Need to convert from shallow to complete for Pkg.clone to work - - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia -e "versioninfo(); - Pkg.clone(pwd(), \"PortAudio\"); Pkg.build(\"PortAudio\")" - -test_script: - - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"PortAudio\")" diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index 16eb07c..0000000 --- a/deps/build.jl +++ /dev/null @@ -1,25 +0,0 @@ -using BinDeps - -@BinDeps.setup - -ENV["JULIA_ROOT"] = abspath(JULIA_HOME, "../../") - -# include alias for WinRPM library -libportaudio = library_dependency("libportaudio", aliases=["libportaudio-2"]) - -# TODO: add other providers with correct names -provides(AptGet, "libportaudio2", libportaudio) -provides(Pacman, "portaudio", libportaudio) - - -@static if is_apple() - using Homebrew - provides(Homebrew.HB, "portaudio", libportaudio) -end - -@static if is_windows() - using WinRPM - provides(WinRPM.RPM, "libportaudio2", libportaudio, os = :Windows) -end - -@BinDeps.install Dict(:libportaudio => :libportaudio, ) diff --git a/deps/src/Makefile b/deps/src/Makefile deleted file mode 100644 index c711226..0000000 --- a/deps/src/Makefile +++ /dev/null @@ -1,80 +0,0 @@ -# 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 -TARGETBASENAME=pa_shim -OBJS = pa_shim.o - -# 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 - -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$(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 -else - CFLAGS += -fPIC - LIBS += - INC += - LDFLAGS += -shared - SHLIB_EXT = so - SHACMD = sha256sum -endif - -SOURCEHASH = $(shell $(SHACMD) pa_shim.c | awk '{print $$1}') -CFLAGS += -DSOURCEHASH=\"$(SOURCEHASH)\" - -TARGET=$(TARGETDIR)/$(TARGETBASENAME)$(HOSTSUFFIX).$(SHLIB_EXT) - -.PHONY: clean cleantemp default - -default: $(TARGET) - -%.o: %.c Makefile - $(CC) $< -c -o $@ $(INC) $(CFLAGS) - -$(TARGETDIR): - mkdir -p $@ - -$(TARGET): $(OBJS) $(TARGETDIR) Makefile - $(CC) $(OBJS) $(LDFLAGS) -o $@ $(LIBS) - -cleantemp: - rm -f $(OBJS) - -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 deleted file mode 100755 index 4e210e1..0000000 --- a/deps/src/build.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/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 deleted file mode 100755 index 7b04f2b..0000000 --- a/deps/src/dockerbuild_cb.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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 deleted file mode 100755 index 090611a..0000000 --- a/deps/src/dockerbuild_hbb.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 deleted file mode 100644 index c6d67cc..0000000 --- a/deps/src/pa_shim.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "portaudio.h" -#include -#include -#include -#include - -#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 sha256 hash of the shim source so we can make sure things are in sync -const char *pa_shim_getsourcehash(void) -{ - // defined on the command-line at build-time - return SOURCEHASH; -} - -/* - * 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; - 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 - 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); - } - } - 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/deps/src/portaudio.h b/deps/src/portaudio.h deleted file mode 100644 index 8a94aaf..0000000 --- a/deps/src/portaudio.h +++ /dev/null @@ -1,1225 +0,0 @@ -#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_arm-linux-gnueabihf.so b/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so deleted file mode 100755 index fc99772..0000000 Binary files a/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so and /dev/null differ diff --git a/deps/usr/lib/pa_shim_i686-linux-gnu.so b/deps/usr/lib/pa_shim_i686-linux-gnu.so deleted file mode 100755 index 4a3af96..0000000 Binary files a/deps/usr/lib/pa_shim_i686-linux-gnu.so and /dev/null differ diff --git a/deps/usr/lib/pa_shim_i686-w64-mingw32.dll b/deps/usr/lib/pa_shim_i686-w64-mingw32.dll deleted file mode 100755 index 956032b..0000000 Binary files a/deps/usr/lib/pa_shim_i686-w64-mingw32.dll and /dev/null differ diff --git a/deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so b/deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so deleted file mode 100755 index 90373e4..0000000 Binary files a/deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so and /dev/null differ diff --git a/deps/usr/lib/pa_shim_x86_64-apple-darwin14.dylib b/deps/usr/lib/pa_shim_x86_64-apple-darwin14.dylib deleted file mode 100755 index b0a5322..0000000 Binary files a/deps/usr/lib/pa_shim_x86_64-apple-darwin14.dylib and /dev/null differ diff --git a/deps/usr/lib/pa_shim_x86_64-linux-gnu.so b/deps/usr/lib/pa_shim_x86_64-linux-gnu.so deleted file mode 100755 index 4b42a0c..0000000 Binary files a/deps/usr/lib/pa_shim_x86_64-linux-gnu.so and /dev/null differ diff --git a/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll b/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll deleted file mode 100755 index 1a39c44..0000000 Binary files a/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll and /dev/null differ diff --git a/examples/audiometer.jl b/examples/audiometer.jl index c0e4059..814d188 100644 --- a/examples/audiometer.jl +++ b/examples/audiometer.jl @@ -25,11 +25,11 @@ function printmeter(metersize, signal, peak) blankchars = max(0, peakpos-meterchars-1) for position in 1:meterchars - print_with_color(barcolor(metersize, position), ">") + printstyled(">", color=barcolor(metersize, position)) end print(" " ^ blankchars) - print_with_color(barcolor(metersize, peakpos), "|") + printstyled("|", color=barcolor(metersize, peakpos)) print(" " ^ (metersize - peakpos)) end diff --git a/examples/spectrum.jl b/examples/spectrum.jl index 88f80fa..29fb90f 100644 --- a/examples/spectrum.jl +++ b/examples/spectrum.jl @@ -3,7 +3,7 @@ module SpectrumExample -using GR, PortAudio, SampledSignals +using GR, PortAudio, SampledSignals, FFTW const N = 1024 const stream = PortAudioStream(1, 0, blocksize=N) diff --git a/examples/waterfall_heatmap.jl b/examples/waterfall_heatmap.jl new file mode 100644 index 0000000..7076f93 --- /dev/null +++ b/examples/waterfall_heatmap.jl @@ -0,0 +1,67 @@ +using Makie +using PortAudio +using DSP + +""" +Slide the values in the given matrix to the right by 1. +The rightmosts column is discarded and the leftmost column is +left alone. +""" +function shift1!(buf::AbstractMatrix) + for col in size(buf,2):-1:2 + @. buf[:, col] = buf[:, col-1] + end +end + +""" +takes a block of audio, FFT it, and write it to the beginning of the buffer +""" +function processbuf!(readbuf, win, dispbuf, fftbuf, fftplan) + readbuf .*= win + A_mul_B!(fftbuf, fftplan, readbuf) + shift1!(dispbuf) + @. dispbuf[end:-1:1,1] = log(clamp(abs(fftbuf[1:D]), 0.0001, Inf)) +end + +function processblock!(src, buf, win, dispbufs, fftbuf, fftplan) + read!(src, buf) + for dispbuf in dispbufs + processbuf!(buf, win, dispbuf, fftbuf, fftplan) + end +end + +N = 1024 # size of audio read +N2 = N÷2+1 # size of rfft output +D = 200 # number of bins to display +M = 200 # amount of history to keep +src = PortAudioStream(1, 2, blocksize=N) +buf = Array{Float32}(N) # buffer for reading +fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) +fftbuf = Array{Complex{Float32}}(N2) # destination buf for FFT +dispbufs = [zeros(Float32, D, M) for i in 1:5, j in 1:5] # STFT bufs +win = gaussian(N, 0.125) + +scene = Scene(resolution=(1000,1000)) + +#pre-fill the display buffer so we can do a reasonable colormap +for _ in 1:M + processblock!(src, buf, win, dispbufs, fftbuf, fftplan) +end + +heatmaps = map(enumerate(IndexCartesian(), dispbufs)) do ibuf + i = ibuf[1] + buf = ibuf[2] + + # some function of the 2D index and the value + heatmap(buf, offset=(i[2]*size(buf, 2), i[1]*size(buf, 1))) +end + +center!(scene) + +while isopen(scene[:screen]) + processblock!(src, buf, dispbufs, fftbuf, fftplan) + for (hm, db) in zip(heatmaps, dispbufs) + hm[:heatmap] = db + end + render_frame(scene) +end diff --git a/examples/waterfall_lines.jl b/examples/waterfall_lines.jl new file mode 100644 index 0000000..bce3b4a --- /dev/null +++ b/examples/waterfall_lines.jl @@ -0,0 +1,38 @@ +using Makie, GeometryTypes +using PortAudio + +N = 1024 # size of audio read +N2 = N÷2+1 # size of rfft output +D = 200 # number of bins to display +M = 100 # number of lines to draw +S = 0.5 # motion speed of lines +src = PortAudioStream(1, 2, blocksize=N) +buf = Array{Float32}(N) +fftbuf = Array{Complex{Float32}}(N2) +magbuf = Array{Float32}(N2) +fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) + +scene = Scene(resolution=(500,500)) +ax = axis(0:0.1:1, 0:0.1:1, 0:0.1:0.5) +center!(scene) + +ls = map(1:M) do _ + yoffset = to_node(to_value(scene[:time])) + offset = lift_node(scene[:time], yoffset) do t, yoff + Point3f0(0.0f0, (t-yoff)*S, 0.0f0) + end + l = lines(linspace(0,1,D), 0.0f0, zeros(Float32, D), + offset=offset, color=(:black, 0.1)) + (yoffset, l) +end + +while isopen(scene[:screen]) + for (yoffset, line) in ls + isopen(scene[:screen]) || break + read!(src, buf) + A_mul_B!(fftbuf, fftplan, buf) + @. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf))/10+0.5 + line[:z] = magbuf[1:D] + push!(yoffset, to_value(scene[:time])) + end +end diff --git a/src/PortAudio.jl b/src/PortAudio.jl index c301b77..8eace2c 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,52 +1,33 @@ -__precompile__() - module PortAudio -using SampledSignals -using RingBuffers -#using Suppressor +using libportaudio_jll, SampledSignals 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("suppressor.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 +import LinearAlgebra +import LinearAlgebra: transpose! export PortAudioStream +include("libportaudio.jl") + # 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. +# data is passed to and from portaudio in chunks with this many frames, because +# we need to interleave the samples const CHUNKSIZE=128 -# ringbuffer to receive errors from the audio processing thread -const ERR_BUFSIZE=512 - -function versioninfo(io::IO=STDOUT) +function versioninfo(io::IO=stdout) println(io, Pa_GetVersionText()) println(io, "Version: ", Pa_GetVersion()) - println(io, "Shim Source Hash: ", shimhash()[1:10]) end -type PortAudioDevice +mutable struct PortAudioDevice name::String hostapi::String maxinchans::Int @@ -76,57 +57,52 @@ devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n") # PortAudioStream ################## -type PortAudioStream{T} +mutable struct PortAudioStream{T} samplerate::Float64 blocksize::Int stream::PaStream + warn_xruns::Bool sink # untyped because of circular type definition source # untyped because of circular type definition - 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 + + # TODO: handle blocksize=0, that should be the default and generally works + # much better than trying to specify + # TODO: expose latency parameter + # TODO: pre-fill outbut buffer on init + # TODO: recover from xruns - currently with low latencies (e.g. 0.01) it + # will run fine for a while and then fail with the first xrun. + # TODO: figure out whether we can get deterministic latency... + # TODO: write a latency tester app function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans, outchans, sr, blocksize, synced) where {T} + inchans, outchans, sr, blocksize, + warn_xruns) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans inparams = (inchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL)) + Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.1, C_NULL)) outparams = (outchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) - this = new(sr, blocksize, C_NULL) - 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)) + Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.1, C_NULL)) + this = new(sr, blocksize, C_NULL, warn_xruns) + # finalizer(close, this) + this.sink = PortAudioSink{T}(outdev.name, this, outchans) + this.source = PortAudioSource{T}(indev.name, this, inchans) + this.stream = suppress_err() do + Pa_OpenStream(inparams, outparams, sr, blocksize, paNoFlag, + nothing, nothing) end - # 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, - this.bufinfo) Pa_StartStream(this.stream) - @async handle_errors(this) this end end +# this is the top-level outer constructor that all the other outer constructors end up calling """ PortAudioStream(inchannels=2, outchannels=2; options...) PortAudioStream(duplexdevice, inchannels=2, outchannels=2; options...) @@ -144,16 +120,14 @@ Options: * `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. +* `warn_xruns`: Display a warning if there is a stream overrun or underrun, + which often happens when Julia is compiling, or with a + particularly large GC run. This can be quite verbose so is + false by default. """ -# this is the top-level outer constructor that all the other outer constructors -# end up calling function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, synced=false) + inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, + warn_xruns=false) if samplerate == -1 sampleratein = indev.defaultsamplerate samplerateout = outdev.defaultsamplerate @@ -168,7 +142,7 @@ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, samplerate = samplerateout end end - PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, synced) + PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, warn_xruns) end # handle device names given as streams @@ -211,12 +185,20 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...) end +# handle do-syntax +function PortAudioStream(fn::Function, args...; kwargs...) + str = PortAudioStream(args...; kwargs...) + try + fn(str) + finally + close(str) + end +end + function close(stream::PortAudioStream) if stream.stream != C_NULL Pa_StopStream(stream.stream) Pa_CloseStream(stream.stream) - close(stream.source) - close(stream.sink) stream.stream = C_NULL end @@ -226,7 +208,8 @@ end isopen(stream::PortAudioStream) = stream.stream != C_NULL SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate -eltype{T}(stream::PortAudioStream{T}) = T +SampledSignals.blocksize(stream::PortAudioStream) = stream.blocksize +eltype(stream::PortAudioStream{T}) where T = T read(stream::PortAudioStream, args...) = read(stream.source, args...) read!(stream::PortAudioStream, args...) = read!(stream.source, args...) @@ -246,32 +229,6 @@ function show(io::IO, stream::PortAudioStream) 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 ################################## @@ -279,19 +236,17 @@ end # Define our source and sink types for (TypeName, Super) in ((:PortAudioSink, :SampleSink), (:PortAudioSource, :SampleSource)) - @eval type $TypeName{T} <: $Super + @eval mutable struct $TypeName{T} <: $Super name::String stream::PortAudioStream{T} chunkbuf::Array{T, 2} - ringbuf::RingBuffer{T} nchannels::Int - function $TypeName{T}(name, stream, channels, ringbufsize) where {T} + function $TypeName{T}(name, stream, channels) 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 = RingBuffer{T}(channels, ringbufsize) - new(name, stream, chunkbuf, ringbuf, channels) + new(name, stream, chunkbuf, channels) end end end @@ -300,30 +255,36 @@ 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 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) +function close(s::Union{PortAudioSink, PortAudioSource}) + throw(ErrorException("Attempted to close PortAudioSink or PortAudioSource. + Close the containing PortAudioStream instead")) +end +isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.stream) name(s::Union{PortAudioSink, PortAudioSource}) = s.name -function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} - println(io, T, "(\"", stream.name, "\")") - print(io, nchannels(stream), " channels") +function show(io::IO, ::Type{PortAudioSink{T}}) where T + print(io, "PortAudioSink{$T}") end -flush(sink::PortAudioSink) = flush(sink.ringbuf) +function show(io::IO, ::Type{PortAudioSource{T}}) where T + print(io, "PortAudioSource{$T}") +end + +function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} + print(io, nchannels(stream), "-channel ", T, "(\"", stream.name, "\")") +end function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount) nwritten = 0 while nwritten < framecount - towrite = min(framecount-nwritten, CHUNKSIZE) + n = min(framecount-nwritten, CHUNKSIZE) # make a buffer of interleaved samples - transpose!(view(sink.chunkbuf, :, 1:towrite), - view(buf, (1:towrite)+nwritten+frameoffset, :)) - n = write(sink.ringbuf, sink.chunkbuf, towrite) + transpose!(view(sink.chunkbuf, :, 1:n), + view(buf, (1:n) .+ nwritten .+ frameoffset, :)) + # TODO: if the stream is closed we just want to return a + # shorter-than-requested frame count instead of throwing an error + Pa_WriteStream(sink.stream.stream, sink.chunkbuf, n, sink.stream.warn_xruns) nwritten += n - # break early if the stream is closed - n < towrite && break end nwritten @@ -332,23 +293,67 @@ end function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameoffset, framecount) nread = 0 while nread < framecount - toread = min(framecount-nread, CHUNKSIZE) - n = read!(source.ringbuf, source.chunkbuf, toread) + n = min(framecount-nread, CHUNKSIZE) + # TODO: if the stream is closed we just want to return a + # shorter-than-requested frame count instead of throwing an error + Pa_ReadStream(source.stream.stream, source.chunkbuf, n, source.stream.warn_xruns) # de-interleave the samples - transpose!(view(buf, (1:toread)+nread+frameoffset, :), - view(source.chunkbuf, :, 1:toread)) + transpose!(view(buf, (1:n) .+ nread .+ frameoffset, :), + view(source.chunkbuf, :, 1:n)) - nread += toread - # break early if the stream is closed - n < toread && break + nread += n end nread 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 handle associated with that ring buffer -notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Void}, ), handle) +function suppress_err(dofunc::Function) + nullfile = @static Sys.iswindows() ? "nul" : "/dev/null" + open(nullfile, "w") do io + redirect_stderr(dofunc, io) + end +end + +function __init__() + if Sys.islinux() + envkey = "ALSA_CONFIG_DIR" + if envkey ∉ keys(ENV) + searchdirs = ["/usr/share/alsa", + "/usr/local/share/alsa", + "/etc/alsa"] + confdir_idx = findfirst(searchdirs) do d + isfile(joinpath(d, "alsa.conf")) + end + if confdir_idx === nothing + throw(ErrorException( + """ + Could not find ALSA config directory. Searched: + $(join(searchdirs, "\n")) + + if ALSA is installed, set the "ALSA_CONFIG_DIR" environment + variable. The given directory should have a file "alsa.conf". + + If it would be useful to others, please file an issue at + https://github.com/JuliaAudio/PortAudio.jl/issues + with your alsa config directory so we can add it to the search + paths. + """)) + end + confdir = searchdirs[confdir_idx] + ENV[envkey] = confdir + end + end + # initialize PortAudio on module load. libportaudio prints a bunch of + # junk to STDOUT on initialization, so we swallow it. + # TODO: actually check the junk to make sure there's nothing in there we + # don't expect + suppress_err() do + Pa_Initialize() + end + + atexit() do + Pa_Terminate() + end +end end # module PortAudio diff --git a/src/libportaudio.jl b/src/libportaudio.jl index ffae4ea..66c75d4 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -9,8 +9,8 @@ const PaHostApiIndex = Cint const PaHostApiTypeId = Cint # PaStream is always used as an opaque type, so we're always dealing # with the pointer -const PaStream = Ptr{Void} -const PaStreamCallback = Void +const PaStream = Ptr{Cvoid} +const PaStreamCallback = Cvoid const PaStreamFlags = Culong const paNoFlag = PaStreamFlags(0x00) @@ -43,20 +43,41 @@ const paContinue = PaStreamCallbackResult(0) const paComplete = PaStreamCallbackResult(1) const paAbort = PaStreamCallbackResult(2) +""" +Call the given expression in a separate thread, waiting on the result. This is +useful when running code that would otherwise block the Julia process (like a +`ccall` into a function that does IO). +""" +macro tcall(ex) + :(fetch(Base.Threads.@spawn $(esc(ex)))) +end + +# because we're calling Pa_ReadStream and PA_WriteStream from separate threads, +# we put a mutex around libportaudio calls +const pamutex = ReentrantLock() + +macro locked(ex) + quote + lock(pamutex) do + $(esc(ex)) + end + end +end + function Pa_Initialize() - err = ccall((:Pa_Initialize, libportaudio), PaError, ()) + err = @locked ccall((:Pa_Initialize, libportaudio), PaError, ()) handle_status(err) end function Pa_Terminate() - err = ccall((:Pa_Terminate, libportaudio), PaError, ()) + err = @locked ccall((:Pa_Terminate, libportaudio), PaError, ()) handle_status(err) end -Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) +Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ()) function Pa_GetVersionText() - versionPtr = ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) + versionPtr = @locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) unsafe_string(versionPtr) end @@ -86,7 +107,7 @@ const pa_host_api_names = Dict{PaHostApiTypeId, String}( 14 => "AudioScience HPI" ) -type PaHostApiInfo +mutable struct PaHostApiInfo struct_version::Cint api_type::PaHostApiTypeId name::Ptr{Cchar} @@ -95,12 +116,12 @@ type PaHostApiInfo defaultOutputDevice::PaDeviceIndex end -Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), +Pa_GetHostApiInfo(i) = unsafe_load(@locked ccall((:Pa_GetHostApiInfo, libportaudio), Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) # Device Functions -type PaDeviceInfo +mutable struct PaDeviceInfo struct_version::Cint name::Ptr{Cchar} host_api::PaHostApiIndex @@ -113,28 +134,28 @@ type PaDeviceInfo default_sample_rate::Cdouble end -Pa_GetDeviceCount() = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) +Pa_GetDeviceCount() = @locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) -Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), +Pa_GetDeviceInfo(i) = unsafe_load(@locked ccall((:Pa_GetDeviceInfo, libportaudio), Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) -Pa_GetDefaultInputDevice() = ccall((:Pa_GetDefaultInputDevice, libportaudio), +Pa_GetDefaultInputDevice() = @locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ()) -Pa_GetDefaultOutputDevice() = ccall((:Pa_GetDefaultOutputDevice, libportaudio), +Pa_GetDefaultOutputDevice() = @locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ()) # Stream Functions -type Pa_StreamParameters +mutable struct Pa_StreamParameters device::PaDeviceIndex channelCount::Cint sampleFormat::PaSampleFormat suggestedLatency::PaTime - hostAPISpecificStreamInfo::Ptr{Void} + hostAPISpecificStreamInfo::Ptr{Cvoid} end -type PaStreamInfo +mutable struct PaStreamInfo structVersion::Cint inputLatency::PaTime outputLatency::PaTime @@ -148,7 +169,7 @@ end # err = ccall((:Pa_OpenDefaultStream, libportaudio), # PaError, (Ref{PaStream}, Cint, Cint, # PaSampleFormat, Cdouble, Culong, -# Ref{Void}, Ref{Void}), +# Ref{Cvoid}, Ref{Cvoid}), # streamPtr, inChannels, outChannels, sampleFormat, sampleRate, # framesPerBuffer, C_NULL, C_NULL) # handle_status(err) @@ -161,47 +182,49 @@ function Pa_OpenStream(inParams, outParams, flags::PaStreamFlags, callback, userdata) streamPtr = Ref{PaStream}(0) - err = ccall((:Pa_OpenStream, libportaudio), PaError, - (Ref{PaStream}, - Ptr{Pa_StreamParameters}, - Ptr{Pa_StreamParameters}, - Cdouble, Culong, PaStreamFlags, - Ptr{Void}, Ptr{Void}), - streamPtr, - inParams, outParams, - sampleRate, framesPerBuffer, flags, - callback, userdata) + err = @locked ccall((:Pa_OpenStream, libportaudio), PaError, + (Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters}, + Cdouble, Culong, PaStreamFlags, Ref{Cvoid}, + # it seems like we should be able to use Ref{T} here, with + # userdata::T above, and avoid the `pointer_from_objref` below. + # that's not working on 0.6 though, and it shouldn't really + # matter because userdata should be GC-rooted anyways + Ptr{Cvoid}), + streamPtr, inParams, outParams, + float(sampleRate), framesPerBuffer, flags, + callback === nothing ? C_NULL : callback, + userdata === nothing ? C_NULL : pointer_from_objref(userdata)) handle_status(err) streamPtr[] end function Pa_StartStream(stream::PaStream) - err = ccall((:Pa_StartStream, libportaudio), PaError, + err = @locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_StopStream(stream::PaStream) - err = ccall((:Pa_StopStream, libportaudio), PaError, + err = @locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_CloseStream(stream::PaStream) - err = ccall((:Pa_CloseStream, libportaudio), PaError, + err = @locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_GetStreamReadAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, + avail = @locked ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, (PaStream,), stream) avail >= 0 || handle_status(avail) avail end function Pa_GetStreamWriteAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, + avail = @locked ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, (PaStream,), stream) avail >= 0 || handle_status(avail) avail @@ -210,9 +233,9 @@ end function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream, buf, frames) + err = @tcall @locked ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) buf end @@ -220,9 +243,9 @@ end function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream, buf, frames) + err = @tcall @locked ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) nothing end @@ -238,13 +261,13 @@ end function handle_status(err::PaError, show_warnings::Bool=true) if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED if show_warnings - msg = ccall((:Pa_GetErrorText, libportaudio), + msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - warn("libportaudio: " * unsafe_string(msg)) + @warn("libportaudio: " * unsafe_string(msg)) end elseif err != PA_NO_ERROR - msg = ccall((:Pa_GetErrorText, libportaudio), + msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - error("libportaudio: " * unsafe_string(msg)) + throw(ErrorException("libportaudio: " * unsafe_string(msg))) end end diff --git a/src/pa_shim.jl b/src/pa_shim.jl deleted file mode 100644 index c1100be..0000000 --- a/src/pa_shim.jl +++ /dev/null @@ -1,61 +0,0 @@ -function init_pa_shim() - 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) - 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 - -""" - 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) diff --git a/src/suppressor.jl b/src/suppressor.jl deleted file mode 100644 index ab297bc..0000000 --- a/src/suppressor.jl +++ /dev/null @@ -1,20 +0,0 @@ -# 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/REQUIRE b/test/REQUIRE deleted file mode 100644 index 557b023..0000000 --- a/test/REQUIRE +++ /dev/null @@ -1 +0,0 @@ -TestSetExtensions diff --git a/test/runtests.jl b/test/runtests.jl index bd77615..777e623 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,183 +1,9 @@ #!/usr/bin/env julia -using Base.Test -using TestSetExtensions using PortAudio -using SampledSignals -using RingBuffers +using Test -# 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 - -"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) - - # 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) - - 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 - testout = rand(Float32, outchans, nframes) # generate some test data to play - write(sinkbuf, testout) # fill the output ringbuffer - 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 - -@testset ExtendedTestSet "PortAudio Tests" begin +@testset "PortAudio Tests" begin @testset "Reports version" begin io = IOBuffer() PortAudio.versioninfo(io) @@ -186,64 +12,7 @@ end @test startswith(result[1], "PortAudio V19") end - @testset "using correct shim version" begin - @test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" - end - - @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 "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 "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 + @testset "Can list devices without crashing" begin + PortAudio.devices() end end diff --git a/test/runtests_local.jl b/test/runtests_local.jl index 76356d9..44cc42a 100644 --- a/test/runtests_local.jl +++ b/test/runtests_local.jl @@ -5,18 +5,18 @@ include("runtests.jl") # these default values are specific to my machines -if is_windows() +if Sys.iswindows() default_indev = "Microphone Array (Realtek High " default_outdev = "Speaker/Headphone (Realtek High" -elseif is_apple() - default_indev = "Built-in Microph" +elseif Sys.isapple() + default_indev = "Built-in Microphone" default_outdev = "Built-in Output" -elseif is_linux() +elseif Sys.islinux() default_indev = "default" default_outdev = "default" end -@testset ExtendedTestSet "Local Tests" begin +@testset "Local Tests" begin @testset "Open Default Device" begin println("Recording...") stream = PortAudioStream(2, 0) @@ -50,12 +50,12 @@ end write(stream, buf) io = IOBuffer() show(io, stream) - @test String(take!(io)) == """ - PortAudio.PortAudioStream{Float32} + @test occursin(""" + PortAudioStream{Float32} Samplerate: 44100.0Hz Buffer Size: 4096 frames 2 channel sink: "$default_outdev" - 2 channel source: "$default_indev\"""" + 2 channel source: "$default_indev\"""", String(take!(io))) close(stream) end @testset "Error on wrong name" begin @@ -68,8 +68,8 @@ end 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 + @test fetch(t1) == 48000 + @test fetch(t2) == 48000 flush(stream) close(stream) end @@ -78,8 +78,8 @@ end 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 + @test fetch(t1) == 48000 + @test fetch(t2) == 48000 close(stream) end end