Compare commits

...

328 commits

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

* fix tests

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

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

* Update README.md

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

* Update src/PortAudio.jl

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

* Update README.md

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

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

* Update README.md

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

* Update README.md

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

* Update README.md

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

* Remove extra stream

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

* specify duration

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

* no redundant tests
2022-03-09 17:24:25 -05:00
Jeff Fessler
9c701415c0
Add audio signal output example (#94) 2022-02-13 22:19:12 -05:00
Jeevith Gnanakumaran
f6cd300ec8
avoid fields with abstract types (#100)
https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-fields-with-abstract-type

The Buffer contains a field which is of type Array{T} where T. This is an abstract type, and to make it concrete, we need to specify the dimension of the array (2).
2022-01-10 12:38:16 -05:00
bramtayl
d570288ebe
no runtime error capturing (#99) 2022-01-07 11:53:34 -05:00
bramtayl
44d4ca38f8
spelling (#98)
* spelling

* version
2022-01-06 09:56:42 -05:00
Robert Luke
3cd4551d81
Remove documentation for depreciated synced keyword (#82) 2021-08-12 13:55:46 -04:00
Robert Luke
7799ea1749
Fix name of documentation scripts (#80) 2021-08-11 19:54:15 +10:00
Robert Luke
8a3b0d2a8a
Revert changes to README 2021-07-31 21:16:47 +10:00
Brandon Taylor
17faf321e7 Add lower bound for suppressor 2021-07-25 17:27:33 -04:00
Brandon Taylor
01c58dab91 Bump version 2021-07-25 15:18:59 -04:00
Brandon Taylor
d6c3595f03 Use Clang wrappers; reduce thread spawning; separate out SampledSignals
fix

fix

use CLANG wrappers

cleanup (again)

more coverage

fix tests

fix?

distinguish error numbers from codes

reduce thread spawning

cleanup

fix?

fix?

coverage

coverage

fix

fix

more cleanup and comments

separate out SampledSignals part

almost there

fix

comments

fix

Add gen README

Update test/runtests.jl

Co-authored-by: Robert Luke <748691+rob-luke@users.noreply.github.com>
performance improvements

fix

more comments

separate messanger from buffer

fix source/sink mix-up

adjust_channels, test device names

slight cleanup

update docs

add links to docs to readme
2021-07-25 13:11:55 -04:00
bramtayl
6a018cfc32
Avoid circular type definition (#78)
* avoid recursion

* reuse ref

* fix
2021-06-14 10:06:02 -04:00
bramtayl
b3cddf5669
run JuliaFormatter (#77) 2021-06-01 13:44:23 -04:00
Bill
89020cafc7
Update for PortAudio.jl architecture and Julia 1+ (#47) 2021-06-01 12:53:08 -04:00
bramtayl
50eb168f9a
More coverage (#76)
* more coverage

* more
2021-06-01 12:39:27 -04:00
bramtayl
dd68835815
Send debug to debug (#74)
* send to debug

* use Suppressor

* actually, this might be nicer as a macro

* return

* fix, add test

* small fix

* Logging target

* send xrun messages to debug

* Add note to README

* Revert "send xrun messages to debug"

This reverts commit d47abb9072.
2021-05-24 17:34:37 -04:00
bramtayl
0187b4937d
don't prefill empty output (#72) 2021-05-21 16:12:47 -04:00
bramtayl
5bdd8975a9
add alsa_plugins (#70)
* add alsa_plugins

* avoid get!
2021-05-21 08:27:33 -04:00
bramtayl
e8c1e6a8f4
Merge pull request #73 from rob-luke/PRtemplate
Add a pull request template
2021-05-14 07:46:55 -04:00
Spencer Russell
94a8a7f283
Merge pull request #71 from bramtayl/handle_null
handle C_NULL errors
2021-05-13 20:41:38 -04:00
Robert Luke
c4e1594518
Add a pull request template
This will encourage people committing code to explain the purpose of their pull request and ease reviewing.
2021-05-14 09:56:01 +10:00
Brandon Taylor
1d9e441168 handle C_NULL errors 2021-05-13 13:59:25 -04:00
bramtayl
ff6dedec1f
Merge pull request #69 from JuliaAudio/revert-61-compathelper/new_version/2021-05-09-00-41-15-913-2231612002
Revert "CompatHelper: add new compat entry for "SampledSignals" at version "2.1""
2021-05-13 12:00:07 -04:00
bramtayl
4652e394d8
Revert "CompatHelper: add new compat entry for "SampledSignals" at version "2.1"" 2021-05-13 11:56:18 -04:00
bramtayl
57a74e0bca
Merge pull request #61 from JuliaAudio/compathelper/new_version/2021-05-09-00-41-15-913-2231612002
CompatHelper: add new compat entry for "SampledSignals" at version "2.1"
2021-05-13 11:54:17 -04:00
bramtayl
06a1a0f243
Combine tests (#65)
* combine tests

* Delete runtests_local.jl

* More robust defaults, add SampledSignals

* get rid of flush

* get rid of flush, update printing

* Create runtests_local.jl

* Rename test/test/runtests_local.jl to test/runtests_local.jl
2021-05-13 11:42:09 -04:00
bramtayl
435e968b5a
Merge pull request #68 from rob-luke/badges
Update badges
2021-05-13 11:41:14 -04:00
Robert Luke
d71d971d66
Update badges
Change badges to github actions and code coverage from travis and appveyor
2021-05-13 09:07:10 +10:00
bramtayl
b5eed5a7c7
Merge pull request #66 from rob-luke/dropci
Remove appveyor and travis
2021-05-10 10:11:16 -04:00
Robert Luke
9f96451356 Remove appveyor and travis 2021-05-10 17:30:24 +10:00
bramtayl
d7d29880d6
Merge pull request #60 from rob-luke/ghtests
MRG: Use github actions for tests and expand platforms and versions
2021-05-09 16:34:32 -04:00
Robert Luke
5754f52034
Test against x86 too 2021-05-10 05:46:24 +10:00
bramtayl
a18ac17eba
Bump version 2021-05-09 12:54:05 -04:00
bramtayl
da0b3de1d8
Merge pull request #59 from rob-luke/secretsfix
Use the correct secrets key
2021-05-09 11:09:32 -04:00
Robert Luke
b905a7f31a Merge remote-tracking branch 'upstream/master' into ghtests 2021-05-09 13:23:01 +10:00
bramtayl
819de99d9c
Add compat entries; remove unused packages (#58) 2021-05-09 13:17:35 +10:00
github-actions[bot]
52be2700bf CompatHelper: add new compat entry for "SampledSignals" at version "2.1" 2021-05-09 00:41:16 +00:00
Robert Luke
30a64d1f45
Update Tests.yml 2021-05-09 10:39:47 +10:00
Robert Luke
b1e973dba2
Revert back to 1.3 as expected 2021-05-09 10:26:41 +10:00
Robert Luke
ab620dc64c
Update Project.toml 2021-05-09 10:12:12 +10:00
Robert Luke
578f34d0e5
Use github actions for tests and expand platforms and versions 2021-05-09 09:53:40 +10:00
Robert Luke
ad9c3142da
Update CompatHelper.yml 2021-05-09 09:47:09 +10:00
Robert Luke
c4423b04bd
Fix secret key name to match settings 2021-05-09 09:46:36 +10:00
bramtayl
7e317452f9
Create TagBot.yml 2021-05-08 10:31:53 -04:00
bramtayl
c05dff245e
Create CompatHelper.yml 2021-05-08 10:26:02 -04:00
Spencer Russell
28c89c24e4
removed outdated info on building the shim from README 2020-04-15 20:27:23 -04:00
Spencer Russell
25f9c1230f improves xrun handling and fixes segfaults on ctrl-c
fixes #20
2020-02-23 23:05:32 -05:00
Spencer Russell
461cdc1557 add disable_sigint to make ctrl-C not crash Julia 2020-02-22 21:42:45 -05:00
jakubwro
b9c604533d script fix 2020-02-21 22:47:10 +01:00
Jakub Wronowski
81720e0155 default latency fix 2020-02-20 23:02:10 +01:00
Jakub Wronowski
03a32623e9 default latency 2020-02-20 22:59:05 +01:00
Jakub Wronowski
c18963ac53 removed SampledSignals.blocksize method 2020-02-20 22:48:04 +01:00
Jakub Wronowski
1a1a10cb69 script fixes 2020-02-18 23:52:15 +01:00
Jakub Wronowski
c3570b0d07 readme and script fix 2020-02-18 23:49:50 +01:00
Jakub Wronowski
1fe68cf857 removed blocksize parameter 2020-02-18 23:04:51 +01:00
Jakub Wronowski
bea3577abe Merge branch 'nocallback' of https://github.com/jakubwro/PortAudio.jl 2020-02-18 21:19:54 +01:00
Spencer Russell
25993bce0e
Merge pull request #40 from JuliaAudio/nocallback
Julia 1.0, Artifacts, JLLs, no ringbuffers
2020-02-17 12:06:45 -06:00
jakubwro
d9ffc44f8c measure signal tuning 2020-02-11 14:07:56 +01:00
jakubwro
a1a2230ed8 script for measuring latency 2020-02-11 13:42:10 +01:00
jakubwro
3ffdbb9bc9 exposed pa stream latency 2020-02-11 00:11:21 +01:00
Spencer Russell
93916a630d Removes crufty files and outdated tests 2020-01-30 15:25:08 -05:00
Spencer Russell
b18b9bdcae only look for ALSA on linux systems 2020-01-30 11:54:16 -05:00
Spencer Russell
9d780e4950 re-enables suppressing portaudio initialization output 2020-01-30 11:33:50 -05:00
Spencer Russell
d069e75a9f bumps latency for more reliable performance 2020-01-30 11:28:44 -05:00
Spencer Russell
f123478231 now using a mutex to protect libportaudio access 2020-01-02 14:23:41 -05:00
Spencer Russell
16d0bc48be adds auto-detection of ALSA config dir 2020-01-02 13:56:46 -05:00
Spencer Russell
9eb565e487 mostly working, but crashes sometimes 2020-01-02 00:02:00 -05:00
Spencer Russell
4c2ad4dc06 some more update-related tweaks 2020-01-01 14:59:46 -05:00
Elliot Saba
a7919c5b64 Upgrade PortAudio to JLL packages 2019-12-27 22:50:45 -08:00
Julian P Samaroo
7944836b49 Fix CI attempt 2 2019-09-11 07:23:13 -05:00
Julian P Samaroo
918e9d6986 Fix CI attempt 1 2019-09-11 07:10:05 -05:00
Julian P Samaroo
1f1f721fec Make things work on Julia v1, use BB for some deps
Removed Compat
Switched to BB repo for libportaudio
Re-enabled a now-passing pa_shim test
2019-09-08 18:59:37 -05:00
Spencer Russell
577d7adfef
Merge pull request #36 from mroavi/julia1
Upgraded audiometer.jl and spectrum.jl
2019-07-30 08:58:34 -04:00
Martin Roa Villescas
221afe9b88 Upgrade spectrum.jl example to Julia 1.* 2019-07-25 14:14:14 +02:00
Martin Roa Villescas
ea2c524426 Upgrade audiometer.jl example to Julia 1.* 2019-07-25 13:46:26 +02:00
Spencer Russell
01ddd6b835 adds warn_xrun option 2019-04-02 14:06:00 -04:00
Spencer Russell
0425fbfe3b Merge branch 'julia1' of https://github.com/JuliaAudio/PortAudio.jl into julia1 2019-04-02 13:39:17 -04:00
Spencer Russell
7f51c78596 enable warnings on xruns 2019-04-02 13:39:12 -04:00
Spencer Russell
207ec25f1e adds some examples of scrolling spctrograms with Makie. Probably with an old version of Makie 2019-02-27 09:03:48 -05:00
Spencer Russell
8bd884d394 fixes field acess error when printing other errors 2019-02-27 09:01:09 -05:00
Spencer Russell
c66ad398bd adds warning TODO for do-syntax 2018-12-05 11:22:28 -05:00
Spencer Russell
8d42b94a6a adds do syntax support 2018-12-05 11:17:36 -05:00
Spencer Russell
7d1be74eae adds workaround for libuv/libuv#1951. PA_SHIM REQUIRES LOCAL BUILD 2018-08-28 13:41:04 -04:00
Spencer Russell
45bfdc4830 now properly closing the error ringbuf 2018-08-16 15:59:00 -04:00
Spencer Russell
bc32d13f7d Ref usage wasn't working on 0.6 2018-08-16 14:41:50 -04:00
Spencer Russell
308e88b7cf rounds up some stray underscores 2018-08-16 14:19:00 -04:00
Spencer Russell
3551896de1 install Compat during testing 2018-08-16 14:11:17 -04:00
Spencer Russell
4d73324a7f now only testing appveyor on 1.0, to save CI time 2018-08-16 13:56:32 -04:00
Spencer Russell
06d3a4b099 now also using master of RingBuffers 2018-08-16 13:03:22 -04:00
Spencer Russell
d14a5f4b1f adds back sudo for travis 2018-08-16 12:47:11 -04:00
Spencer Russell
b1e0183538 now testing on 0.6, 0.7, and 1.0 2018-08-16 12:41:35 -04:00
Spencer Russell
f6213dc5ef some more upgrades, changes a bunch of Ptrs to Refs 2018-08-15 23:18:44 -04:00
Spencer Russell
ca7c8b91d8
Merge pull request #27 from EMCP/master
getting a warning when executing regarding abs()
2018-08-10 13:35:16 -04:00
emcp
f1828824a1 Found another warning regarding abs. 2018-08-09 17:32:05 +02:00
Erik
23f657dbbe
getting a warning when executing regarding abs() 2018-08-08 21:11:55 +02:00
WooKyoung Noh
5823404f1a Compat Julia 0.7 2018-06-21 17:02:34 +09:00
Spencer Russell
03aefe619d Merge pull request #11 from tkelman/patch-1
use 0.6.0-pre as minimum julia version in REQUIRE
2017-05-28 22:15:17 -04:00
Tony Kelman
312d4a90ca be even more specific 2017-05-25 15:07:41 -07:00
Tony Kelman
54ac000878 use 0.6.0-pre as minimum julia version in REQUIRE
`mutable struct` syntax won't work on early 0.6.0-dev versions,
so better to stick to the julia-0.5-compatible versions of the package there
2017-05-25 15:02:15 -07:00
Spencer Russell
ce51f8497f removes one last compat 2017-05-21 22:39:15 -04:00
Spencer Russell
1b26ea2f0f README badge tweaking [ci skip] 2017-05-19 23:31:17 -04:00
Spencer Russell
f06a53363b removes spurious Compat 2017-05-19 23:29:14 -04:00
Spencer Russell
5ea61ea30e Merge pull request #9 from JuliaAudio/crosscompile
Switches to using native extension for ringbuffer exchange. fixes #6.
2017-05-19 22:22:10 -04:00
Spencer Russell
f57d201ef4 Merge branch 'master' into crosscompile 2017-05-19 18:00:06 -04:00
Spencer Russell
9dfb27a002 Merge pull request #10 from staticfloat/updated_ci_url
Update CI URLs to point to new caching infrastructure
2017-05-19 17:58:09 -04:00
Spencer Russell
47ea6a0c30 adds TestSetExtensions to test/REQUIRE 2017-05-19 01:10:19 -04:00
Spencer Russell
de0dd1054f adds docstring for PortAudioStream 2017-05-19 01:03:43 -04:00
Spencer Russell
860b54ade0 now actuall test on CI 2017-05-18 21:57:01 -04:00
Spencer Russell
a7cc0672a5 splits tests so we can run as much as possible during CI 2017-05-18 21:36:01 -04:00
Spencer Russell
e45ca2e0b6 adds 32-bit windows testing for appveyor 2017-05-18 21:23:14 -04:00
Spencer Russell
e7b67133b3 tweaks one of the write tests 2017-05-18 21:19:03 -04:00
Spencer Russell
efd70272ab adds cross-compiled multiplatform builds and infrastructure to load them 2017-05-18 12:38:13 -04:00
Spencer Russell
3e7c6b5c1b fixes test opening default devices 2017-05-17 00:34:45 -04:00
Spencer Russell
acaa305dfa adds linux build of pa_shim, removes Suppressor dependency 2017-05-17 00:32:19 -04:00
Spencer Russell
c58143404f some CI and REQUIRE tweaks 2017-05-16 23:26:02 -04:00
Elliot Saba
af8f53eb0e Update CI URLs to point to new caching infrastructure 2017-05-16 17:26:12 -07:00
Spencer Russell
c53ac388e9 allow 0.6 prerelease 2017-05-15 23:02:43 -04:00
Spencer Russell
85c34d3906 adds ps_shim lib build for OSX 2017-05-15 22:57:18 -04:00
Spencer Russell
0c36e1eec5 mostly adding tests and fixing bugs. passing tests now 2017-05-11 00:58:49 -04:00
Spencer Russell
9e3e66d37a now reports hash of source file used to build shim 2017-05-09 13:08:00 -04:00
Spencer Russell
5c40329df6 new C-based ringbuffer handling seems to be mostly working 2017-05-09 11:46:11 -04:00
Spencer Russell
ba5f60e097 Removes duplicate using SampledSignals in runtests.jl 2016-10-27 13:18:16 -04:00
Spencer Russell
a24cf8e9bc bumps SampledSignals required version 2016-09-29 23:18:14 -04:00
Spencer Russell
15dcee6245 now just using floating-point samplerate 2016-09-29 02:31:07 -04:00
Spencer Russell
eaa5aa96bb updates SampledSignals dependency to v0.2.0 2016-09-10 01:28:19 -04:00
Spencer Russell
9b67ba645c now builds branches in appveyor 2016-09-10 01:13:48 -04:00
Spencer Russell
4fe967465c updates for SampledSignals 0.2.0 API 2016-09-04 13:05:42 -04:00
Spencer Russell
7012c445f8 adds CI badges to README [ci skip] 2016-08-26 13:17:54 -04:00
Spencer Russell
b5f125c1b1 fixes broken deps file 2016-08-26 13:03:59 -04:00
Spencer Russell
5f53bcebc3 silences depwarn by switching to at-static rather than at-windows_only, etc. 2016-08-26 12:35:24 -04:00
Spencer Russell
f19e9ca160 removes now-unnecessary checkouts from appveyor cfg 2016-08-22 23:16:32 -04:00
Spencer Russell
b6f59a1e9d updates README now that we don't need Compat master [ci skip] 2016-08-22 12:04:19 -04:00
Spencer Russell
1e4bfaff06 updating dependencies now that more of them are tagged 2016-08-22 12:00:23 -04:00
Spencer Russell
93334b0de9 adds documentation for synced kwarg [ci skip] 2016-08-17 10:59:03 -04:00
Spencer Russell
80af028efb swallows STDERR to remove some spurrious warnings from PortAudio and OSX 2016-08-16 19:19:35 -04:00
Spencer Russell
fcf87c0c61 now setting ringbuf size and prefill to twice blocksize 2016-08-16 18:26:38 -04:00
Spencer Russell
1d5ca112eb back to duplex-by-default but now with optional synchronization 2016-08-16 18:10:03 -04:00
Spencer Russell
7ea9da7e09 now always using a rational sample rate when using system default 2016-08-09 00:56:30 -04:00
Spencer Russell
de5753e7f2 updates example in README [ci skip] 2016-08-09 00:23:51 -04:00
Spencer Russell
1d16bdecba now checking out SampledSignals on CI, too 2016-08-09 00:09:50 -04:00
Spencer Russell
44339ff755 now checking out RingBuffers on CI, too 2016-08-09 00:02:49 -04:00
Spencer Russell
5460e461bd adds sudo back to travis config [appveyor skip] 2016-08-08 23:57:26 -04:00
Spencer Russell
09b1cd3e47 adds Compat checkout to appveyor and travis configs 2016-08-08 23:53:59 -04:00
Spencer Russell
3f5f107c81 now travis and appveyor should at least test installability 2016-08-08 23:39:46 -04:00
Spencer Russell
e7cdbad4b3 now pulls samplerate from device by default. fixes #4 2016-08-08 23:34:54 -04:00
Spencer Russell
fb30e51f91 fixes tests to they pass. issue filed for large resampling 2016-08-08 21:31:29 -04:00
Spencer Russell
2cc49cc3e0 fixes error with multichannel reading 2016-08-08 21:28:34 -04:00
Spencer Russell
ede482ce6f defaults to output-only, bigger ringbuf, fixes issue with multichannel writing 2016-08-08 20:25:02 -04:00
Spencer Russell
0d64e4bd0c now will do partial ringbuf read/writes in partial underflow conditions. crashes on 0.5 --inline-no 2016-08-01 13:20:53 -04:00
Spencer Russell
fd425b3ace fixes bug when there's less than a full blocksize available in the ringbuffer 2016-07-31 01:42:47 -04:00
Spencer Russell
7cccb28d2b fixes another allocation when run with inlining off 2016-07-30 02:05:00 -04:00
Spencer Russell
829a09a2ae fixes munged case problem 2016-07-30 01:33:37 -04:00
Spencer Russell
30803bce97 renames bufsize to blocksize 2016-07-29 23:56:37 -04:00
Spencer Russell
77dcb8965c seems to be mostly working with lockfree ringbuffer 2016-07-29 01:44:02 -04:00
Spencer Russell
e40933b97b fixes 0.5 depwarns except AsyncCondition stuff 2016-07-28 00:55:56 -04:00
Spencer Russell
6433b6fb93 adds alias so Pkg.build works on WinRPM 2016-04-02 15:33:59 -04:00
Spencer Russell
2889669b7f adds RingBuffers to REQUIRE 2016-03-31 11:35:58 -04:00
Spencer Russell
390ae258bd adds SampledSignals to REQUIRE 2016-03-31 11:35:10 -04:00
Spencer Russell
2bda1cf25e fixes missing newline in stream printing 2016-03-31 11:22:25 -04:00
Spencer Russell
43292ccaf8 updates for SampleTypes-to-SampledSignals rename 2016-03-31 11:07:46 -04:00
Spencer Russell
547cce821a simplifies GR example 2016-03-28 12:20:47 -04:00
Spencer Russell
2366f76a06 adds attribution for GR example 2016-03-28 00:27:58 -04:00
Spencer Russell
8e0bd3c255 adds realtime spectrum plot using GR.jl 2016-03-28 00:20:53 -04:00
Spencer Russell
6970d2b81e updates audiometer example for recent Stream changes 2016-03-23 23:41:26 -04:00
Spencer Russell
db2f697d6c fixes channel count wrongness in README 2016-03-23 23:35:07 -04:00
Spencer Russell
8a7a7d5baa moves most stream config to keyword args and updates README 2016-03-23 23:25:03 -04:00
Spencer Russell
096cfd49da adds pass-through read/write methods to PortAudioStream 2016-03-23 22:25:46 -04:00
Spencer Russell
8a3bebff2c removes a few pieces of cruft 2016-03-23 19:54:14 -04:00
Spencer Russell
ae923ba3a6 adds nicer show method for PortAudioStream 2016-03-23 19:49:30 -04:00
Spencer Russell
56cf15e9df removes extra asynccondition step now that our task isn't blocking 2016-03-23 19:37:42 -04:00
Spencer Russell
f868087e99 switches to using RingBuffer to better handle sub-buffer writes 2016-03-23 19:15:07 -04:00
Spencer Russell
333bbbf8d8 forwards InterruptException to waiters instead of killing audio task 2016-03-23 17:55:03 -04:00
Spencer Russell
5854270183 callback-based interface mostly working, but lots of dropouts 2016-03-23 13:10:14 -04:00
Spencer Russell
f02e733fe7 callback creation/scheduling seems to be working 2016-03-22 22:45:40 -04:00
Spencer Russell
bea06357b8 in-progress converting to callback API and supporting duplex streams 2016-03-22 02:03:43 -04:00
Spencer Russell
c3d8723b5b adds some tests and fixes, test coverage at 95% 2016-03-20 05:09:56 -04:00
Spencer Russell
cf7f170c72 adds audiometer example 2016-03-20 04:05:21 -04:00
Spencer Russell
641de1e92b adds PortAudio to module list in last example so it's complete 2016-03-20 03:54:44 -04:00
Spencer Russell
ee4d05fcb2 reworks default source/sink loading so now we get the name 2016-03-20 03:52:02 -04:00
Spencer Russell
f7b9b48658 some minor README tweaks 2016-03-20 03:27:30 -04:00
Spencer Russell
64a08bc90f adds better source/sink show method and more docs/examples to README 2016-03-20 03:23:28 -04:00
Spencer Russell
e51c980f24 adds ability to open device by name 2016-03-20 02:12:21 -04:00
Spencer Russell
738c4cff4b adds functionality to open a specific device instance 2016-03-20 02:07:39 -04:00
Spencer Russell
b8e5c8786e adds sudo to travis config 2016-03-20 01:24:23 -04:00
Spencer Russell
d419f51f68 refactores source/sink construction a bit 2016-03-20 01:20:59 -04:00
Spencer Russell
f834773b34 removes some unused dependencies and trying a new APT package name 2016-03-20 00:59:36 -04:00
Spencer Russell
d193a9c83d releases read/write busy flag on an exception 2016-03-20 00:53:08 -04:00
Spencer Russell
8ee46f5125 removes commented-out old code 2016-03-20 00:28:56 -04:00
Spencer Russell
559dca24c2 adds sink support 2016-03-20 00:25:30 -04:00
Spencer Russell
9b57992d83 adds queuing for writers 2016-03-19 23:59:19 -04:00
Spencer Russell
9bafaeaa45 refactored to remove background task and only write to PA during user writes 2016-03-19 23:50:15 -04:00
Spencer Russell
6830452b9c multichannel output working now 2016-03-19 22:45:00 -04:00
Spencer Russell
4cb94e168d now tests run, but fail because there aren't any 2016-03-19 21:56:42 -04:00
Spencer Russell
958cccf16f adds runtests.sh script 2016-03-19 21:55:35 -04:00
Spencer Russell
fae9b38224 massive restructuring. mono output to default sink works 2016-03-19 21:54:46 -04:00
Spencer Russell
80d329eb39 stripped out non-portaudio stuff and starting to reorganize portaudio stuff 2016-03-18 22:15:00 -04:00
Spencer Russell
c4dfef9178 more fixes to get the tests running without deprecations on 0.4 and 0.3 2015-11-12 09:51:12 -05:00
Bill
826fdafe5f Update portaudio.jl 2015-11-03 18:58:57 -10:00
Bill
348e2576f9 Update nodes.jl 2015-11-03 18:26:24 -10:00
Bill
3c663c675a Update AudioIO.jl 2015-11-03 17:38:05 -10:00
Bill
1ad2d5b27d Update sndfile.jl
compatibility 0.3 -> 0.4 changes
2015-11-03 13:49:36 -10:00
Bill
2c4b21940c Update sndfile.jl 2015-11-03 09:50:42 -10:00
Bill
e87ef290a2 Update nodes.jl
compatibility of union syntax from 0.3 to 0.4
2015-11-03 09:47:50 -10:00
Bill
6786c2b4bb Update AudioIO.jl
Add compatibility from 0.3 to 0.4
2015-11-03 09:46:02 -10:00
Bill
57fcabb6de Update REQUIRE 2015-11-03 09:44:30 -10:00
Bill
3f90e3c907 Update portaudio.jl
Add methods to use audio streams other than the default output stream
2015-11-01 22:55:55 -10:00
Bill
9c23d02066 Update portaudio.jl 2015-11-01 01:09:30 -10:00
Spencer Russell
26fd1fdf23 adds coverage files to gitignore 2014-11-21 23:16:30 -05:00
Spencer Russell
87a160ba78 adds wrapper for Pa_GetVersionText 2014-11-21 23:16:12 -05:00
Spencer Russell
d871de743f Merge pull request #40 from goretkin/fix_add
fix constant addition
2014-11-13 20:03:18 -05:00
Gustavo Goretkin
a55b59d481 fix constant addition
due to
julia> s = 1.0 + SinOsc()
ERROR: `convert` has no method matching convert(::Type{AudioNode{T<:AudioRenderer}}, ::Float64)
 in OffsetRenderer at /Users/goretkin/.julia/v0.3/AudioIO/src/nodes.jl:178
 in + at /Users/goretkin/.julia/v0.3/AudioIO/src/operators.jl:16
2014-11-12 18:00:12 -05:00
Spencer Russell
569cdf0d73 changes WinRPM packages names hopefully to the correct ones 2014-10-26 14:47:36 -04:00
Spencer Russell
4ebd7b946d adds WinRPM to REQUIRE and build.jl 2014-09-04 09:18:03 -04:00
Spencer Russell
8d291288cc removes unncessary Homebrew check in build.jl 2014-08-30 11:46:05 -04:00
Spencer Russell
4f1ec99080 now all badges are SVG 2014-08-29 15:22:26 -04:00
Spencer Russell
2f8464a1eb adds Pkgs.julialang.org status badge 2014-08-29 13:30:33 -04:00
Spencer Russell
9e02643482 allows under/over-flow warnings to be turned on and off 2014-08-29 10:42:11 -04:00
Spencer Russell
f75de3299f adds test and default channel for AudioInput 2014-08-28 17:05:15 -04:00
Spencer Russell
eaca8109f5 now using a pure-julia portaudio wrapper and the read/write API 2014-08-28 16:05:51 -04:00
Spencer Russell
f4adf31d15 finally gets rid of old crufty commented out code 2014-08-28 13:43:36 -04:00
Spencer Russell
cbcfedbad0 adds shim revision check so we know if it gets out of sync 2014-08-28 13:36:38 -04:00
Spencer Russell
ead54a9bdd Merge pull request #24 from tlycken/patch-1
Install with Pkg.clone(pwd()) instead of ln -s, adds unshallow to fix cloning problem
2014-08-28 10:23:08 -04:00
Tomas Lycken
e9483f1782 Fetch unshallow 2014-08-28 16:16:30 +02:00
Tomas Lycken
93c2215e2f Install with Pkg.clone(pwd()) instead of ln -s
After seeing the message [here](https://groups.google.com/forum/?fromgroups=#!topic/julia-users/Kt2KtQm2O1Q) I wanted to see if installing the package differently would help. Since a PR triggers Travis, this was the easiest way to check my assumption :P
2014-08-28 16:09:26 +02:00
Spencer Russell
ae76badebb adds method to get rate of a file 2014-08-27 10:58:17 -04:00
Spencer Russell
87925d1d8d moves the quotes around in travis config 2014-08-25 19:11:33 -04:00
Spencer Russell
e64ea0b26c travis config now checks out BinDeps master, until the next release 2014-08-25 17:04:12 -04:00
Spencer Russell
0c396b095b adds Pacman support, currently only works with BinDeps master 2014-08-25 16:50:04 -04:00
Spencer Russell
0d06ce2c96 adds some more docs on File IO and Julia syntax highlighting 2014-08-22 06:15:42 -04:00
Spencer Russell
5ce3c0cc97 badge formatting 2014-08-22 04:40:32 -04:00
Spencer Russell
942fae05ca adds coverage badge to README 2014-08-22 04:39:37 -04:00
Spencer Russell
3601ea2098 adds coverage arg to Pkg.test instead of using --code-coverage 2014-08-22 04:33:38 -04:00
Spencer Russell
36362d45cf now only submitting coverage info for the release test, no need to send it twice 2014-08-22 04:23:10 -04:00
Spencer Russell
8d456062ca fixes coverage line in .travis.yml 2014-08-22 04:20:49 -04:00
Spencer Russell
86e459cc87 adds coverage stuff to test running 2014-08-22 04:15:13 -04:00
Spencer Russell
dd28b530ea adds try-finally to open so it's actually safe 2014-08-21 01:05:03 -04:00
Spencer Russell
2620e6ecb2 fixes issue and now passes test for stereo file playback (mixing to mono) 2014-08-13 12:31:56 -04:00
Spencer Russell
5f074a4f87 adds stereo wav test file 2014-08-12 21:53:44 -04:00
Spencer Russell
11e6dce20c now we have a failing test for stereo file playback 2014-08-12 19:26:03 -04:00
Spencer Russell
edf2f8fd04 now transposing the audio so columns are channels on a file read 2014-08-12 19:22:19 -04:00
Spencer Russell
d4b4b33361 test for mono file playback now passing 2014-08-12 13:24:31 -04:00
Spencer Russell
c22b6b2347 merges in @bauglir's changes 2014-08-11 18:33:40 -04:00
Spencer Russell
20ecbb5b8e adds a failing test for file playback 2014-08-11 18:25:12 -04:00
Spencer Russell
10802a61f3 some minor test file restructuring 2014-08-11 18:09:51 -04:00
Spencer Russell
89043b80c9 deprecates af_open in favor of AudioIO.open 2014-08-11 17:34:21 -04:00
Joris Kraak
26cf69ad94 Make audio file reads return data as n_frames x n_channels 2014-08-01 17:58:39 +02:00
Joris Kraak
b79b47a461 Add audio file seek capabilities 2014-08-01 14:06:14 +02:00
Joris Kraak
19c33d71c6 Remove unnecessary value check
Due to the way the `render` function was rewritten in eabf971, the audio
variable is always going to be an array of `AudioSample`s, so a check to
see if it is equal to `nothing` is no longer applicable.
2014-08-01 14:04:38 +02:00
Joris Kraak
0cd33b8289 Stop trying to read from audio file if no data is available
As soon as libsndfile has finished reading a file it no longer returns
any data. This can send the `render` method for `FileRenderer` into an
infinite loop. This way if not enough data is available a partial buffer
is returned.
2014-08-01 14:01:49 +02:00
Joris Kraak
289d7a56cd Support playing sound from stereo files 2014-08-01 14:00:24 +02:00
Spencer Russell
18f0cee2e5 now shipping pre-built shim binary for linux also 2014-07-27 15:39:24 -04:00
Spencer Russell
f6544cf7fd now only building the shim for linux installs 2014-07-27 14:46:46 -04:00
Spencer Russell
6af95e6f57 now tracking the compiled shim for OSX 2014-07-27 14:38:59 -04:00
Spencer Russell
9d9af6e4cd Merge pull request #18 from staticfloat/sf/bindeps
Fixes to work with BinDeps/Homebrew properly
2014-07-27 14:20:19 -04:00
Elliot Saba
864f35db75 Fixes to work with BinDeps/Homebrew properly 2014-07-27 13:51:02 -04:00
Spencer Russell
3d1def96ec fixes to work with new support in Homebrew.jl for libsndfile and portaudio 2014-07-27 12:57:18 -04:00
Spencer Russell
450a9e9184 drops allocation by making the sample rate Float32 2014-07-01 02:32:02 -04:00
Spencer Russell
6e6ca7d1fe adds tests for multiplying signals together 2014-07-01 02:06:30 -04:00
Spencer Russell
9c5c01aa7b now disabling 0.2 testing and bumps required Julia version in REQUIRE 2014-06-30 22:18:32 -04:00
Spencer Russell
326f28a2b5 now explicitly installing factcheck on julia 0.2 test rig 2014-06-30 22:13:48 -04:00
Spencer Russell
48113ff5f3 hopefully fixes test running on julia 0.2 2014-06-30 22:09:02 -04:00
Spencer Russell
68414ef7cf now running travis testing on both release and nightly julia builds 2014-06-30 22:04:20 -04:00
Spencer Russell
9bed45bbc8 fixes path handling in test to be more robust and work with Pkg.test 2014-06-30 21:45:08 -04:00
Spencer Russell
131fe7804d Merge branch 'master' of git://github.com/ssfrr/AudioIO.jl 2014-06-30 21:33:18 -04:00
Spencer Russell
00e98a2b88 Merge pull request #17 from Calder/master
Added support for multiple consecutive linear ramps.
2014-06-30 21:33:07 -04:00
Spencer Russell
389636fda2 ports tests over to RunTest.jl 2014-06-30 21:24:29 -04:00
Calder Coalson
8c134edc26 Adjusted buffer length so buffer ends at the end of the ramp. 2014-06-28 16:33:34 -05:00
Calder Coalson
4eb15ae29f Added support for consecutive linear ramps. 2014-06-28 15:22:35 -05:00
Spencer Russell
201d2852c6 adds -L/usr/local/lib to shim Makefile, so it compiles on Iain's OSX machine 2014-06-28 13:11:32 -05:00
Spencer Russell
16857615d3 removes contributors list form README, it's on github 2014-06-27 12:30:59 -05:00
Spencer Russell
fb26c6f011 weakens the get_audio_devices test so it should pass on Travis 2014-06-27 12:23:54 -05:00
Spencer Russell
7cfdff73ac removes buffer allocation in Offset render func 2014-06-27 12:22:30 -05:00
Spencer Russell
d5ae9237d6 adds ability to multiple AudioNodes. NEEDS TESTS 2014-06-27 00:31:10 -05:00
Spencer Russell
36c95f1a16 adds constant offset ability. No tests! 2014-06-26 16:01:06 -05:00
Spencer Russell
91aa390554 adds annotation to base render function 2014-06-26 15:36:52 -05:00
Spencer Russell
b07230919d simplifies AudioNode subtype declarations 2014-06-26 12:21:01 -05:00
Spencer Russell
eabf9717aa adds some new ways to read audiofiles 2014-06-26 09:12:53 -05:00
Spencer Russell
8cc51a435c drops allocation of MixRenderer to 64bytes 2014-06-25 23:16:07 -04:00
Spencer Russell
b8802449c9 tweaks TestNode so it doesn't allocate substantially 2014-06-25 23:01:27 -04:00
Spencer Russell
fab2f8ea8d render alloc on ArrayPlayer down to 192bytes. can probably do better 2014-06-25 22:51:25 -04:00
Spencer Russell
66654d3aaf dropped allocation on SinOsc{AudioNode} 2014-06-25 22:40:23 -04:00
Spencer Russell
e451509293 got LinRamp allocation down to 240bytes 2014-06-25 22:25:35 -04:00
Spencer Russell
6a065f0af2 eeked some more allocation out of SinOsc 2014-06-25 22:08:03 -04:00
Spencer Russell
7aafc697b0 SinOsc{Float32} allocation is way down 2014-06-25 21:51:16 -04:00
Spencer Russell
dd49d374fa adds test to SinOsc render to test for allocation 2014-06-25 18:17:13 -04:00
Spencer Russell
efd841d74d adds ability to control SinOsc freq from a signal 2014-06-24 03:30:38 -04:00
Spencer Russell
bf2e5bfb84 adds LinRamp node 2014-06-24 01:35:02 -04:00
Spencer Russell
a334a44a2e adds WhiteNoise AudioNode 2014-06-23 19:17:28 -04:00
Spencer Russell
9312fa745d ports AudioInput to new type structure 2014-06-23 18:48:01 -04:00
Spencer Russell
afe4e0d8be fixes imprecise SinOsc 2014-06-23 17:58:32 -04:00
Spencer Russell
f240f3fbf6 fixes issue where portaudio task was killing the root mixer 2014-06-23 17:38:50 -04:00
Spencer Russell
45cae6bed4 adds some design notes 2014-06-23 14:33:06 -04:00
Spencer Russell
a1ed357629 refactored AudioNode to contain a AudioRenderer, tests passing 2014-06-23 02:10:35 -04:00
Spencer Russell
0ddd57c0a9 Merge branch 'master' into dev_select 2014-06-22 13:29:40 -04:00
Spencer Russell
c5587a9b47 adds julia 0.2+ to REQUIRE 2014-06-12 16:49:17 -04:00
Spencer Russell
e48c6361fd adds Gain node with * operator 2014-05-23 20:59:22 -04:00
Spencer Russell
0d84cb409b removes flac test as it was broken on osx, adds get_audio_devices 2014-05-02 16:10:54 -04:00
Joris Kraak
81a8503c1d Add mutable streaming node example
This demo requires an input hooked up to the default recording device
and will stream that input to the default output device for 10 seconds,
alternating between a muted and unmuted state every second
2014-03-25 16:02:42 +01:00
Joris Kraak
7831578955 Add mono audio input to PortAudio shim
The way the PortAudio callback works it is possible to reuse the buffer
used for sharing output audio data between Julia and the C-library.

The output data can be pushed from the shared buffer to the PortAudio's
output buffer, after which the same location in the buffer can be used
for storing the data read from the input buffer.

This does assume equal lengths for the in- and output buffers.
2014-03-25 12:35:04 +01:00
Spencer Russell
eb34b85922 adds Jiahao's async harmony to lilyplay example 2014-01-23 23:06:06 -05:00
Spencer Russell
f7df8623b9 adds lilyplay.jl, a great example script from Jiahao Chen 2014-01-23 22:30:36 -05:00
Spencer Russell
0fa2977889 Merge pull request #8 from zhemao/master
Add basic support for file output
2014-01-23 09:06:03 -08:00
Spencer Russell
1f5d9a2612 fixes a test breakage 2014-01-22 10:46:54 -05:00
Spencer Russell
f0480dc63c creates notes dir 2014-01-18 16:10:08 -05:00
Spencer Russell
22dba358b8 adds ability to wait for an AudioNode 2014-01-13 19:19:56 -05:00
Howard Mao
255050b6c5 add a few more asserts 2014-01-11 11:37:12 -05:00
Howard Mao
d3ae48dd09 add basic support for file output 2014-01-11 11:23:58 -05:00
Spencer Russell
83c4199096 README formatting 2014-01-10 22:40:47 -05:00
Spencer Russell
d100457717 adds contributors list to README 2014-01-10 22:40:13 -05:00
Spencer Russell
e060c0a9bc another function rename in the test 2014-01-10 22:36:41 -05:00
Spencer Russell
854eee5c5c adds libsndfile to build.jl dependencies 2014-01-10 22:32:59 -05:00
Spencer Russell
0ca3627668 fixes function name in file reading test 2014-01-10 22:30:52 -05:00
Spencer Russell
207fa26fd9 some minor tweaks and adds a couple convenience functions to file playback 2014-01-10 15:56:42 -05:00
Howard Mao
7d7fd71341 add a FileInput AudioNode type 2014-01-08 18:25:55 -05:00
Howard Mao
5afba7136b add libsndfile bindings 2014-01-06 11:24:53 -05:00
Spencer Russell
1fe3e123c1 tweaks install instructions 2014-01-06 00:30:16 -05:00
Spencer Russell
31c912ce77 tweaks build status image to point to master branch status 2014-01-06 00:21:23 -05:00
Spencer Russell
7ed79f833b adds homebrew dependency handling for OSX 2014-01-05 23:44:50 -05:00
Spencer Russell
c2fdb53925 adds REQUIRE file with BinDeps listed 2014-01-05 23:22:30 -05:00
40 changed files with 2763 additions and 836 deletions

9
.JuliaFormatter.toml Normal file
View file

@ -0,0 +1,9 @@
always_for_in = true
whitespace_typedefs = true
whitespace_ops_in_indices = true
remove_extra_newlines = true
import_to_using = true
short_to_long_function_def = true
format_docstrings = true
align_pair_arrow = false
conditional_to_if = true

22
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,22 @@
Thanks for contributing a pull request!
Please be aware that we are a loose team of volunteers so patience is
necessary. Assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
Again, thanks for contributing!
#### What does this implement/fix?
Explain your changes. Please be as descriptive as possible.
#### Reference issue
Example: Fixes #1234.
#### Additional information
Any additional information you think is important.

25
.github/workflows/CompatHelper.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: CompatHelper
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: "Install CompatHelper"
run: |
import Pkg
name = "CompatHelper"
uuid = "aa819f21-2bde-4658-8897-bab36330d9b7"
version = "2"
Pkg.add(; name, uuid, version)
shell: julia --color=yes {0}
- name: "Run CompatHelper"
run: |
import CompatHelper
CompatHelper.main()
shell: julia --color=yes {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}

16
.github/workflows/Documentation.yml vendored Normal file
View file

@ -0,0 +1,16 @@
name: Build documentation
on:
push:
branches:
- 'master'
jobs:
document:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@latest
with:
version: '1.6'
- uses: julia-actions/julia-docdeploy@releases/v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

15
.github/workflows/TagBot.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: TagBot
on:
issue_comment:
types:
- created
workflow_dispatch:
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.COMPATHELPER_PRIV }}

41
.github/workflows/Tests.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Tests
on:
pull_request:
push:
branches:
- master
tags: '*'
jobs:
test:
timeout-minutes: 30
name: ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.6'
- '1'
- 'nightly'
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
- x86
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
file: lcov.info

11
.gitignore vendored
View file

@ -1,5 +1,12 @@
*.swp
*.so
*.o
deps/usr
deps/deps.jl
deps/build.log
docs/build
*.wav
*.flac
*.cov
coverage
deps/usr/lib/pa_shim.so
deps/usr/lib/pa_shim.dylib
deps/usr/lib/pa_shim.dll

View file

@ -1,13 +0,0 @@
language: cpp
compiler:
- clang
notifications:
email: false
before_install:
- sudo add-apt-repository ppa:staticfloat/julia-deps -y
- sudo add-apt-repository ppa:staticfloat/julianightlies -y
- sudo apt-get update -qq -y
- sudo apt-get install libpcre3-dev julia -y
script:
- julia -e 'Pkg.init(); run(`ln -s $(pwd()) $(Pkg.dir("AudioIO"))`); Pkg.pin("AudioIO"); Pkg.resolve(); Pkg.add("BinDeps"); Pkg.build("AudioIO")'
- test/test.jl

View file

@ -19,3 +19,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
suppressor.jl includes code from the Suppressor.jl package, licensed under the
MIT "Expat" License:
Copyright (c) 2016: Ismael Venegas Castelló.

26
Project.toml Normal file
View file

@ -0,0 +1,26 @@
name = "PortAudio"
uuid = "80ea8bcb-4634-5cb3-8ee8-a132660d1d2d"
repo = "https://github.com/JuliaAudio/PortAudio.jl.git"
version = "1.3.0"
[deps]
alsa_plugins_jll = "5ac2f6bb-493e-5871-9171-112d4c21a6e7"
libportaudio_jll = "2d7b7beb-0762-5160-978e-1ab83a1e8a31"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
[compat]
julia = "1.6"
alsa_plugins_jll = "1.2.2"
libportaudio_jll = "19.6.0"
SampledSignals = "2.1.1"
Suppressor = "0.2"
[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
LibSndFile = "b13ce0c6-77b0-50c6-a2db-140568b8d1a5"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Documenter", "LibSndFile", "Test"]

166
README.md
View file

@ -1,78 +1,126 @@
AudioIO.jl
==========
PortAudio.jl
============
[![Build Status](https://travis-ci.org/ssfrr/AudioIO.jl.png)](https://travis-ci.org/ssfrr/AudioIO.jl)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaAudio.github.io/PortAudio.jl/dev)
[![Tests](https://github.com/JuliaAudio/PortAudio.jl/actions/workflows/Tests.yml/badge.svg)](https://github.com/JuliaAudio/PortAudio.jl/actions/workflows/Tests.yml)
[![codecov](https://codecov.io/gh/JuliaAudio/PortAudio.jl/branch/master/graph/badge.svg?token=mgDAi8ulPY)](https://codecov.io/gh/JuliaAudio/PortAudio.jl)
AudioIO is a Julia library for interfacing to audio streams, which include
playing to and recording from sound cards, reading and writing audio files,
sending to network audio streams, etc. Currently only playing to the sound card
through PortAudio is supported. It is under heavy development, so the API could
change, there will be bugs, there are important missing features.
If you want to try it anyways, from your julia console:
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.
julia> Pkg.clone("https://github.com/ssfrr/AudioIO.jl.git")
julia> Pkg.build("AudioIO")
## Opening a stream
Basic Array Playback
--------------------
The easiest way to open a source or sink is with the default `PortAudioStream()` constructor,
which will open a 2-in, 2-out stream to your system's default device(s).
The constructor can also take the input and output channel counts as positional arguments,
or a variety of other keyword arguments.
If named keyword arguments `latency` or `samplerate` are unspecified, then PortAudio will use device defaults.
Arrays in various formats can be played through your soundcard. Currently the
native format that is delivered to the PortAudio backend is Float32 in the
range of [-1, 1]. Arrays in other sizes of float are converted. Arrays
in Signed or Unsigned Integer types are scaled so that the full range is
mapped to [-1, 1] floating point values.
```julia
PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000, latency=0.1)
```
To play a 1-second burst of noise:
You can open a specific device by adding it as the first argument, either as a `PortAudioDevice` instance or by name. You can also give separate names or devices if you want different input and output devices
julia> v = rand(44100) * 0.1
julia> play(v)
```julia
PortAudioStream(device::PortAudioDevice, args...; kwargs...)
PortAudioStream(devname::AbstractString, args...; kwargs...)
```
AudioNodes
----------
You can get a list of your system's devices with the `PortAudio.devices()` function:
In addition to the basic `play` function you can create more complex networks
of AudioNodes in a render chain. In fact, when using the basic `play` to play
an Array, behind the scenes an instance of the ArrayPlayer type is created
and added to the master AudioMixer inputs. Audionodes also implement a `stop`
function, which will remove them from the render graph. When an implicit
AudioNode is created automatically, such as when using `play` on an Array, the
`play` function should return the audio node that is playing the Array, so it
can be stopped if desired.
```julia
julia> PortAudio.devices()
14-element Vector{PortAudio.PortAudioDevice}:
"sof-hda-dsp: - (hw:0,0)" 2→2
"sof-hda-dsp: - (hw:0,3)" 0→2
"sof-hda-dsp: - (hw:0,4)" 0→2
"sof-hda-dsp: - (hw:0,5)" 0→2
"upmix" 8→8
"vdownmix" 6→6
"dmix" 0→2
"default" 32→32
```
To explictly do the same as above:
## Reading and Writing
julia> v = rand(44100) * 0.1
julia> player = ArrayPlayer(v)
julia> play(player)
The `PortAudioStream` type has `source` and `sink` fields which are of type `PortAudioSource <: SampleSource` and `PortAudioSink <: SampleSink`, respectively. are subtypes of `SampleSource` and `SampleSink`, respectively (from [SampledSignals.jl](https://github.com/JuliaAudio/SampledSignals.jl)). This means they support all the stream and buffer features defined there. For example, if you load SampledSignals with `using SampledSignals` you can read 5 seconds to a buffer with `buf = read(stream.source, 5s)`, regardless of the sample rate of the device.
To generate 2 sin tones:
PortAudio.jl also provides convenience wrappers around the `PortAudioStream` type so you can read and write to it directly, e.g. `write(stream, stream)` will set up a loopback that will read from the input and play it back on the output.
julia> osc1 = SinOsc(440)
julia> osc2 = SinOsc(660)
julia> play(osc1)
julia> play(osc2)
julia> stop(osc1)
julia> stop(osc2)
## Debugging
All AudioNodes must implement a `render` function that can be called to
retreive the next block of audio.
If you are experiencing issues and wish to view detailed logging and debug information, set
AudioStreams
------------
```
ENV["JULIA_DEBUG"] = :PortAudio
```
AudioStreams represent an external source or destination for audio, such as the
sound card. The `play` function attaches AudioNodes to the default stream
unless a stream is given as the 2nd argument.
before using the package.
AudioStream is an abstract type, which currently has a PortAudioStream subtype
that writes to the sound card, and a TestAudioStream that is used in the unit
tests.
## Examples
Currently only 1 stream at a time is supported so there's no reason to provide
an explicit stream to the `play` function. The stream has a root mixer field
which is an instance of the AudioMixer type, so that multiple AudioNodes
can be heard at the same time. Whenever a new frame of audio is needed by the
sound card, the stream calls the `render` method on the root audio mixer, which
will in turn call the `render` methods on any input AudioNodes that are set
up as inputs.
### Set up an audio pass-through from microphone to speaker
```julia
stream = PortAudioStream(2, 2)
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
PortAudioStream("default", "default") do stream
write(stream, stream)
end
```
### Record 10 seconds of audio and save to an ogg file
```julia
julia> import LibSndFile # must be in Manifest for FileIO.save to work
julia> using PortAudio: PortAudioStream
julia> using SampledSignals: s
julia> using FileIO: save
julia> stream = PortAudioStream(1, 0) # default input (e.g., built-in microphone)
PortAudioStream{Float32}
Samplerate: 44100.0Hz
2 channel source: "default"
julia> buf = read(stream, 10s)
480000-frame, 2-channel SampleBuf{Float32, 2, SIUnits.SIQuantity{Int64,0,0,-1,0,0,0,0,0,0}}
10.0 s at 48000 s⁻¹
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁
▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁
julia> close(stream)
julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf)
```
### Play an audio signal through the default sound output device
```julia
using PortAudio, SampledSignals
S = 8192 # sampling rate (samples / second)
x = cos.(2pi*(1:2S)*440/S) # A440 tone for 2 seconds
PortAudioStream(0, 2; samplerate=S) do stream
write(stream, x)
end
```

24
deps/build.jl vendored
View file

@ -1,24 +0,0 @@
using BinDeps
@BinDeps.setup
ENV["JULIA_ROOT"] = abspath(JULIA_HOME, "../../")
libportaudio = library_dependency("libportaudio")
# TODO: add other providers with correct names
provides(AptGet,
{"portaudio19-dev" => libportaudio}
)
@BinDeps.install [:libportaudio => :libportaudio]
cd(joinpath(Pkg.dir(), "AudioIO", "deps", "src") )
run(`make`)
if (!ispath("../usr"))
run(`mkdir ../usr`)
end
if (!ispath("../usr/lib"))
run(`mkdir ../usr/lib`)
end
run(`mv libportaudio_shim.$(BinDeps.shlib_ext) ../usr/lib`)

51
deps/src/Makefile vendored
View file

@ -1,51 +0,0 @@
# Makefile lifted from Clang.jl
all: default
ifeq (exists, $(shell [ -e Make.user ] && echo exists ))
include Make.user
endif
.PHONY: all clean check-env default
#check-env:
#ifndef JULIA_INC
# $(error Environment variable JULIA_INC is not set.)
#endif
#INC =-I"$(JULIA_INC)"
FLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC
CFLAGS =-g
LIBS =-lportaudio
LINUX_LIBS =-lrt
LINUX_LDFLAGS =-rdynamic
OBJS = shim.o
# Figure out OS and architecture
OS = $(shell uname)
ifeq ($(OS), MINGW32_NT-6.1)
OS=WINNT
endif
# file extensions and platform-specific libs
ifeq ($(OS), WINNT)
SHLIB_EXT = dll
else ifeq ($(OS), Darwin)
SHLIB_EXT = dylib
else
LIBS += $(LINUX_LIBS)
LDFLAGS += $(LINUX_LDFLAGS)
SHLIB_EXT = so
endif
default: libportaudio_shim.$(SHLIB_EXT)
%.o: %.c Makefile
$(CC) $< -fPIC -c -o $@ $(INC) $(CFLAGS) $(FLAGS)
libportaudio_shim.$(SHLIB_EXT): $(OBJS)
$(CC) $(OBJS) -shared -o $@ $(LDFLAGS) $(LIBS)
clean:
rm -f *.o *.$(SHLIB_EXT)

110
deps/src/shim.c vendored
View file

@ -1,110 +0,0 @@
#include <portaudio.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
static int paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData);
static PaStream *AudioStream;
static int JuliaPipeReadFD = 0;
static int JuliaPipeWriteFD = 0;
static sem_t CSemaphore;
static void *OutData = NULL;
static unsigned long OutFrames = 0;
int make_pipe(void)
{
int pipefd[2];
pipe(pipefd);
JuliaPipeReadFD = pipefd[0];
JuliaPipeWriteFD = pipefd[1];
sem_init(&CSemaphore, 0, 0);
return JuliaPipeReadFD;
}
void wake_callback_thread(void *outData, unsigned int outFrames)
{
OutData = outData;
OutFrames = outFrames;
sem_post(&CSemaphore);
}
PaError open_stream(unsigned int sampleRate, unsigned int bufSize)
{
PaError err;
err = Pa_OpenDefaultStream(&AudioStream,
0, /* no input channels */
1, /* mono output */
paFloat32, /* 32 bit floating point output */
sampleRate,
bufSize, /* frames per buffer, i.e. the number of sample frames
that PortAudio will request from the callback. Many
apps may want to use paFramesPerBufferUnspecified,
which tells PortAudio to pick the best, possibly
changing, buffer size.*/
paCallback, /* this is your callback function */
NULL); /*This is a pointer that will be passed to your callback*/
if(err != paNoError)
{
return err;
}
err = Pa_StartStream(AudioStream);
if(err != paNoError)
{
return err;
}
return paNoError;
}
//PaError stop_sin(void)
//{
// PaError err;
// err = Pa_StopStream(sin_stream);
// if(err != paNoError)
// {
// return err;
// }
//
// err = Pa_CloseStream(sin_stream);
// if( err != paNoError )
// {
// return err;
// }
// return paNoError;
//}
/*
* 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().
*/
static int paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
unsigned int i;
unsigned char fd_data = 0;
sem_wait(&CSemaphore);
for(i=0; i<framesPerBuffer; i++)
{
((float *)outputBuffer)[i] = ((float *)OutData)[i];
}
// TODO: copy the input data somewhere
write(JuliaPipeWriteFD, &fd_data, 1);
return 0;
}

2
docs/Project.toml Normal file
View file

@ -0,0 +1,2 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

12
docs/make.jl Normal file
View file

@ -0,0 +1,12 @@
using PortAudio
using Documenter: deploydocs, makedocs
makedocs(
sitename = "PortAudio.jl",
modules = [PortAudio],
pages = [
"Public interface" => "index.md",
"Internals" => "internals.md"
]
)
deploydocs(repo = "github.com/JuliaAudio/PortAudio.jl.git")

10
docs/src/index.md Normal file
View file

@ -0,0 +1,10 @@
# Public interface
```@index
Pages = ["index.md"]
```
```@autodocs
Modules = [PortAudio]
Private = false
```

10
docs/src/internals.md Normal file
View file

@ -0,0 +1,10 @@
# Internals
```@index
Pages = ["internals.md"]
```
```@autodocs
Modules = [PortAudio]
Public = false
```

55
examples/audiometer.jl Normal file
View file

@ -0,0 +1,55 @@
using PortAudio
"""
Continuously read from the default audio input and plot an
ASCII level/peak meter
"""
function micmeter(metersize)
mic = PortAudioStream(1, 0; latency = 0.1)
signalmax = zero(eltype(mic))
println("Press Ctrl-C to quit")
while true
block = read(mic, 512)
blockmax = maximum(abs.(block)) # find the maximum value in the block
signalmax = max(signalmax, blockmax) # keep the maximum value ever
print("\r") # reset the cursor to the beginning of the line
printmeter(metersize, blockmax, signalmax)
end
end
"""
Print an ASCII level meter of the given size. Signal and peak
levels are assumed to be scaled from 0.0-1.0, with peak >= signal
"""
function printmeter(metersize, signal, peak)
# calculate the positions in terms of characters
peakpos = clamp(round(Int, peak * metersize), 0, metersize)
meterchars = clamp(round(Int, signal * metersize), 0, peakpos - 1)
blankchars = max(0, peakpos - meterchars - 1)
for position in 1:meterchars
printstyled(">", color = barcolor(metersize, position))
end
print(" "^blankchars)
printstyled("|", color = barcolor(metersize, peakpos))
print(" "^(metersize - peakpos))
end
"""
Compute the proper color for a given position in the bar graph. The first
half of the bar should be green, then the remainder is yellow except the final
character, which is red.
"""
function barcolor(metersize, position)
if position / metersize <= 0.5
:green
elseif position == metersize
:red
else
:yellow
end
end
micmeter(80)

179
examples/lilyplay.jl Normal file
View file

@ -0,0 +1,179 @@
using Distributed, PortAudio
# Modified from Jiahao Chen's example in the obsolete AudioIO module.
# Will use first output device found in system's listing or DEFAULTDEVICE if set below
const DEFAULTDEVICE = -1
function paudio()
devs = PortAudio.devices()
if DEFAULTDEVICE < 0
devnum = findfirst(x -> x.maxoutchans > 0, devs)
(devnum == nothing) && error("No output device for audio found")
else
devnum = DEFAULTDEVICE + 1
end
return ostream = PortAudioStream(devs[devnum].name, 0, 2)
end
play(ostream, sample::Array{Float64, 1}) = write(ostream, sample)
play(ostr, sample::Array{Int64, 1}) = play(ostr, Float64.(sample))
struct Note{S <: Real, T <: Real}
pitch::S
duration::T
sustained::Bool
end
function play(
ostream,
A::Note,
samplingfreq::Real = 44100,
shape::Function = t -> 0.6sin(t) + 0.2sin(2t) + 0.05 * sin(8t),
)
timesamples = 0:(1 / samplingfreq):(A.duration * (A.sustained ? 0.98 : 0.9))
v = Float64[shape(2π * A.pitch * t) for t in timesamples]
if !A.sustained
decay_length = div(length(timesamples), 5)
v[(end - decay_length):(end - 1)] =
v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length)
end
play(ostream, v)
sleep(A.duration)
end
function parsevoice(melody::String; tempo = 132, beatunit = 4, lyrics = nothing)
ostream = paudio() # initialize audio for output
lyrics_syllables = lyrics == nothing ? nothing : split(lyrics)
lyrics_syllables != nothing && (lyrics_syllables[end] *= "\n")
note_idx = 1
oldduration = 4
for line in split(melody, '\n')
percent_idx = findfirst('%', line) # Trim comment
percent_idx == nothing || (line = line[1:(percent_idx - 1)])
for token in split(line)
pitch, duration, dotted, sustained = parsetoken(token)
duration == nothing && (duration = oldduration)
oldduration = duration
dotted && (duration *= 1.5)
if lyrics_syllables != nothing && 1 <= note_idx <= length(lyrics_syllables)
# Print the lyrics, omitting hyphens
if lyrics_syllables[note_idx][end] == '-'
print(join(split(lyrics_syllables[note_idx][:], "")[1:(end - 1)]), "")
else
print(lyrics_syllables[note_idx], ' ')
end
end
play(ostream, Note(pitch, (beatunit / duration) * (60 / tempo), sustained))
note_idx += 1
end
end
end
function parsetoken(token, Atuning::Real = 220)
state = :findpitch
pitch = 0.0
sustain = dotted = false
lengthbuf = Char[]
for char in token
if state == :findpitch
scale_idx =
something(findfirst(char, String(collect('a':'g'))), 0) +
something(findfirst(char, String(collect('A':'G'))), 0)
if scale_idx != 0
halfsteps = [12, 14, 3, 5, 7, 8, 10]
pitch = Atuning * 2^(halfsteps[scale_idx] / 12)
state = :findlength
elseif char == 'r'
pitch, state = 0, :findlength
else
error("unknown pitch: $char")
end
elseif state == :findlength
if char == '#'
pitch *= 2^(1 / 12) # sharp
elseif char == 'b'
pitch /= 2^(1 / 12) # flat
elseif char == '\''
pitch *= 2 # higher octave
elseif char == ','
pitch /= 2 # lower octave
elseif char == '.'
dotted = true # dotted note
elseif char == '~'
sustain = true # tied note
else
push!(lengthbuf, char)
# Check for "is" and "es" suffixes for sharps and flats
if length(lengthbuf) >= 2
if lengthbuf[(end - 1):end] == "is"
pitch *= 2^(1 / 12)
lengthbuf = lengthbuf[1:(end - 2)]
elseif lengthbuf[(end - 1):end] == "es"
pitch /= 2^(1 / 12)
lengthbuf = lengthbuf[1:(end - 2)]
end
end
end
end
end
#finalize length
lengthstr = String(lengthbuf)
duration = isempty(lengthstr) ? nothing : tryparse(Int, lengthstr)
return (pitch, duration, sustain, dotted)
end
parsevoice(
"""
f# f# g a a g f# e d d e f# f#~ f#8 e e2
f#4 f# g a a g f# e d d e f# e~ e8 d d2
e4 e f# d e f#8~ g8 f#4 d e f#8~ g f#4 e d e a,
f#2 f#4 g a a g f# e d d e f# e~ e8 d8 d2""",
lyrics = """
Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um!
Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum!
Dei- ne Zau- ber bin den - wie- der, was die - Mo- de streng ge- theilt,
al- le mensch- en wer- den Brü- der wo dein sanf- ter Flü- - gel weilt.
""",
)
# And now with harmony!
soprano = @spawn parsevoice(
"""
f'#. f'#. g'. a'. a'. g'. f'#. e'~ e'8 d.'4 d.' e.' f#'. f#'.~ f#' e'8 e'4~ e'2
""",
lyrics = "Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um!",
)
alto = @spawn parsevoice("""
a. a. a. a. a. a. a. a~ g8 f#.4 a. a. a. a.~ a a8 a4~ a2
""")
tenor = @spawn parsevoice("""
d. d. e. f#. f#. e. d. d~ e8 f#.4 f#. a,. d. d.~ d c#8 c#4 c#2
""")
bass = @spawn parsevoice("""
d. d. d. d. a,. a,. a,. b,~ c8 d. a., a., a., a., a, a8, a,4 a,2
""")
wait(soprano)
wait(alto)
wait(tenor)
wait(bass)
soprano = @spawn parsevoice(
"""
f'#.4 f'#. g'. a'. a'. g'. f'#. e'. d'. d'. e'. f'#. e'.~ e' d'8 d'4~ d'2
""",
lyrics = "Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum!",
)
alto = @spawn parsevoice("""
a.4 a. b. c'. c'. b. a. g. f#. f#. g. f#. g.~ g4 f#8 f#~ f#2
""")
tenor = @spawn parsevoice("""
d.4 d. d. d. d. d. d. d. d. d. c#. d. c#.~ c# d8 d d2
""")
bass = @spawn parsevoice("""
d.4 d. d. d. a,. a,. a,. a., a., a., a., a., a.,~ a, a,8 d, d,2
""")
wait(soprano)
wait(alto)
wait(tenor)
wait(bass)

View file

@ -0,0 +1,65 @@
using PortAudio
using DSP
function create_measure_signal()
signal = zeros(Float32, 20000)
for i in 1:3
signal = vcat(signal, rand(Float32, 100), zeros(Float32, i * 10000))
end
return signal
end
function measure_latency(in_latency = 0.1, out_latency = 0.1; is_warmup = false)
in_stream = PortAudioStream(1, 0; latency = in_latency)
out_stream = PortAudioStream(0, 1; latency = out_latency)
cond = Base.Event()
writer_start_time = Int64(0)
reader_start_time = Int64(0)
reader = Threads.@spawn begin
wait(cond)
writer_start_time = time_ns() |> Int64
return read(in_stream, 100000)
end
signal = create_measure_signal()
writer = Threads.@spawn begin
wait(cond)
reader_start_time = time_ns() |> Int64
write(out_stream, signal)
end
notify(cond)
wait(reader)
wait(writer)
recorded = collect(reader.result)[:, 1]
close(in_stream)
close(out_stream)
diff = reader_start_time - writer_start_time |> abs
diff_in_ms = diff / 10^6 # 1 ms = 10^6 ns
if !is_warmup && diff_in_ms > 1
@warn "Threads start time difference $diff_in_ms ms is bigger than 1 ms"
end
delay = finddelay(recorded, signal) / 48000
return trunc(Int, delay * 1000)# result in ms
end
measure_latency(0.1, 0.1; is_warmup = true) # warmup
latencies = [0.1, 0.01, 0.005]
for in_latency in latencies
for out_latency in latencies
measure = measure_latency(in_latency, out_latency)
println("$measure ms latency for in_latency=$in_latency, out_latency=$out_latency")
end
end

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

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

20
examples/spectrum.jl Normal file
View file

@ -0,0 +1,20 @@
# plot a real-time spectrogram. This example is adapted from the GR example
# at http://gr-framework.org/examples/audio_ex.html
module SpectrumExample
using GR, PortAudio, SampledSignals, FFTW
const N = 1024
const stream = PortAudioStream(1, 0)
const buf = read(stream, N)
const fmin = 0Hz
const fmax = 10000Hz
const fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])]
while true
read!(stream, buf)
plot(fs, abs.(fft(buf)[fmin..fmax]), xlim = (fs[1], fs[end]), ylim = (0, 100))
end
end

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

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

View file

@ -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)
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

View file

@ -0,0 +1,43 @@
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)
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

1
gen/README.md Normal file
View file

@ -0,0 +1 @@
The clang generators will automatically generate wrappers for a C library based on its headers. So everything you see in libportaudio.jl is automatically generated from the C library. If a newer version of portaudio adds more features, we won't have to add new wrappers: clang will handle it for us. It is easy to use currently unused features: the wrappers have already been written for us. Even though it does an admirable job, clang doesn't handle errors and set locks. Fortunately, it's very easy to add secondary wrappers, or just do it at point of use.

16
gen/generator.jl Normal file
View file

@ -0,0 +1,16 @@
using Clang.Generators
using libportaudio_jll
cd(@__DIR__)
include_dir = joinpath(libportaudio_jll.artifact_dir, "include") |> normpath
portaudio_h = joinpath(include_dir, "portaudio.h")
options = load_options(joinpath(@__DIR__, "generator.toml"))
args = get_default_args()
push!(args, "-I$include_dir")
ctx = create_context(portaudio_h, args, options)
build!(ctx)

9
gen/generator.toml Normal file
View file

@ -0,0 +1,9 @@
[general]
library_name = "libportaudio"
output_file_path = "../src/LibPortAudio.jl"
module_name = "LibPortAudio"
jll_pkg_name = "libportaudio_jll"
export_symbol_prefixes = ["Pa", "pa"]
use_julia_native_enum_type = true
auto_mutability = true

View file

@ -1,82 +0,0 @@
module AudioIO
# export the basic API
export play, stop
# default stream used when none is given
_stream = nothing
################## Types ####################
typealias AudioSample Float32
# A frame of audio, possibly multi-channel
typealias AudioBuf Array{AudioSample}
# A node in the render tree
abstract AudioNode
# A stream of audio (for instance that writes to hardware)
# All AudioStream subtypes should have a mixer and info field
abstract AudioStream
# Info about the hardware device
type DeviceInfo
sample_rate::Integer
buf_size::Integer
end
include("nodes.jl")
include("portaudio.jl")
############ Exported Functions #############
# Play an AudioNode by adding it as an input to the root mixer node
function play(node::AudioNode, stream::AudioStream)
node.active = true
add_input(stream.mixer, node)
return node
end
# If the stream is not given, use the default global PortAudio stream
function play(node::AudioNode)
global _stream
if _stream == nothing
_stream = PortAudioStream()
end
play(node, _stream)
end
# Allow users to play a raw array by wrapping it in an ArrayPlayer
function play(arr::AudioBuf, args...)
player = ArrayPlayer(arr)
play(player, args...)
end
# If the array is the wrong floating type, convert it
function play{T <: FloatingPoint}(arr::Array{T}, args...)
arr = convert(AudioBuf, arr)
play(arr, args...)
end
# If the array is an integer type, scale to [-1, 1] floating point
# integer audio can be slightly (by 1) more negative than positive,
# so we just scale so that +/- typemax(T) becomes +/- 1
function play{T <: Signed}(arr::Array{T}, args...)
arr = arr / typemax(T)
play(arr, args...)
end
function play{T <: Unsigned}(arr::Array{T}, args...)
zero = (typemax(T) + 1) / 2
range = floor(typemax(T) / 2)
arr = (arr - zero) / range
play(arr, args...)
end
function stop(node::AudioNode)
node.active = false
return node
end
end # module AudioIO

1069
src/PortAudio.jl Normal file

File diff suppressed because it is too large Load diff

439
src/libportaudio.jl Normal file
View file

@ -0,0 +1,439 @@
module LibPortAudio
using libportaudio_jll
export libportaudio_jll
function Pa_GetVersion()
ccall((:Pa_GetVersion, libportaudio), Cint, ())
end
function Pa_GetVersionText()
ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ())
end
mutable struct PaVersionInfo
versionMajor::Cint
versionMinor::Cint
versionSubMinor::Cint
versionControlRevision::Ptr{Cchar}
versionText::Ptr{Cchar}
end
# no prototype is found for this function at portaudio.h:114:22, please use with caution
function Pa_GetVersionInfo()
ccall((:Pa_GetVersionInfo, libportaudio), Ptr{PaVersionInfo}, ())
end
const PaError = Cint
@enum PaErrorCode::Int32 begin
paNoError = 0
paNotInitialized = -10000
paUnanticipatedHostError = -9999
paInvalidChannelCount = -9998
paInvalidSampleRate = -9997
paInvalidDevice = -9996
paInvalidFlag = -9995
paSampleFormatNotSupported = -9994
paBadIODeviceCombination = -9993
paInsufficientMemory = -9992
paBufferTooBig = -9991
paBufferTooSmall = -9990
paNullCallback = -9989
paBadStreamPtr = -9988
paTimedOut = -9987
paInternalError = -9986
paDeviceUnavailable = -9985
paIncompatibleHostApiSpecificStreamInfo = -9984
paStreamIsStopped = -9983
paStreamIsNotStopped = -9982
paInputOverflowed = -9981
paOutputUnderflowed = -9980
paHostApiNotFound = -9979
paInvalidHostApi = -9978
paCanNotReadFromACallbackStream = -9977
paCanNotWriteToACallbackStream = -9976
paCanNotReadFromAnOutputOnlyStream = -9975
paCanNotWriteToAnInputOnlyStream = -9974
paIncompatibleStreamHostApi = -9973
paBadBufferPtr = -9972
end
function Pa_GetErrorText(errorCode)
ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), errorCode)
end
function Pa_Initialize()
ccall((:Pa_Initialize, libportaudio), PaError, ())
end
function Pa_Terminate()
ccall((:Pa_Terminate, libportaudio), PaError, ())
end
const PaDeviceIndex = Cint
const PaHostApiIndex = Cint
function Pa_GetHostApiCount()
ccall((:Pa_GetHostApiCount, libportaudio), PaHostApiIndex, ())
end
function Pa_GetDefaultHostApi()
ccall((:Pa_GetDefaultHostApi, libportaudio), PaHostApiIndex, ())
end
@enum PaHostApiTypeId::UInt32 begin
paInDevelopment = 0
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
end
mutable struct PaHostApiInfo
structVersion::Cint
type::PaHostApiTypeId
name::Ptr{Cchar}
deviceCount::Cint
defaultInputDevice::PaDeviceIndex
defaultOutputDevice::PaDeviceIndex
end
function Pa_GetHostApiInfo(hostApi)
ccall(
(:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo},
(PaHostApiIndex,),
hostApi,
)
end
function Pa_HostApiTypeIdToHostApiIndex(type)
ccall(
(:Pa_HostApiTypeIdToHostApiIndex, libportaudio),
PaHostApiIndex,
(PaHostApiTypeId,),
type,
)
end
function Pa_HostApiDeviceIndexToDeviceIndex(hostApi, hostApiDeviceIndex)
ccall(
(:Pa_HostApiDeviceIndexToDeviceIndex, libportaudio),
PaDeviceIndex,
(PaHostApiIndex, Cint),
hostApi,
hostApiDeviceIndex,
)
end
mutable struct PaHostErrorInfo
hostApiType::PaHostApiTypeId
errorCode::Clong
errorText::Ptr{Cchar}
end
function Pa_GetLastHostErrorInfo()
ccall((:Pa_GetLastHostErrorInfo, libportaudio), Ptr{PaHostErrorInfo}, ())
end
function Pa_GetDeviceCount()
ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())
end
function Pa_GetDefaultInputDevice()
ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
end
function Pa_GetDefaultOutputDevice()
ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
end
const PaTime = Cdouble
const PaSampleFormat = Culong
mutable struct PaDeviceInfo
structVersion::Cint
name::Ptr{Cchar}
hostApi::PaHostApiIndex
maxInputChannels::Cint
maxOutputChannels::Cint
defaultLowInputLatency::PaTime
defaultLowOutputLatency::PaTime
defaultHighInputLatency::PaTime
defaultHighOutputLatency::PaTime
defaultSampleRate::Cdouble
end
function Pa_GetDeviceInfo(device)
ccall((:Pa_GetDeviceInfo, libportaudio), Ptr{PaDeviceInfo}, (PaDeviceIndex,), device)
end
struct PaStreamParameters
device::PaDeviceIndex
channelCount::Cint
sampleFormat::PaSampleFormat
suggestedLatency::PaTime
hostApiSpecificStreamInfo::Ptr{Cvoid}
end
function Pa_IsFormatSupported(inputParameters, outputParameters, sampleRate)
ccall(
(:Pa_IsFormatSupported, libportaudio),
PaError,
(Ptr{PaStreamParameters}, Ptr{PaStreamParameters}, Cdouble),
inputParameters,
outputParameters,
sampleRate,
)
end
const PaStream = Cvoid
const PaStreamFlags = Culong
mutable struct PaStreamCallbackTimeInfo
inputBufferAdcTime::PaTime
currentTime::PaTime
outputBufferDacTime::PaTime
end
const PaStreamCallbackFlags = Culong
@enum PaStreamCallbackResult::UInt32 begin
paContinue = 0
paComplete = 1
paAbort = 2
end
# typedef int PaStreamCallback ( const void * input , void * output , unsigned long frameCount , const PaStreamCallbackTimeInfo * timeInfo , PaStreamCallbackFlags statusFlags , void * userData )
const PaStreamCallback = Cvoid
function Pa_OpenStream(
stream,
inputParameters,
outputParameters,
sampleRate,
framesPerBuffer,
streamFlags,
streamCallback,
userData,
)
ccall(
(:Pa_OpenStream, libportaudio),
PaError,
(
Ptr{Ptr{PaStream}},
Ptr{PaStreamParameters},
Ptr{PaStreamParameters},
Cdouble,
Culong,
PaStreamFlags,
Ptr{Cvoid},
Ptr{Cvoid},
),
stream,
inputParameters,
outputParameters,
sampleRate,
framesPerBuffer,
streamFlags,
streamCallback,
userData,
)
end
function Pa_OpenDefaultStream(
stream,
numInputChannels,
numOutputChannels,
sampleFormat,
sampleRate,
framesPerBuffer,
streamCallback,
userData,
)
ccall(
(:Pa_OpenDefaultStream, libportaudio),
PaError,
(
Ptr{Ptr{PaStream}},
Cint,
Cint,
PaSampleFormat,
Cdouble,
Culong,
Ptr{Cvoid},
Ptr{Cvoid},
),
stream,
numInputChannels,
numOutputChannels,
sampleFormat,
sampleRate,
framesPerBuffer,
streamCallback,
userData,
)
end
function Pa_CloseStream(stream)
ccall((:Pa_CloseStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
# typedef void PaStreamFinishedCallback ( void * userData )
const PaStreamFinishedCallback = Cvoid
function Pa_SetStreamFinishedCallback(stream, streamFinishedCallback)
ccall(
(:Pa_SetStreamFinishedCallback, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}),
stream,
streamFinishedCallback,
)
end
function Pa_StartStream(stream)
ccall((:Pa_StartStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_StopStream(stream)
ccall((:Pa_StopStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_AbortStream(stream)
ccall((:Pa_AbortStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_IsStreamStopped(stream)
ccall((:Pa_IsStreamStopped, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_IsStreamActive(stream)
ccall((:Pa_IsStreamActive, libportaudio), PaError, (Ptr{PaStream},), stream)
end
mutable struct PaStreamInfo
structVersion::Cint
inputLatency::PaTime
outputLatency::PaTime
sampleRate::Cdouble
end
function Pa_GetStreamInfo(stream)
ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo}, (Ptr{PaStream},), stream)
end
function Pa_GetStreamTime(stream)
ccall((:Pa_GetStreamTime, libportaudio), PaTime, (Ptr{PaStream},), stream)
end
function Pa_GetStreamCpuLoad(stream)
ccall((:Pa_GetStreamCpuLoad, libportaudio), Cdouble, (Ptr{PaStream},), stream)
end
function Pa_ReadStream(stream, buffer, frames)
ccall(
(:Pa_ReadStream, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}, Culong),
stream,
buffer,
frames,
)
end
function Pa_WriteStream(stream, buffer, frames)
ccall(
(:Pa_WriteStream, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}, Culong),
stream,
buffer,
frames,
)
end
function Pa_GetStreamReadAvailable(stream)
ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
end
function Pa_GetStreamWriteAvailable(stream)
ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
end
function Pa_GetSampleSize(format)
ccall((:Pa_GetSampleSize, libportaudio), PaError, (PaSampleFormat,), format)
end
function Pa_Sleep(msec)
ccall((:Pa_Sleep, libportaudio), Cvoid, (Clong,), msec)
end
const paNoDevice = PaDeviceIndex(-1)
const paUseHostApiSpecificDeviceSpecification = PaDeviceIndex(-2)
const paFloat32 = PaSampleFormat(0x00000001)
const paInt32 = PaSampleFormat(0x00000002)
const paInt24 = PaSampleFormat(0x00000004)
const paInt16 = PaSampleFormat(0x00000008)
const paInt8 = PaSampleFormat(0x00000010)
const paUInt8 = PaSampleFormat(0x00000020)
const paCustomFormat = PaSampleFormat(0x00010000)
const paNonInterleaved = PaSampleFormat(0x80000000)
const paFormatIsSupported = 0
const paFramesPerBufferUnspecified = 0
const paNoFlag = PaStreamFlags(0)
const paClipOff = PaStreamFlags(0x00000001)
const paDitherOff = PaStreamFlags(0x00000002)
const paNeverDropInput = PaStreamFlags(0x00000004)
const paPrimeOutputBuffersUsingStreamCallback = PaStreamFlags(0x00000008)
const paPlatformSpecificFlags = PaStreamFlags(0xffff0000)
const paInputUnderflow = PaStreamCallbackFlags(0x00000001)
const paInputOverflow = PaStreamCallbackFlags(0x00000002)
const paOutputUnderflow = PaStreamCallbackFlags(0x00000004)
const paOutputOverflow = PaStreamCallbackFlags(0x00000008)
const paPrimingOutput = PaStreamCallbackFlags(0x00000010)
# exports
const PREFIXES = ["Pa", "pa"]
for name in names(@__MODULE__; all = true), prefix in PREFIXES
if startswith(string(name), prefix)
@eval export $name
end
end
end # module

View file

@ -1,139 +0,0 @@
export SinOsc, AudioMixer, ArrayPlayer, AudioInput
#### SinOsc ####
# Generates a sin tone at the given frequency
type SinOsc <: AudioNode
active::Bool
freq::Real
phase::FloatingPoint
function SinOsc(freq::Real)
new(false, freq, 0.0)
end
end
function render(node::SinOsc, device_input::AudioBuf, info::DeviceInfo)
phase = AudioSample[1:info.buf_size] * 2pi * node.freq / info.sample_rate
phase += node.phase
node.phase = phase[end]
return sin(phase), node.active
end
#### AudioMixer ####
# Mixes a set of inputs equally
# a convenience alias used in the array of mix inputs
typealias MaybeAudioNode Union(AudioNode, Nothing)
const MAX_MIXER_INPUTS = 32
type AudioMixer <: AudioNode
active::Bool
mix_inputs::Array{MaybeAudioNode}
function AudioMixer{T <: AudioNode}(mix_inputs::Array{T})
input_array = Array(MaybeAudioNode, MAX_MIXER_INPUTS)
fill!(input_array, nothing)
for (i, node) in enumerate(mix_inputs)
input_array[i] = node
end
new(false, input_array)
end
function AudioMixer()
AudioMixer(AudioNode[])
end
end
# TODO: at some point we need to figure out what the general API is for wiring
# up AudioNodes to each other
function add_input(mixer::AudioMixer, in_node::AudioNode)
for (i, node) in enumerate(mixer.mix_inputs)
if node === nothing
mixer.mix_inputs[i] = in_node
return
end
end
error("Mixer input array is full")
end
# removes the given node from the mix inputs. If the node isn't an input the
# function returns without error
function remove_input(mixer::AudioMixer, in_node::AudioNode)
for (i, node) in enumerate(mixer.mix_inputs)
if node === in_node
mixer.mix_inputs[i] = nothing
return
end
end
# not an error if we didn't find it
end
function render(node::AudioMixer, device_input::AudioBuf, info::DeviceInfo)
# TODO: we probably want to pre-allocate this buffer and share between
# render calls. Unfortunately we don't know the right size when the object
# is created, so maybe we check the size on every render call and only
# re-allocate when the size changes? I suppose that's got to be cheaper
# than the GC and allocation every frame
mix_buffer = zeros(AudioSample, info.buf_size)
for in_node in node.mix_inputs
if in_node !== nothing
in_buffer, active = render(in_node, device_input, info)
mix_buffer += in_buffer
if !active
remove_input(node, in_node)
end
end
end
return mix_buffer, node.active
end
#### Array Player ####
# Plays a AudioBuf by rendering it out piece-by-piece
type ArrayPlayer <: AudioNode
active::Bool
arr::AudioBuf
arr_index::Int
function ArrayPlayer(arr::AudioBuf)
new(false, arr, 1)
end
end
function render(node::ArrayPlayer, device_input::AudioBuf, info::DeviceInfo)
# TODO: this should remove itself from the render tree when playback is
# complete
i = node.arr_index
range_end = min(i + info.buf_size-1, length(node.arr))
output = node.arr[i:range_end]
if length(output) < info.buf_size
# we're finished with the array, pad with zeros and clear our active
# flag
output = vcat(output, zeros(AudioSample, info.buf_size - length(output)))
node.active = false
end
node.arr_index = range_end + 1
return output, node.active
end
#### AudioInput ####
# Renders incoming audio input from the hardware
type AudioInput <: AudioNode
active::Bool
channel::Int
function AudioInput(channel::Int)
new(false, channel)
end
end
function render(node::AudioInput, device_input::AudioBuf, info::DeviceInfo)
@assert size(device_input, 1) == info.buf_size
return device_input[:, node.channel], node.active
end

View file

@ -1,176 +0,0 @@
typealias PaTime Cdouble
typealias PaError Cint
typealias PaSampleFormat Culong
typealias PaStream Void
const PA_NO_ERROR = 0
const libportaudio_shim = find_library(["libportaudio_shim",],
[Pkg.dir("AudioIO", "deps", "usr", "lib"),])
# track whether we've already inited PortAudio
portaudio_inited = false
################## Types ####################
type PortAudioStream <: AudioStream
mixer::AudioMixer
info::DeviceInfo
function PortAudioStream(sample_rate::Int=44100, buf_size::Int=1024)
global portaudio_inited
if !portaudio_inited
@assert(libportaudio_shim != "", "Failed to find required library libportaudio_shim. Try re-running the package script using Pkg.build(\"AudioIO\"), then reloading with reload(\"AudioIO\")")
init_portaudio()
portaudio_inited = true
else
error("Currently only 1 stream is supported at a time")
end
mixer = AudioMixer()
stream = new(mixer, DeviceInfo(sample_rate, buf_size))
# we need to start up the stream with the portaudio library
open_portaudio_stream(stream)
return stream
end
end
############ Internal Functions ############
function wake_callback_thread(out_array)
ccall((:wake_callback_thread, libportaudio_shim), Void,
(Ptr{Void}, Cuint),
out_array, size(out_array, 1))
end
function init_portaudio()
info("Initializing PortAudio. Expect errors as we scan devices")
err = ccall((:Pa_Initialize, "libportaudio"), PaError, ())
handle_status(err)
end
function open_portaudio_stream(stream::PortAudioStream)
# starts up a stream with the portaudio library and associates it with the
# given AudioIO PortAudioStream
# TODO: handle more streams
fd = ccall((:make_pipe, libportaudio_shim), Cint, ())
info("Launching PortAudio Task...")
function task_wrapper()
portaudio_task(fd, stream)
end
schedule(Task(task_wrapper))
# TODO: test not yielding here
yield()
info("Audio Task Yielded, starting the stream...")
err = ccall((:open_stream, libportaudio_shim), PaError,
(Cuint, Cuint),
stream.info.sample_rate, stream.info.buf_size)
handle_status(err)
info("Portaudio stream started.")
end
function handle_status(err::PaError)
if err != PA_NO_ERROR
msg = ccall((:Pa_GetErrorText, "libportaudio"),
Ptr{Cchar}, (PaError,), err)
error("libportaudio: " * bytestring(msg))
end
end
function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream)
info("Audio Task Launched")
in_array = zeros(AudioSample, stream.info.buf_size)
desc_bytes = Cchar[0]
jl_stream = fdio(jl_filedesc)
jl_rawfd = RawFD(jl_filedesc)
try
while true
# assume the root mixer is always active
out_array::AudioBuf, _::Bool = render(stream.mixer, in_array,
stream.info)
# wake the C code so it knows we've given it some more data
wake_callback_thread(out_array)
# wait for new data to be available from the sound card (and for it
# to have processed our last frame of data). At some point we
# should do something with the data we get from the callback
wait(jl_rawfd, readable=true)
# read from the file descriptor so that it's empty. We're using
# ccall here because readbytes() was blocking the whole julia
# thread. This shouldn't block at all because we just waited on it
ccall(:read, Clong, (Cint, Ptr{Void}, Culong),
jl_filedesc, desc_bytes, 1)
end
finally
# TODO: we need to close the stream here. Otherwise the audio callback
# will segfault accessing the output array if there were exceptions
# thrown in the render loop
end
end
# Old code for reference during initial development. We can get rid of this
# once the library is a little more mature
#type PaStreamCallbackTimeInfo
# inputBufferAdcTime::PaTime
# currentTime::PaTime
# outputBufferDacTime::PaTime
#end
#
#typealias PaStreamCallbackFlags Culong
#
#
#function stream_callback{T}( input_::Ptr{T},
# output_::Ptr{T},
# frame_count::Culong,
# time_info::Ptr{PaStreamCallbackTimeInfo},
# status_flags::PaStreamCallbackFlags,
# user_data::Ptr{Void})
#
#
# println("stfl:$status_flags \tframe_count:$frame_count")
#
# ret = 0
# return convert(Cint,ret)::Cint #continue stream
#
#end
#
#T=Float32
#stream_callback_c = cfunction(stream_callback,Cint,
#(Ptr{T},Ptr{T},Culong,Ptr{PaStreamCallbackTimeInfo},PaStreamCallbackFlags,Ptr{Void})
#)
#stream_obj = Array(Ptr{PaStream},1)
#
#pa_err = ccall(
#(:Pa_Initialize,"libportaudio"),
#PaError,
#(),
#)
#
#println(get_error_text(pa_err))
#
#pa_err = ccall(
#(:Pa_OpenDefaultStream,"libportaudio"),
#PaError,
#(Ptr{Ptr{PaStream}},Cint,Cint,PaSampleFormat,Cdouble,Culong,Ptr{Void},Any),
#stream_obj,0,1,0x1,8000.0,4096,stream_callback_c,None
#)
#
#println(get_error_text(pa_err))
#
#function start_stream(stream)
# pa_err = ccall(
# (:Pa_StartStream,"libportaudio"),
# PaError,
# (Ptr{PaStream},),
# stream
# )
# println(get_error_text(pa_err))
#end
#
#end #module

29
src/precompile.jl Normal file
View file

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

256
test/runtests.jl Executable file
View file

@ -0,0 +1,256 @@
#!/usr/bin/env julia
using Base.Sys: iswindows
using Documenter: doctest
using PortAudio:
combine_default_sample_rates,
devices,
get_default_input_index,
get_default_output_index,
get_device,
get_input_type,
get_output_type,
handle_status,
initialize,
name,
PortAudioException,
PortAudio,
PortAudioDevice,
PortAudioStream,
safe_load,
seek_alsa_conf,
terminate,
write_buffer
using PortAudio.LibPortAudio:
Pa_AbortStream,
PaError,
PaErrorCode,
paFloat32,
Pa_GetDefaultHostApi,
Pa_GetDeviceInfo,
Pa_GetHostApiCount,
Pa_GetLastHostErrorInfo,
Pa_GetSampleSize,
Pa_GetStreamCpuLoad,
Pa_GetStreamInfo,
Pa_GetStreamReadAvailable,
Pa_GetStreamTime,
Pa_GetStreamWriteAvailable,
Pa_GetVersionInfo,
Pa_HostApiDeviceIndexToDeviceIndex,
paHostApiNotFound,
Pa_HostApiTypeIdToHostApiIndex,
PaHostErrorInfo,
paInDevelopment,
paInvalidDevice,
Pa_IsFormatSupported,
Pa_IsStreamActive,
paNoError,
paNoFlag,
paNotInitialized,
Pa_OpenDefaultStream,
paOutputUnderflowed,
Pa_SetStreamFinishedCallback,
Pa_Sleep,
Pa_StopStream,
PaStream,
PaStreamInfo,
PaStreamParameters,
PaVersionInfo
using SampledSignals: nchannels, s, SampleBuf, samplerate, SinSource
using Test: @test, @test_logs, @test_nowarn, @testset, @test_throws
@testset "Tests without sound" begin
@testset "Reports version" begin
io = IOBuffer()
PortAudio.versioninfo(io)
result = split(String(take!((io))), "\n")
# make sure this is the same version I tested with
@test startswith(result[1], "PortAudio V19")
end
@testset "Can list devices without crashing" begin
display(devices())
println()
end
@testset "libortaudio without sound" begin
@test handle_status(Pa_GetHostApiCount()) >= 0
@test handle_status(Pa_GetDefaultHostApi()) >= 0
# version info not available on windows?
if !Sys.iswindows()
@test safe_load(Pa_GetVersionInfo(), ErrorException("no info")) isa
PaVersionInfo
end
@test safe_load(Pa_GetLastHostErrorInfo(), ErrorException("no info")) isa
PaHostErrorInfo
@test PaErrorCode(Pa_IsFormatSupported(C_NULL, C_NULL, 0.0)) == paInvalidDevice
@test PaErrorCode(
Pa_OpenDefaultStream(Ref(C_NULL), 0, 0, paFloat32, 0.0, 0, C_NULL, C_NULL),
) == paInvalidDevice
end
@testset "Errors without sound" begin
@test sprint(showerror, PortAudioException(paNotInitialized)) ==
"PortAudioException: PortAudio not initialized"
@test_throws KeyError("foobarbaz") get_device("foobarbaz")
@test_throws KeyError(-1) get_device(-1)
@test_throws ArgumentError("Could not find alsa.conf in ()") seek_alsa_conf(())
@test_logs (:warn, "libportaudio: Output underflowed") handle_status(
PaError(paOutputUnderflowed),
)
@test_throws PortAudioException(paNotInitialized) handle_status(
PaError(paNotInitialized),
)
Pa_Sleep(1)
@test Pa_GetSampleSize(paFloat32) == 4
end
# make sure we can terminate, then reinitialize
terminate()
initialize()
end
if isempty(devices())
@test_throws ArgumentError("No input device available") get_default_input_index()
else
@testset "Tests with sound" begin
# these default values are specific to local machines
input_name = get_device(get_default_input_index()).name
output_name = get_device(get_default_output_index()).name
@testset "Interactive tests" begin
println("Recording...")
stream = PortAudioStream(input_name, output_name, 2, 0; adjust_channels = true)
buffer = read(stream, 5s)
@test size(buffer) ==
(round(Int, 5 * samplerate(stream)), nchannels(stream.source))
close(stream)
sleep(1)
println("Playing back recording...")
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
write(stream, buffer)
end
sleep(1)
println("Testing pass-through")
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
write_buffer(stream.sink_messenger.buffer, acquire_lock = false)
sink = stream.sink
source = stream.source
@test sprint(show, stream) == """
PortAudioStream{Float32}
Samplerate: 44100Hz
2 channel sink: $(repr(output_name))
2 channel source: $(repr(input_name))"""
@test sprint(show, source) == "2 channel source: $(repr(input_name))"
@test sprint(show, sink) == "2 channel sink: $(repr(output_name))"
write(stream, stream, 5s)
@test PaErrorCode(handle_status(Pa_StopStream(stream.pointer_to))) == paNoError
@test isopen(stream)
close(stream)
sleep(1)
@test !isopen(stream)
@test !isopen(sink)
@test !isopen(source)
println("done")
end
@testset "Samplerate-converting writing" begin
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
write(
stream,
SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]),
3s,
)
println("expected blip")
write(
stream,
SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]),
3s,
)
end
end
sleep(1)
# no way to check that the right data is actually getting read or written here,
# but at least it's not crashing.
@testset "Queued Writing" begin
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
buffer = SampleBuf(
rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1,
samplerate(stream),
)
frame_count_1 = @async write(stream, buffer)
frame_count_2 = @async write(stream, buffer)
@test fetch(frame_count_1) == 48000
println("expected blip")
@test fetch(frame_count_2) == 48000
end
sleep(1)
end
@testset "Queued Reading" begin
PortAudioStream(input_name, output_name, 2, 0; adjust_channels = true) do stream
buffer = SampleBuf(
rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1,
samplerate(stream),
)
frame_count_1 = @async read!(stream, buffer)
frame_count_2 = @async read!(stream, buffer)
@test fetch(frame_count_1) == 48000
@test fetch(frame_count_2) == 48000
end
sleep(1)
end
@testset "Constructors" begin
PortAudioStream(2, maximum; adjust_channels = true) do stream
@test isopen(stream)
end
PortAudioStream(output_name; adjust_channels = true) do stream
@test isopen(stream)
end
PortAudioStream(input_name, output_name; adjust_channels = true) do stream
@test isopen(stream)
end
end
@testset "Errors with sound" begin
big = typemax(Int)
@test_throws DomainError(
typemax(Int),
"$big exceeds maximum output channels for $output_name",
) PortAudioStream(input_name, output_name, 0, big)
@test_throws ArgumentError("Input or output must have at least 1 channel") PortAudioStream(
input_name,
output_name,
0,
0;
adjust_channels = true,
)
@test_throws ArgumentError("""
Default sample rate 0 for input \"$input_name\" disagrees with
default sample rate 1 for output \"$output_name\".
Please specify a sample rate.
""") combine_default_sample_rates(
get_device(input_name),
0,
get_device(output_name),
1,
)
end
@testset "libportaudio with sound" begin
@test PaErrorCode(Pa_HostApiTypeIdToHostApiIndex(paInDevelopment)) ==
paHostApiNotFound
@test Pa_HostApiDeviceIndexToDeviceIndex(paInDevelopment, 0) == 0
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
pointer_to = stream.pointer_to
@test handle_status(Pa_GetStreamReadAvailable(pointer_to)) >= 0
@test handle_status(Pa_GetStreamWriteAvailable(pointer_to)) >= 0
@test Bool(handle_status(Pa_IsStreamActive(pointer_to)))
@test safe_load(Pa_GetStreamInfo(pointer_to), ErrorException("no info")) isa
PaStreamInfo
@test Pa_GetStreamTime(pointer_to) >= 0
@test Pa_GetStreamCpuLoad(pointer_to) >= 0
@test PaErrorCode(handle_status(Pa_AbortStream(pointer_to))) == paNoError
@test PaErrorCode(
handle_status(Pa_SetStreamFinishedCallback(pointer_to, C_NULL)),
) == paNoError
end
end
doctest(PortAudio)
end

95
test/runtests_local.jl Normal file
View file

@ -0,0 +1,95 @@
# This file has runs the normal tests and also adds tests that can only be run
# locally on a machine with a sound card. It's mostly to put the library through
# its paces assuming a human is listening.
include("runtests.jl")
# these default values are specific to my machines
if Sys.iswindows()
default_indev = "Microphone Array (Realtek High "
default_outdev = "Speaker/Headphone (Realtek High"
elseif Sys.isapple()
default_indev = "Built-in Microphone"
default_outdev = "Built-in Output"
elseif Sys.islinux()
default_indev = "default"
default_outdev = "default"
end
@testset "Local Tests" begin
@testset "Open Default Device" begin
println("Recording...")
stream = PortAudioStream(2, 0)
buf = read(stream, 5s)
close(stream)
@test size(buf) == (round(Int, 5 * samplerate(stream)), nchannels(stream.source))
println("Playing back recording...")
stream = PortAudioStream(0, 2)
write(stream, buf)
println("flushing...")
flush(stream)
close(stream)
println("Testing pass-through")
stream = PortAudioStream(2, 2)
write(stream, stream, 5s)
flush(stream)
close(stream)
println("done")
end
@testset "Samplerate-converting writing" begin
stream = PortAudioStream(0, 2)
write(stream, SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]), 3s)
write(stream, SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]), 3s)
flush(stream)
close(stream)
end
@testset "Open Device by name" begin
stream = PortAudioStream(default_indev, default_outdev)
buf = read(stream, 0.001s)
@test size(buf) ==
(round(Int, 0.001 * samplerate(stream)), nchannels(stream.source))
write(stream, buf)
io = IOBuffer()
show(io, stream)
@test occursin(
"""
PortAudioStream{Float32}
Samplerate: 44100.0Hz
Buffer Size: 4096 frames
2 channel sink: "$default_outdev"
2 channel source: "$default_indev\"""",
String(take!(io)),
)
close(stream)
end
@testset "Error on wrong name" begin
@test_throws ErrorException PortAudioStream("foobarbaz")
end
# no way to check that the right data is actually getting read or written here,
# but at least it's not crashing.
@testset "Queued Writing" begin
stream = PortAudioStream(0, 2)
buf = SampleBuf(
rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1,
samplerate(stream),
)
t1 = @async write(stream, buf)
t2 = @async write(stream, buf)
@test fetch(t1) == 48000
@test fetch(t2) == 48000
flush(stream)
close(stream)
end
@testset "Queued Reading" begin
stream = PortAudioStream(2, 0)
buf = SampleBuf(
rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1,
samplerate(stream),
)
t1 = @async read!(stream, buf)
t2 = @async read!(stream, buf)
@test fetch(t1) == 48000
@test fetch(t2) == 48000
close(stream)
end
end

View file

@ -1,17 +0,0 @@
#!/usr/bin/env julia
test_regex = r"^test_.*\.jl$"
test_dir = "test"
test_files = filter(n -> ismatch(test_regex, n), readdir(test_dir))
if length(test_files) == 0
error("No test files found. Make sure you're running from the root directory")
end
for test_file in test_files
info("")
info("Running tests from \"$(test_file)\"...")
info("===================================================================")
include(test_file)
info("===================================================================")
end

View file

@ -1,74 +0,0 @@
using Base.Test
using AudioIO
const TEST_SAMPLERATE = 44100
const TEST_BUF_SIZE = 1024
type TestAudioStream <: AudioIO.AudioStream
mixer::AudioMixer
info::AudioIO.DeviceInfo
function TestAudioStream()
mixer = AudioMixer()
new(mixer, AudioIO.DeviceInfo(TEST_SAMPLERATE, TEST_BUF_SIZE))
end
end
# render the stream and return the next block of audio. This is used in testing
# to simulate the audio callback that's normally called by the device.
function process(stream::TestAudioStream)
in_array = zeros(AudioIO.AudioSample, stream.info.buf_size)
out_array, _ = AudioIO.render(stream.mixer, in_array, stream.info)
return out_array
end
#### Test playing back various vector types ####
# data shared between tests, for convenience
t = linspace(0, 2, 2 * 44100)
phase = 2pi * 100 * t
## Test Float32 arrays, this is currently the native audio playback format
info("Testing Playing Float32 arrays...")
f32 = convert(Array{Float32}, sin(phase))
test_stream = TestAudioStream()
player = play(f32, test_stream)
@test process(test_stream) == f32[1:TEST_BUF_SIZE]
info("Testing Playing Float64 arrays...")
f64 = convert(Array{Float64}, sin(phase))
test_stream = TestAudioStream()
player = play(f64, test_stream)
@test process(test_stream) == convert(AudioIO.AudioBuf, f64[1:TEST_BUF_SIZE])
info("Testing Playing Int8(Signed) arrays...")
i8 = Int8[-127:127]
test_stream = TestAudioStream()
player = play(i8, test_stream)
@test_approx_eq(process(test_stream)[1:255],
convert(AudioIO.AudioBuf, linspace(-1.0, 1.0, 255)))
info("Testing Playing Uint8(Unsigned) arrays...")
# for unsigned 8-bit audio silence is represented as 128, so the symmetric range
# is 1-255
ui8 = Uint8[1:255]
test_stream = TestAudioStream()
player = play(ui8, test_stream)
@test_approx_eq(process(test_stream)[1:255],
convert(AudioIO.AudioBuf, linspace(-1.0, 1.0, 255)))
info("Testing AudioNode Stopping...")
test_stream = TestAudioStream()
node = SinOsc(440)
@test !node.active
play(node, test_stream)
@test node.active
process(test_stream)
stop(node)
@test !node.active
# give the render task a chance to clean up
process(test_stream)
@test process(test_stream) == zeros(AudioIO.AudioSample, TEST_BUF_SIZE)

View file

@ -1,89 +0,0 @@
using Base.Test
using AudioIO
test_info = AudioIO.DeviceInfo(44100, 512)
dev_input = zeros(AudioIO.AudioSample, test_info.buf_size)
# A TestNode just renders out 1:buf_size each frame
type TestNode <: AudioIO.AudioNode
active::Bool
function TestNode()
return new(false)
end
end
function AudioIO.render(node::TestNode,
device_input::AudioIO.AudioBuf,
info::AudioIO.DeviceInfo)
return AudioIO.AudioSample[1:info.buf_size], node.active
end
#### AudioMixer Tests ####
# TODO: there should be a setup/teardown mechanism and some way to isolate
# tests
info("Testing AudioMixer...")
mix = AudioMixer()
render_output, active = AudioIO.render(mix, dev_input, test_info)
@test render_output == zeros(AudioIO.AudioSample, test_info.buf_size)
testnode = TestNode()
mix = AudioMixer([testnode])
render_output, active = AudioIO.render(mix, dev_input, test_info)
@test render_output == AudioIO.AudioSample[1:test_info.buf_size]
test1 = TestNode()
test2 = TestNode()
mix = AudioMixer([test1, test2])
render_output, active = AudioIO.render(mix, dev_input, test_info)
# make sure the two inputs are being added together
@test render_output == 2 * AudioIO.AudioSample[1:test_info.buf_size]
# now we'll stop one of the inputs and make sure it gets removed
# TODO: this test should depend on the render output, not on the internals of
# the mixer
stop(test1)
AudioIO.render(mix, dev_input, test_info)
@test !in(test1, mix.mix_inputs)
stop(mix)
render_output, active = AudioIO.render(mix, dev_input, test_info)
@test !active
info("Testing SinOSC...")
freq = 440
t = linspace(1 / test_info.sample_rate,
test_info.buf_size / test_info.sample_rate,
test_info.buf_size)
test_vect = convert(AudioIO.AudioBuf, sin(2pi * t * freq))
osc = SinOsc(freq)
render_output, active = AudioIO.render(osc, dev_input, test_info)
@test_approx_eq(render_output, test_vect)
stop(osc)
render_output, active = AudioIO.render(osc, dev_input, test_info)
@test !active
info("Testing ArrayPlayer...")
v = rand(AudioIO.AudioSample, 44100)
player = ArrayPlayer(v)
player.active = true
render_output, active = AudioIO.render(player, dev_input, test_info)
@test render_output == v[1:test_info.buf_size]
@test active
render_output, active = AudioIO.render(player, dev_input, test_info)
@test render_output == v[(test_info.buf_size + 1) : (2*test_info.buf_size)]
@test active
stop(player)
render_output, active = AudioIO.render(player, dev_input, test_info)
@test !active
# give a vector just a bit larger than 1 buffer size
v = rand(AudioIO.AudioSample, test_info.buf_size + 1)
player = ArrayPlayer(v)
player.active = true
_, active = AudioIO.render(player, dev_input, test_info)
@test active
_, active = AudioIO.render(player, dev_input, test_info)
@test !active