From 8d353a5b3b8d54a9bf3000da7588cf8ec1d50d80 Mon Sep 17 00:00:00 2001 From: Diane Bruce Date: Thu, 6 Jul 2006 03:57:24 +0000 Subject: [PATCH] - Import of portaudio v19 git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/trunk@189 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- portaudio-v19/LICENSE.txt | 65 + portaudio-v19/Makefile | 186 + portaudio-v19/Makefile.bak | 186 + portaudio-v19/Makefile.in | 185 + portaudio-v19/README.txt | 81 + portaudio-v19/SConscript | 198 + portaudio-v19/SConstruct | 78 + portaudio-v19/V19-devel-readme.txt | 222 + portaudio-v19/aclocal.m4 | 57 + portaudio-v19/config.cache | 30 + portaudio-v19/config.doxy | 185 + portaudio-v19/config.guess | 1407 +++++++ portaudio-v19/config.log | 23 + portaudio-v19/config.status | 175 + portaudio-v19/config.sub | 1504 +++++++ portaudio-v19/configure | 1990 +++++++++ portaudio-v19/configure.in | 250 ++ portaudio-v19/docs/index.html | 60 + portaudio-v19/docs/latency.html | 192 + portaudio-v19/docs/pa_impl_guide.html | 197 + portaudio-v19/docs/pa_impl_startstop.html | 190 + portaudio-v19/docs/pa_tut_asio.html | 55 + portaudio-v19/docs/pa_tut_callback.html | 91 + portaudio-v19/docs/pa_tut_devs.html | 65 + portaudio-v19/docs/pa_tut_explore.html | 42 + portaudio-v19/docs/pa_tut_init.html | 43 + portaudio-v19/docs/pa_tut_mac.html | 41 + portaudio-v19/docs/pa_tut_mac_osx.html | 46 + portaudio-v19/docs/pa_tut_open.html | 52 + portaudio-v19/docs/pa_tut_oss.html | 46 + portaudio-v19/docs/pa_tut_over.html | 92 + portaudio-v19/docs/pa_tut_pc.html | 78 + portaudio-v19/docs/pa_tut_run.html | 56 + portaudio-v19/docs/pa_tut_rw.html | 79 + portaudio-v19/docs/pa_tut_term.html | 47 + portaudio-v19/docs/pa_tut_util.html | 55 + portaudio-v19/docs/pa_tutorial.html | 46 + portaudio-v19/docs/portaudio_h.txt | 425 ++ portaudio-v19/docs/portaudio_icmc2001.pdf | Bin 0 -> 50968 bytes portaudio-v19/docs/proposals.html | 36 + portaudio-v19/docs/releases.html | 339 ++ portaudio-v19/fixdir.bat | 19 + portaudio-v19/fixfile.bat | 7 + portaudio-v19/index.html | 89 + portaudio-v19/install-sh | 251 ++ portaudio-v19/pa_asio/ASIO-README.txt | 137 + .../pa_asio/Callback_adaptation_.pdf | Bin 0 -> 50527 bytes portaudio-v19/pa_asio/Pa_ASIO.pdf | Bin 0 -> 50778 bytes .../pa_asio/iasiothiscallresolver.cpp | 563 +++ portaudio-v19/pa_asio/iasiothiscallresolver.h | 197 + portaudio-v19/pa_asio/pa_asio.cpp | 2958 ++++++++++++++ portaudio-v19/pa_asio/pa_asio.h | 122 + portaudio-v19/pa_beos/PlaybackNode.cc | 538 +++ portaudio-v19/pa_beos/PlaybackNode.h | 108 + portaudio-v19/pa_beos/pa_beos_mk.cc | 441 ++ portaudio-v19/pa_common/pa_allocation.c | 234 ++ portaudio-v19/pa_common/pa_allocation.h | 95 + portaudio-v19/pa_common/pa_converters.c | 1926 +++++++++ portaudio-v19/pa_common/pa_converters.h | 254 ++ portaudio-v19/pa_common/pa_cpuload.c | 96 + portaudio-v19/pa_common/pa_cpuload.h | 63 + portaudio-v19/pa_common/pa_dither.c | 204 + portaudio-v19/pa_common/pa_dither.h | 91 + portaudio-v19/pa_common/pa_endianness.h | 111 + portaudio-v19/pa_common/pa_front.c | 1978 +++++++++ portaudio-v19/pa_common/pa_hostapi.h | 244 ++ portaudio-v19/pa_common/pa_process.c | 1763 ++++++++ portaudio-v19/pa_common/pa_process.h | 741 ++++ portaudio-v19/pa_common/pa_skeleton.c | 807 ++++ portaudio-v19/pa_common/pa_stream.c | 141 + portaudio-v19/pa_common/pa_stream.h | 196 + portaudio-v19/pa_common/pa_trace.c | 88 + portaudio-v19/pa_common/pa_trace.h | 70 + portaudio-v19/pa_common/pa_types.h | 65 + portaudio-v19/pa_common/pa_util.h | 167 + portaudio-v19/pa_common/portaudio.h | 1124 +++++ portaudio-v19/pa_dll_switch/PaDllEntry.h | 184 + .../pa_dll_switch/letter_from_tim_010817.txt | Bin 0 -> 1176 bytes portaudio-v19/pa_dll_switch/loadPA_DLL.cpp | 203 + portaudio-v19/pa_dll_switch/pa_lib.c | 827 ++++ portaudio-v19/pa_dll_switch/portaudio.h | 439 ++ portaudio-v19/pa_jack/pa_jack.c | 1714 ++++++++ portaudio-v19/pa_linux_alsa/pa_linux_alsa.c | 3590 ++++++++++++++++ portaudio-v19/pa_linux_alsa/pa_linux_alsa.h | 64 + portaudio-v19/pa_mac/pa_mac_hostapis.c | 79 + portaudio-v19/pa_mac_core/notes.txt | 124 + portaudio-v19/pa_mac_core/pa_mac_core.h | 64 + portaudio-v19/pa_mac_core/pa_mac_core_auhal.c | 1934 +++++++++ portaudio-v19/pa_mac_core/pa_mac_core_old.c | 907 +++++ .../pa_mac_core/pa_mac_core_utilities.c | 466 +++ portaudio-v19/pa_mac_sm/pa_mac_sm.c | 1656 ++++++++ portaudio-v19/pa_sgi/pa_sgi.c | 1417 +++++++ portaudio-v19/pa_tests/README.txt | 60 + portaudio-v19/pa_tests/debug_convert.c | 131 + portaudio-v19/pa_tests/debug_dither_calc.c | 55 + portaudio-v19/pa_tests/debug_dual.c | 183 + portaudio-v19/pa_tests/debug_multi_in.c | 179 + portaudio-v19/pa_tests/debug_multi_out.c | 144 + portaudio-v19/pa_tests/debug_record.c | 339 ++ portaudio-v19/pa_tests/debug_record_reuse.c | 351 ++ portaudio-v19/pa_tests/debug_sine.c | 192 + portaudio-v19/pa_tests/debug_sine_amp.c | 157 + portaudio-v19/pa_tests/debug_sine_formats.c | 202 + portaudio-v19/pa_tests/debug_srate.c | 265 ++ portaudio-v19/pa_tests/debug_test1.c | 114 + portaudio-v19/pa_tests/pa_devs.c | 230 ++ portaudio-v19/pa_tests/pa_fuzz.c | 168 + portaudio-v19/pa_tests/pa_minlat.c | 187 + portaudio-v19/pa_tests/paqa_devs.c | 347 ++ portaudio-v19/pa_tests/paqa_errs.c | 380 ++ portaudio-v19/pa_tests/patest1.c | 192 + portaudio-v19/pa_tests/patest_buffer.c | 180 + portaudio-v19/pa_tests/patest_callbackstop.c | 221 + portaudio-v19/pa_tests/patest_clip.c | 178 + portaudio-v19/pa_tests/patest_dither.c | 178 + portaudio-v19/pa_tests/patest_hang.c | 152 + portaudio-v19/pa_tests/patest_in_overflow.c | 224 + portaudio-v19/pa_tests/patest_latency.c | 182 + portaudio-v19/pa_tests/patest_leftright.c | 173 + portaudio-v19/pa_tests/patest_longsine.c | 140 + portaudio-v19/pa_tests/patest_many.c | 198 + portaudio-v19/pa_tests/patest_maxsines.c | 204 + portaudio-v19/pa_tests/patest_mono.c | 145 + .../patest_mono_asio_channel_select.c | 155 + portaudio-v19/pa_tests/patest_multi_sine.c | 194 + portaudio-v19/pa_tests/patest_out_underflow.c | 217 + portaudio-v19/pa_tests/patest_pink.c | 269 ++ portaudio-v19/pa_tests/patest_prime.c | 223 + portaudio-v19/pa_tests/patest_read_record.c | 229 ++ portaudio-v19/pa_tests/patest_record.c | 337 ++ portaudio-v19/pa_tests/patest_ringmix.c | 79 + portaudio-v19/pa_tests/patest_saw.c | 125 + portaudio-v19/pa_tests/patest_sine.c | 151 + portaudio-v19/pa_tests/patest_sine8.c | 212 + portaudio-v19/pa_tests/patest_sine_formats.c | 196 + portaudio-v19/pa_tests/patest_sine_time.c | 208 + portaudio-v19/pa_tests/patest_start_stop.c | 162 + portaudio-v19/pa_tests/patest_stop.c | 313 ++ portaudio-v19/pa_tests/patest_stop_playout.c | 432 ++ portaudio-v19/pa_tests/patest_sync.c | 259 ++ portaudio-v19/pa_tests/patest_toomanysines.c | 173 + portaudio-v19/pa_tests/patest_two_rates.c | 168 + portaudio-v19/pa_tests/patest_underflow.c | 151 + portaudio-v19/pa_tests/patest_wire.c | 284 ++ portaudio-v19/pa_tests/patest_write_sine.c | 147 + portaudio-v19/pa_unix/pa_unix_hostapis.c | 64 + portaudio-v19/pa_unix/pa_unix_util.c | 192 + portaudio-v19/pa_unix/pa_unix_util.h | 73 + portaudio-v19/pa_unix_oss/low_latency_tip.txt | Bin 0 -> 3111 bytes portaudio-v19/pa_unix_oss/pa_unix_oss.c | 1923 +++++++++ portaudio-v19/pa_unix_oss/pa_unix_oss.c.orig | 1922 +++++++++ portaudio-v19/pa_unix_oss/recplay.c | 114 + portaudio-v19/pa_win/dev-cpp/Makefile-dll | 78 + portaudio-v19/pa_win/dev-cpp/Makefile-static | 75 + .../pa_win/dev-cpp/portaudio-dll.dev | 209 + .../pa_win/dev-cpp/portaudio-static.dev | 209 + portaudio-v19/pa_win/dev-cpp/readme.txt | 23 + portaudio-v19/pa_win/msvc/Makefile.msvc | 159 + portaudio-v19/pa_win/msvc/clean.bat | 7 + portaudio-v19/pa_win/msvc/make.bat | 8 + portaudio-v19/pa_win/msvc/portaudio.def | 43 + portaudio-v19/pa_win/msvc/readme.txt | 56 + portaudio-v19/pa_win/msvc/setenv.bat | 1 + portaudio-v19/pa_win/pa_win_hostapis.c | 86 + portaudio-v19/pa_win/pa_win_util.c | 134 + .../pa_win/pa_x86_plain_converters.c | 1167 ++++++ .../pa_win/pa_x86_plain_converters.h | 19 + portaudio-v19/pa_win_ds/dsound_wrapper.c | 616 +++ portaudio-v19/pa_win_ds/dsound_wrapper.h | 130 + portaudio-v19/pa_win_ds/pa_win_ds.c | 1857 +++++++++ portaudio-v19/pa_win_wdmks/pa_win_wdmks.c | 3269 +++++++++++++++ portaudio-v19/pa_win_wdmks/readme.txt | 82 + portaudio-v19/pa_win_wmme/pa_win_wmme.c | 3623 +++++++++++++++++ portaudio-v19/pa_win_wmme/pa_win_wmme.h | 160 + portaudio-v19/pablio/README.txt | 39 + portaudio-v19/pablio/pablio.c | 307 ++ portaudio-v19/pablio/pablio.def | 35 + portaudio-v19/pablio/pablio.h | 109 + portaudio-v19/pablio/ringbuffer.c | 199 + portaudio-v19/pablio/ringbuffer.h | 101 + portaudio-v19/pablio/test_rw.c | 99 + portaudio-v19/pablio/test_rw_echo.c | 123 + portaudio-v19/pablio/test_w_saw.c | 108 + portaudio-v19/pablio/test_w_saw8.c | 106 + portaudio-v19/testcvs/changeme.txt | 8 + 185 files changed, 67019 insertions(+) create mode 100644 portaudio-v19/LICENSE.txt create mode 100644 portaudio-v19/Makefile create mode 100644 portaudio-v19/Makefile.bak create mode 100644 portaudio-v19/Makefile.in create mode 100644 portaudio-v19/README.txt create mode 100644 portaudio-v19/SConscript create mode 100644 portaudio-v19/SConstruct create mode 100644 portaudio-v19/V19-devel-readme.txt create mode 100644 portaudio-v19/aclocal.m4 create mode 100644 portaudio-v19/config.cache create mode 100644 portaudio-v19/config.doxy create mode 100755 portaudio-v19/config.guess create mode 100644 portaudio-v19/config.log create mode 100755 portaudio-v19/config.status create mode 100755 portaudio-v19/config.sub create mode 100755 portaudio-v19/configure create mode 100644 portaudio-v19/configure.in create mode 100644 portaudio-v19/docs/index.html create mode 100644 portaudio-v19/docs/latency.html create mode 100644 portaudio-v19/docs/pa_impl_guide.html create mode 100644 portaudio-v19/docs/pa_impl_startstop.html create mode 100644 portaudio-v19/docs/pa_tut_asio.html create mode 100644 portaudio-v19/docs/pa_tut_callback.html create mode 100644 portaudio-v19/docs/pa_tut_devs.html create mode 100644 portaudio-v19/docs/pa_tut_explore.html create mode 100644 portaudio-v19/docs/pa_tut_init.html create mode 100644 portaudio-v19/docs/pa_tut_mac.html create mode 100644 portaudio-v19/docs/pa_tut_mac_osx.html create mode 100644 portaudio-v19/docs/pa_tut_open.html create mode 100644 portaudio-v19/docs/pa_tut_oss.html create mode 100644 portaudio-v19/docs/pa_tut_over.html create mode 100644 portaudio-v19/docs/pa_tut_pc.html create mode 100644 portaudio-v19/docs/pa_tut_run.html create mode 100644 portaudio-v19/docs/pa_tut_rw.html create mode 100644 portaudio-v19/docs/pa_tut_term.html create mode 100644 portaudio-v19/docs/pa_tut_util.html create mode 100644 portaudio-v19/docs/pa_tutorial.html create mode 100644 portaudio-v19/docs/portaudio_h.txt create mode 100644 portaudio-v19/docs/portaudio_icmc2001.pdf create mode 100644 portaudio-v19/docs/proposals.html create mode 100644 portaudio-v19/docs/releases.html create mode 100755 portaudio-v19/fixdir.bat create mode 100755 portaudio-v19/fixfile.bat create mode 100644 portaudio-v19/index.html create mode 100755 portaudio-v19/install-sh create mode 100644 portaudio-v19/pa_asio/ASIO-README.txt create mode 100644 portaudio-v19/pa_asio/Callback_adaptation_.pdf create mode 100644 portaudio-v19/pa_asio/Pa_ASIO.pdf create mode 100644 portaudio-v19/pa_asio/iasiothiscallresolver.cpp create mode 100644 portaudio-v19/pa_asio/iasiothiscallresolver.h create mode 100644 portaudio-v19/pa_asio/pa_asio.cpp create mode 100644 portaudio-v19/pa_asio/pa_asio.h create mode 100644 portaudio-v19/pa_beos/PlaybackNode.cc create mode 100644 portaudio-v19/pa_beos/PlaybackNode.h create mode 100644 portaudio-v19/pa_beos/pa_beos_mk.cc create mode 100644 portaudio-v19/pa_common/pa_allocation.c create mode 100644 portaudio-v19/pa_common/pa_allocation.h create mode 100644 portaudio-v19/pa_common/pa_converters.c create mode 100644 portaudio-v19/pa_common/pa_converters.h create mode 100644 portaudio-v19/pa_common/pa_cpuload.c create mode 100644 portaudio-v19/pa_common/pa_cpuload.h create mode 100644 portaudio-v19/pa_common/pa_dither.c create mode 100644 portaudio-v19/pa_common/pa_dither.h create mode 100644 portaudio-v19/pa_common/pa_endianness.h create mode 100644 portaudio-v19/pa_common/pa_front.c create mode 100644 portaudio-v19/pa_common/pa_hostapi.h create mode 100644 portaudio-v19/pa_common/pa_process.c create mode 100644 portaudio-v19/pa_common/pa_process.h create mode 100644 portaudio-v19/pa_common/pa_skeleton.c create mode 100644 portaudio-v19/pa_common/pa_stream.c create mode 100644 portaudio-v19/pa_common/pa_stream.h create mode 100644 portaudio-v19/pa_common/pa_trace.c create mode 100644 portaudio-v19/pa_common/pa_trace.h create mode 100644 portaudio-v19/pa_common/pa_types.h create mode 100644 portaudio-v19/pa_common/pa_util.h create mode 100644 portaudio-v19/pa_common/portaudio.h create mode 100644 portaudio-v19/pa_dll_switch/PaDllEntry.h create mode 100644 portaudio-v19/pa_dll_switch/letter_from_tim_010817.txt create mode 100644 portaudio-v19/pa_dll_switch/loadPA_DLL.cpp create mode 100644 portaudio-v19/pa_dll_switch/pa_lib.c create mode 100644 portaudio-v19/pa_dll_switch/portaudio.h create mode 100644 portaudio-v19/pa_jack/pa_jack.c create mode 100644 portaudio-v19/pa_linux_alsa/pa_linux_alsa.c create mode 100644 portaudio-v19/pa_linux_alsa/pa_linux_alsa.h create mode 100644 portaudio-v19/pa_mac/pa_mac_hostapis.c create mode 100644 portaudio-v19/pa_mac_core/notes.txt create mode 100644 portaudio-v19/pa_mac_core/pa_mac_core.h create mode 100644 portaudio-v19/pa_mac_core/pa_mac_core_auhal.c create mode 100644 portaudio-v19/pa_mac_core/pa_mac_core_old.c create mode 100644 portaudio-v19/pa_mac_core/pa_mac_core_utilities.c create mode 100644 portaudio-v19/pa_mac_sm/pa_mac_sm.c create mode 100644 portaudio-v19/pa_sgi/pa_sgi.c create mode 100644 portaudio-v19/pa_tests/README.txt create mode 100644 portaudio-v19/pa_tests/debug_convert.c create mode 100644 portaudio-v19/pa_tests/debug_dither_calc.c create mode 100644 portaudio-v19/pa_tests/debug_dual.c create mode 100644 portaudio-v19/pa_tests/debug_multi_in.c create mode 100644 portaudio-v19/pa_tests/debug_multi_out.c create mode 100644 portaudio-v19/pa_tests/debug_record.c create mode 100644 portaudio-v19/pa_tests/debug_record_reuse.c create mode 100644 portaudio-v19/pa_tests/debug_sine.c create mode 100644 portaudio-v19/pa_tests/debug_sine_amp.c create mode 100644 portaudio-v19/pa_tests/debug_sine_formats.c create mode 100644 portaudio-v19/pa_tests/debug_srate.c create mode 100644 portaudio-v19/pa_tests/debug_test1.c create mode 100644 portaudio-v19/pa_tests/pa_devs.c create mode 100644 portaudio-v19/pa_tests/pa_fuzz.c create mode 100644 portaudio-v19/pa_tests/pa_minlat.c create mode 100644 portaudio-v19/pa_tests/paqa_devs.c create mode 100644 portaudio-v19/pa_tests/paqa_errs.c create mode 100644 portaudio-v19/pa_tests/patest1.c create mode 100644 portaudio-v19/pa_tests/patest_buffer.c create mode 100644 portaudio-v19/pa_tests/patest_callbackstop.c create mode 100644 portaudio-v19/pa_tests/patest_clip.c create mode 100644 portaudio-v19/pa_tests/patest_dither.c create mode 100644 portaudio-v19/pa_tests/patest_hang.c create mode 100644 portaudio-v19/pa_tests/patest_in_overflow.c create mode 100644 portaudio-v19/pa_tests/patest_latency.c create mode 100644 portaudio-v19/pa_tests/patest_leftright.c create mode 100644 portaudio-v19/pa_tests/patest_longsine.c create mode 100644 portaudio-v19/pa_tests/patest_many.c create mode 100644 portaudio-v19/pa_tests/patest_maxsines.c create mode 100644 portaudio-v19/pa_tests/patest_mono.c create mode 100644 portaudio-v19/pa_tests/patest_mono_asio_channel_select.c create mode 100644 portaudio-v19/pa_tests/patest_multi_sine.c create mode 100644 portaudio-v19/pa_tests/patest_out_underflow.c create mode 100644 portaudio-v19/pa_tests/patest_pink.c create mode 100644 portaudio-v19/pa_tests/patest_prime.c create mode 100644 portaudio-v19/pa_tests/patest_read_record.c create mode 100644 portaudio-v19/pa_tests/patest_record.c create mode 100644 portaudio-v19/pa_tests/patest_ringmix.c create mode 100644 portaudio-v19/pa_tests/patest_saw.c create mode 100644 portaudio-v19/pa_tests/patest_sine.c create mode 100644 portaudio-v19/pa_tests/patest_sine8.c create mode 100644 portaudio-v19/pa_tests/patest_sine_formats.c create mode 100644 portaudio-v19/pa_tests/patest_sine_time.c create mode 100644 portaudio-v19/pa_tests/patest_start_stop.c create mode 100644 portaudio-v19/pa_tests/patest_stop.c create mode 100644 portaudio-v19/pa_tests/patest_stop_playout.c create mode 100644 portaudio-v19/pa_tests/patest_sync.c create mode 100644 portaudio-v19/pa_tests/patest_toomanysines.c create mode 100644 portaudio-v19/pa_tests/patest_two_rates.c create mode 100644 portaudio-v19/pa_tests/patest_underflow.c create mode 100644 portaudio-v19/pa_tests/patest_wire.c create mode 100644 portaudio-v19/pa_tests/patest_write_sine.c create mode 100644 portaudio-v19/pa_unix/pa_unix_hostapis.c create mode 100644 portaudio-v19/pa_unix/pa_unix_util.c create mode 100644 portaudio-v19/pa_unix/pa_unix_util.h create mode 100644 portaudio-v19/pa_unix_oss/low_latency_tip.txt create mode 100644 portaudio-v19/pa_unix_oss/pa_unix_oss.c create mode 100644 portaudio-v19/pa_unix_oss/pa_unix_oss.c.orig create mode 100644 portaudio-v19/pa_unix_oss/recplay.c create mode 100644 portaudio-v19/pa_win/dev-cpp/Makefile-dll create mode 100644 portaudio-v19/pa_win/dev-cpp/Makefile-static create mode 100644 portaudio-v19/pa_win/dev-cpp/portaudio-dll.dev create mode 100644 portaudio-v19/pa_win/dev-cpp/portaudio-static.dev create mode 100644 portaudio-v19/pa_win/dev-cpp/readme.txt create mode 100644 portaudio-v19/pa_win/msvc/Makefile.msvc create mode 100755 portaudio-v19/pa_win/msvc/clean.bat create mode 100755 portaudio-v19/pa_win/msvc/make.bat create mode 100644 portaudio-v19/pa_win/msvc/portaudio.def create mode 100644 portaudio-v19/pa_win/msvc/readme.txt create mode 100755 portaudio-v19/pa_win/msvc/setenv.bat create mode 100644 portaudio-v19/pa_win/pa_win_hostapis.c create mode 100644 portaudio-v19/pa_win/pa_win_util.c create mode 100644 portaudio-v19/pa_win/pa_x86_plain_converters.c create mode 100644 portaudio-v19/pa_win/pa_x86_plain_converters.h create mode 100644 portaudio-v19/pa_win_ds/dsound_wrapper.c create mode 100644 portaudio-v19/pa_win_ds/dsound_wrapper.h create mode 100644 portaudio-v19/pa_win_ds/pa_win_ds.c create mode 100644 portaudio-v19/pa_win_wdmks/pa_win_wdmks.c create mode 100644 portaudio-v19/pa_win_wdmks/readme.txt create mode 100644 portaudio-v19/pa_win_wmme/pa_win_wmme.c create mode 100644 portaudio-v19/pa_win_wmme/pa_win_wmme.h create mode 100644 portaudio-v19/pablio/README.txt create mode 100644 portaudio-v19/pablio/pablio.c create mode 100644 portaudio-v19/pablio/pablio.def create mode 100644 portaudio-v19/pablio/pablio.h create mode 100644 portaudio-v19/pablio/ringbuffer.c create mode 100644 portaudio-v19/pablio/ringbuffer.h create mode 100644 portaudio-v19/pablio/test_rw.c create mode 100644 portaudio-v19/pablio/test_rw_echo.c create mode 100644 portaudio-v19/pablio/test_w_saw.c create mode 100644 portaudio-v19/pablio/test_w_saw8.c create mode 100644 portaudio-v19/testcvs/changeme.txt diff --git a/portaudio-v19/LICENSE.txt b/portaudio-v19/LICENSE.txt new file mode 100644 index 000000000..105da3f75 --- /dev/null +++ b/portaudio-v19/LICENSE.txt @@ -0,0 +1,65 @@ +Portable header file to contain: +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +Implementation files to contain: +/* + * PortAudio Portable Real-Time Audio Library + * Latest version at: http://www.audiomulch.com/portaudio/ + * Implementation + * Copyright (c) 1999-2000 + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ \ No newline at end of file diff --git a/portaudio-v19/Makefile b/portaudio-v19/Makefile new file mode 100644 index 000000000..61b734506 --- /dev/null +++ b/portaudio-v19/Makefile @@ -0,0 +1,186 @@ +# Generated automatically from Makefile.in by configure. +# +# PortAudio V19 Makefile.in +# +# Dominic Mazzoni +# + +PREFIX = /usr/local +CC = cc +CFLAGS = -Ipa_common -g -O2 -Wall -pedantic -pipe -fPIC -DPA_LITTLE_ENDIAN -DSIZEOF_SHORT=2 -DSIZEOF_INT=4 -DSIZEOF_LONG=4 -DHAVE_LIBPTHREAD=1 -DPA_USE_OSS=1 +LIBS = -lpthread -lm -lpthread +AR = /usr/bin/ar +RANLIB = ranlib +INSTALL = /usr/bin/install -c +SHARED_FLAGS = -shared -fPIC +DLL_LIBS = +CXXFLAGS = +NASM = +NASMOPT = + +OTHER_OBJS = pa_unix_oss/pa_unix_oss.o pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o + +PALIB = libportaudio.a +PADLL = libportaudio.so +PADLLV = $(PADLL).0.0.19 +PAINC = pa_common/portaudio.h + +COMMON_OBJS = \ + pa_common/pa_allocation.o \ + pa_common/pa_converters.o \ + pa_common/pa_cpuload.o \ + pa_common/pa_dither.o \ + pa_common/pa_front.o \ + pa_common/pa_process.o \ + pa_common/pa_skeleton.o \ + pa_common/pa_stream.o \ + pa_common/pa_trace.o + +TESTS = \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_callbackstop \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_prime \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat + +# Most of these don't compile yet. Put them in TESTS, above, if +# you want to try to compile them... +ALL_TESTS = \ + bin/debug_convert \ + bin/debug_dither_calc \ + bin/debug_dual \ + bin/debug_multi_in \ + bin/debug_multi_out \ + bin/debug_record \ + bin/debug_record_reuse \ + bin/debug_sine_amp \ + bin/debug_sine \ + bin/debug_sine_formats \ + bin/debug_srate \ + bin/debug_test1 \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine + +OBJS = $(COMMON_OBJS) $(OTHER_OBJS) + +all: lib/$(PALIB) lib/$(PADLLV) tests + +tests: bin/ $(TESTS) + +lib/$(PALIB): lib/ $(OBJS) Makefile $(PAINC) + $(AR) ruv lib/$(PALIB) $(OBJS) + $(RANLIB) lib/$(PALIB) + +lib/$(PADLLV): lib/ $(OBJS) Makefile $(PAINC) + $(CC) $(SHARED_FLAGS) -o lib/$(PADLLV) $(OBJS) $(DLL_LIBS) + +$(TESTS): bin/%: lib/$(PALIB) Makefile $(PAINC) pa_tests/%.c + $(CC) -o $@ $(CFLAGS) pa_tests/$*.c lib/$(PALIB) $(LIBS) + +install: lib/$(PALIB) lib/$(PADLLV) + $(INSTALL) -d $(PREFIX)/lib + $(INSTALL) -m 644 lib/$(PADLLV) $(PREFIX)/lib/$(PADLLV) + $(INSTALL) -m 644 lib/$(PALIB) $(PREFIX)/lib/$(PALIB) + cd $(PREFIX)/lib && rm -f $(PADLL) && ln -s $(PADLLV) $(PADLL) + $(INSTALL) -d $(PREFIX)/include + $(INSTALL) -m 644 pa_common/portaudio.h $(PREFIX)/include/portaudio.h + @echo "" + @echo "------------------------------------------------------------" + @echo "PortAudio was successfully installed." + @echo "" + @echo "On some systems (e.g. Linux) you should run 'ldconfig' now" + @echo "to make the shared object available. You may also need to" + @echo "modify your LD_LIBRARY_PATH environment variable to include" + @echo "the directory $(PREFIX)/lib" + @echo "------------------------------------------------------------" + @echo "" + +uninstall: + rm -f $(PREFIX)/lib/$(PADLLV) + rm -f $(PREFIX)/lib/$(PALIB) + rm -f $(PREFIX)/lib/$(PADLL) + rm -f $(PREFIX)/include/portaudio.h + +clean: + rm -f $(OBJS) $(TESTS) lib/$(PALIB) lib/$(PADLLV) + +%.o: %.c Makefile $(PAINC) + $(CC) -c $(CFLAGS) $< -o $@ + +%.o: %.cpp Makefile $(PAINC) + $(CXX) -c $(CXXFLAGS) $< -o $@ + +%.o: %.asm + $(NASM) $(NASMOPT) -o $@ $< + +bin: + mkdir bin + +lib: + mkdir lib + + + diff --git a/portaudio-v19/Makefile.bak b/portaudio-v19/Makefile.bak new file mode 100644 index 000000000..cace57300 --- /dev/null +++ b/portaudio-v19/Makefile.bak @@ -0,0 +1,186 @@ +# Generated automatically from Makefile.in by configure. +# +# PortAudio V19 Makefile.in +# +# Dominic Mazzoni +# + +PREFIX = /usr/local +CC = cc +CFLAGS = -g -O2 -Wall -pedantic -pipe -fPIC -DPA_LITTLE_ENDIAN -I/usr/local/include -Ipa_common -DSIZEOF_SHORT=2 -DSIZEOF_INT=4 -DSIZEOF_LONG=4 -DHAVE_LIBPTHREAD=1 -DPA_USE_JACK=1 -DPA_USE_OSS=1 +LIBS = -lpthread -pthread -L/usr/local/lib -ljack -lm -lpthread +AR = /usr/bin/ar +RANLIB = ranlib +INSTALL = /usr/bin/install -c -o root -g wheel +SHARED_FLAGS = -shared -fPIC +DLL_LIBS = -pthread -L/usr/local/lib -ljack +CXXFLAGS = -O2 -pipe +NASM = +NASMOPT = + +OTHER_OBJS = pa_jack/pa_jack.o pa_unix_oss/pa_unix_oss.o pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o + +PALIB = libportaudio.a +PADLL = libportaudio.so +PADLLV = $(PADLL).0.0.19 +PAINC = pa_common/portaudio.h + +COMMON_OBJS = \ + pa_common/pa_allocation.o \ + pa_common/pa_converters.o \ + pa_common/pa_cpuload.o \ + pa_common/pa_dither.o \ + pa_common/pa_front.o \ + pa_common/pa_process.o \ + pa_common/pa_skeleton.o \ + pa_common/pa_stream.o \ + pa_common/pa_trace.o + +TESTS = \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_callbackstop \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_prime \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat + +# Most of these don't compile yet. Put them in TESTS, above, if +# you want to try to compile them... +ALL_TESTS = \ + bin/debug_convert \ + bin/debug_dither_calc \ + bin/debug_dual \ + bin/debug_multi_in \ + bin/debug_multi_out \ + bin/debug_record \ + bin/debug_record_reuse \ + bin/debug_sine_amp \ + bin/debug_sine \ + bin/debug_sine_formats \ + bin/debug_srate \ + bin/debug_test1 \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine + +OBJS = $(COMMON_OBJS) $(OTHER_OBJS) + +all: lib/$(PALIB) lib/$(PADLLV) + +tests: bin/ $(TESTS) + +lib/$(PALIB): lib/ $(OBJS) Makefile $(PAINC) + $(AR) ruv lib/$(PALIB) $(OBJS) + $(RANLIB) lib/$(PALIB) + +lib/$(PADLLV): lib/ $(OBJS) Makefile $(PAINC) + $(CC) $(SHARED_FLAGS) -o lib/$(PADLLV) $(OBJS) $(DLL_LIBS) + +$(TESTS): bin/%: lib/$(PALIB) Makefile $(PAINC) pa_tests/%.c + $(CC) -o $@ $(CFLAGS) pa_tests/$*.c lib/$(PALIB) $(LIBS) + +install: lib/$(PALIB) lib/$(PADLLV) + $(INSTALL) -d $(PREFIX)/lib + $(INSTALL) -m 644 lib/$(PADLLV) $(PREFIX)/lib/$(PADLLV) + $(INSTALL) -m 644 lib/$(PALIB) $(PREFIX)/lib/$(PALIB) + cd $(PREFIX)/lib && rm -f $(PADLL) && ln -s $(PADLLV) $(PADLL) + $(INSTALL) -d $(PREFIX)/include + $(INSTALL) -m 644 pa_common/portaudio.h $(PREFIX)/include/portaudio.h + @echo "" + @echo "------------------------------------------------------------" + @echo "PortAudio was successfully installed." + @echo "" + @echo "On some systems (e.g. Linux) you should run 'ldconfig' now" + @echo "to make the shared object available. You may also need to" + @echo "modify your LD_LIBRARY_PATH environment variable to include" + @echo "the directory $(PREFIX)/lib" + @echo "------------------------------------------------------------" + @echo "" + +uninstall: + rm -f $(PREFIX)/lib/$(PADLLV) + rm -f $(PREFIX)/lib/$(PALIB) + rm -f $(PREFIX)/lib/$(PADLL) + rm -f $(PREFIX)/include/portaudio.h + +clean: + rm -f $(OBJS) $(TESTS) lib/$(PALIB) lib/$(PADLLV) + +%.o: %.c Makefile $(PAINC) + $(CC) -c $(CFLAGS) $< -o $@ + +%.o: %.cpp Makefile $(PAINC) + $(CXX) -c $(CXXFLAGS) $< -o $@ + +%.o: %.asm + $(NASM) $(NASMOPT) -o $@ $< + +bin: + mkdir bin + +lib: + mkdir lib + + + diff --git a/portaudio-v19/Makefile.in b/portaudio-v19/Makefile.in new file mode 100644 index 000000000..9ba7a319c --- /dev/null +++ b/portaudio-v19/Makefile.in @@ -0,0 +1,185 @@ +# +# PortAudio V19 Makefile.in +# +# Dominic Mazzoni +# + +PREFIX = @prefix@ +CC = @CC@ +CFLAGS = -Ipa_common @CFLAGS@ @DEFS@ +LIBS = @LIBS@ +AR = @AR@ +RANLIB = @RANLIB@ +INSTALL = @INSTALL@ +SHARED_FLAGS = @SHARED_FLAGS@ +DLL_LIBS = @DLL_LIBS@ +CXXFLAGS = @CXXFLAGS@ +NASM = @NASM@ +NASMOPT = @NASMOPT@ + +OTHER_OBJS = @OTHER_OBJS@ + +PALIB = libportaudio.a +PADLL = @PADLL@ +PADLLV = $(PADLL).0.0.19 +PAINC = pa_common/portaudio.h + +COMMON_OBJS = \ + pa_common/pa_allocation.o \ + pa_common/pa_converters.o \ + pa_common/pa_cpuload.o \ + pa_common/pa_dither.o \ + pa_common/pa_front.o \ + pa_common/pa_process.o \ + pa_common/pa_skeleton.o \ + pa_common/pa_stream.o \ + pa_common/pa_trace.o + +TESTS = \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_callbackstop \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_prime \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat + +# Most of these don't compile yet. Put them in TESTS, above, if +# you want to try to compile them... +ALL_TESTS = \ + bin/debug_convert \ + bin/debug_dither_calc \ + bin/debug_dual \ + bin/debug_multi_in \ + bin/debug_multi_out \ + bin/debug_record \ + bin/debug_record_reuse \ + bin/debug_sine_amp \ + bin/debug_sine \ + bin/debug_sine_formats \ + bin/debug_srate \ + bin/debug_test1 \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat \ + bin/paqa_devs \ + bin/paqa_errs \ + bin/patest1 \ + bin/patest_buffer \ + bin/patest_clip \ + bin/patest_dither \ + bin/patest_hang \ + bin/patest_in_overflow \ + bin/patest_latency \ + bin/patest_leftright \ + bin/patest_longsine \ + bin/patest_many \ + bin/patest_maxsines \ + bin/patest_multi_sine \ + bin/patest_out_underflow \ + bin/patest_pink \ + bin/patest_read_record \ + bin/patest_record \ + bin/patest_ringmix \ + bin/patest_saw \ + bin/patest_sine8 \ + bin/patest_sine \ + bin/patest_sine_formats \ + bin/patest_sine_time \ + bin/patest_start_stop \ + bin/patest_stop \ + bin/patest_sync \ + bin/patest_toomanysines \ + bin/patest_underflow \ + bin/patest_wire \ + bin/patest_write_sine + +OBJS = $(COMMON_OBJS) $(OTHER_OBJS) + +all: lib/$(PALIB) lib/$(PADLLV) tests + +tests: bin/ $(TESTS) + +lib/$(PALIB): lib/ $(OBJS) Makefile $(PAINC) + $(AR) ruv lib/$(PALIB) $(OBJS) + $(RANLIB) lib/$(PALIB) + +lib/$(PADLLV): lib/ $(OBJS) Makefile $(PAINC) + $(CC) $(SHARED_FLAGS) -o lib/$(PADLLV) $(OBJS) $(DLL_LIBS) + +$(TESTS): bin/%: lib/$(PALIB) Makefile $(PAINC) pa_tests/%.c + $(CC) -o $@ $(CFLAGS) pa_tests/$*.c lib/$(PALIB) $(LIBS) + +install: lib/$(PALIB) lib/$(PADLLV) + $(INSTALL) -d $(PREFIX)/lib + $(INSTALL) -m 644 lib/$(PADLLV) $(PREFIX)/lib/$(PADLLV) + $(INSTALL) -m 644 lib/$(PALIB) $(PREFIX)/lib/$(PALIB) + cd $(PREFIX)/lib && rm -f $(PADLL) && ln -s $(PADLLV) $(PADLL) + $(INSTALL) -d $(PREFIX)/include + $(INSTALL) -m 644 pa_common/portaudio.h $(PREFIX)/include/portaudio.h + @echo "" + @echo "------------------------------------------------------------" + @echo "PortAudio was successfully installed." + @echo "" + @echo "On some systems (e.g. Linux) you should run 'ldconfig' now" + @echo "to make the shared object available. You may also need to" + @echo "modify your LD_LIBRARY_PATH environment variable to include" + @echo "the directory $(PREFIX)/lib" + @echo "------------------------------------------------------------" + @echo "" + +uninstall: + rm -f $(PREFIX)/lib/$(PADLLV) + rm -f $(PREFIX)/lib/$(PALIB) + rm -f $(PREFIX)/lib/$(PADLL) + rm -f $(PREFIX)/include/portaudio.h + +clean: + rm -f $(OBJS) $(TESTS) lib/$(PALIB) lib/$(PADLLV) + +%.o: %.c Makefile $(PAINC) + $(CC) -c $(CFLAGS) $< -o $@ + +%.o: %.cpp Makefile $(PAINC) + $(CXX) -c $(CXXFLAGS) $< -o $@ + +%.o: %.asm + $(NASM) $(NASMOPT) -o $@ $< + +bin: + mkdir bin + +lib: + mkdir lib + + + diff --git a/portaudio-v19/README.txt b/portaudio-v19/README.txt new file mode 100644 index 000000000..4cfc6166e --- /dev/null +++ b/portaudio-v19/README.txt @@ -0,0 +1,81 @@ +README for PortAudio +Implementations for PC DirectSound and Mac SoundManager + +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com// + * + * Copyright (c) 1999-2000 Phil Burk and Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +PortAudio is a portable audio I/O library designed for cross-platform +support of audio. It uses a callback mechanism to request audio processing. +Audio can be generated in various formats, including 32 bit floating point, +and will be converted to the native format internally. + +Documentation: + See "pa_common/portaudio.h" for API spec. + See docs folder for a tutorial. + Also see http://www.portaudio.com/docs/ + And see "pa_tests/patest_saw.c" for an example. + +For information on compiling programs with PortAudio, please see the +tutorial at: + + http://www.portaudio.com/docs/pa_tutorial.html + +Important Files and Folders: + pa_common/ = platform independant code + pa_common/portaudio.h = header file for PortAudio API. Specifies API. + pa_common/pa_lib.c = host independant code for all implementations. + + pablio = simple blocking read/write interface + +Platform Implementations + pa_asio = ASIO for Windows and Macintosh + pa_beos = BeOS + pa_mac_sm = Macintosh Sound Manager for OS 8,9 and Carbon + pa_mac_core = Macintosh Core Audio for OS X + pa_sgi = Silicon Graphics AL + pa_unix_oss = OSS implementation for various Unixes + pa_win_ds = Windows Direct Sound + pa_win_wmme = Windows MME (most widely supported) + +Test Programs + pa_tests/pa_fuzz.c = guitar fuzz box + pa_tests/pa_devs.c = print a list of available devices + pa_tests/pa_minlat.c = determine minimum latency for your machine + pa_tests/paqa_devs.c = self test that opens all devices + pa_tests/paqa_errs.c = test error detection and reporting + pa_tests/patest_clip.c = hear a sine wave clipped and unclipped + pa_tests/patest_dither.c = hear effects of dithering (extremely subtle) + pa_tests/patest_pink.c = fun with pink noise + pa_tests/patest_record.c = record and playback some audio + pa_tests/patest_maxsines.c = how many sine waves can we play? Tests Pa_GetCPULoad(). + pa_tests/patest_sine.c = output a sine wave in a simple PA app + pa_tests/patest_sync.c = test syncronization of audio and video + pa_tests/patest_wire.c = pass input to output, wire simulator diff --git a/portaudio-v19/SConscript b/portaudio-v19/SConscript new file mode 100644 index 000000000..252ef5b26 --- /dev/null +++ b/portaudio-v19/SConscript @@ -0,0 +1,198 @@ +import os.path, copy, sys + +def checkSymbol(conf, header, library=None, symbol=None, autoadd=True, critical=False, pkgName=None): + env = conf.env + if library is None: + library = "c" # Standard library + autoadd = False + + if pkgName is not None: + origLibPath = copy.copy(env.get("LIBPATH", None)) + origLibs = copy.copy(env.get("LIBS", None)) + origLinkFlags = copy.copy(env.get("LINKFLAGS", None)) + origCppFlags = copy.copy(env.get("CPPFLAGS", None)) + origCppPath = copy.copy(env.get("CPPPATH", None)) + origCcFlags = copy.copy(env.get("CCFLAGS", None)) + origAsFlags = copy.copy(env.get("ASFLAGS", None)) + + try: env.ParseConfig("pkg-config --silence-errors %s --cflags --libs" % pkgName) + except: pass + else: + # I see no other way of checking that the parsing succeeded, if it did add no more linking parameters + if env["LIBS"] != origLibs: + autoadd = False + + try: + if not conf.CheckCHeader(header, include_quotes="<>"): + raise ConfigurationError("missing header %s" % header) + if symbol is not None and not conf.CheckLib(library, symbol, language="C", autoadd=autoadd): + raise ConfigurationError("missing symbol %s in library %s" % (symbol, library)) + except ConfigurationError: + if pkgName is not None: + # Restore any changes made by ParseConfig + if origLibPath is not None: + env["LIBPATH"] = origLibPath + if origLibs is not None: + env["LIBS"] = origLibs + if origLinkFlags is not None: + env["LINKFLAGS"] = origLinkFlags + if origCppFlags is not None: + env["CPPFLAGS"] = origCppFlags + if origCppPath is not None: + env["CPPPATH"] = origCppPath + if origCcFlags is not None: + env["CCFLAGS"] = origCcFlags + if origAsFlags is not None: + env["ASFLAGS"] = origAsFlags + + if not critical: + return False + raise + + return True + +Import("env", "Platform", "Posix", "ConfigurationError") + +# Store all signatures in one file, otherwise .sconsign files will get installed along with our own files +env.SConsignFile() + +neededLibs = [] +optionalImpls = {} +if Platform in Posix: + neededLibs += [("pthread", "pthread.h", "pthread_create"), ("m", "math.h", "sin")] + if env["useALSA"]: + optionalImpls["ALSA"] = ("asound", "alsa/asoundlib.h", "snd_pcm_open") + if env["useJACK"]: + optionalImpls["JACK"] = ("jack", "jack/jack.h", "jack_client_new") + if env["useOSS"]: + # TODO: It looks like the prefix for soundcard.h depends on the platform + optionalImpls["OSS"] = ("oss", "sys/soundcard.h", None) +else: + raise ConfigurationError("unknown platform %s" % Platform) + +if Platform == "darwin": + env.Append(LINKFLAGS=["-framework CoreAudio", "-framework AudioToolBox"]) + env.Append(CPPDEFINES=["PA_USE_COREAUDIO"]) +elif Platform == "cygwin": + env.Append(LIBS=["winmm"]) +elif Platform == "irix": + neededLibs += [("audio", "dmedia/audio.h", "alOpenPort"), ("dmedia", "dmedia/dmedia.h", "dmGetUST")] + env.Append(CPPDEFINES=["PA_USE_SGI"]) + +def CheckCTypeSize(context, tp): + context.Message("Checking the size of C type %s..." % tp) + ret = context.TryRun(""" +#include + +int main() { + printf("%%d", sizeof(%s)); + return 0; +} +""" % tp, ".c") + if not ret[0]: + context.Result(" Couldn't obtain size of type %s!" % tp) + return None + + assert ret[1] + sz = int(ret[1]) + context.Result("%d" % sz) + return sz + +if sys.byteorder == "little": + env.Append(CPPDEFINES=["PA_LITTLE_ENDIAN"]) +elif sys.byteorder == "big": + env.Append(CPPDEFINES=["PA_BIG_ENDIAN"]) +else: + raise ConfigurationError("unknown byte order: %s" % sys.byteorder) +if env["enableDebugOutput"]: + env.Append(CPPDEFINES=["PA_ENABLE_DEBUG_OUTPUT"]) + +# Start configuration + +conf = env.Configure(log_file="sconf.log", custom_tests={"CheckCTypeSize": CheckCTypeSize}) +env.Append(CPPDEFINES=["SIZEOF_SHORT=%d" % conf.CheckCTypeSize("short")]) +env.Append(CPPDEFINES=["SIZEOF_INT=%d" % conf.CheckCTypeSize("int")]) +env.Append(CPPDEFINES=["SIZEOF_LONG=%d" % conf.CheckCTypeSize("long")]) +if checkSymbol(conf, "time.h", "rt", "clock_gettime"): + env.Append(CPPDEFINES=["HAVE_CLOCK_GETTIME"]) +if checkSymbol(conf, "time.h", "nanosleep"): + env.Append(CPPDEFINES=["HAVE_NANOSLEEP"]) + +for lib, hdr, sym in neededLibs: + checkSymbol(conf, hdr, lib, sym, critical=True) +for name, val in optionalImpls.items(): + lib, hdr, sym = val + if checkSymbol(conf, hdr, lib, sym, critical=False, pkgName=name.lower()): + env.Append(CPPDEFINES=["PA_USE_%s=1" % name.upper()]) + +# Configuration finished +env = conf.Finish() + +CommonSources = [os.path.join("pa_common", f) for f in "pa_allocation.c pa_converters.c pa_cpuload.c pa_dither.c pa_front.c \ + pa_process.c pa_skeleton.c pa_stream.c pa_trace.c".split()] + +ImplSources = [] +if Platform in Posix: + env.AppendUnique(LINKFLAGS=["-pthread"]) + BaseCFlags = "-Wall -pedantic -pipe -pthread" + DebugCFlags = "-g" + OptCFlags = "-O2" + ImplSources += [os.path.join("pa_unix", f) for f in "pa_unix_hostapis.c pa_unix_util.c".split()] + +if "ALSA" in optionalImpls: + ImplSources.append(os.path.join("pa_linux_alsa", "pa_linux_alsa.c")) +if "JACK" in optionalImpls: + ImplSources.append(os.path.join("pa_jack", "pa_jack.c")) +if "OSS" in optionalImpls: + ImplSources.append(os.path.join("pa_unix_oss", "pa_unix_oss.c")) + +env["CCFLAGS"] = BaseCFlags +if env["enableDebug"]: + env["CCFLAGS"] += " " + DebugCFlags +if env["enableOptimize"]: + env["CCFLAGS"] += " " + OptCFlags +if not env["enableAsserts"]: + env.Append(CPPDEFINES=["-DNDEBUG"]) +if env["customCFlags"]: + env["CCFLAGS"] += " " + env["customCFlags"] + +sources = CommonSources + ImplSources + +if env["enableShared"]: + sharedLib = env.SharedLibrary(target="portaudio", source=sources) +else: + sharedLib = None +staticLib = env.StaticLibrary(target="portaudio", source=sources) + +if Platform in Posix: + prefix = env["prefix"] + env.Alias("install", env.Install(os.path.join(prefix, "include"), os.path.join("pa_common", "portaudio.h"))) + + if env["enableStatic"]: + env.Alias("install", env.Install(os.path.join(prefix, "lib"), staticLib)) + + def symlink(env, target, source): + trgt = str(target[0]) + src = str(source[0]) + print trgt, src + + if os.path.exists(trgt): + os.remove(trgt) + os.symlink("libportaudio.so.0.0.19", trgt) + if env["enableShared"]: + env.Alias("install", env.InstallAs(target=os.path.join(prefix, "lib", "%s.0.0.19") % sharedLib[0], source=sharedLib)) + env.Alias("install", env.Command(os.path.join(prefix, "lib", "libportaudio.so"), sharedLib, symlink)) + +testNames = ["patest_sine", "paqa_devs", "paqa_errs", "patest1", "patest_buffer", "patest_callbackstop", "patest_clip", \ + "patest_dither", "patest_hang", "patest_in_overflow", "patest_latency", "patest_leftright", "patest_longsine", \ + "patest_many", "patest_maxsines", "patest_multi_sine", "patest_out_underflow", "patest_pink", "patest_prime", \ + "patest_read_record", "patest_record", "patest_ringmix", "patest_saw", "patest_sine8", "patest_sine", \ + "patest_sine_time", "patest_start_stop", "patest_stop", "patest_sync", \ + "patest_toomanysines", "patest_underflow", "patest_wire", "patest_write_sine", "pa_devs", "pa_fuzz", "pa_minlat"] + +# The test directory ("bin") should be in the top-level PA directory, the calling script should ensure that SCons doesn't +# switch current directory by calling SConscriptChdir(False). Specifying an absolute directory for each target means it won't +# be relative to the build directory +tests = [env.Program(target=os.path.join(os.getcwd(), "bin", name), source=[os.path.join("pa_tests", name + ".c"), staticLib]) for name in testNames] + +Return("sources", "sharedLib", "staticLib") diff --git a/portaudio-v19/SConstruct b/portaudio-v19/SConstruct new file mode 100644 index 000000000..07e219a8b --- /dev/null +++ b/portaudio-v19/SConstruct @@ -0,0 +1,78 @@ +import sys, os.path + +class ConfigurationError(Exception): + def __init__(self, reason): + Exception.__init__(self, "Configuration failed: %s" % reason) + +def _PackageOption(pkgName, default="yes"): + return BoolOption("use%s" % pkgName[0].upper() + pkgName[1:], "use %s if available" % (pkgName), default) + +def _BoolOption(opt, explanation, default="yes"): + return BoolOption("enable%s" % opt[0].upper() + opt[1:], explanation, default) + +def _DirectoryOption(path, help, default): + return PathOption(path, help, default) + # Incompatible with the latest stable SCons + # return PathOption(path, help, default, PathOption.PathIsDir) + +def _EnumOption(opt, explanation, allowedValues, default): + assert default in allowedValues + return EnumOption("with%s" % opt[0].upper() + opt[1:], explanation, default, allowed_values=allowedValues) + +def getPlatform(): + global __platform + try: return __platform + except NameError: + env = Environment() + if env["PLATFORM"] == "posix": + if sys.platform[:5] == "linux": + __platform = "linux" + else: + raise ConfigurationError("Unknown platform %s" % sys.platform) + else: + if not env["PLATFORM"] in ("win32", "cygwin") + Posix: + raise ConfigurationError("Unknown platform %s" % env["PLATFORM"]) + __platform = env["PLATFORM"] + + return __platform + +# sunos, aix, hpux, irix, sunos appear to be platforms known by SCons, assuming they're POSIX compliant +Posix = ("linux", "darwin", "sunos", "aix", "hpux", "irix", "sunos") +Windows = ("win32", "cygwin") +env = Environment(CPPPATH="pa_common") + +Platform = getPlatform() + +opts = Options("options.cache", args=ARGUMENTS) +if Platform in Posix: + opts.AddOptions( + _DirectoryOption("prefix", "installation prefix", "/usr/local"), + _PackageOption("ALSA"), + _PackageOption("OSS"), + _PackageOption("JACK"), + ) +elif Platform in Windows: + if Platform == "cygwin": + opts.AddOptions(_DirectoryOption("prefix", "installation prefix", "/usr/local")) + opts.AddOptions(_EnumOption("winAPI", "Windows API to use", ("wmme", "directx", "asio"), "wmme")) + +if Platform == "darwin": + opts.AddOptions(_EnumOption("macAPI", "Mac API to use", ("asio", "core", "sm"), "core")) + +opts.AddOptions( + _BoolOption("shared", "create shared library"), + _BoolOption("static", "create static library"), + _BoolOption("debug", "compile with debug symbols"), + _BoolOption("optimize", "compile with optimization", default="no"), + _BoolOption("asserts", "runtime assertions are helpful for debugging, but can be detrimental to performance", default="yes"), + _BoolOption("debugOutput", "enable debug output", default="no"), + ("customCFlags", "customize compilation of C code", ""), + ) + +opts.Update(env) +opts.Save("options.cache", env) + +Help(opts.GenerateHelpText(env)) + +env.SConscriptChdir(False) +SConscript("SConscript", build_dir=".build_scons", exports=["env", "Platform", "Posix", "ConfigurationError"], duplicate=False) diff --git a/portaudio-v19/V19-devel-readme.txt b/portaudio-v19/V19-devel-readme.txt new file mode 100644 index 000000000..ae5570eb6 --- /dev/null +++ b/portaudio-v19/V19-devel-readme.txt @@ -0,0 +1,222 @@ +STATUS: + +MME, DirectSound and ASIO versions are more-or-less working. See FIXMEs @todos +and the proposals matrix at portaudio.com for further status. + +The pa_tests directory contains tests. pa_tests/README.txt notes which tests +currently build. + +The PaUtil support code is finished enough for other implementations to be +ported. No changes are expected to be made to the definition of the PaUtil +functions. + +Note that it's not yet 100% clear how the current support functions +will interact with blocking read/write streams. + +BUILD INSTRUCTIONS + +to build tests/patest_sine.c you will need to compile and link the following +files (MME) +pa_common\pa_process.c +pa_common\pa_skeleton.c +pa_common\pa_stream.c +pa_common\pa_trace.c +pa_common\pa_converters.c +pa_common\pa_cpuload.c +pa_common\pa_dither.c +pa_common\pa_front.c +pa_common\pa_allocation.h +pa_win\pa_win_util.c +pa_win\pa_win_hostapis.c +pa_win_wmme\pa_win_wmme.c + +see below for a description of these files. + + +FILES: + +portaudio.h + public api header file + +pa_front.c + implements the interface defined in portaudio.h. manages multiple host apis. + validates function parameters before calling through to host apis. tracks + open streams and closes them at Pa_Terminate(). + +pa_util.h + declares utility functions for use my implementations. including utility + functions which must be implemented separately for each platform. + +pa_hostapi.h + hostapi representation structure used to interface between pa_front.c + and implementations + +pa_stream.c/h + stream interface and representation structures and helper functions + used to interface between pa_front.c and implementations + +pa_cpuload.c/h + source and header for cpu load calculation facility + +pa_trace.c/h + source and header for debug trace log facility + +pa_converters.c/h + sample buffer conversion facility + +pa_dither.c/h + dither noise generator + +pa_process.c/h + callback buffer processing facility including interleave and block adaption + +pa_allocation.c/h + allocation context for tracking groups of allocations + +pa_skeleton.c + an skeleton implementation showing how the common code can be used. + +pa_win_util.c + Win32 implementation of platform specific PaUtil functions (memory allocation, + usec clock, Pa_Sleep().) The file will be used with all Win32 host APIs. + +pa_win_hostapis.c + contains the paHostApiInitializers array and an implementation of + Pa_GetDefaultHostApi() for win32 builds. + +pa_win_wmme.c + Win32 host api implementation for the windows multimedia extensions audio API. + +pa_win_wmme.h + public header file containing interfaces to mme-specific functions and the + deviceInfo data structure. + + +CODING GUIDELINES: + +naming conventions: + #defines begin with PA_ + #defines local to a file end with _ + global utility variables begin with paUtil + global utility types begin with PaUtil (including function types) + global utility functions begin with PaUtil_ + static variables end with _ + static constants begin with const and end with _ + static funtions have no special prefix/suffix + +In general, implementations should declare all of their members static, +except for their initializer which should be exported. All exported names +should be preceeded by Pa_ where MN is the module name, for example +the windows mme initializer should be named PaWinWmme_Initialize(). + +Every host api should define an initializer which returns an error code +and a PaHostApiInterface*. The initializer should only return an error other +than paNoError if it encounters an unexpected and fatal error (memory allocation +error for example). In general, there may be conditions under which it returns +a NULL interface pointer and also returns paNoError. For example, if the ASIO +implementation detects that ASIO is not installed, it should return a +NULL interface, and paNoError. + +Platform-specific shared functions should begin with Pa_ where PN is the +platform name. eg. PaWin_ for windows, PaUnix_ for unix. + +The above two conventions should also be followed whenever it is necessary to +share functions accross multiple source files. + +Two utilities for debug messages are provided. The PA_DEBUG macro defined in +pa_implementation.h provides a simple way to print debug messages to stderr. +Due to real-time performance issues, PA_DEBUG may not be suitable for use +within the portaudio processing callback, or in other threads. In such cases +the event tracing facility provided in pa_trace.h may be more appropriate. + +If PA_LOG_API_CALLS is defined, all calls to the public PortAudio API +will be logged to stderr along with parameter and return values. + + +TODO: + (this list is totally out of date) + + finish coding converter functions in pa_converters.c (anyone?) + + implement block adaption in pa_process.c (phil?) + + fix all current tests to work with new code. this should mostly involve + changing PortAudioStream to PaStream, and GetDefaultDeviceID to GetDefaultDevice etc. + + write some new tests to exercise the multi-api functions + + write (doxygen) documentation for pa_trace (phil?) + + remove unused typeids from PaHostAPITypeID + + create a global configuration file which documents which PA_ defines can be + used for configuration + + need a coding standard for comment formatting + + migrate directx (phil) + + migrate asio (ross?, stephane?) + + see top of pa_win_wmme.c for MME todo items (ross) + + write style guide document (ross) + + +DESIGN ISSUES: + (this list is totally out of date) + + consider removing Pa_ConvertHostApiDeviceIndexToGlobalDeviceIndex() from the API + + switch to new latency parameter mechanism now (?) + + question: if input or outputDriverInfo structures are passed for a different + hostApi from the one being called, do we return an error or just ignore + them? (i think return error) + + consider renaming PortAudioCallback to PaStreamCallback + + consider renaming PaError, PaResult + + +ASSORTED DISORGANISED NOTES: + + NOTE: + pa_lib.c performs the following validations for Pa_OpenStream() which we do not currently do: + - checks the device info to make sure that the device supports the requested sample rate, + it may also change the sample rate to the "closest available" sample rate if it + is within a particular error margin + + rationale for breaking up internalPortAudioStream: + each implementation has its own requirements and behavior, and should be + able to choose the best way to operate without being limited by the + constraints imposed by a common infrastructure. in other words the + implementations should be able to pick and choose services from the + common infrastructure. currently identified services include: + + - cpu load tracking + - buffering and conversion service (same code works for input and output) + - should support buffer multiplexing (non-integer length input and output buffers) + - in-place conversion where possible (only for callback, read/write always copies) + - should manage allocation of temporary buffers if necessary + - instrumentation (should be able to be disabled): callback count, framesProcessed + - common data: magic, streamInterface, callback, userdata + + +- conversion functions: + - should handle temp buffer allocation + - dithering (random number state per-stream) + - buffer size mismatches + - with new buffer slip rules, temp buffers may always be needed + - we should aim for in-place conversion wherever possible + - does phil's code support in-place conversion? (yes) + +- dicuss relationship between user and host buffer sizes + - completely independent.. individual implementations may constrain + host buffer sizes if necessary + + +- discuss device capabilities: + - i'd like to be able to request certain information: + - channel count for example + diff --git a/portaudio-v19/aclocal.m4 b/portaudio-v19/aclocal.m4 new file mode 100644 index 000000000..c80e0acfc --- /dev/null +++ b/portaudio-v19/aclocal.m4 @@ -0,0 +1,57 @@ + +dnl PKG_CHECK_MODULES(GSTUFF, gtk+-2.0 >= 1.3 glib = 1.3.4, action-if, action-not) +dnl defines GSTUFF_LIBS, GSTUFF_CFLAGS, see pkg-config man page +dnl also defines GSTUFF_PKG_ERRORS on error +AC_DEFUN(PKG_CHECK_MODULES, [ + succeeded=no + + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + AC_MSG_CHECKING(for $2) + + if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + succeeded=yes + + AC_MSG_CHECKING($1_CFLAGS) + $1_CFLAGS=`$PKG_CONFIG --cflags "$2"` + AC_MSG_RESULT($$1_CFLAGS) + + AC_MSG_CHECKING($1_LIBS) + $1_LIBS=`$PKG_CONFIG --libs "$2"` + AC_MSG_RESULT($$1_LIBS) + else + $1_CFLAGS="" + $1_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + $1_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + ifelse([$4], ,echo $$1_PKG_ERRORS,) + fi + + AC_SUBST($1_CFLAGS) + AC_SUBST($1_LIBS) + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + ifelse([$3], , :, [$3]) + else + ifelse([$4], , AC_MSG_ERROR([Library requirements ($2) not met; consider adjusting the PKG_CONFIG_PATH environment variable if your libraries are in a nonstandard prefix so pkg-config can find them.]), [$4]) + fi +]) + + diff --git a/portaudio-v19/config.cache b/portaudio-v19/config.cache new file mode 100644 index 000000000..e4d56e18d --- /dev/null +++ b/portaudio-v19/config.cache @@ -0,0 +1,30 @@ +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +ac_cv_c_bigendian=${ac_cv_c_bigendian=no} +ac_cv_lib_asound_snd_pcm_open=${ac_cv_lib_asound_snd_pcm_open=no} +ac_cv_lib_pthread_pthread_create=${ac_cv_lib_pthread_pthread_create=yes} +ac_cv_path_AR=${ac_cv_path_AR=/usr/bin/ar} +ac_cv_path_PKG_CONFIG=${ac_cv_path_PKG_CONFIG=/usr/local/bin/pkg-config} +ac_cv_path_install=${ac_cv_path_install='/usr/bin/install -c'} +ac_cv_prog_CC=${ac_cv_prog_CC=cc} +ac_cv_prog_RANLIB=${ac_cv_prog_RANLIB=ranlib} +ac_cv_prog_cc_cross=${ac_cv_prog_cc_cross=no} +ac_cv_prog_cc_g=${ac_cv_prog_cc_g=yes} +ac_cv_prog_cc_works=${ac_cv_prog_cc_works=yes} +ac_cv_prog_gcc=${ac_cv_prog_gcc=yes} +ac_cv_sizeof_int=${ac_cv_sizeof_int=4} +ac_cv_sizeof_long=${ac_cv_sizeof_long=4} +ac_cv_sizeof_short=${ac_cv_sizeof_short=2} +lt_cv_sys_max_cmd_len=${lt_cv_sys_max_cmd_len=262144} diff --git a/portaudio-v19/config.doxy b/portaudio-v19/config.doxy new file mode 100644 index 000000000..f6509a32d --- /dev/null +++ b/portaudio-v19/config.doxy @@ -0,0 +1,185 @@ +# Doxyfile 1.2.13-20020210 + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = PortAudio +PROJECT_NUMBER = 2.0 +OUTPUT_DIRECTORY = "./docs/" +OUTPUT_LANGUAGE = English +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +INTERNAL_DOCS = NO +STRIP_CODE_COMMENTS = YES +CASE_SENSE_NAMES = YES +SHORT_NAMES = NO +HIDE_SCOPE_NAMES = NO +VERBATIM_HEADERS = YES +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +ALIASES = +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = ./pa_common ./pa_win_wmme ./pa_asio ./pa_win_ds ./pa_linux_alsa ./pa_unix_oss ./pa_jack ./pa_tests +FILE_PATTERNS = *.h *.c *.cpp +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = doxygen_html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +HIDE_UNDOC_RELATIONS = NO +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO +CGI_NAME = search.cgi +CGI_URL = +DOC_URL = +DOC_ABSPATH = +BIN_ABSPATH = /usr/local/bin/ +EXT_DOC_PATHS = diff --git a/portaudio-v19/config.guess b/portaudio-v19/config.guess new file mode 100755 index 000000000..0e30d56e9 --- /dev/null +++ b/portaudio-v19/config.guess @@ -0,0 +1,1407 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003 Free Software Foundation, Inc. + +timestamp='2003-07-02' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + macppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvmeppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mipseb-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sun3:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha*:OpenVMS:*:*) + echo alpha-hp-vms + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit 0 ;; + DRS?6000:UNIX_SV:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7 && exit 0 ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c \ + && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && exit 0 + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + # avoid double evaluation of $set_cc_for_build + test -n "$CC_FOR_BUILD" || eval $set_cc_for_build + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + *:UNICOS/mp:*:*) + echo nv1-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*|*:GNU/FreeBSD:*:*) + # Determine whether the default compiler uses glibc. + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #if __GLIBC__ >= 2 + LIBC=gnu + #else + LIBC= + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + # GNU/FreeBSD systems have a "k" prefix to indicate we are using + # FreeBSD's kernel, but not the complete OS. + case ${LIBC} in gnu) kernel_only='k' ;; esac + echo ${UNAME_MACHINE}-unknown-${kernel_only}freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit 0 ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit 0 ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + case `uname -p` in + *86) UNAME_PROCESSOR=i686 ;; + powerpc) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-[DGKLNPTVW]:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/portaudio-v19/config.log b/portaudio-v19/config.log new file mode 100644 index 000000000..8f29e67d0 --- /dev/null +++ b/portaudio-v19/config.log @@ -0,0 +1,23 @@ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +configure:618: checking for gcc +configure:731: checking whether the C compiler (cc ) works +configure:747: cc -o conftest conftest.c 1>&5 +configure:773: checking whether the C compiler (cc ) is a cross-compiler +configure:778: checking whether we are using GNU C +configure:806: checking whether cc accepts -g +configure:840: checking for ranlib +configure:898: checking for a BSD compatible install +configure:953: checking for ar +configure:991: checking whether byte ordering is bigendian +configure:1083: checking for snd_pcm_open in -lasound +configure:1131: checking host system type +configure:1159: checking for pkg-config +configure:1203: checking for jack +configure:1210: checking JACK_CFLAGS +configure:1215: checking JACK_LIBS +configure:1244: checking size of short +configure:1283: checking size of int +configure:1322: checking size of long +configure:1632: checking for pthread_create in -lpthread diff --git a/portaudio-v19/config.status b/portaudio-v19/config.status new file mode 100755 index 000000000..9ca00d332 --- /dev/null +++ b/portaudio-v19/config.status @@ -0,0 +1,175 @@ +#! /bin/sh +# Generated automatically by configure. +# Run this file to recreate the current configuration. +# This directory was configured as follows, +# on host night.db.net: +# +# ./configure --with-jack=no +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: ./config.status [--recheck] [--version] [--help]" +for ac_option +do + case "$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running ${CONFIG_SHELL-/bin/sh} ./configure --with-jack=no --no-create --no-recursion" + exec ${CONFIG_SHELL-/bin/sh} ./configure --with-jack=no --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "./config.status generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "$ac_cs_usage"; exit 0 ;; + *) echo "$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=. +ac_given_INSTALL="/usr/bin/install -c" + +trap 'rm -fr Makefile conftest*; exit 1' 1 2 15 + +# Protect against being on the right side of a sed subst in config.status. +sed 's/%@/@@/; s/@%/@@/; s/%g$/@g/; /@g$/s/[\\&%]/\\&/g; + s/@@/%@/; s/@@/@%/; s/@g$/%g/' > conftest.subs <<\CEOF +/^[ ]*VPATH[ ]*=[^:]*$/d + +s%@SHELL@%/usr/local/bin/bash%g +s%@CFLAGS@%-g -O2 -Wall -pedantic -pipe -fPIC -DPA_LITTLE_ENDIAN%g +s%@CPPFLAGS@%%g +s%@CXXFLAGS@%%g +s%@FFLAGS@%%g +s%@DEFS@% -DSIZEOF_SHORT=2 -DSIZEOF_INT=4 -DSIZEOF_LONG=4 -DHAVE_LIBPTHREAD=1 -DPA_USE_OSS=1 %g +s%@LDFLAGS@%%g +s%@LIBS@%-lpthread -lm -lpthread%g +s%@exec_prefix@%${prefix}%g +s%@prefix@%/usr/local%g +s%@program_transform_name@%s,x,x,%g +s%@bindir@%${exec_prefix}/bin%g +s%@sbindir@%${exec_prefix}/sbin%g +s%@libexecdir@%${exec_prefix}/libexec%g +s%@datadir@%${prefix}/share%g +s%@sysconfdir@%${prefix}/etc%g +s%@sharedstatedir@%${prefix}/com%g +s%@localstatedir@%${prefix}/var%g +s%@libdir@%${exec_prefix}/lib%g +s%@includedir@%${prefix}/include%g +s%@oldincludedir@%/usr/include%g +s%@infodir@%${prefix}/info%g +s%@mandir@%${prefix}/man%g +s%@CC@%cc%g +s%@RANLIB@%ranlib%g +s%@INSTALL_PROGRAM@%${INSTALL}%g +s%@INSTALL_SCRIPT@%${INSTALL_PROGRAM}%g +s%@INSTALL_DATA@%${INSTALL} -m 644%g +s%@AR@%/usr/bin/ar%g +s%@host@%i386-unknown-freebsd7.0%g +s%@host_alias@%i386-unknown-freebsd7.0%g +s%@host_cpu@%i386%g +s%@host_vendor@%unknown%g +s%@host_os@%freebsd7.0%g +s%@PKG_CONFIG@%/usr/local/bin/pkg-config%g +s%@JACK_CFLAGS@%-I/usr/local/include %g +s%@JACK_LIBS@%-pthread -L/usr/local/lib -ljack %g +s%@OTHER_OBJS@% pa_unix_oss/pa_unix_oss.o pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o%g +s%@PADLL@%libportaudio.so%g +s%@SHARED_FLAGS@%-shared -fPIC%g +s%@DLL_LIBS@%%g +s%@NASM@%%g +s%@NASMOPT@%%g + +CEOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi + +CONFIG_FILES=${CONFIG_FILES-"Makefile"} +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + + + +exit 0 diff --git a/portaudio-v19/config.sub b/portaudio-v19/config.sub new file mode 100755 index 000000000..9d7f73390 --- /dev/null +++ b/portaudio-v19/config.sub @@ -0,0 +1,1504 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003 Free Software Foundation, Inc. + +timestamp='2003-07-04' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | kfreebsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k \ + | m32r | m68000 | m68k | m88k | mcore \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | msp430 \ + | ns16k | ns32k \ + | openrisc | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xscale | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | amd64-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* \ + | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* \ + | m32r-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | msp430-* \ + | none-* | np1-* | nv1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \ + | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \ + | xtensa-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nv1) + basic_machine=nv1-cray + os=-unicosmp + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + or32 | or32-*) + basic_machine=or32-unknown + os=-coff + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -kfreebsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/portaudio-v19/configure b/portaudio-v19/configure new file mode 100755 index 000000000..017015446 --- /dev/null +++ b/portaudio-v19/configure @@ -0,0 +1,1990 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.13 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --with-alsa (default=yes)" +ac_help="$ac_help + --with-jack (default=yes)" +ac_help="$ac_help + --with-oss (default=yes)" +ac_help="$ac_help + --with-host_os (no default)" +ac_help="$ac_help + --with-winapi ((wmme/directx/asio) default=wmme)" +ac_help="$ac_help + --with-macapi ((asio/core/sm) default=core)" +ac_help="$ac_help + --with-asiodir (default=/usr/local/asiosdk2)" +ac_help="$ac_help + --with-dxdir (default=/usr/local/dx7sdk)" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.13" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=pa_common/portaudio.h + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +ac_exeext= +ac_objext=o +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + + +# Check whether --with-alsa or --without-alsa was given. +if test "${with_alsa+set}" = set; then + withval="$with_alsa" + with_alsa=$withval +else + with_alsa="yes" +fi + + +# Check whether --with-jack or --without-jack was given. +if test "${with_jack+set}" = set; then + withval="$with_jack" + with_jack=$withval +else + with_jack="yes" +fi + + +# Check whether --with-oss or --without-oss was given. +if test "${with_oss+set}" = set; then + withval="$with_oss" + with_oss=$withval +else + with_oss="yes" +fi + + +# Check whether --with-host_os or --without-host_os was given. +if test "${with_host_os+set}" = set; then + withval="$with_host_os" + host_os=$withval +fi + + +# Check whether --with-winapi or --without-winapi was given. +if test "${with_winapi+set}" = set; then + withval="$with_winapi" + with_winapi=$withval +else + with_winapi="wmme" +fi + + +# Check whether --with-macapi or --without-macapi was given. +if test "${with_macapi+set}" = set; then + withval="$with_macapi" + with_macapi=$withval +else + with_macapi="core" +fi + + +# Check whether --with-asiodir or --without-asiodir was given. +if test "${with_asiodir+set}" = set; then + withval="$with_asiodir" + with_asiodir=$withval +else + with_asiodir="/usr/local/asiosdk2" +fi + + +# Check whether --with-dxdir or --without-dxdir was given. +if test "${with_dxdir+set}" = set; then + withval="$with_dxdir" + with_dxdir=$withval +else + with_dxdir="/usr/local/dx7sdk" +fi + + + + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:618: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:648: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_prog_rejected=no + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test -z "$CC"; then + case "`uname -s`" in + *win32* | *WIN32*) + # Extract the first word of "cl", so it can be a program name with args. +set dummy cl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:699: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="cl" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + ;; + esac + fi + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:731: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext << EOF + +#line 742 "configure" +#include "confdefs.h" + +main(){return(0);} +EOF +if { (eval echo configure:747: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:773: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:778: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes +else + GCC= +fi + +ac_test_CFLAGS="${CFLAGS+set}" +ac_save_CFLAGS="$CFLAGS" +CFLAGS= +echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:806: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 +if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi + +# Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:840: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_RANLIB="ranlib" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":" +fi +fi +RANLIB="$ac_cv_prog_RANLIB" +if test -n "$RANLIB"; then + echo "$ac_t""$RANLIB" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:898: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +# Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:953: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_AR'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$AR" in + /*) + ac_cv_path_AR="$AR" # Let the user override the test with a path. + ;; + ?:/*) + ac_cv_path_AR="$AR" # Let the user override the test with a dos path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_AR="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_AR" && ac_cv_path_AR="no" + ;; +esac +fi +AR="$ac_cv_path_AR" +if test -n "$AR"; then + echo "$ac_t""$AR" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if [ $AR = "no" ] ; then + { echo "configure: error: "Could not find ar - needed to create a library"" 1>&2; exit 1; }; +fi + +echo $ac_n "checking whether byte ordering is bigendian""... $ac_c" 1>&6 +echo "configure:991: checking whether byte ordering is bigendian" >&5 +if eval "test \"`echo '$''{'ac_cv_c_bigendian'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_cv_c_bigendian=unknown +# See if sys/param.h defines the BYTE_ORDER macro. +cat > conftest.$ac_ext < +#include +int main() { + +#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN + bogus endian macros +#endif +; return 0; } +EOF +if { (eval echo configure:1009: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + # It does; now see whether it defined to BIG_ENDIAN or not. +cat > conftest.$ac_ext < +#include +int main() { + +#if BYTE_ORDER != BIG_ENDIAN + not big endian +#endif +; return 0; } +EOF +if { (eval echo configure:1024: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_bigendian=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_c_bigendian=no +fi +rm -f conftest* +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 +fi +rm -f conftest* +if test $ac_cv_c_bigendian = unknown; then +if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + ac_cv_c_bigendian=no +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_c_bigendian=yes +fi +rm -fr conftest* +fi + +fi +fi + +echo "$ac_t""$ac_cv_c_bigendian" 1>&6 +if test $ac_cv_c_bigendian = yes; then + cat >> confdefs.h <<\EOF +#define WORDS_BIGENDIAN 1 +EOF + +fi + + + +echo $ac_n "checking for snd_pcm_open in -lasound""... $ac_c" 1>&6 +echo "configure:1083: checking for snd_pcm_open in -lasound" >&5 +ac_lib_var=`echo asound'_'snd_pcm_open | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lasound $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + have_alsa=yes +else + echo "$ac_t""no" 1>&6 +have_alsa=no +fi + + + +# Make sure we can run config.sub. +if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then : +else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; } +fi + +echo $ac_n "checking host system type""... $ac_c" 1>&6 +echo "configure:1131: checking host system type" >&5 + +host_alias=$host +case "$host_alias" in +NONE) + case $nonopt in + NONE) + if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then : + else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; } + fi ;; + *) host_alias=$nonopt ;; + esac ;; +esac + +host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias` +host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` +echo "$ac_t""$host" 1>&6 + + + + succeeded=no + + if test -z "$PKG_CONFIG"; then + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1159: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_PKG_CONFIG'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$PKG_CONFIG" in + /*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + ?:/*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a dos path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_PKG_CONFIG="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_PKG_CONFIG" && ac_cv_path_PKG_CONFIG="no" + ;; +esac +fi +PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +if test -n "$PKG_CONFIG"; then + echo "$ac_t""$PKG_CONFIG" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + echo $ac_n "checking for jack""... $ac_c" 1>&6 +echo "configure:1203: checking for jack" >&5 + + if $PKG_CONFIG --exists "jack" ; then + echo "$ac_t""yes" 1>&6 + succeeded=yes + + echo $ac_n "checking JACK_CFLAGS""... $ac_c" 1>&6 +echo "configure:1210: checking JACK_CFLAGS" >&5 + JACK_CFLAGS=`$PKG_CONFIG --cflags "jack"` + echo "$ac_t""$JACK_CFLAGS" 1>&6 + + echo $ac_n "checking JACK_LIBS""... $ac_c" 1>&6 +echo "configure:1215: checking JACK_LIBS" >&5 + JACK_LIBS=`$PKG_CONFIG --libs "jack"` + echo "$ac_t""$JACK_LIBS" 1>&6 + else + JACK_CFLAGS="" + JACK_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + JACK_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "jack"` + + fi + + + + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + have_jack=yes + else + have_jack=no + fi + + + +echo $ac_n "checking size of short""... $ac_c" 1>&6 +echo "configure:1244: checking size of short" >&5 +if eval "test \"`echo '$''{'ac_cv_sizeof_short'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(short)); + exit(0); +} +EOF +if { (eval echo configure:1263: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + ac_cv_sizeof_short=`cat conftestval` +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_sizeof_short=0 +fi +rm -fr conftest* +fi + +fi +echo "$ac_t""$ac_cv_sizeof_short" 1>&6 +cat >> confdefs.h <&6 +echo "configure:1283: checking size of int" >&5 +if eval "test \"`echo '$''{'ac_cv_sizeof_int'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(int)); + exit(0); +} +EOF +if { (eval echo configure:1302: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + ac_cv_sizeof_int=`cat conftestval` +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_sizeof_int=0 +fi +rm -fr conftest* +fi + +fi +echo "$ac_t""$ac_cv_sizeof_int" 1>&6 +cat >> confdefs.h <&6 +echo "configure:1322: checking size of long" >&5 +if eval "test \"`echo '$''{'ac_cv_sizeof_long'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else + cat > conftest.$ac_ext < +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(long)); + exit(0); +} +EOF +if { (eval echo configure:1341: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + ac_cv_sizeof_long=`cat conftestval` +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_sizeof_long=0 +fi +rm -fr conftest* +fi + +fi +echo "$ac_t""$ac_cv_sizeof_long" 1>&6 +cat >> confdefs.h <> confdefs.h <<\EOF +#define PA_USE_COREAUDIO 1 +EOF + + OTHER_OBJS="pa_mac/pa_mac_hostapis.o pa_unix/pa_unix_util.o pa_mac_core/pa_mac_core.o"; + LIBS="-framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon"; + PADLL="libportaudio.dylib"; + SHARED_FLAGS="-framework CoreAudio -framework AudioToolbox -dynamiclib -framework AudioUnit -framework Carbon"; + if [ $with_macapi = "asio" ] ; then + if [ $with_asiodir ] ; then + ASIODIR="$with_asiodir"; + else + ASIODIR="/usr/local/asiosdk2"; + fi + echo "ASIODIR: $ASIODIR"; + + OTHER_OBJS="$CFLAGS pa_asio/iasiothiscallresolver.o $ASIODIR/host/asiodrivers.o $ASIODIR/common/asio.o $ASIODIR/host/mac/asioshlib.o"; + CFLAGS="$CFLAGS -Ipa_asio -I$ASIDIR/host/mac -I$ASIODIR/common"; + CXXFLAGS="$CFLAGS"; + fi + ;; + + mingw* ) + + echo "WINAPI: $with_winapi" + if [ $with_winapi = "directx" ] ; then + if [ $with_dxdir ] ; then + DXDIR="$with_dxdir"; + else + DXDIR="/usr/local/dx7sdk"; + fi + echo "DXDIR: $DXDIR" + OTHER_OBJS="pa_win_ds/pa_win_ds.o pa_win_ds/dsound_wrapper.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -ldsound -lole32"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -L./dx7sdk/lib -ldsound -lole32"; + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\""; + #CFLAGS="$CFLAGS -I$VC98 -DPA_NO_WMME -DPA_NO_ASIO"; + CFLAGS="$CFLAGS -Ipa_common -I$DXDIR/include -DPA_NO_WMME -DPA_NO_ASIO" -DPA_NO_WDMKS; + elif [ $with_winapi = "asio" ] ; then + if [ $with_asiodir ] ; then + ASIODIR="$with_asiodir"; + else + ASIODIR="/usr/local/asiosdk2"; + fi + echo "ASIODIR: $ASIODIR" + + OTHER_OBJS="pa_asio/pa_asio.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o pa_asio/iasiothiscallresolver.o $ASIODIR/common/asio.o $ASIODIR/host/asiodrivers.o $ASIODIR/host/pc/asiolist.o"; + LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + CFLAGS="$CFLAGS -ffast-math -fomit-frame-pointer -Ipa_common -Ipa_asio -I$ASIODIR/host/pc -I$ASIODIR/common -I$ASIODIR/host -DPA_NO_WMME -DPA_NO_DS -DPA_NO_WDMKS -DWINDOWS"; + CXXFLAGS="$CFLAGS"; + elif [ $with_winapi = "wdmks" ] ; then + if [ $with_dxdir ] ; then + DXDIR="$with_dxdir"; + else + DXDIR="/usr/local/dx7sdk"; + fi + echo "DXDIR: $DXDIR" + OTHER_OBJS="pa_win_wdmks/pa_win_wdmks.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -luuid -lsetupapi -lole32"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -L./dx7sdk/lib -luuid -lsetupapi -lole32"; + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\""; + #CFLAGS="$CFLAGS -I$VC98 -DPA_NO_WMME -DPA_NO_ASIO"; + CFLAGS="$CFLAGS -Ipa_common -I$DXDIR/include -DPA_NO_WMME -DPA_NO_DS -DPA_NO_ASIO"; + else # WMME default + OTHER_OBJS="pa_win_wmme/pa_win_wmme.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm"; + CFLAGS="$CFLAGS -Ipa_common -DPA_NO_DS -DPA_NO_ASIO -DPA_NO_WDMKS"; + fi + ;; + + cygwin* ) + + OTHER_OBJS="pa_win_wmme/pa_win_wmme.o"; + LIBS="-lwinmm -lm"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm"; + ;; + + irix* ) + echo $ac_n "checking for pthread_create in -lpthread""... $ac_c" 1>&6 +echo "configure:1471: checking for pthread_create in -lpthread" >&5 +ac_lib_var=`echo pthread'_'pthread_create | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lpthread $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo pthread | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +{ echo "configure: error: IRIX posix thread library not found!" 1>&2; exit 1; } +fi + + echo $ac_n "checking for alOpenPort in -laudio""... $ac_c" 1>&6 +echo "configure:1519: checking for alOpenPort in -laudio" >&5 +ac_lib_var=`echo audio'_'alOpenPort | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-laudio $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo audio | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +{ echo "configure: error: IRIX audio library not found!" 1>&2; exit 1; } +fi + + echo $ac_n "checking for dmGetUST in -ldmedia""... $ac_c" 1>&6 +echo "configure:1567: checking for dmGetUST in -ldmedia" >&5 +ac_lib_var=`echo dmedia'_'dmGetUST | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldmedia $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo dmedia | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +{ echo "configure: error: IRIX digital media library not found!" 1>&2; exit 1; } +fi + + + cat >> confdefs.h <<\EOF +#define PA_USE_SGI 1 +EOF + + + CFLAGS="$CFLAGS -D_REENTRANT" + + OTHER_OBJS="pa_sgi/pa_sgi.o pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o"; + + LIBS="-lm -ldmedia -laudio -lpthread"; + PADLL="libportaudio.so"; + SHARED_FLAGS="-shared"; + ;; + + *) + + echo $ac_n "checking for pthread_create in -lpthread""... $ac_c" 1>&6 +echo "configure:1632: checking for pthread_create in -lpthread" >&5 +ac_lib_var=`echo pthread'_'pthread_create | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lpthread $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo pthread | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +{ echo "configure: error: libpthread not found!" 1>&2; exit 1; } +fi + + + if [ $have_alsa = "yes" ] && [ $with_alsa != "no" ] ; then + LIBS="$LIBS -lasound" + DLL_LIBS="$DLL_LIBS -lasound" + OTHER_OBJS="$OTHER_OBJS pa_linux_alsa/pa_linux_alsa.o" + cat >> confdefs.h <<\EOF +#define PA_USE_ALSA 1 +EOF + + fi + + if [ $have_jack = "yes" ] && [ $with_jack != "no" ] ; then + LIBS="$LIBS $JACK_LIBS" + DLL_LIBS="$DLL_LIBS $JACK_LIBS" + CFLAGS="$CFLAGS $JACK_CFLAGS" + OTHER_OBJS="$OTHER_OBJS pa_jack/pa_jack.o" + cat >> confdefs.h <<\EOF +#define PA_USE_JACK 1 +EOF + + fi + + if [ $with_oss != "no" ] ; then + OTHER_OBJS="$OTHER_OBJS pa_unix_oss/pa_unix_oss.o" + cat >> confdefs.h <<\EOF +#define PA_USE_OSS 1 +EOF + + fi + LIBS="$LIBS -lm -lpthread"; + PADLL="libportaudio.so"; + SHARED_FLAGS="-shared -fPIC"; + + OTHER_OBJS="$OTHER_OBJS pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o" +esac + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +cat > conftest.defs <<\EOF +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%-D\1=\2%g +s%[ `~#$^&*(){}\\|;'"<>?]%\\&%g +s%\[%\\&%g +s%\]%\\&%g +s%\$%$$%g +EOF +DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '` +rm -f conftest.defs + + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@SHELL@%$SHELL%g +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@FFLAGS@%$FFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@CC@%$CC%g +s%@RANLIB@%$RANLIB%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@AR@%$AR%g +s%@host@%$host%g +s%@host_alias@%$host_alias%g +s%@host_cpu@%$host_cpu%g +s%@host_vendor@%$host_vendor%g +s%@host_os@%$host_os%g +s%@PKG_CONFIG@%$PKG_CONFIG%g +s%@JACK_CFLAGS@%$JACK_CFLAGS%g +s%@JACK_LIBS@%$JACK_LIBS%g +s%@OTHER_OBJS@%$OTHER_OBJS%g +s%@PADLL@%$PADLL%g +s%@SHARED_FLAGS@%$SHARED_FLAGS%g +s%@DLL_LIBS@%$DLL_LIBS%g +s%@NASM@%$NASM%g +s%@NASMOPT@%$NASMOPT%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/portaudio-v19/configure.in b/portaudio-v19/configure.in new file mode 100644 index 000000000..640f87800 --- /dev/null +++ b/portaudio-v19/configure.in @@ -0,0 +1,250 @@ +dnl +dnl portaudio V19 configure.in script +dnl +dnl Dominic Mazzoni, Arve Knudsen +dnl + +dnl Require autoconf >= 2.13 +AC_PREREQ(2.13) + +dnl Init autoconf and make sure configure is being called +dnl from the right directory +AC_INIT([pa_common/portaudio.h]) + +dnl Specify options + +AC_ARG_WITH(alsa, + [ --with-alsa (default=yes)], + with_alsa=$withval, with_alsa="yes") + +AC_ARG_WITH(jack, + [ --with-jack (default=yes)], + with_jack=$withval, with_jack="yes") + +AC_ARG_WITH(oss, + [ --with-oss (default=yes)], + with_oss=$withval, with_oss="yes") + +AC_ARG_WITH(host_os, + [ --with-host_os (no default)], + host_os=$withval) + +AC_ARG_WITH(winapi, + [ --with-winapi ((wmme/directx/asio) default=wmme)], + with_winapi=$withval, with_winapi="wmme") + +dnl Mac API added for ASIO, can have other api's listed +AC_ARG_WITH(macapi, + [ --with-macapi ((asio/core/sm) default=core)], + with_macapi=$withval, with_macapi="core") + +AC_ARG_WITH(asiodir, + [ --with-asiodir (default=/usr/local/asiosdk2)], + with_asiodir=$withval, with_asiodir="/usr/local/asiosdk2") + +AC_ARG_WITH(dxdir, + [ --with-dxdir (default=/usr/local/dx7sdk)], + with_dxdir=$withval, with_dxdir="/usr/local/dx7sdk") + + +dnl Checks for programs. + +AC_PROG_CC +AC_PROG_RANLIB +AC_PROG_INSTALL +AC_PATH_PROG(AR, ar, no) +if [[ $AR = "no" ]] ; then + AC_MSG_ERROR("Could not find ar - needed to create a library"); +fi + +dnl This must be one of the first tests we do or it will fail... +AC_C_BIGENDIAN + +dnl checks for various host APIs and arguments to configure that +dnl turn them on or off + +AC_CHECK_LIB(asound, snd_pcm_open, have_alsa=yes, have_alsa=no) + +dnl Determine the host description for the subsequent test. +dnl PKG_CHECK_MODULES seems to check and set the host variable also, but +dnl that then requires pkg-config availability which is not standard on +dnl MinGW systems and can be a pain to install. +AC_CANONICAL_HOST + +PKG_CHECK_MODULES(JACK, jack, have_jack=yes, have_jack=no) + +dnl sizeof checks: we will need a 16-bit and a 32-bit type + +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(long) + +dnl extra variables +AC_SUBST(OTHER_OBJS) +AC_SUBST(PADLL) +AC_SUBST(SHARED_FLAGS) +AC_SUBST(DLL_LIBS) +AC_SUBST(CXXFLAGS) +AC_SUBST(NASM) +AC_SUBST(NASMOPT) + +CFLAGS="-g -O2 -Wall -pedantic -pipe -fPIC" + +if [[ $ac_cv_c_bigendian = "yes" ]] ; then + CFLAGS="$CFLAGS -DPA_BIG_ENDIAN" +else + CFLAGS="$CFLAGS -DPA_LITTLE_ENDIAN" +fi + +case "${host_os}" in + darwin* ) + dnl Mac OS X configuration + + AC_DEFINE(PA_USE_COREAUDIO) + OTHER_OBJS="pa_mac/pa_mac_hostapis.o pa_unix/pa_unix_util.o pa_mac_core/pa_mac_core.o"; + LIBS="-framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon"; + PADLL="libportaudio.dylib"; + SHARED_FLAGS="-framework CoreAudio -framework AudioToolbox -framework -framework AudioUnit -framework Carbon -dynamiclib"; + if [[ $with_macapi = "asio" ]] ; then + if [[ $with_asiodir ]] ; then + ASIODIR="$with_asiodir"; + else + ASIODIR="/usr/local/asiosdk2"; + fi + echo "ASIODIR: $ASIODIR"; + + OTHER_OBJS="$CFLAGS pa_asio/iasiothiscallresolver.o $ASIODIR/host/asiodrivers.o $ASIODIR/common/asio.o $ASIODIR/host/mac/asioshlib.o"; + CFLAGS="$CFLAGS -Ipa_asio -I$ASIDIR/host/mac -I$ASIODIR/common"; + CXXFLAGS="$CFLAGS"; + fi + ;; + + mingw* ) + dnl MingW configuration + + echo "WINAPI: $with_winapi" + if [[ $with_winapi = "directx" ]] ; then + if [[ $with_dxdir ]] ; then + DXDIR="$with_dxdir"; + else + DXDIR="/usr/local/dx7sdk"; + fi + echo "DXDIR: $DXDIR" + OTHER_OBJS="pa_win_ds/pa_win_ds.o pa_win_ds/dsound_wrapper.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -ldsound -lole32"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -L./dx7sdk/lib -ldsound -lole32"; + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\""; + #CFLAGS="$CFLAGS -I$VC98 -DPA_NO_WMME -DPA_NO_ASIO"; + CFLAGS="$CFLAGS -Ipa_common -I$DXDIR/include -DPA_NO_WMME -DPA_NO_ASIO" -DPA_NO_WDMKS; + elif [[ $with_winapi = "asio" ]] ; then + if [[ $with_asiodir ]] ; then + ASIODIR="$with_asiodir"; + else + ASIODIR="/usr/local/asiosdk2"; + fi + echo "ASIODIR: $ASIODIR" + + OTHER_OBJS="pa_asio/pa_asio.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o pa_asio/iasiothiscallresolver.o $ASIODIR/common/asio.o $ASIODIR/host/asiodrivers.o $ASIODIR/host/pc/asiolist.o"; + LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + CFLAGS="$CFLAGS -ffast-math -fomit-frame-pointer -Ipa_common -Ipa_asio -I$ASIODIR/host/pc -I$ASIODIR/common -I$ASIODIR/host -DPA_NO_WMME -DPA_NO_DS -DPA_NO_WDMKS -DWINDOWS"; + CXXFLAGS="$CFLAGS"; + elif [[ $with_winapi = "wdmks" ]] ; then + if [[ $with_dxdir ]] ; then + DXDIR="$with_dxdir"; + else + DXDIR="/usr/local/dx7sdk"; + fi + echo "DXDIR: $DXDIR" + OTHER_OBJS="pa_win_wdmks/pa_win_wdmks.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -luuid -lsetupapi -lole32"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm -lm -L./dx7sdk/lib -luuid -lsetupapi -lole32"; + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\""; + #CFLAGS="$CFLAGS -I$VC98 -DPA_NO_WMME -DPA_NO_ASIO"; + CFLAGS="$CFLAGS -Ipa_common -I$DXDIR/include -DPA_NO_WMME -DPA_NO_DS -DPA_NO_ASIO"; + else # WMME default + OTHER_OBJS="pa_win_wmme/pa_win_wmme.o pa_win/pa_win_hostapis.o pa_win/pa_win_util.o"; + LIBS="-lwinmm -lm -lstdc++ -lole32 -luuid"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm"; + CFLAGS="$CFLAGS -Ipa_common -DPA_NO_DS -DPA_NO_ASIO -DPA_NO_WDMKS"; + fi + ;; + + cygwin* ) + dnl Cygwin configuration + + OTHER_OBJS="pa_win_wmme/pa_win_wmme.o"; + LIBS="-lwinmm -lm"; + PADLL="portaudio.dll"; + SHARED_FLAGS="-shared -mthreads"; + DLL_LIBS="-lwinmm"; + ;; + + irix* ) + dnl SGI IRIX audio library (AL) configuration (Pieter, oct 2-13, 2003). + dnl The 'dmedia' library is needed to read the Unadjusted System Time (UST). + dnl + AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR([IRIX posix thread library not found!])) + AC_CHECK_LIB(audio, alOpenPort, , AC_MSG_ERROR([IRIX audio library not found!])) + AC_CHECK_LIB(dmedia, dmGetUST, , AC_MSG_ERROR([IRIX digital media library not found!])) + + dnl See the '#ifdef PA_USE_SGI' in file pa_unix/pa_unix_hostapis.c + dnl which selects the appropriate PaXXX_Initialize() function. + dnl + AC_DEFINE(PA_USE_SGI) + + dnl The _REENTRANT option for pthread safety. Perhaps not necessary but it 'll do no harm. + dnl + CFLAGS="$CFLAGS -D_REENTRANT" + + OTHER_OBJS="pa_sgi/pa_sgi.o pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o"; + + dnl SGI books say -lpthread should be the last of the libs mentioned. + dnl + LIBS="-lm -ldmedia -laudio -lpthread"; + PADLL="libportaudio.so"; + SHARED_FLAGS="-shared"; + ;; + + *) + dnl Unix configuration + + AC_CHECK_LIB(pthread, pthread_create, + , + AC_MSG_ERROR([libpthread not found!])) + + if [[ $have_alsa = "yes" ] && [ $with_alsa != "no" ]] ; then + LIBS="$LIBS -lasound" + DLL_LIBS="$DLL_LIBS -lasound" + OTHER_OBJS="$OTHER_OBJS pa_linux_alsa/pa_linux_alsa.o" + AC_DEFINE(PA_USE_ALSA) + fi + + if [[ $have_jack = "yes" ] && [ $with_jack != "no" ]] ; then + LIBS="$LIBS $JACK_LIBS" + DLL_LIBS="$DLL_LIBS $JACK_LIBS" + CFLAGS="$CFLAGS $JACK_CFLAGS" + OTHER_OBJS="$OTHER_OBJS pa_jack/pa_jack.o" + AC_DEFINE(PA_USE_JACK) + fi + + if [[ $with_oss != "no" ]] ; then + OTHER_OBJS="$OTHER_OBJS pa_unix_oss/pa_unix_oss.o" + AC_DEFINE(PA_USE_OSS) + fi + LIBS="$LIBS -lm -lpthread"; + PADLL="libportaudio.so"; + SHARED_FLAGS="-shared -fPIC"; + + OTHER_OBJS="$OTHER_OBJS pa_unix/pa_unix_hostapis.o pa_unix/pa_unix_util.o" +esac + +AC_OUTPUT([Makefile]) diff --git a/portaudio-v19/docs/index.html b/portaudio-v19/docs/index.html new file mode 100644 index 000000000..7d9b248d3 --- /dev/null +++ b/portaudio-v19/docs/index.html @@ -0,0 +1,60 @@ + + + + + + + + + PortAudio Docs + + +  +
+ + + +
+
+

+PortAudio Documentation

+
+ +

Copyright 2000 Phil Burk and Ross Bencina +
  +

+API Reference

+ +
The Application Programmer Interface is documented in "portaudio.h".
+ +

+Tutorial

+ +
Describes how to write audio programs using the PortAudio API.
+ +

+Implementation Guide

+ +
Describes how to write an implementation of PortAudio for a +new computer platform.
+ +

+Paper Presented at ICMC2001 (PDF)

+ +
Describes the PortAudio API and discusses implementation issues. +Written July 2001.
+ +

+Improving Latency

+ +
How to tune your computer to achieve the lowest possible audio +delay.
+ +

+Proposed Changes

+ +
Describes API changes being considered by the developer community. +Feedback welcome.
+Return to PortAudio Home Page + + diff --git a/portaudio-v19/docs/latency.html b/portaudio-v19/docs/latency.html new file mode 100644 index 000000000..87f1d1222 --- /dev/null +++ b/portaudio-v19/docs/latency.html @@ -0,0 +1,192 @@ + + + + + + + + + PortAudio Implementation - Start/Stop + + +  +
+ + + +
+
+

+PortAudio Latency

+
+ +

This page discusses the issues of audio latency for PortAudio +. It offers suggestions on how to lower latency to improve the responsiveness +of applications. +

What is Latency? +
PortAudio and Latency +
Macintosh +
Unix +
WIndows
+By Phil Burk, Copyright 2002 Phil Burk and Ross Bencina +

+What is Latency?

+Latency is basically longest time that you have to wait before you obtain +a desired result. For digital audio output it is the time between making +a sound in software and finally hearing it. +

Consider the example of pressing a key on the ASCII keyboard to play +a note. There are several stages in this process which each contribute +their own latency. First the operating system must respond to the keypress. +Then the audio signal generated must work its way through the PortAudio +buffers. Then it must work its way through the audio card hardware. Then +it must go through the audio amplifier which is very quick and then travel +through the air. Sound travels at abous one foot per millisecond through +air so placing speakers across the room can add 5-20 msec of delay. +

The reverse process occurs when recording or responding to audio input. +If you are processing audio, for example if you implement a software guitar +fuzz box, then you have both the audio input and audio output latencies +added together. +

The audio buffers are used to prevent glitches in the audio stream. +The user software writes audio into the output buffers. That audio is read +by the low level audio driver or by DMA and sent to the DAC. If the computer +gets busy doing something like reading the disk or redrawing the screen, +then it may not have time to fill the audio buffer. The audio hardware +then runs out of audio data, which causes a glitch. By using a large enough +buffer we can ensure that there is always enough audio data for the audio +hardware to play. But if the buffer is too large then the latency is high +and the system feels sluggish. If you play notes on the keyboard then the +"instrument" will feel unresponsive. So you want the buffers to be as small +as possible without glitching. +

+PortAudio and Latency

+The only delay that PortAudio can control is the total length of its buffers. +The Pa_OpenStream() call takes two parameters: numBuffers and framesPerBuffer. +The latency is also affected by the sample rate which we will call framesPerSecond. +A frame is a set of samples that occur simultaneously. For a stereo stream, +a frame is two samples. +

The latency in milliseconds due to this buffering  is: +

latency_msec = 1000 * numBuffers * framesPerBuffer / framesPerSecond
+This is not the total latency, as we have seen, but it is the part we can +control. +

If you call Pa_OpenStream() with numBuffers equal to zero, then PortAudio +will select a conservative number that will prevent audio glitches. If +you still get glitches, then you can pass a larger value for numBuffers +until the glitching stops. if you try to pass a numBuffers value that is +too small, then PortAudio will use its own idea of the minimum value. +

PortAudio decides on the minimum number of buffers in a conservative +way based on the frameRate, operating system and other variables. You can +query the value that PortAudio will use by calling: +

int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate +);
+On some systems you can override the PortAudio minimum if you know your +system can handle a lower value. You do this by setting an environment +variable called PA_MIN_LATENCY_MSEC which is read by PortAudio when it +starts up. This is supported on the PortAudio implementations for Windows +MME, Windows DirectSound, and Unix OSS. +

+Macintosh

+The best thing you can do to improve latency on Mac OS 8 and 9 is to turn +off Virtual Memory. PortAudio V18 will detect that Virtual Memory is turned +off and use a very low latency. +

For Mac OS X the latency is very low because Apple Core Audio is so +well written. You can set the PA_MIN_LATENCY_MSEC variable using: +

setenv PA_MIN_LATENCY_MSEC 4
+ +

+Unix

+PortAudio under Unix currently uses a backgroud thread that reads and writes +to OSS. This gives you decent but not great latency. But if you raise the +priority of the background thread to a very priority then you can get under +10 milliseconds latency. In order to raise your priority you must run the +PortAudio program as root! You must also set PA_MIN_LATENCY_MSEC using +the appropriate command for your shell. +

+Windows

+Latency under Windows is a complex issue because of all the alternative +operating system versions and device drivers. I have seen latency range +from 8 milliseconds to 400 milliseconds. The worst case is when using Windows +NT. Windows 98 is a little better, and Windows XP can be quite good if +properly tuned. +

The underlying audio API also makes a lot of difference. If the audio +device has its own DirectSound driver then DirectSound can often provide +better latency than WMME. But if a real DirectSound driver is not available +for your device then it is emulated using WMME and the latency can be very +high. That's where I saw the 400 millisecond latency. The ASIO implementation +is generally very good and will give the lowest latency if available. +

You can set the PA_MIN_LATENCY_MSEC variable to 50, for example, by +entering in MS-DOS: +

set PA_MIN_LATENCY_MSEC=50
+If you enter this in a DOS window then you must run the PortAudio program +from that same window for the variable to have an effect. You can add that +line to your C:\AUTOEXEC.BAT file and reboot if you want it to affect any +PortAudio based program. +

For Windows XP, you can set environment variables as follows: +

    +
  1. +Select "Control Panel" from the "Start Menu".
  2. + +
  3. +Launch the "System" Control Panel
  4. + +
  5. +Click on the "Advanced" tab.
  6. + +
  7. +Click on the "Environment Variables" button.
  8. + +
  9. +Click "New" button under  User Variables.
  10. + +
  11. +Enter PA_MIN_LATENCY_MSEC for the name and some optimistic number for the +value.
  12. + +
  13. +Click OK, OK, OK.
  14. +
+ +

+Improving Latency on Windows

+There are several steps you can take to improve latency under windows. +
    +
  1. +Avoid reading or writng to disk when doing audio.
  2. + +
  3. +Turn off all automated background tasks such as email clients, virus scanners, +backup programs, FTP servers, web servers, etc. when doing audio.
  4. + +
  5. +Disconnect from the network to prevent network traffic from interrupting +your CPU.
  6. +
+Important: Windows XP users can also tune the OS to favor background +tasks, such as audio, over foreground tasks, such as word processing. I +lowered my latency from 40 to 10 milliseconds using this simple technique. +
    +
  1. +Select "Control Panel" from the "Start Menu".
  2. + +
  3. +Launch the "System" Control Panel
  4. + +
  5. +Click on the "Advanced" tab.
  6. + +
  7. +Click on the "Settings" button in the Performance area.
  8. + +
  9. +Click on the "Advanced" tab.
  10. + +
  11. +Select "Background services" in the Processor Scheduling area.
  12. + +
  13. +Click OK, OK.
  14. +
+Please let us know if you have others sugestions for lowering latency. +
  +
  + + diff --git a/portaudio-v19/docs/pa_impl_guide.html b/portaudio-v19/docs/pa_impl_guide.html new file mode 100644 index 000000000..50abc3045 --- /dev/null +++ b/portaudio-v19/docs/pa_impl_guide.html @@ -0,0 +1,197 @@ + + + + + + + + + PortAudio Implementation - Start/Stop + + +  +
+ + + +
+
+

+PortAudio Implementation Guide

+
+ +

This document describes how to implement the PortAudio API on a new +computer platform. Implementing PortAudio on a new platform, makes it possible +to port many existing audio applications to that platform. +

By Phil Burk +
Copyright 2000 Phil Burk and Ross Bencina +

Note that the license says: "Any person wishing to distribute modifications +to the Software is requested to send the modifications to the original +developer so that they can be incorporated into the canonical version.". +So when you have finished a new implementation, please send it back to +us at  "http://www.portaudio.com" +so that we can make it available for other users. Thank you! +

+Download the Latest PortAudio Implementation

+Always start with the latest implementation available at "http://www.portaudio.com". +Look for the nightly snapshot under the CVS section. +

+Select an Existing Implementation as a Basis

+The fastest way to get started is to take an existing implementation and +translate it for your new platform. Choose an implementation whose architecture +is as close as possible to your target. +
    +
  • +DirectSound Implementation - pa_win_ds - Uses a timer callback for the +background "thread". Polls a circular buffer and writes blocks of data +to keep it full.
  • + +
  • +Windows MME - pa_win_wmme - Spawns an actual Win32 thread. Writes blocks +of data to the HW device and waits for events that signal buffer completion.
  • + +
  • +Linux OSS - pa_linux - Spawns a real thread that writes to the "/dev/dsp" +stream using blocking I/O calls.
  • +
+When you write a new implementation, you will be using some code that is +in common with all implementations. This code is in the folder "pa_common". +It provides various functions such as parameter checking, error code to +text conversion, sample format conversion, clipping and dithering, etc. +

The code that you write will go into a separate folder called "pa_{os}_{api}". +For example, code specific to the DirectSound interface for Windows goes +in "pa_win_ds". +

+Read Docs and Code

+Famialiarize yourself with the system by reading the documentation provided. +here is a suggested order: +
    +
  1. +User Programming Tutorial
  2. + +
  3. +Header file "pa_common/portaudio.h" which defines API.
  4. + +
  5. +Header file "pa_common/pa_host.h" for host dependant code. This definces +the routine you will need to provide.
  6. + +
  7. +Shared code in "pa_common/pa_lib.c".
  8. + +
  9. +Docs on Implementation of Start/Stop +code.
  10. +
+ +

+Implement  Output to Default Device

+Now we are ready to crank some code. For instant gratification, let's try +to play a sine wave. +
    +
  1. +Link the test program "pa_tests/patest_sine.c" with the file "pa_lib.c" +and the implementation specific file you are creating.
  2. + +
  3. +For now, just stub out the device query code and the audio input code.
  4. + +
  5. +Modify PaHost_OpenStream() to open your default target device and get everything +setup.
  6. + +
  7. +Modify PaHost_StartOutput() to start playing audio.
  8. + +
  9. +Modify PaHost_StopOutput() to stop audio.
  10. + +
  11. +Modify PaHost_CloseStream() to clean up. Free all memory that you allocated +in PaHost_OpenStream().
  12. + +
  13. +Keep cranking until you can play a sine wave using "patest_sine.c".
  14. + +
  15. +Once that works, try "patest_pink.c", "patest_clip.c", "patest_sine8.c".
  16. + +
  17. +To test your Open and Close code, try "patest_many.c".
  18. + +
  19. +Now test to make sure that the three modes of stopping are properly supported +by running "patest_stop.c".
  20. + +
  21. +Test your implementation of time stamping with "patest_sync.c".
  22. +
+ +

+Implement Device Queries

+Now that output is working, lets implement the code for querying what devices +are available to the user. Run "pa_tests/pa_devs.c". It should print all +of the devices available and their characteristics. +

+Implement Input

+Implement audio input and test it with: +
    +
  1. +patest_record.c - record in half duplex, play back as recorded.
  2. + +
  3. +patest_wire.c - full duplex, copies input to output. Note that some HW +may not support full duplex.
  4. + +
  5. +patest_fuzz.c - plug in your guitar and get a feel for why latency is an +important issue in computer music.
  6. + +
  7. +paqa_devs.c - try to open every device and use it with every possible format
  8. +
+ +

+Debugging Tools

+You generally cannot use printf() calls to debug real-time processes because +they disturb the timing. Also calling printf() from your background thread +or interrupt could crash the machine. So PA includes a tool for capturing +events and storing the information while it is running. It then prints +the events when Pa_Terminate() is called. +
    +
  1. +To enable trace mode, change TRACE_REALTIME_EVENTS in "pa_common/pa_trace.h" +from a (0) to a (1).
  2. + +
  3. +Link with "pa_common/pa_trace.c".
  4. + +
  5. +Add trace messages to your code by calling:
  6. + +
       void AddTraceMessage( char *msg, int data ); +
    for example +
       AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", +past->past_NumCallbacks ); +
  7. +Run your program. You will get a dump of events at the end.
  8. + +
  9. +You can leave the trace messages in your code. They will turn to NOOPs +when you change TRACE_REALTIME_EVENTS back to (0).
  10. +
+ +

+Delivery

+Please send your new code along with notes on the implementation back to +us at "http://www.portaudio.com". +We will review the implementation and post it with your name. If you had +to make any modifications to the code in "pa_common" or "pa_tests" please +send us those modifications and your notes. We will try to merge your changes +so that the "pa_common" code works with all implementations. +

If you have suggestions for how to make future implementations easier, +please let us know. +
THANKS! +
  + + diff --git a/portaudio-v19/docs/pa_impl_startstop.html b/portaudio-v19/docs/pa_impl_startstop.html new file mode 100644 index 000000000..0f2d0ce51 --- /dev/null +++ b/portaudio-v19/docs/pa_impl_startstop.html @@ -0,0 +1,190 @@ + + + + + + + + + PortAudio Implementation - Start/Stop + + +  +

+ + + +
+
+

+PortAudio Implementation

+
+ +

+Starting and Stopping Streams

+PortAudio is generally executed in two "threads". The foreground thread +is the application thread. The background "thread" may be implemented as +an actual thread, an interrupt handler, or a callback from a timer thread. +

There are three ways that PortAudio can stop a stream. In each case +we look at the sequence of events and the messages sent between the two +threads. The following variables are contained in the internalPortAudioStream. +

int   past_IsActive;     +/* Background is still playing. */ +
int   past_StopSoon;     /* Stop +when last buffer done. */ +
int   past_StopNow;      /* +Stop IMMEDIATELY. */
+ +

+Pa_AbortStream()

+This function causes the background thread to terminate as soon as possible +and audio I/O to stop abruptly. +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Foreground ThreadBackground Thread
sets StopNow
sees StopNow
clears IsActive, stops thread
waits for thread to exit
turns off audio I/O
+ +

+Pa_StopStream()

+This function stops the user callback function from being called and then +waits for all audio data written to the output buffer to be played. In +a system with very low latency, you may not hear any difference between +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Foreground ThreadBackground Thread
sets StopSoon
stops calling user callback
continues until output buffer empty
clears IsActive, stops thread
waits for thread to exit
turns off audio I/O
+ +

+User callback returns one.

+If the user callback returns one then the user callback function will no +longer be called. Audio output will continue until all audio data written +to the output buffer has been played. Then the audio I/O is stopped, the +background thread terminates, and the stream becomes inactive. +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Foreground ThreadBackground Thread
callback returns 1
sets StopSoon
stops calling user callback
continues until output buffer empty
clears IsActive, stops thread
waits for thread to exit
turns off audio I/O
+ +
  + + diff --git a/portaudio-v19/docs/pa_tut_asio.html b/portaudio-v19/docs/pa_tut_asio.html new file mode 100644 index 000000000..1c3e5bfa4 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_asio.html @@ -0,0 +1,55 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Compiling for ASIO (Windows or Macintosh)

+ +
ASIO is a low latency audio API from Steinberg. To compile +an ASIO application, you must first download +the ASIO SDK from Steinberg. You also need to obtain ASIO drivers for +your audio device. +

Note: I am using '/' as a file separator below. On Macintosh replace +'/' with ':'. On Windows, replace '/' with '\'. +

 To use ASIO with the PortAudio library add the following source +files to your project: +

+
pa_asio/pa_asio.cpp
+
+and also these files from the ASIO SDK: +
+
common/asio.cpp
+host/asiodrivers.cpp
+host/asiolist.cpp
+
+and add these directories to the path for include files +
+
asiosdk2/host/pc   (for Windows)
+asiosdk2/common
+asiosdk2/host
+
+You may try compiling the "pa_tests/patest_saw.c" file first because it +is the simplest.
+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_callback.html b/portaudio-v19/docs/pa_tut_callback.html new file mode 100644 index 000000000..f5ccaf0f1 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_callback.html @@ -0,0 +1,91 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Writing a Callback Function

+ +
To write a program using PortAudio, you must include the "portaudio.h" +include file. You may wish to read "portaudio.h" +because it contains a complete description of the PortAudio functions and +constants. +
+
#include "portaudio.h"
+
+The next task is to write your custom callback function. It is a function +that is called by the PortAudio engine whenever it has captured audio data, +or when it needs more audio data for output. +

Your callback function is often called by an interrupt, or low level +process so you should not do any complex system activities like allocating +memory, or reading or writing files, or printf(). Just crunch numbers and +generate audio signals. What is safe or not safe will vary from platform +to platform. On the Macintosh, for example, you can only call "interrupt +safe" routines. Also do not call any PortAudio functions in the callback +except for Pa_StreamTime() and Pa_GetCPULoad(). +

Your callback function must return an int and accept the exact parameters +specified in this typedef: +

+
typedef int (PortAudioCallback)(
+               void *inputBuffer, void *outputBuffer,
+               unsigned long framesPerBuffer,
+               PaTimestamp outTime, void *userData );
+
+Here is an example callback function from the test file "patests/patest_saw.c". +It calculates a simple left and right sawtooth signal and writes it to +the output buffer. Notice that in this example, the signals are of float +data type. The signals must be between -1.0 and +1.0. You can also use +16 bit integers or other formats which are specified during setup. You +can pass a pointer to your data structure through PortAudio which will +appear as userData. +
+
int patestCallback(  void *inputBuffer, void *outputBuffer,
+                     unsigned long framesPerBuffer,
+                     PaTimestamp outTime, void *userData )
+{
+    unsigned int i;
+/* Cast data passed through stream to our structure type. */
+    paTestData *data = (paTestData*)userData;
+    float *out = (float*)outputBuffer;
+        
+    for( i=0; i<framesPerBuffer; i++ )
+    {
+    /* Stereo channels are interleaved. */
+        *out++ = data->left_phase;              /* left */
+        *out++ = data->right_phase;             /* right */
+
+    /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */
+        data->left_phase += 0.01f;
+    /* When signal reaches top, drop back down. */
+        if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f;
+
+    /* higher pitch so we can distinguish left and right. */
+        data->right_phase += 0.03f; 
+        if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f;
+    }
+    return 0;
+}
+
+
+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_devs.html b/portaudio-v19/docs/pa_tut_devs.html new file mode 100644 index 000000000..1756992cb --- /dev/null +++ b/portaudio-v19/docs/pa_tut_devs.html @@ -0,0 +1,65 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Querying for Available Devices

+ +
There are often several different audio devices available in +a computer with different capabilities. They can differ in the sample rates +supported, bit widths, etc. PortAudio provides a simple way to query for +the available devices, and then pass the selected device to Pa_OpenStream(). +For an example, see the file "pa_tests/pa_devs.c". +

To determine the number of devices: +

+
numDevices = Pa_CountDevices();
+
+You can then query each device in turn by calling Pa_GetDeviceInfo() with +an index. +
+
for( i=0; i<numDevices; i++ ) {
+     pdi = Pa_GetDeviceInfo( i );
+
+It will return a pointer to a PaDeviceInfo structure which is +defined as: +
+
typedef struct{
+    int structVersion; 
+    const char *name;
+    int maxInputChannels;
+    int maxOutputChannels;
+/* Number of discrete rates, or -1 if range supported. */
+    int numSampleRates;
+/* Array of supported sample rates, or {min,max} if range supported. */
+    const double *sampleRates;
+    PaSampleFormat nativeSampleFormat;
+}PaDeviceInfo;
+
+If the device supports a continuous range of sample rates, then numSampleRates +will equal -1, and the sampleRates array will have two values, the minimum  +and maximum rate. +

The device information is allocated by Pa_Initialize() and freed by +Pa_Terminate() so you do not have to free() the structure returned by Pa_GetDeviceInfo().

+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_explore.html b/portaudio-v19/docs/pa_tut_explore.html new file mode 100644 index 000000000..91c08a5b7 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_explore.html @@ -0,0 +1,42 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Exploring PortAudio

+ +
Now that you have a good idea of how PortAudio works, you can +try out the test programs. +
    +
  • +For an example of playing a sine wave, see "pa_tests/patest_sine.c".
  • + +
  • +For an example of recording and playing back a sound, see  "pa_tests/patest_record.c".
  • +
+I also encourage you to examine the source for the PortAudio libraries. +If you have suggestions on ways to improve them, please let us know. if +you want to implement PortAudio on a new platform, please let us know as +well so we can coordinate people's efforts.
+home | contents +| previous |  next + + diff --git a/portaudio-v19/docs/pa_tut_init.html b/portaudio-v19/docs/pa_tut_init.html new file mode 100644 index 000000000..91bfa8d98 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_init.html @@ -0,0 +1,43 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Initializing PortAudio

+ +
Before making any other calls to PortAudio, you must call Pa_Initialize(). +This will trigger a scan of available devices which can be queried later. +Like most PA functions, it will return a result of type paError. +If the result is not paNoError, then an error has occurred. +
+
err = Pa_Initialize();
+if( err != paNoError ) goto error;
+
+You can get a text message that explains the error message by passing it +to +
+
printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );
+
+
+home | contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_mac.html b/portaudio-v19/docs/pa_tut_mac.html new file mode 100644 index 000000000..bf3dafd1a --- /dev/null +++ b/portaudio-v19/docs/pa_tut_mac.html @@ -0,0 +1,41 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Compiling for Macintosh

+ +
To compile a Macintosh application with the PortAudio library, +add the following source files to your project: +
+
pa_mac:pa_mac.c
+pa_common:pa_lib.c
+pa_common:portaudio.h
+pa_common:pa_host.h
+
+Also add the Apple SoundLib to your project. +

You may try compiling the "pa_tests:patest_saw.c" file first because +it is the simplest.

+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_mac_osx.html b/portaudio-v19/docs/pa_tut_mac_osx.html new file mode 100644 index 000000000..3af8c140f --- /dev/null +++ b/portaudio-v19/docs/pa_tut_mac_osx.html @@ -0,0 +1,46 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Compiling for Macintosh OS X

+ +
To compile a Macintosh OS X CoreAudio application with the +PortAudio library: +

Create a new ProjectBuilder project. You can use a "Tool" project to +run the PortAudio examples. +

Add the following source files to your Project: +

+
pa_mac_core/pa_mac_core.c
+pa_common/pa_lib.c
+pa_common/portaudio.h
+pa_common/pa_host.h
+pa_common/pa_convert.c
+
+Add the Apple CoreAudio.framework to your project by selecting "Add FrameWorks..." +from the Project menu. +

Compile and run the "pa_tests:patest_saw.c" file first because it is +the simplest.

+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_open.html b/portaudio-v19/docs/pa_tut_open.html new file mode 100644 index 000000000..1621ef30f --- /dev/null +++ b/portaudio-v19/docs/pa_tut_open.html @@ -0,0 +1,52 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Opening a Stream using Defaults

+ +
The next step is to open a stream which is similar to opening +a file. You can specify whether you want audio input and/or output, how +many channels, the data format, sample rate, etc. There are two calls for +opening streams, Pa_OpenStream() and Pa_OpenDefaultStream(). +

Pa_OpenStream() takes extra  parameters which give you +more control. You can normally just use Pa_OpenDefaultStream() +which just calls Pa_OpenStream() with some reasonable +default values.  Let's open a stream for stereo output, using floating +point data, at 44100 Hz. +

+
err = Pa_OpenDefaultStream(
+    &stream,        /* passes back stream pointer */
+    0,              /* no input channels */
+    2,              /* stereo output */
+    paFloat32,      /* 32 bit floating point output */
+    44100,          /* sample rate */
+    256,            /* frames per buffer */
+    0,              /* number of buffers, if zero then use default minimum */
+    patestCallback, /* specify our custom callback */
+    &data );        /* pass our data through to callback */
+
+If you want to use 16 bit integer data, pass paInt16 instead of +paFloat32.
+home | contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_oss.html b/portaudio-v19/docs/pa_tut_oss.html new file mode 100644 index 000000000..1bb76f259 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_oss.html @@ -0,0 +1,46 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Compiling for Unix OSS

+ +
[Skip this page if you are not using Unix and OSS] +

We currently support the OSS +audio drivers for Linux, Solaris, and FreeBSD. We hope to someday support +the newer ALSA drivers. +

    +
  1. +cd to pa_unix_oss directory
  2. + +
  3. +Edit the Makefile and uncomment one of the tests. You may try compiling +the "patest_sine.c" file first because it is very simple.
  4. + +
  5. +gmake run
  6. +
+
+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_over.html b/portaudio-v19/docs/pa_tut_over.html new file mode 100644 index 000000000..baa99205b --- /dev/null +++ b/portaudio-v19/docs/pa_tut_over.html @@ -0,0 +1,92 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Overview of PortAudio

+ +
PortAudio is a library that provides streaming audio input +and output. It is a cross-platform API (Application Programming Interface) +that works on Windows, Macintosh, Unix running OSS, SGI, BeOS, and perhaps +other platforms by the time you read this. This means that you can write +a simple 'C' program to process or generate an audio signal, and that program +can run on several different types of computer just by recompiling the +source code. +

Here are the steps to writing a PortAudio application: +

    +
  1. +Write a callback function that will be called by PortAudio when audio processing +is needed.
  2. + +
  3. +Initialize the PA library and open a stream for audio I/O.
  4. + +
  5. +Start the stream. Your callback function will be now be called repeatedly +by PA in the background.
  6. + +
  7. +In your callback you can read audio data from the inputBuffer and/or write +data to the outputBuffer.
  8. + +
  9. +Stop the stream by returning 1 from your callback, or by calling a stop +function.
  10. + +
  11. +Close the stream and terminate the library.
  12. +
+
+ +
There is also another interface +provided that allows you to generate audio in the foreground. You then +simply write data to the stream and the tool will not return until it is +ready to accept more data. This interface is simpler to use but is usually +not preferred for large applications because it requires that you launch +a thread to perform the synthesis. Launching a thread may be difficult +on non-multi-tasking systems such as the Macintosh prior to MacOS X. +

Let's continue by building a simple application that will play a sawtooth +wave. +

Please select the page for the specific implementation you would like +to use: +

+or continue with the next page of the programming +tutorial.
+home | +contents +| previous + + diff --git a/portaudio-v19/docs/pa_tut_pc.html b/portaudio-v19/docs/pa_tut_pc.html new file mode 100644 index 000000000..87e5f9a02 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_pc.html @@ -0,0 +1,78 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Compiling for Windows (WMME or DirectSound)

+ +
To compile PortAudio for Windows, you can choose between two +options. One implementation uses the DirectSound API. The other uses the +Windows MultiMedia Extensions API (aka WMME or WAVE). +

Some advantages of using DirectSound are that DirectSound may have lower +latency than WMME, and supports effects processing plugins. But one disadvantage +is that DirectSound is not installed on all PCs, and is not well supported +under Windows NT. So WMME is the best choice for most projects. +

For either implementation add the following source files to your project: +

+
pa_common\pa_lib.c
+pa_common\portaudio.h
+pa_common\pa_host.h
+
+Link with the system library "winmm.lib". For Visual C++: +
    +
  1. +select "Settings..." from the "Project" menu,
  2. + +
  3. +select the project name in the tree on the left,
  4. + +
  5. +choose "All Configurations" in the popup menu above the tree,
  6. + +
  7. +select the "Link" tab,
  8. + +
  9. +enter "winmm.lib", without quotes, as the first item in the "Object/library +modules:" field.
  10. +
+WMME - To use the WMME implementation, add the following source +files to your project: +
pa_win_wmme/pa_win_wmme.c
+DirectSound - If you want to use the DirectSound implementation +of PortAudio then you must have a recent copy of the free +DirectX +SDK for Developers from Microsoft installed on your computer. To compile +an application add the following source files to your project: +
+
pa_win_ds\dsound_wrapper.c
+pa_win_ds\pa_dsound.c
+
+Link with the system library "dsound.lib" using the procedure described +above for "winmm.lib".
+ +
You might try compiling the "pa_tests\patest_saw.c" file first +because it is the simplest.
+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_run.html b/portaudio-v19/docs/pa_tut_run.html new file mode 100644 index 000000000..5c70d0895 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_run.html @@ -0,0 +1,56 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Starting and Stopping a Stream

+ +
The stream will not start running until you call Pa_StartStream(). +Then it will start calling your callback function to perform the audio +processing. +
+
err = Pa_StartStream( stream );
+if( err != paNoError ) goto error;
+
+At this point, audio is being generated. You can communicate to your callback +routine through the data structure you passed in on the open call, or through +global variables, or using other interprocess communication techniques. +Please be aware that your callback function may be called at interrupt +time when your foreground process is least expecting it. So avoid sharing +complex data structures that are easily corrupted like double linked lists. +

In many of the tests we simply sleep for a few seconds so we can hear +the sound. This is easy to do with Pa_Sleep() which will sleep for some +number of milliseconds. Do not rely on this function for accurate scheduling. +it is mostly for writing examples. +

+
/* Sleep for several seconds. */
+Pa_Sleep(NUM_SECONDS*1000);
+
+When you are through, you can stop the stream from the foreground. +
+
err = Pa_StopStream( stream );
+if( err != paNoError ) goto error;
+
+You can also stop the stream by returning 1 from your custom callback function.
+home | contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_rw.html b/portaudio-v19/docs/pa_tut_rw.html new file mode 100644 index 000000000..93c7b8bb3 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_rw.html @@ -0,0 +1,79 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Blocking Read/Write Functions

+ +
[Note: These functions are not part of the official PortAudio +API. They are simply built on top of PortAudio as an extra utility. Also +note that they are under evaluation and their definition may change.] +

There are two fundamentally different ways to design an audio API. One +is to use callback functions the way we have already shown. The callback +function operates under an interrupt or background thread This leaves the +foreground application free to do other things while the audio just runs +in the background. But this can sometimes be awkward. +

So we have provided an alternative technique that lets a program generate +audio in the foreground and then just write it to the audio stream as if +it was a file. If there is not enough room in the audio buffer for more +data, then the write function will just block until more room is available. +This can make it very easy to write an audio example. To use this tool, +you must add the files "pablio/pablio.c" and "pablio/ringbuffer.c" to your +project. You must also: +

+
#include "pablio.h"
+
+Here is a short excerpt of a program that opens a stream for input and +output. It then reads a block of samples from input, and writes them to +output, in a loop.  The complete example can be found in "pablio/test_rw.c". +
+
    #define SAMPLES_PER_FRAME     (2)
+    #define FRAMES_PER_BLOCK    (1024)
+    SAMPLE          samples[SAMPLES_PER_FRAME * FRAMES_PER_BLOCK];
+    PaError         err;
+    PABLIO_Stream  *aStream;
+
+/* Open simplified blocking I/O layer on top of PortAudio. */
+    err = OpenAudioStream( &rwbl, SAMPLE_RATE, paFloat32,
+                         (PABLIO_READ_WRITE | PABLIO_STEREO) );
+    if( err != paNoError ) goto error;
+
+/* Process samples in the foreground. */
+    for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i++ )
+    {
+    /* Read one block of data into sample array from audio input. */
+        ReadAudioStream( aStream, samples, FRAMES_PER_BLOCK );
+    /*
+    ** At this point you could process the data in samples array,
+    ** and write the result back to the same samples array.
+    */
+    /* Write that same frame of data to output. */
+        WriteAudioStream( aStream, samples, FRAMES_PER_BLOCK );
+    }
+
+    CloseAudioStream( aStream );
+
+
+home | +contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_term.html b/portaudio-v19/docs/pa_tut_term.html new file mode 100644 index 000000000..1c72209f1 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_term.html @@ -0,0 +1,47 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Terminating PortAudio

+ +
You can start and stop a stream as many times as you like. +But when you are done using it, you should close it by calling:
+ +
+
+
err = Pa_CloseStream( stream );
+if( err != paNoError ) goto error;
+
+Then when you are done using PortAudio, you should terminate the whole +system by calling: +
+
Pa_Terminate();
+
+That's basically it. You can now write an audio program in 'C' that will +run on multiple platforms, for example PCs and Macintosh. +

In the rest of the tutorial we will look at some additional utility +functions, and a different way of using PortAudio that does not require +the use of a callback function.

+home | contents +| previousnext + + diff --git a/portaudio-v19/docs/pa_tut_util.html b/portaudio-v19/docs/pa_tut_util.html new file mode 100644 index 000000000..f4b547501 --- /dev/null +++ b/portaudio-v19/docs/pa_tut_util.html @@ -0,0 +1,55 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

+Utility Functions

+ +
Here are several more functions that are not critical, but +may be handy when using PortAudio. +

Pa_StreamActive() returns one when the stream in playing audio, zero +when not playing, or a negative error number if the stream is invalid. +The stream is active between calls to Pa_StartStream() and Pa_StopStream(), +but may also become inactive if the callback returns a non-zero value. +In the latter case, the stream is considered inactive after the last buffer +has finished playing. +

+
PaError Pa_StreamActive( PortAudioStream *stream );
+
+Pa_StreamTime() returns the number of samples that have been generated. +PaTimeStamp is a double precision number which is a convenient way to pass +big numbers around even though we only need integers. +
+
PaTimestamp Pa_StreamTime( PortAudioStream *stream );
+
+The "CPU Load" is a fraction of total CPU time consumed by the stream's +audio processing. A value of 0.5 would imply that PortAudio and the sound +generating callback was consuming roughly 50% of the available CPU time. +This function may be called from the callback function or the application. +
+
double Pa_GetCPULoad( PortAudioStream* stream );
+
+
+home | +contents | previous +|  next + + diff --git a/portaudio-v19/docs/pa_tutorial.html b/portaudio-v19/docs/pa_tutorial.html new file mode 100644 index 000000000..1371c44f0 --- /dev/null +++ b/portaudio-v19/docs/pa_tutorial.html @@ -0,0 +1,46 @@ + + + + + + + + + PortAudio Tutorial + + +  +
+ + + +
+
+

+PortAudio Tutorial

+
+ +

Copyright 2000 Phil Burk and Ross Bencina +

+Table of Contents

+ +
Overview of PortAudio +
Compiling for Macintosh OS 7,8,9 +
Compiling for Macintosh OS X +
Compiling for Windows (DirectSound and WMME) +
Compiling for ASIO on Windows or Mac OS +8,9 +
Compiling for Unix OSS +
Writing a Callback Function +
Initializing PortAudio +
Opening a Stream using Defaults +
Starting and Stopping a Stream +
Cleaning Up +
Utilities +
Querying for Devices +
Blocking Read/Write Functions +
Exploring the PortAudio Package
+home | contents | +previous |  next + + diff --git a/portaudio-v19/docs/portaudio_h.txt b/portaudio-v19/docs/portaudio_h.txt new file mode 100644 index 000000000..6d60086fc --- /dev/null +++ b/portaudio-v19/docs/portaudio_h.txt @@ -0,0 +1,425 @@ +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. +*/ + +PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. +*/ + +PaError Pa_Terminate( void ); + +/* + Return host specific error. + This can be called after receiving a paHostError. +*/ +long Pa_GetHostError( void ); + +/* + Translate the error number into a human readable message. +*/ +const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all devices. + + The floating point representation uses +1.0 and -1.0 as the respective + maximum and minimum. + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. Device 0 is always the "default" + device and should support at least stereo in and out if that is available + on the taget platform _even_ if this involves kludging an input/output + device on platforms that usually separate input from output. Other platform + specific devices are specified by positive device ids. +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; +/* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; +/* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} PaDeviceInfo; + + +int Pa_CountDevices(); +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() + + Return the default device ID or paNoDevice if there is no devices. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "pa_devs". +*/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ); +PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time useful for syncronisation. The type is used in the outTime + argument to the callback function and the result of Pa_StreamTime() +*/ + +typedef double PaTimestamp; + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + referring to the device specified by id. + If id is out of range the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must + not be manipulated or freed. The pointer is guaranteed to be valid until + between calls to Pa_Initialize() and Pa_Terminate(). +*/ + +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID devID ); + +/* + PortAudioCallback is implemented by clients of the portable audio api. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a nonzero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + nonzero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + + [ suggestions? ] +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable defult clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + device input record for the device specified in the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the sound devices native formats (nativeSampleFormats in the device info + record) and additionally 16 and 32 bit integer and 32 bit floating point + formats. Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate for input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered + communication with the platform specific audio routines. This parameter is + provided only as a guide - and does not imply that an implementation must + use multibuffered i/o when reliable double buffering is available (such as + SndPlayDoubleBuffer() on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behavior of the + streaming process. Some flags may only be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Apon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a nonzero error code is returned (see + PAError above) and the value of stream is invalid. + +*/ + +PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that + opens the default input and/or ouput devices. Most parameters have + identical meaning to their Pa_OpenStream() counterparts, with the following + exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened (same as passing paNoDevice in the device arguments to Pa_OpenStream() ) + + sampleFormat applies to both the input and output buffers. +*/ + +PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. +*/ + +PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + Pa_StopStream() waits until all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. +*/ + +PaError Pa_StartStream( PortAudioStream *stream ); + +PaError Pa_StopStream( PortAudioStream *stream ); + +PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one when the stream is playing audio, + zero when not playing, or a negative error number if the + stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. +*/ + +PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time for the stream in samples. + This time may be used as a time reference (for example syncronising audio to + MIDI). +*/ + +PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + The "CPU Load" is a fraction of total CPU time consumed by the + stream's audio processing. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. +*/ +double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Use Pa_GetMinNumBuffers() to determine minimum number of buffers required for + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. +*/ + +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely + on this for accurate musical timing. +*/ +void Pa_Sleep( long msec ); + +/* + Return size in bytes of a single sample in a given PaSampleFormat + or paSampleFormatNotSupported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/portaudio-v19/docs/portaudio_icmc2001.pdf b/portaudio-v19/docs/portaudio_icmc2001.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dd074b7d334d9b0d4415330c2a123131aa130115 GIT binary patch literal 50968 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
_indHVG)3^ZrAZg!fH65_!ltCUc#av(>q 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 + +Proposed Changes to PortAudio API + + + + + +  +

+ + + +
+
+

Proposed Changes to PortAudio API

+
+
+

PortAudio Home Page

+ +

Updated: July 27, 2002

+

The Proposals Have Moved

+ +

+All PortAudio Enhancement Proposal documentation has moved. On the web site, it is now located at: +http://www.portaudio.com/docs/proposals + +On the CVS server it is now located in a module named "pa_proposals". +

+ + + + diff --git a/portaudio-v19/docs/releases.html b/portaudio-v19/docs/releases.html new file mode 100644 index 000000000..aec80a1cb --- /dev/null +++ b/portaudio-v19/docs/releases.html @@ -0,0 +1,339 @@ + + + + + + + + + PortAudio Release Notes + + +  +
+ + + +
+
+

+PortAudio - Release Notes

+
+ +

Link to PortAudio Home Page +

+V18 - 5/6/02

+ +
All source code and documentation now under CVS. +

Ran most of the code through AStyle +to cleanup ragged indentation caused by using different editors. Used this +command: +
   astyle --style=ansi -c -o --convert-tabs --indent-preprocessor +*.c

+ +
Added "pa_common/pa_convert.c" for Mac OS X. Start of new conversion +utilities. +

ASIO +

    +
  • +New Pa_ASIO_Adaptor_Init function to init Callback adpatation variables,
  • + +
  • +Cleanup of Pa_ASIO_Callback_Input
  • + +
  • +Break apart device loading to debug random failure in Pa_ASIO_QueryDeviceInfo
  • + +
  • +Deallocate all resources in PaHost_Term for cases where Pa_CloseStream +is not called properly
  • + +
  • +New Pa_ASIO_loadDriver that calls CoInitialize on each thread on Windows. +Allows use by multiple threads.
  • + +
  • +Correct error code management in PaHost_Term, removed various compiler +warning
  • + +
  • +Add Mac includes for <Devices.h> and <Timer.h>
  • + +
  • +Pa_ASIO_QueryDeviceInfo bug correction, memory allocation checking, better +error handling
  • +
+Mac OS X +
    +
  • +Major cleanup and improvements.
  • + +
  • +Fixed device queries for numChannels and sampleRates,
  • + +
  • +Audio input works if using same CoreAudio device (some HW devices make +separate CoreAudio devices).
  • + +
  • +Added paInt16, paInt8, format using new "pa_common/pa_convert.c" file.
  • + +
  • +Return error if opened in mono mode cuz not supported.
  • + +
  • +Check for getenv("PA_MIN_LATEWNCY_MSEC") to set latency externally.
  • + +
  • +Use getrusage() instead of gettimeofday() for CPU Load calculation.
  • +
+Windows MME +
    +
  • +Fixed bug that caused TIMEOUT in Pa_StopStream(). Added check for past_StopSoon() +in Pa_TimeSlice(). Thanks Julien Maillard.
  • + +
  • +Detect Win XP versus NT, use lower latency.
  • + +
  • +Fix DBUG typo;
  • + +
  • +removed init of CurrentCount which was not compiling on Borland
  • + +
  • +general cleanup, factored streamData alloc and cpu usage initialization
  • + +
  • +stopped counting WAVE_MAPPER when there were no audio cards plugged in
  • +
+Windows DirectSound +
    +
  • +Detect Win XP and Win 2K properly when determining latency.
  • +
+Unix OSS +
    +
  • +Use high real-time priority if app is running with root priveledges. Lowers +latency.
  • + +
  • +Added watch dog thread that prevents real-time thread from hogging CPU +and hanging the computer.
  • + +
  • +Check error return from read() and write().
  • + +
  • +Check CPU endianness instead of assuming Little Endian.
  • +
+
+ +

+V17 - 10/15/01

+ +
Unix OSS +
    +
  • +Set num channels back to two after device query for ALSA. This fixed a +bug in V16 that sometimes caused a failure when querying for the sample +rates. Thanks Stweart Greenhill.
  • +
+
+ +
+

+Macintosh Sound Manager

+ +
    +
  • +Use NewSndCallBackUPP() for CARBON compatibility.
  • +
+
+ +

+V16 - 9/27/01

+ +
Added Alpha implementations for ASIO, SGI, and BeOS! +
  +
  • +CPULoad is now calculated based on the time spent to generate a known number +of frames. This is more accurate than a simple percentage of real-time. +Implemented in pa_unix_oss, pa_win_wmme and pa_win_ds.
  • + +
  • +Fix dither and shift for recording PaUInt8 format data.
  • + +
  • +Added "patest_maxsines.c" which tests Pa_GetCPULoad().
  • +
    + +
    +

    +Windows WMME

    + +
      +
    • +sDevicePtrs now allocated using GlobalAlloc(). This prevents a +crash in Pa_Terminate() on Win2000. Thanks Mike Berry for finding this. +Thanks Mike Berry.
    • + +
    • +Pass process instead of thread to SetPriorityClass(). This fixes +a bug that caused the priority to not be increased. Thanks to Alberto di +Bene for spotting this.
    • +
    + +

    +Windows DirectSound

    + +
      +
    • +Casts for compiling with __MWERKS__ CodeWarrior.
    • +
    + +

    +UNIX OSS

    + +
      +
    • +Derived from Linux OSS implementation.
    • + +
    • +Numerous patches from Heiko Purnhagen, Stephen Brandon, etc.
    • + +
    • +Improved query mechanism which often bailed out unnecessarily.
    • + +
    • +Removed sNumDevices and potential related bugs,
    • + +
    • +Use getenv("PA_MIN_LATENCY_MSEC") in code to set desired latency. +User can set by entering:
    • + +
          export PA_MIN_LATENCY_MSEC=40
    + +

    +Macintosh Sound Manager

    + +
      +
    • +Pass unused event to WaitNextEvent instead of NULL to prevent Mac OSX crash. +Thanks Dominic Mazzoni.
    • + +
    • +Use requested number of input channels.
    • + +
       
    +
    + +

    +V15 - 5/29/01

    + +
    +
      +
    • +New Linux OSS Beta
    • +
    + +

    +Windows WMME

    + +
      +
    • + sDevicePtrs now allocated based on sizeof(pointer). Was allocating +too much space.
    • + +
    • + Check for excessive numbers of channels. Some drivers reported bogus +numbers.
    • + +
    • +Apply Mike Berry's changes for CodeWarrior on PC including condition including +of memory.h, and explicit typecasting on memory allocation.
    • +
    + +

    +Macintosh Sound Manager

    + +
      +
    • +ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID.
    • + +
    • +Device Scan was crashing for anything other than siBadSoundInDevice, but +some Macs may return other errors! Caused failure to init on some G4s under +OS9.
    • + +
    • +Fix TIMEOUT in record mode.
    • + +
    • +Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON
    • +
    +
    + +

    +V14 - 2/6/01

    + +
    +
      +
    • +Added implementation for Windows MultiMedia Extensions (WMME) by Ross and +Phil
    • + +
    • +Changed Pa_StopStream() so that it waits for the buffers to drain.
    • + +
    • +Added Pa_AbortStream() that stops immediately without waiting.
    • + +
    • +Added new test: patest_stop.c to test above two mods.
    • + +
    • +Fixed Pa_StreamTime() so that it returns current play position instead +of the write position. Added "patest_sync.c" to demo audio/video sync.
    • + +
    • +Improved stability of Macintosh implementation. Added timeouts to prevent +hangs.
    • + +
    • +Added Pa_GetSampleSize( PaSampleFormat format );
    • + +
    • +Changes some "int"s to "long"s so that PA works properly on Macintosh which +often compiles using 16 bit ints.
    • + +
    • +Added Implementation Guide
    • +
    +
    + +

    +V12 - 1/9/01

    + +
    +
      +
    • +Mac now scans for and queries all devices. But it does not yet support +selecting any other than the default device.
    • + +
    • +Blocking I/O calls renamed to separate them from the PortAudio API.
    • + +
    • +Cleaned up indentation problems with tabs versus spaces.
    • + +
    • +Now attempts to correct bogus sample rate info returned from DirectSound +device queries.
    • +
    +
    + + + diff --git a/portaudio-v19/fixdir.bat b/portaudio-v19/fixdir.bat new file mode 100755 index 000000000..92d6c7470 --- /dev/null +++ b/portaudio-v19/fixdir.bat @@ -0,0 +1,19 @@ +rem Use Astyle to fix style in 'C' files +cd %1% + +fixlines -p *.c +fixlines -p *.cpp +fixlines -p *.cc + +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.c +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.cpp +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.cc +del *.orig +@rem convert line terminators to Unix style LFs +fixlines -u *.c +fixlines -u *.cpp +fixlines -u *.cc +fixlines -u *.h +del *.bak + +cd ..\ diff --git a/portaudio-v19/fixfile.bat b/portaudio-v19/fixfile.bat new file mode 100755 index 000000000..48f6fbc2f --- /dev/null +++ b/portaudio-v19/fixfile.bat @@ -0,0 +1,7 @@ +rem Use Astyle to fix style in a file +fixlines -p %1% +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor %1% +del %1%.orig +@rem convert line terminators to Unix style LFs +fixlines -u %1% +del %1%.bak diff --git a/portaudio-v19/index.html b/portaudio-v19/index.html new file mode 100644 index 000000000..91e5b4c38 --- /dev/null +++ b/portaudio-v19/index.html @@ -0,0 +1,89 @@ + + + + + + + + + PortAudio Implementations for DirectSound + + +  +
    + + + +
    +
    +

    +PortAudio - Portable Audio Library

    +
    + +

    Last updated 5/6/02. +

    PortAudio is a cross platform, open-source, audio +I/O library proposed by Ross Bencina to the music-dsp +mailing list. It lets you write simple audio programs in 'C' that will +compile and run on Windows, Macintosh, Unix, BeOS. PortAudio is +intended to promote the exchange of audio synthesis software between developers +on different platforms. +

    For complete information on PortAudio and to download the latest releases, +please visit "http://www.portaudio.com". +
      +
      +

    +

    +Click here for Documentation

    + +

    +

    + +

    +Contacts and E-Mail List

    + +
      +
    • +If you are using or implementing PortAudio then please join the PortAudio +mail list generously administered by +Bill +Eldridge.
    • + +
    • +If you find bugs in one of these implementations, or have suggestions, +please e-mail them to Phil Burk.
    • + +
    • +If you make improvements to the library, please send them to us so we can +incorporate the improvements.
    • +
    + +

    +License

    +PortAudio Portable Real-Time Audio Library +
    Copyright (c) 1999-2000 Ross Bencina and Phil Burk +

    Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: +

      +
    • +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software.
    • + +
    • +Any person wishing to distribute modifications to the Software is requested +to send the modifications to the original developer so that they can be +incorporated into the canonical version.
    • +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND ON INFRINGEMENT. +
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +THE USE OR OTHER DEALINGS IN THE SOFTWARE. +
      + + diff --git a/portaudio-v19/install-sh b/portaudio-v19/install-sh new file mode 100755 index 000000000..e9de23842 --- /dev/null +++ b/portaudio-v19/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/portaudio-v19/pa_asio/ASIO-README.txt b/portaudio-v19/pa_asio/ASIO-README.txt new file mode 100644 index 000000000..9fb74ae16 --- /dev/null +++ b/portaudio-v19/pa_asio/ASIO-README.txt @@ -0,0 +1,137 @@ +ASIO-README.txt + +This document contains information to help you compile PortAudio with +ASIO support. If you find any omissions or errors in this document +please notify Ross Bencina . + + +Building PortAudio with ASIO support +------------------------------------ + +To build PortAudio with ASIO support you need to compile and link with +pa_asio.c, and files from the ASIO SDK (see below), along with the common +files from pa_common/ and platform specific files from pa_win/ (for Win32) +or pa_mac/ (for Macintosh). + +If you are compiling with a non-Microsoft compiler on windows, also +compile and link with iasiothiscallresolver.cpp (see below for +an explanation). + +For some platforms (MingW, possibly Mac), you may simply +be able to type: + +./configure --with-host_os=mingw --with-winapi=asio [--with-asiodir=/usr/local/asiosdk2] +make + +./configure --with-host_os=darwin --with-winapi=asio [--with-asiodir=/usr/local/asiosdk2] +make + +and life will be good. + + +Obtaining the ASIO SDK +---------------------- + +In order to build PortAudio with ASIO support, you need to download +the ASIO SDK (version 2.0) from Steinberg. Steinberg makes the ASIO +SDK available to anyone free of charge, however they do not permit its +source code to be distributed. + +NOTE: In some cases the ASIO SDK may require patching, see below +for further details. + +http://www.steinberg.net/en/ps/support/3rdparty/asio_sdk/ + +If the above link is broken search Google for: +"download steinberg ASIO SDK" + + + +Building the ASIO SDK on Macintosh +---------------------------------- + +To build the ASIO SDK on Macintosh you need to compile and link with the +following files from the ASIO SDK: + +host/asiodrivers.cpp +host/mac/asioshlib.cpp +host/mac/codefragements.cpp + + + +Building the ASIO SDK on Windows +-------------------------------- + +To build the ASIO SDK on Windows you need to compile and link with the +following files from the ASIO SDK: + +asio_sdk\common\asio.cpp +asio_sdk\host\asiodrivers.cpp +asio_sdk\host\pc\asiolist.cpp + +You may also need to adjust your include paths to support inclusion of +header files from the above directories. + +The ASIO SDK depends on the following COM API functions: +CoInitialize, CoUninitialize, CoCreateInstance, CLSIDFromString +For compilation with MinGW you will need to link with -lole32, for +Borland link with Import32.lib. + + + +Non-Microsoft (MSVC) Compilers on Windows including Borland and GCC +------------------------------------------------------------------- + +Steinberg did not specify a calling convention in the IASIO interface +definition. This causes the Microsoft compiler to use the proprietary +thiscall convention which is not compatible with other compilers, such +as compilers from Borland (BCC and C++Builder) and GNU (gcc). +Steinberg's ASIO SDK will compile but crash on initialization if +compiled with a non-Microsoft compiler on Windows. + +PortAudio solves this problem using the iasiothiscallresolver library +which is included in the distribution. When building ASIO support for +non-Microsoft compilers, be sure to compile and link with +iasiothiscallresolver.cpp. Note that iasiothiscallresolver includes +conditional directives which cause it to have no effect if it is +compiled with a Microsoft compiler, or on the Macintosh. + +If you use configure and make (see above), this should be handled +automatically for you. + +For further information about the IASIO thiscall problem see this page: +http://www.audiomulch.com/~rossb/code/calliasio + + + +Macintosh ASIO SDK Bug Patch +---------------------------- + +There is a bug in the ASIO SDK that causes the Macintosh version to +often fail during initialization. Below is a patch that you can apply. + +In codefragments.cpp replace getFrontProcessDirectory function with +the following one (GetFrontProcess replaced by GetCurrentProcess). + + +bool CodeFragments::getFrontProcessDirectory(void *specs) +{ + FSSpec *fss = (FSSpec *)specs; + ProcessInfoRec pif; + ProcessSerialNumber psn; + + memset(&psn,0,(long)sizeof(ProcessSerialNumber)); + // if(GetFrontProcess(&psn) == noErr) // wrong !!! + if(GetCurrentProcess(&psn) == noErr) // correct !!! + { + pif.processName = 0; + pif.processAppSpec = fss; + pif.processInfoLength = sizeof(ProcessInfoRec); + if(GetProcessInformation(&psn, &pif) == noErr) + return true; + } + return false; +} + + +--- diff --git a/portaudio-v19/pa_asio/Callback_adaptation_.pdf b/portaudio-v19/pa_asio/Callback_adaptation_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..76bf67863524626aa079d74629c9dd9f720b0b54 GIT binary patch literal 50527 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 + + BACKGROUND + + The IASIO interface declared in the Steinberg ASIO 2 SDK declares + functions with no explicit calling convention. This causes MSVC++ to default + to using the thiscall convention, which is a proprietary convention not + implemented by some non-microsoft compilers - notably borland BCC, + C++Builder, and gcc. MSVC++ is the defacto standard compiler used by + Steinberg. As a result of this situation, the ASIO sdk will compile with + any compiler, however attempting to execute the compiled code will cause a + crash due to different default calling conventions on non-Microsoft + compilers. + + IASIOThiscallResolver solves the problem by providing an adapter class that + delegates to the IASIO interface using the correct calling convention + (thiscall). Due to the lack of support for thiscall in the Borland and GCC + compilers, the calls have been implemented in assembly language. + + A number of macros are defined for thiscall function calls with different + numbers of parameters, with and without return values - it may be possible + to modify the format of these macros to make them work with other inline + assemblers. + + + THISCALL DEFINITION + + A number of definitions of the thiscall calling convention are floating + around the internet. The following definition has been validated against + output from the MSVC++ compiler: + + For non-vararg functions, thiscall works as follows: the object (this) + pointer is passed in ECX. All arguments are passed on the stack in + right to left order. The return value is placed in EAX. The callee + clears the passed arguments from the stack. + + + FINDING FUNCTION POINTERS FROM AN IASIO POINTER + + The first field of a COM object is a pointer to its vtble. Thus a pointer + to an object implementing the IASIO interface also points to a pointer to + that object's vtbl. The vtble is a table of function pointers for all of + the virtual functions exposed by the implemented interfaces. + + If we consider a variable declared as a pointer to IASO: + + IASIO *theAsioDriver + + theAsioDriver points to: + + object implementing IASIO + { + IASIOvtbl *vtbl + other data + } + + in other words, theAsioDriver points to a pointer to an IASIOvtbl + + vtbl points to a table of function pointers: + + IASIOvtbl ( interface IASIO : public IUnknown ) + { + (IUnknown functions) + 0 virtual HRESULT STDMETHODCALLTYPE (*QueryInterface)(REFIID riid, void **ppv) = 0; + 4 virtual ULONG STDMETHODCALLTYPE (*AddRef)() = 0; + 8 virtual ULONG STDMETHODCALLTYPE (*Release)() = 0; + + (IASIO functions) + 12 virtual ASIOBool (*init)(void *sysHandle) = 0; + 16 virtual void (*getDriverName)(char *name) = 0; + 20 virtual long (*getDriverVersion)() = 0; + 24 virtual void (*getErrorMessage)(char *string) = 0; + 28 virtual ASIOError (*start)() = 0; + 32 virtual ASIOError (*stop)() = 0; + 36 virtual ASIOError (*getChannels)(long *numInputChannels, long *numOutputChannels) = 0; + 40 virtual ASIOError (*getLatencies)(long *inputLatency, long *outputLatency) = 0; + 44 virtual ASIOError (*getBufferSize)(long *minSize, long *maxSize, + long *preferredSize, long *granularity) = 0; + 48 virtual ASIOError (*canSampleRate)(ASIOSampleRate sampleRate) = 0; + 52 virtual ASIOError (*getSampleRate)(ASIOSampleRate *sampleRate) = 0; + 56 virtual ASIOError (*setSampleRate)(ASIOSampleRate sampleRate) = 0; + 60 virtual ASIOError (*getClockSources)(ASIOClockSource *clocks, long *numSources) = 0; + 64 virtual ASIOError (*setClockSource)(long reference) = 0; + 68 virtual ASIOError (*getSamplePosition)(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0; + 72 virtual ASIOError (*getChannelInfo)(ASIOChannelInfo *info) = 0; + 76 virtual ASIOError (*createBuffers)(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) = 0; + 80 virtual ASIOError (*disposeBuffers)() = 0; + 84 virtual ASIOError (*controlPanel)() = 0; + 88 virtual ASIOError (*future)(long selector,void *opt) = 0; + 92 virtual ASIOError (*outputReady)() = 0; + }; + + The numbers in the left column show the byte offset of each function ptr + from the beginning of the vtbl. These numbers are used in the code below + to select different functions. + + In order to find the address of a particular function, theAsioDriver + must first be dereferenced to find the value of the vtbl pointer: + + mov eax, theAsioDriver + mov edx, [theAsioDriver] // edx now points to vtbl[0] + + Then an offset must be added to the vtbl pointer to select a + particular function, for example vtbl+44 points to the slot containing + a pointer to the getBufferSize function. + + Finally vtbl+x must be dereferenced to obtain the value of the function + pointer stored in that address: + + call [edx+44] // call the function pointed to by + // the value in the getBufferSize field of the vtbl + + + SEE ALSO + + Martin Fay's OpenASIO DLL at http://www.martinfay.com solves the same + problem by providing a new COM interface which wraps IASIO with an + interface that uses portable calling conventions. OpenASIO must be compiled + with MSVC, and requires that you ship the OpenASIO DLL with your + application. + + + ACKNOWLEDGEMENTS + + Ross Bencina: worked out the thiscall details above, wrote the original + Borland asm macros, and a patch for asio.cpp (which is no longer needed). + Thanks to Martin Fay for introducing me to the issues discussed here, + and to Rene G. Ceballos for assisting with asm dumps from MSVC++. + + Antti Silvast: converted the original calliasio to work with gcc and NASM + by implementing the asm code in a separate file. + + Fraser Adams: modified the original calliasio containing the Borland inline + asm to add inline asm for gcc i.e. Intel syntax for Borland and AT&T syntax + for gcc. This seems a neater approach for gcc than to have a separate .asm + file and it means that we only need one version of the thiscall patch. + + Fraser Adams: rewrote the original calliasio patch in the form of the + IASIOThiscallResolver class in order to avoid modifications to files from + the Steinberg SDK, which may have had potential licence issues. + + Andrew Baldwin: contributed fixes for compatibility problems with more + recent versions of the gcc assembler. +*/ + + +// We only need IASIOThiscallResolver at all if we are on Win32. For other +// platforms we simply bypass the IASIOThiscallResolver definition to allow us +// to be safely #include'd whatever the platform to keep client code portable +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) + + +// If microsoft compiler we can call IASIO directly so IASIOThiscallResolver +// is not used. +#if !defined(_MSC_VER) + + +#include +#include + +// We have a mechanism in iasiothiscallresolver.h to ensure that asio.h is +// #include'd before it in client code, we do NOT want to do this test here. +#define iasiothiscallresolver_sourcefile 1 +#include "iasiothiscallresolver.h" +#undef iasiothiscallresolver_sourcefile + +// iasiothiscallresolver.h redefines ASIOInit for clients, but we don't want +// this macro defined in this translation unit. +#undef ASIOInit + + +// theAsioDriver is a global pointer to the current IASIO instance which the +// ASIO SDK uses to perform all actions on the IASIO interface. We substitute +// our own forwarding interface into this pointer. +extern IASIO* theAsioDriver; + + +// The following macros define the inline assembler for BORLAND first then gcc + +#if defined(__BCPLUSPLUS__) || defined(__BORLANDC__) + + +#define CALL_THISCALL_0( resultName, thisPtr, funcOffset )\ + void *this_ = (thisPtr); \ + __asm { \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + } + + +#define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + void *doubleParamPtr_ (¶m1); \ + __asm { \ + mov eax, doubleParamPtr_ ; \ + push [eax+4] ; \ + push [eax] ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param2 ; \ + push eax ; \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param4 ; \ + push eax ; \ + mov eax, param3 ; \ + push eax ; \ + mov eax, param2 ; \ + push eax ; \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#elif defined(__GNUC__) + + +#define CALL_THISCALL_0( resultName, thisPtr, funcOffset ) \ + __asm__ __volatile__ ("movl (%1), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"c"(thisPtr) /* Input Operands */ \ + ); \ + + +#define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl %0\n\t" \ + "movl (%1), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + : /* Output Operands */ \ + :"r"(param1), /* Input Operands */ \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl %1\n\t" \ + "movl (%2), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param1), /* Input Operands */ \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl 4(%1)\n\t" \ + "pushl (%1)\n\t" \ + "movl (%2), %%edx\n\t" \ + "call *"#funcOffset"(%%edx);\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"a"(¶m1), /* Input Operands */ \ + /* Note: Using "r" above instead of "a" fails */ \ + /* when using GCC 3.3.3, and maybe later versions*/\ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \ + __asm__ __volatile__ ("pushl %1\n\t" \ + "pushl %2\n\t" \ + "movl (%3), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param2), /* Input Operands */ \ + "r"(param1), \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\ + __asm__ __volatile__ ("pushl %1\n\t" \ + "pushl %2\n\t" \ + "pushl %3\n\t" \ + "pushl %4\n\t" \ + "movl (%5), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param4), /* Input Operands */ \ + "r"(param3), \ + "r"(param2), \ + "r"(param1), \ + "c"(thisPtr) \ + ); \ + +#endif + + + +// Our static singleton instance. +IASIOThiscallResolver IASIOThiscallResolver::instance; + +// Constructor called to initialize static Singleton instance above. Note that +// it is important not to clear that_ incase it has already been set by the call +// to placement new in ASIOInit(). +IASIOThiscallResolver::IASIOThiscallResolver() +{ +} + +// Constructor called from ASIOInit() below +IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that) +: that_( that ) +{ +} + +// Implement IUnknown methods as assert(false). IASIOThiscallResolver is not +// really a COM object, just a wrapper which will work with the ASIO SDK. +// If you wanted to use ASIO without the SDK you might want to implement COM +// aggregation in these methods. +HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv) +{ + (void)riid; // suppress unused variable warning + + assert( false ); // this function should never be called by the ASIO SDK. + + *ppv = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef() +{ + assert( false ); // this function should never be called by the ASIO SDK. + + return 1; +} + +ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release() +{ + assert( false ); // this function should never be called by the ASIO SDK. + + return 1; +} + + +// Implement the IASIO interface methods by performing the vptr manipulation +// described above then delegating to the real implementation. +ASIOBool IASIOThiscallResolver::init(void *sysHandle) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 12, sysHandle ); + return result; +} + +void IASIOThiscallResolver::getDriverName(char *name) +{ + CALL_VOID_THISCALL_1( that_, 16, name ); +} + +long IASIOThiscallResolver::getDriverVersion() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 20 ); + return result; +} + +void IASIOThiscallResolver::getErrorMessage(char *string) +{ + CALL_VOID_THISCALL_1( that_, 24, string ); +} + +ASIOError IASIOThiscallResolver::start() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 28 ); + return result; +} + +ASIOError IASIOThiscallResolver::stop() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 32 ); + return result; +} + +ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels ); + return result; +} + +ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency ); + return result; +} + +ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize, + long *preferredSize, long *granularity) +{ + ASIOBool result; + CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity ); + return result; +} + +ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 52, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 60, clocks, numSources ); + return result; +} + +ASIOError IASIOThiscallResolver::setClockSource(long reference) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 64, reference ); + return result; +} + +ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 68, sPos, tStamp ); + return result; +} + +ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 72, info ); + return result; +} + +ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos, + long numChannels, long bufferSize, ASIOCallbacks *callbacks) +{ + ASIOBool result; + CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks ); + return result; +} + +ASIOError IASIOThiscallResolver::disposeBuffers() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 80 ); + return result; +} + +ASIOError IASIOThiscallResolver::controlPanel() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 84 ); + return result; +} + +ASIOError IASIOThiscallResolver::future(long selector,void *opt) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 88, selector, opt ); + return result; +} + +ASIOError IASIOThiscallResolver::outputReady() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 92 ); + return result; +} + + +// Implement our substitute ASIOInit() method +ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info) +{ + // To ensure that our instance's vptr is correctly constructed, even if + // ASIOInit is called prior to main(), we explicitly call its constructor + // (potentially over the top of an existing instance). Note that this is + // pretty ugly, and is only safe because IASIOThiscallResolver has no + // destructor and contains no objects with destructors. + new((void*)&instance) IASIOThiscallResolver( theAsioDriver ); + + // Interpose between ASIO client code and the real driver. + theAsioDriver = &instance; + + // Note that we never need to switch theAsioDriver back to point to the + // real driver because theAsioDriver is reset to zero in ASIOExit(). + + // Delegate to the real ASIOInit + return ::ASIOInit(info); +} + + +#endif /* !defined(_MSC_VER) */ + +#endif /* Win32 */ + diff --git a/portaudio-v19/pa_asio/iasiothiscallresolver.h b/portaudio-v19/pa_asio/iasiothiscallresolver.h new file mode 100644 index 000000000..2ecfed799 --- /dev/null +++ b/portaudio-v19/pa_asio/iasiothiscallresolver.h @@ -0,0 +1,197 @@ +// **************************************************************************** +// File: IASIOThiscallResolver.h +// Description: The IASIOThiscallResolver class implements the IASIO +// interface and acts as a proxy to the real IASIO interface by +// calling through its vptr table using the thiscall calling +// convention. To put it another way, we interpose +// IASIOThiscallResolver between ASIO SDK code and the driver. +// This is necessary because most non-Microsoft compilers don't +// implement the thiscall calling convention used by IASIO. +// +// iasiothiscallresolver.cpp contains the background of this +// problem plus a technical description of the vptr +// manipulations. +// +// In order to use this mechanism one simply has to add +// iasiothiscallresolver.cpp to the list of files to compile +// and #include +// +// Note that this #include must come after the other ASIO SDK +// #includes, for example: +// +// #include +// #include +// #include +// #include +// #include +// +// Actually the important thing is to #include +// after . We have +// incorporated a test to enforce this ordering. +// +// The code transparently takes care of the interposition by +// using macro substitution to intercept calls to ASIOInit() +// and ASIOExit(). We save the original ASIO global +// "theAsioDriver" in our "that" variable, and then set +// "theAsioDriver" to equal our IASIOThiscallResolver instance. +// +// Whilst this method of resolving the thiscall problem requires +// the addition of #include to client +// code it has the advantage that it does not break the terms +// of the ASIO licence by publishing it. We are NOT modifying +// any Steinberg code here, we are merely implementing the IASIO +// interface in the same way that we would need to do if we +// wished to provide an open source ASIO driver. +// +// For compilation with MinGW -lole32 needs to be added to the +// linker options. For BORLAND, linking with Import32.lib is +// sufficient. +// +// The dependencies are with: CoInitialize, CoUninitialize, +// CoCreateInstance, CLSIDFromString - used by asiolist.cpp +// and are required on Windows whether ThiscallResolver is used +// or not. +// +// Searching for the above strings in the root library path +// of your compiler should enable the correct libraries to be +// identified if they aren't immediately obvious. +// +// Note that the current implementation of IASIOThiscallResolver +// is not COM compliant - it does not correctly implement the +// IUnknown interface. Implementing it is not necessary because +// it is not called by parts of the ASIO SDK which call through +// theAsioDriver ptr. The IUnknown methods are implemented as +// assert(false) to ensure that the code fails if they are +// ever called. +// Restrictions: None. Public Domain & Open Source distribute freely +// You may use IASIOThiscallResolver commercially as well as +// privately. +// You the user assume the responsibility for the use of the +// files, binary or text, and there is no guarantee or warranty, +// expressed or implied, including but not limited to the +// implied warranties of merchantability and fitness for a +// particular purpose. You assume all responsibility and agree +// to hold no entity, copyright holder or distributors liable +// for any loss of data or inaccurate representations of data +// as a result of using IASIOThiscallResolver. +// Version: 1.4 Added separate macro CALL_THISCALL_1_DOUBLE from +// Andrew Baldwin, and volatile for whole gcc asm blocks, +// both for compatibility with newer gcc versions. Cleaned up +// Borland asm to use one less register. +// 1.3 Switched to including assert.h for better compatibility. +// Wrapped entire .h and .cpp contents with a check for +// _MSC_VER to provide better compatibility with MS compilers. +// Changed Singleton implementation to use static instance +// instead of freestore allocated instance. Removed ASIOExit +// macro as it is no longer needed. +// 1.2 Removed semicolons from ASIOInit and ASIOExit macros to +// allow them to be embedded in expressions (if statements). +// Cleaned up some comments. Removed combase.c dependency (it +// doesn't compile with BCB anyway) by stubbing IUnknown. +// 1.1 Incorporated comments from Ross Bencina including things +// such as changing name from ThiscallResolver to +// IASIOThiscallResolver, tidying up the constructor, fixing +// a bug in IASIOThiscallResolver::ASIOExit() and improving +// portability through the use of conditional compilation +// 1.0 Initial working version. +// Created: 6/09/2003 +// Authors: Fraser Adams +// Ross Bencina +// Rene G. Ceballos +// Martin Fay +// Antti Silvast +// Andrew Baldwin +// +// **************************************************************************** + + +#ifndef included_iasiothiscallresolver_h +#define included_iasiothiscallresolver_h + +// We only need IASIOThiscallResolver at all if we are on Win32. For other +// platforms we simply bypass the IASIOThiscallResolver definition to allow us +// to be safely #include'd whatever the platform to keep client code portable +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) + + +// If microsoft compiler we can call IASIO directly so IASIOThiscallResolver +// is not used. +#if !defined(_MSC_VER) + + +// The following is in order to ensure that this header is only included after +// the other ASIO headers (except for the case of iasiothiscallresolver.cpp). +// We need to do this because IASIOThiscallResolver works by eclipsing the +// original definition of ASIOInit() with a macro (see below). +#if !defined(iasiothiscallresolver_sourcefile) + #if !defined(__ASIO_H) + #error iasiothiscallresolver.h must be included AFTER asio.h + #endif +#endif + +#include +#include /* From ASIO SDK */ + + +class IASIOThiscallResolver : public IASIO { +private: + IASIO* that_; // Points to the real IASIO + + static IASIOThiscallResolver instance; // Singleton instance + + // Constructors - declared private so construction is limited to + // our Singleton instance + IASIOThiscallResolver(); + IASIOThiscallResolver(IASIO* that); +public: + + // Methods from the IUnknown interface. We don't fully implement IUnknown + // because the ASIO SDK never calls these methods through theAsioDriver ptr. + // These methods are implemented as assert(false). + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv); + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); + + // Methods from the IASIO interface, implemented as forwarning calls to that. + virtual ASIOBool init(void *sysHandle); + virtual void getDriverName(char *name); + virtual long getDriverVersion(); + virtual void getErrorMessage(char *string); + virtual ASIOError start(); + virtual ASIOError stop(); + virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels); + virtual ASIOError getLatencies(long *inputLatency, long *outputLatency); + virtual ASIOError getBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity); + virtual ASIOError canSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate); + virtual ASIOError setSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources); + virtual ASIOError setClockSource(long reference); + virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp); + virtual ASIOError getChannelInfo(ASIOChannelInfo *info); + virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks); + virtual ASIOError disposeBuffers(); + virtual ASIOError controlPanel(); + virtual ASIOError future(long selector,void *opt); + virtual ASIOError outputReady(); + + // Class method, see ASIOInit() macro below. + static ASIOError ASIOInit(ASIODriverInfo *info); // Delegates to ::ASIOInit +}; + + +// Replace calls to ASIOInit with our interposing version. +// This macro enables us to perform thiscall resolution simply by #including +// after the asio #includes (this file _must_ be +// included _after_ the asio #includes) + +#define ASIOInit(name) IASIOThiscallResolver::ASIOInit((name)) + + +#endif /* !defined(_MSC_VER) */ + +#endif /* Win32 */ + +#endif /* included_iasiothiscallresolver_h */ + + diff --git a/portaudio-v19/pa_asio/pa_asio.cpp b/portaudio-v19/pa_asio/pa_asio.cpp new file mode 100644 index 000000000..6593e7277 --- /dev/null +++ b/portaudio-v19/pa_asio/pa_asio.cpp @@ -0,0 +1,2958 @@ +/* + * $Id$ + * Portable Audio I/O Library for ASIO Drivers + * + * Author: Stephane Letz + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2000-2002 Stephane Letz, Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Modification History + + 08-03-01 First version : Stephane Letz + 08-06-01 Tweaks for PC, use C++, buffer allocation, Float32 to Int32 conversion : Phil Burk + 08-20-01 More conversion, PA_StreamTime, Pa_GetHostError : Stephane Letz + 08-21-01 PaUInt8 bug correction, implementation of ASIOSTFloat32LSB and ASIOSTFloat32MSB native formats : Stephane Letz + 08-24-01 MAX_INT32_FP hack, another Uint8 fix : Stephane and Phil + 08-27-01 Implementation of hostBufferSize < userBufferSize case, better management of the ouput buffer when + the stream is stopped : Stephane Letz + 08-28-01 Check the stream pointer for null in bufferSwitchTimeInfo, correct bug in bufferSwitchTimeInfo when + the stream is stopped : Stephane Letz + 10-12-01 Correct the PaHost_CalcNumHostBuffers function: computes FramesPerHostBuffer to be the lowest that + respect requested FramesPerUserBuffer and userBuffersPerHostBuffer : Stephane Letz + 10-26-01 Management of hostBufferSize and userBufferSize of any size : Stephane Letz + 10-27-01 Improve calculus of hostBufferSize to be multiple or divisor of userBufferSize if possible : Stephane and Phil + 10-29-01 Change MAX_INT32_FP to (2147483520.0f) to prevent roundup to 0x80000000 : Phil Burk + 10-31-01 Clear the ouput buffer and user buffers in PaHost_StartOutput, correct bug in GetFirstMultiple : Stephane Letz + 11-06-01 Rename functions : Stephane Letz + 11-08-01 New Pa_ASIO_Adaptor_Init function to init Callback adpatation variables, cleanup of Pa_ASIO_Callback_Input: Stephane Letz + 11-29-01 Break apart device loading to debug random failure in Pa_ASIO_QueryDeviceInfo ; Phil Burk + 01-03-02 Desallocate all resources in PaHost_Term for cases where Pa_CloseStream is not called properly : Stephane Letz + 02-01-02 Cleanup, test of multiple-stream opening : Stephane Letz + 19-02-02 New Pa_ASIO_loadDriver that calls CoInitialize on each thread on Windows : Stephane Letz + 09-04-02 Correct error code management in PaHost_Term, removes various compiler warning : Stephane Letz + 12-04-02 Add Mac includes for and : Phil Burk + 13-04-02 Removes another compiler warning : Stephane Letz + 30-04-02 Pa_ASIO_QueryDeviceInfo bug correction, memory allocation checking, better error handling : D Viens, P Burk, S Letz + 12-06-02 Rehashed into new multi-api infrastructure, added support for all ASIO sample formats : Ross Bencina + 18-06-02 Added pa_asio.h, PaAsio_GetAvailableLatencyValues() : Ross B. + 21-06-02 Added SelectHostBufferSize() which selects host buffer size based on user latency parameters : Ross Bencina + ** NOTE maintanance history is now stored in CVS ** +*/ + +/** @file + + Note that specific support for paInputUnderflow, paOutputOverflow and + paNeverDropInput is not necessary or possible with this driver due to the + synchronous full duplex double-buffered architecture of ASIO. + + @todo check that CoInitialize()/CoUninitialize() are always correctly + paired, even in error cases. + + @todo implement host api specific extension to set i/o buffer sizes in frames + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo implement IsFormatSupported + + @todo work out how to implement stream stoppage from callback and + implement IsStreamActive properly. Stream stoppage could be implemented + using a high-priority thread blocked on an Event which is signalled + by the callback. Or, we could just call ASIO stop from the callback + and see what happens. + + @todo rigorously check asio return codes and convert to pa error codes + + @todo Different channels of a multichannel stream can have different sample + formats, but we assume that all are the same as the first channel for now. + Fixing this will require the block processor to maintain per-channel + conversion functions - could get nasty. + + @todo investigate whether the asio processNow flag needs to be honoured + + @todo handle asioMessages() callbacks in a useful way, or at least document + what cases we don't handle. + + @todo miscellaneous other FIXMEs + + @todo provide an asio-specific method for setting the systems specific + value (aka main window handle) - check that this matches the value + passed to PaAsio_ShowControlPanel, or remove it entirely from + PaAsio_ShowControlPanel. - this would allow PaAsio_ShowControlPanel + to be called for the currently open stream (at present all streams + must be closed). +*/ + + + +#include +#include +#include +//#include + +#include +#include + +#include "portaudio.h" +#include "pa_asio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "asiosys.h" +#include "asio.h" +#include "asiodrivers.h" +#include "iasiothiscallresolver.h" + +/* +#if MAC +#include +#include +#include +#else +*/ +/* +#include +#include +#include +*/ +/* +#endif +*/ + +/* external references */ +extern AsioDrivers* asioDrivers ; +bool loadAsioDriver(char *name); + + +/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ +/* not tested at all since new code was introduced. */ +#define CARBON_COMPATIBLE (0) + + + + +/* prototypes for functions declared in this file */ + +extern "C" PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +/* our ASIO callback functions */ + +static void bufferSwitch(long index, ASIOBool processNow); +static ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow); +static void sampleRateChanged(ASIOSampleRate sRate); +static long asioMessages(long selector, long value, void* message, double* opt); + +static ASIOCallbacks asioCallbacks_ = + { bufferSwitch, sampleRateChanged, asioMessages, bufferSwitchTimeInfo }; + + +#define PA_ASIO_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paASIO, errorCode, errorText ) + + +static void PaAsio_SetLastSystemError( DWORD errorCode ) +{ + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( paASIO, errorCode, (const char*)lpMsgBuf ); + LocalFree( lpMsgBuf ); +} + +#define PA_ASIO_SET_LAST_SYSTEM_ERROR( errorCode ) \ + PaAsio_SetLastSystemError( errorCode ) + + +static const char* PaAsio_GetAsioErrorText( ASIOError asioError ) +{ + const char *result; + + switch( asioError ){ + case ASE_OK: + case ASE_SUCCESS: result = "Success"; break; + case ASE_NotPresent: result = "Hardware input or output is not present or available"; break; + case ASE_HWMalfunction: result = "Hardware is malfunctioning"; break; + case ASE_InvalidParameter: result = "Input parameter invalid"; break; + case ASE_InvalidMode: result = "Hardware is in a bad mode or used in a bad mode"; break; + case ASE_SPNotAdvancing: result = "Hardware is not running when sample position is inquired"; break; + case ASE_NoClock: result = "Sample clock or rate cannot be determined or is not present"; break; + case ASE_NoMemory: result = "Not enough memory for completing the request"; break; + default: result = "Unknown ASIO error"; break; + } + + return result; +} + + +#define PA_ASIO_SET_LAST_ASIO_ERROR( asioError ) \ + PaUtil_SetLastHostErrorInfo( paASIO, asioError, PaAsio_GetAsioErrorText( asioError ) ) + + + + +// Atomic increment and decrement operations +#if MAC + /* need to be implemented on Mac */ + inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast(v));} +#elif WINDOWS + inline long PaAsio_AtomicIncrement(volatile long* v) {return InterlockedIncrement(const_cast(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return InterlockedDecrement(const_cast(v));} +#endif + + + +typedef struct PaAsioDriverInfo +{ + ASIODriverInfo asioDriverInfo; + long inputChannelCount, outputChannelCount; + long bufferMinSize, bufferMaxSize, bufferPreferredSize, bufferGranularity; + bool postOutput; +} +PaAsioDriverInfo; + + +/* PaAsioHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + void *systemSpecific; + + /* the ASIO C API only allows one ASIO driver to be open at a time, + so we keep track of whether we have the driver open here, and + use this information to return errors from OpenStream if the + driver is already open. + + openAsioDeviceIndex will be PaNoDevice if there is no device open + and a valid pa_asio (not global) device index otherwise. + + openAsioDriverInfo is populated with the driver info for the + currently open device (if any) + */ + PaDeviceIndex openAsioDeviceIndex; + PaAsioDriverInfo openAsioDriverInfo; +} +PaAsioHostApiRepresentation; + + +/* + Retrieve driver names from ASIO, returned in a char** + allocated in . +*/ +static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount ) +{ + char **result = 0; + int i; + + result =(char**)PaUtil_GroupAllocateMemory( + group, sizeof(char*) * driverCount ); + if( !result ) + goto error; + + result[0] = (char*)PaUtil_GroupAllocateMemory( + group, 32 * driverCount ); + if( !result[0] ) + goto error; + + for( i=0; igetDriverNames( result, driverCount ); + +error: + return result; +} + + +static PaSampleFormat AsioSampleTypeToPaNativeSampleFormat(ASIOSampleType type) +{ + switch (type) { + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + return paInt16; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + return paFloat32; + + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + return paInt32; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + return paInt24; + + default: + return paCustomFormat; + } +} + +void AsioSampleTypeLOG(ASIOSampleType type) +{ + switch (type) { + case ASIOSTInt16MSB: PA_DEBUG(("ASIOSTInt16MSB\n")); break; + case ASIOSTInt16LSB: PA_DEBUG(("ASIOSTInt16LSB\n")); break; + case ASIOSTFloat32MSB:PA_DEBUG(("ASIOSTFloat32MSB\n"));break; + case ASIOSTFloat32LSB:PA_DEBUG(("ASIOSTFloat32LSB\n"));break; + case ASIOSTFloat64MSB:PA_DEBUG(("ASIOSTFloat64MSB\n"));break; + case ASIOSTFloat64LSB:PA_DEBUG(("ASIOSTFloat64LSB\n"));break; + case ASIOSTInt32MSB: PA_DEBUG(("ASIOSTInt32MSB\n")); break; + case ASIOSTInt32LSB: PA_DEBUG(("ASIOSTInt32LSB\n")); break; + case ASIOSTInt32MSB16:PA_DEBUG(("ASIOSTInt32MSB16\n"));break; + case ASIOSTInt32LSB16:PA_DEBUG(("ASIOSTInt32LSB16\n"));break; + case ASIOSTInt32MSB18:PA_DEBUG(("ASIOSTInt32MSB18\n"));break; + case ASIOSTInt32MSB20:PA_DEBUG(("ASIOSTInt32MSB20\n"));break; + case ASIOSTInt32MSB24:PA_DEBUG(("ASIOSTInt32MSB24\n"));break; + case ASIOSTInt32LSB18:PA_DEBUG(("ASIOSTInt32LSB18\n"));break; + case ASIOSTInt32LSB20:PA_DEBUG(("ASIOSTInt32LSB20\n"));break; + case ASIOSTInt32LSB24:PA_DEBUG(("ASIOSTInt32LSB24\n"));break; + case ASIOSTInt24MSB: PA_DEBUG(("ASIOSTInt24MSB\n")); break; + case ASIOSTInt24LSB: PA_DEBUG(("ASIOSTInt24LSB\n")); break; + default: PA_DEBUG(("Custom Format%d\n",type));break; + + } +} + +static int BytesPerAsioSample( ASIOSampleType sampleType ) +{ + switch (sampleType) { + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + return 2; + + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + return 8; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + return 4; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + return 3; + + default: + return 0; + } +} + + +static void Swap16( void *buffer, long shift, long count ) +{ + unsigned short *p = (unsigned short*)buffer; + unsigned short temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p++ = (unsigned short)((temp<<8) | (temp>>8)); + } +} + +static void Swap24( void *buffer, long shift, long count ) +{ + unsigned char *p = (unsigned char*)buffer; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p = *(p+2); + *(p+2) = temp; + p += 3; + } +} + +#define PA_SWAP32_( x ) ((x>>24) | ((x>>8)&0xFF00) | ((x<<8)&0xFF0000) | (x<<24)); + +static void Swap32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p++ = PA_SWAP32_( temp); + } +} + +static void SwapShiftLeft32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + temp = PA_SWAP32_( temp); + *p++ = temp << shift; + } +} + +static void ShiftRightSwap32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p >> shift; + *p++ = PA_SWAP32_( temp); + } +} + +static void ShiftLeft32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + *p++ = temp << shift; + } +} + +static void ShiftRight32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + *p++ = temp >> shift; + } +} + +#define PA_SWAP_( x, y ) temp=x; x = y; y = temp; + +static void Swap64ConvertFloat64ToFloat32( void *buffer, long shift, long count ) +{ + double *in = (double*)buffer; + float *out = (float*)buffer; + unsigned char *p; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + p = (unsigned char*)in; + PA_SWAP_( p[0], p[7] ); + PA_SWAP_( p[1], p[6] ); + PA_SWAP_( p[2], p[5] ); + PA_SWAP_( p[3], p[4] ); + + *out++ = (float) (*in++); + } +} + +static void ConvertFloat64ToFloat32( void *buffer, long shift, long count ) +{ + double *in = (double*)buffer; + float *out = (float*)buffer; + (void) shift; /* unused parameter */ + + while( count-- ) + *out++ = (float) (*in++); +} + +static void ConvertFloat32ToFloat64Swap64( void *buffer, long shift, long count ) +{ + float *in = ((float*)buffer) + (count-1); + double *out = ((double*)buffer) + (count-1); + unsigned char *p; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + *out = *in--; + + p = (unsigned char*)out; + PA_SWAP_( p[0], p[7] ); + PA_SWAP_( p[1], p[6] ); + PA_SWAP_( p[2], p[5] ); + PA_SWAP_( p[3], p[4] ); + + out--; + } +} + +static void ConvertFloat32ToFloat64( void *buffer, long shift, long count ) +{ + float *in = ((float*)buffer) + (count-1); + double *out = ((double*)buffer) + (count-1); + (void) shift; /* unused parameter */ + + while( count-- ) + *out-- = *in--; +} + +#ifdef MAC +#define PA_MSB_IS_NATIVE_ +#undef PA_LSB_IS_NATIVE_ +#endif + +#ifdef WINDOWS +#undef PA_MSB_IS_NATIVE_ +#define PA_LSB_IS_NATIVE_ +#endif + +typedef void PaAsioBufferConverter( void *, long, long ); + +static void SelectAsioToPaConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift ) +{ + *shift = 0; + *converter = 0; + + switch (type) { + case ASIOSTInt16MSB: + /* dest: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTInt16LSB: + /* dest: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTFloat32MSB: + /* dest: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat32LSB: + /* dest: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat64MSB: + /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap64ConvertFloat64ToFloat32; + #else + *converter = ConvertFloat64ToFloat32; + #endif + break; + case ASIOSTFloat64LSB: + /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap64ConvertFloat64ToFloat32; + #else + *converter = ConvertFloat64ToFloat32; + #endif + break; + case ASIOSTInt32MSB: + /* dest: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32LSB: + /* dest: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32MSB16: + /* dest: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 16; + break; + case ASIOSTInt32MSB18: + /* dest: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 14; + break; + case ASIOSTInt32MSB20: + /* dest: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 12; + break; + case ASIOSTInt32MSB24: + /* dest: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 8; + break; + case ASIOSTInt32LSB16: + /* dest: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 16; + break; + case ASIOSTInt32LSB18: + /* dest: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 14; + break; + case ASIOSTInt32LSB20: + /* dest: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 12; + break; + case ASIOSTInt32LSB24: + /* dest: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 8; + break; + case ASIOSTInt24MSB: + /* dest: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + case ASIOSTInt24LSB: + /* dest: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + } +} + + +static void SelectPaToAsioConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift ) +{ + *shift = 0; + *converter = 0; + + switch (type) { + case ASIOSTInt16MSB: + /* src: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTInt16LSB: + /* src: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTFloat32MSB: + /* src: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat32LSB: + /* src: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat64MSB: + /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ConvertFloat32ToFloat64Swap64; + #else + *converter = ConvertFloat32ToFloat64; + #endif + break; + case ASIOSTFloat64LSB: + /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ConvertFloat32ToFloat64Swap64; + #else + *converter = ConvertFloat32ToFloat64; + #endif + break; + case ASIOSTInt32MSB: + /* src: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32LSB: + /* src: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32MSB16: + /* src: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 16; + break; + case ASIOSTInt32MSB18: + /* src: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 14; + break; + case ASIOSTInt32MSB20: + /* src: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 12; + break; + case ASIOSTInt32MSB24: + /* src: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 8; + break; + case ASIOSTInt32LSB16: + /* src: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 16; + break; + case ASIOSTInt32LSB18: + /* src: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 14; + break; + case ASIOSTInt32LSB20: + /* src: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 12; + break; + case ASIOSTInt32LSB24: + /* src: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 8; + break; + case ASIOSTInt24MSB: + /* src: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + case ASIOSTInt24LSB: + /* src: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + } +} + + +typedef struct PaAsioDeviceInfo +{ + PaDeviceInfo commonDeviceInfo; + long minBufferSize; + long maxBufferSize; + long preferredBufferSize; + long bufferGranularity; + + ASIOChannelInfo *asioChannelInfos; +} +PaAsioDeviceInfo; + + +PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + + if( result == paNoError ) + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + + if( result == paNoError ) + { + PaAsioDeviceInfo *asioDeviceInfo = + (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + *minLatency = asioDeviceInfo->minBufferSize; + *maxLatency = asioDeviceInfo->maxBufferSize; + *preferredLatency = asioDeviceInfo->preferredBufferSize; + *granularity = asioDeviceInfo->bufferGranularity; + } + } + + return result; +} + + + +/* + load the asio driver named by and return statistics about + the driver in info. If no error occurred, the driver will remain open + and must be closed by the called by calling ASIOExit() - if an error + is returned the driver will already be closed. +*/ +static PaError LoadAsioDriver( const char *driverName, + PaAsioDriverInfo *driverInfo, void *systemSpecific ) +{ + PaError result = paNoError; + ASIOError asioError; + int asioIsInitialized = 0; + + if( !loadAsioDriver( const_cast(driverName) ) ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" ); + goto error; + } + + memset( &driverInfo->asioDriverInfo, 0, sizeof(ASIODriverInfo) ); + driverInfo->asioDriverInfo.asioVersion = 2; + driverInfo->asioDriverInfo.sysRef = systemSpecific; + if( (asioError = ASIOInit( &driverInfo->asioDriverInfo )) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + else + { + asioIsInitialized = 1; + } + + if( (asioError = ASIOGetChannels(&driverInfo->inputChannelCount, + &driverInfo->outputChannelCount)) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + + if( (asioError = ASIOGetBufferSize(&driverInfo->bufferMinSize, + &driverInfo->bufferMaxSize, &driverInfo->bufferPreferredSize, + &driverInfo->bufferGranularity)) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + + if( ASIOOutputReady() == ASE_OK ) + driverInfo->postOutput = true; + else + driverInfo->postOutput = false; + + return result; + +error: + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ 13 /* must be the same number of elements as in the array below */ +static ASIOSampleRate defaultSampleRateSearchOrder_[] + = {44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, + 192000.0, 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + + +PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, driverCount; + PaAsioHostApiRepresentation *asioHostApi; + PaAsioDeviceInfo *deviceInfoArray; + char **names; + PaAsioDriverInfo paAsioDriverInfo; + + asioHostApi = (PaAsioHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaAsioHostApiRepresentation) ); + if( !asioHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + asioHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !asioHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + asioHostApi->systemSpecific = 0; + asioHostApi->openAsioDeviceIndex = paNoDevice; + + *hostApi = &asioHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + + (*hostApi)->info.type = paASIO; + (*hostApi)->info.name = "ASIO"; + (*hostApi)->info.deviceCount = 0; + + #ifdef WINDOWS + /* use desktop window as system specific ptr */ + asioHostApi->systemSpecific = GetDesktopWindow(); + CoInitialize(NULL); + #endif + + /* MUST BE CHECKED : to force fragments loading on Mac */ + loadAsioDriver( "dummy" ); + + /* driverCount is the number of installed drivers - not necessarily + the number of installed physical devices. */ + #if MAC + driverCount = asioDrivers->getNumFragments(); + #elif WINDOWS + driverCount = asioDrivers->asioGetNumDev(); + #endif + + if( driverCount > 0 ) + { + names = GetAsioDriverNames( asioHostApi->allocations, driverCount ); + if( !names ) + { + result = paInsufficientMemory; + goto error; + } + + + /* allocate enough space for all drivers, even if some aren't installed */ + + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, sizeof(PaDeviceInfo*) * driverCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaAsioDeviceInfo*)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, sizeof(PaAsioDeviceInfo) * driverCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < driverCount; ++i ) + { + + PA_DEBUG(("ASIO names[%d]:%s\n",i,names[i])); + + // Since portaudio opens ALL ASIO drivers, and no one else does that, + // we face fact that some drivers were not meant for it, drivers which act + // like shells on top of REAL drivers, for instance. + // so we get duplicate handles, locks and other problems. + // so lets NOT try to load any such wrappers. + // The ones i [davidv] know of so far are: + + if ( strcmp (names[i],"ASIO DirectX Full Duplex Driver") == 0 + || strcmp (names[i],"ASIO Multimedia Driver") == 0 + || strncmp(names[i],"Premiere",8) == 0 //"Premiere Elements Windows Sound 1.0" + || strncmp(names[i],"Adobe",5) == 0 ) //"Adobe Default Windows Sound 1.5" + { + PA_DEBUG(("BLACKLISTED!!!\n")); + continue; + } + + + /* Attempt to load the asio driver... */ + if( LoadAsioDriver( names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) + { + PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo; + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->name = names[i]; + PA_DEBUG(("PaAsio_Initialize: drv:%d name = %s\n", i,deviceInfo->name)); + PA_DEBUG(("PaAsio_Initialize: drv:%d inputChannels = %d\n", i, paAsioDriverInfo.inputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d outputChannels = %d\n", i, paAsioDriverInfo.outputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMinSize = %d\n", i, paAsioDriverInfo.bufferMinSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMaxSize = %d\n", i, paAsioDriverInfo.bufferMaxSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferPreferredSize = %d\n", i, paAsioDriverInfo.bufferPreferredSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferGranularity = %d\n", i, paAsioDriverInfo.bufferGranularity)); + + deviceInfo->maxInputChannels = paAsioDriverInfo.inputChannelCount; + deviceInfo->maxOutputChannels = paAsioDriverInfo.outputChannelCount; + + deviceInfo->defaultSampleRate = 0.; + bool foundDefaultSampleRate = false; + for( int j=0; j < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++j ) + { + ASIOError asioError = ASIOCanSampleRate( defaultSampleRateSearchOrder_[j] ); + if( asioError != ASE_NoClock && asioError != ASE_NotPresent ) + { + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[j]; + foundDefaultSampleRate = true; + break; + } + } + + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultSampleRate = %f\n", i, deviceInfo->defaultSampleRate)); + + if( foundDefaultSampleRate ){ + + /* calculate default latency values from bufferPreferredSize + for default low latency, and bufferPreferredSize * 3 + for default high latency. + use the default sample rate to convert from samples to + seconds. Without knowing what sample rate the user will + use this is the best we can do. + */ + + double defaultLowLatency = + paAsioDriverInfo.bufferPreferredSize / deviceInfo->defaultSampleRate; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + + long defaultHighLatencyBufferSize = + paAsioDriverInfo.bufferPreferredSize * 3; + + if( defaultHighLatencyBufferSize > paAsioDriverInfo.bufferMaxSize ) + defaultHighLatencyBufferSize = paAsioDriverInfo.bufferMaxSize; + + double defaultHighLatency = + defaultHighLatencyBufferSize / deviceInfo->defaultSampleRate; + + if( defaultHighLatency < defaultLowLatency ) + defaultHighLatency = defaultLowLatency; /* just incase the driver returns something strange */ + + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + }else{ + + deviceInfo->defaultLowInputLatency = 0.; + deviceInfo->defaultLowOutputLatency = 0.; + deviceInfo->defaultHighInputLatency = 0.; + deviceInfo->defaultHighOutputLatency = 0.; + } + + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowInputLatency = %f\n", i, deviceInfo->defaultLowInputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowOutputLatency = %f\n", i, deviceInfo->defaultLowOutputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighInputLatency = %f\n", i, deviceInfo->defaultHighInputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighOutputLatency = %f\n", i, deviceInfo->defaultHighOutputLatency)); + + asioDeviceInfo->minBufferSize = paAsioDriverInfo.bufferMinSize; + asioDeviceInfo->maxBufferSize = paAsioDriverInfo.bufferMaxSize; + asioDeviceInfo->preferredBufferSize = paAsioDriverInfo.bufferPreferredSize; + asioDeviceInfo->bufferGranularity = paAsioDriverInfo.bufferGranularity; + + + asioDeviceInfo->asioChannelInfos = (ASIOChannelInfo*)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, + sizeof(ASIOChannelInfo) * (deviceInfo->maxInputChannels + + deviceInfo->maxOutputChannels) ); + if( !asioDeviceInfo->asioChannelInfos ) + { + result = paInsufficientMemory; + goto error; + } + + int a; + + for( a=0; a < deviceInfo->maxInputChannels; ++a ){ + asioDeviceInfo->asioChannelInfos[a].channel = a; + asioDeviceInfo->asioChannelInfos[a].isInput = ASIOTrue; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[a] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } + + for( a=0; a < deviceInfo->maxOutputChannels; ++a ){ + int b = deviceInfo->maxInputChannels + a; + asioDeviceInfo->asioChannelInfos[b].channel = a; + asioDeviceInfo->asioChannelInfos[b].isInput = ASIOFalse; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[b] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } + + + /* unload the driver */ + ASIOExit(); + + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + } + + if( (*hostApi)->info.deviceCount > 0 ) + { + (*hostApi)->info.defaultInputDevice = 0; + (*hostApi)->info.defaultOutputDevice = 0; + } + else + { + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + } + + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &asioHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &asioHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( asioHostApi ) + { + if( asioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( asioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); + } + + PaUtil_FreeMemory( asioHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( asioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( asioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); + } + + PaUtil_FreeMemory( asioHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result = paNoError; + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + PaAsioDriverInfo *driverInfo = &asioHostApi->openAsioDriverInfo; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaDeviceIndex asioDeviceIndex; + ASIOError asioError; + + if( inputParameters && outputParameters ) + { + /* full duplex ASIO stream must use the same device for input and output */ + + if( inputParameters->device != outputParameters->device ) + return paBadIODeviceCombination; + } + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = inputParameters->device; + + /* validate inputStreamInfo */ + /** @todo do more validation here */ + // if( inputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = outputParameters->device; + + /* validate outputStreamInfo */ + /** @todo do more validation here */ + // if( outputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + + + /* if an ASIO device is open we can only get format information for the currently open device */ + + if( asioHostApi->openAsioDeviceIndex != paNoDevice + && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) + { + return paDeviceUnavailable; + } + + + /* NOTE: we load the driver and use its current settings + rather than the ones in our device info structure which may be stale */ + + /* open the device if it's not already open */ + if( asioHostApi->openAsioDeviceIndex == paNoDevice ) + { + result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + driverInfo, asioHostApi->systemSpecific ); + if( result != paNoError ) + return result; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > 0 ) + { + if( inputChannelCount > driverInfo->inputChannelCount ) + { + result = paInvalidChannelCount; + goto done; + } + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount ) + { + if( outputChannelCount > driverInfo->outputChannelCount ) + { + result = paInvalidChannelCount; + goto done; + } + } + + /* query for sample rate support */ + asioError = ASIOCanSampleRate( sampleRate ); + if( asioError == ASE_NoClock || asioError == ASE_NotPresent ) + { + result = paInvalidSampleRate; + goto done; + } + +done: + /* close the device if it wasn't already open */ + if( asioHostApi->openAsioDeviceIndex == paNoDevice ) + { + ASIOExit(); /* not sure if we should check for errors here */ + } + + if( result == paNoError ) + return paFormatIsSupported; + else + return result; +} + + + +/* PaAsioStream - a stream data structure specifically for this implementation */ + +typedef struct PaAsioStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaAsioHostApiRepresentation *asioHostApi; + unsigned long framesPerHostCallback; + + /* ASIO driver info - these may not be needed for the life of the stream, + but store them here until we work out how format conversion is going + to work. */ + + ASIOBufferInfo *asioBufferInfos; + ASIOChannelInfo *asioChannelInfos; + long inputLatency, outputLatency; // actual latencies returned by asio + + long inputChannelCount, outputChannelCount; + bool postOutput; + + void **bufferPtrs; /* this is carved up for inputBufferPtrs and outputBufferPtrs */ + void **inputBufferPtrs[2]; + void **outputBufferPtrs[2]; + + PaAsioBufferConverter *inputBufferConverter; + long inputShift; + PaAsioBufferConverter *outputBufferConverter; + long outputShift; + + volatile bool stopProcessing; + int stopPlayoutCount; + HANDLE completedBuffersPlayedEvent; + + bool streamFinishedCallbackCalled; + volatile int isActive; + volatile bool zeroOutput; /* all future calls to the callback will output silence */ + + volatile long reenterCount; + volatile long reenterError; + + PaStreamCallbackFlags callbackFlags; +} +PaAsioStream; + +static PaAsioStream *theAsioStream = 0; /* due to ASIO sdk limitations there can be only one stream */ + + +static void ZeroOutputBuffers( PaAsioStream *stream, long index ) +{ + int i; + + for( i=0; i < stream->outputChannelCount; ++i ) + { + void *buffer = stream->asioBufferInfos[ i + stream->inputChannelCount ].buffers[index]; + + int bytesPerSample = BytesPerAsioSample( stream->asioChannelInfos[ i + stream->inputChannelCount ].type ); + + memset( buffer, 0, stream->framesPerHostCallback * bytesPerSample ); + } +} + + +static unsigned long SelectHostBufferSize( unsigned long suggestedLatencyFrames, + PaAsioDriverInfo *driverInfo ) +{ + unsigned long result; + + if( suggestedLatencyFrames == 0 ) + { + result = driverInfo->bufferPreferredSize; + } + else{ + if( suggestedLatencyFrames <= (unsigned long)driverInfo->bufferMinSize ) + { + result = driverInfo->bufferMinSize; + } + else if( suggestedLatencyFrames >= (unsigned long)driverInfo->bufferMaxSize ) + { + result = driverInfo->bufferMaxSize; + } + else + { + if( driverInfo->bufferGranularity == -1 ) + { + /* power-of-two */ + result = 2; + + while( result < suggestedLatencyFrames ) + result *= 2; + + if( result < (unsigned long)driverInfo->bufferMinSize ) + result = driverInfo->bufferMinSize; + + if( result > (unsigned long)driverInfo->bufferMaxSize ) + result = driverInfo->bufferMaxSize; + } + else if( driverInfo->bufferGranularity == 0 ) + { + /* the documentation states that bufferGranularity should be + zero when bufferMinSize, bufferMaxSize and + bufferPreferredSize are the same. We assume that is the case. + */ + + result = driverInfo->bufferPreferredSize; + } + else + { + /* modulo granularity */ + + unsigned long remainder = + suggestedLatencyFrames % driverInfo->bufferGranularity; + + if( remainder == 0 ) + { + result = suggestedLatencyFrames; + } + else + { + result = suggestedLatencyFrames + + (driverInfo->bufferGranularity - remainder); + + if( result > (unsigned long)driverInfo->bufferMaxSize ) + result = driverInfo->bufferMaxSize; + } + } + } + } + + return result; +} + + +/* returns channelSelectors if present */ + +static PaError ValidateAsioSpecificStreamInfo( + const PaStreamParameters *streamParameters, + const PaAsioStreamInfo *streamInfo, + int deviceChannelCount, + int **channelSelectors ) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaAsioStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( streamInfo->flags & paAsioUseChannelSelectors ) + *channelSelectors = streamInfo->channelSelectors; + + if( !(*channelSelectors) ) + return paIncompatibleHostApiSpecificStreamInfo; + + for( int i=0; i < streamParameters->channelCount; ++i ){ + if( (*channelSelectors)[i] < 0 + || (*channelSelectors)[i] >= deviceChannelCount ){ + return paInvalidChannelCount; + } + } + } + + return paNoError; +} + + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + PaAsioStream *stream = 0; + PaAsioStreamInfo *inputStreamInfo, *outputStreamInfo; + unsigned long framesPerHostBuffer; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames; + unsigned long suggestedOutputLatencyFrames; + PaDeviceIndex asioDeviceIndex; + ASIOError asioError; + int asioIsInitialized = 0; + int asioBuffersCreated = 0; + int completedBuffersPlayedEventInited = 0; + int i; + PaAsioDriverInfo *driverInfo; + int *inputChannelSelectors = 0; + int *outputChannelSelectors = 0; + + /* unless we move to using lower level ASIO calls, we can only have + one device open at a time */ + if( asioHostApi->openAsioDeviceIndex != paNoDevice ){ + PA_DEBUG(("OpenStream paDeviceUnavailable\n")); + return paDeviceUnavailable; + } + + if( inputParameters && outputParameters ) + { + /* full duplex ASIO stream must use the same device for input and output */ + + if( inputParameters->device != outputParameters->device ){ + PA_DEBUG(("OpenStream paBadIODeviceCombination\n")); + return paBadIODeviceCombination; + } + } + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatencyFrames = (unsigned long)((inputParameters->suggestedLatency * sampleRate)+0.5f); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = inputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + + /* validate hostApiSpecificStreamInfo */ + inputStreamInfo = (PaAsioStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( inputParameters, inputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxInputChannels, + &inputChannelSelectors + ); + if( result != paNoError ) return result; + } + else + { + inputChannelCount = 0; + inputSampleFormat = 0; + suggestedInputLatencyFrames = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatencyFrames = (unsigned long)((outputParameters->suggestedLatency * sampleRate)+0.5f); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = outputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + + /* validate hostApiSpecificStreamInfo */ + outputStreamInfo = (PaAsioStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( outputParameters, outputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxOutputChannels, + &outputChannelSelectors + ); + if( result != paNoError ) return result; + } + else + { + outputChannelCount = 0; + outputSampleFormat = 0; + suggestedOutputLatencyFrames = 0; + } + + driverInfo = &asioHostApi->openAsioDriverInfo; + + /* NOTE: we load the driver and use its current settings + rather than the ones in our device info structure which may be stale */ + + result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + driverInfo, asioHostApi->systemSpecific ); + if( result == paNoError ) + asioIsInitialized = 1; + else{ + PA_DEBUG(("OpenStream ERROR1\n")); + goto error; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > 0 ) + { + if( inputChannelCount > driverInfo->inputChannelCount ) + { + result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR2\n")); + goto error; + } + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount ) + { + if( outputChannelCount > driverInfo->outputChannelCount ) + { + result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR3\n")); + goto error; + } + } + + + // check that the device supports the requested sample rate + + asioError = ASIOCanSampleRate( sampleRate ); + PA_DEBUG(("ASIOCanSampleRate(%f):%d\n",sampleRate, asioError )); + + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOCanSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + + + // retrieve the current sample rate, we only change to the requested + // sample rate if the device is not already in that rate. + + ASIOSampleRate oldRate; + asioError = ASIOGetSampleRate(&oldRate); + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOGetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + PA_DEBUG(("ASIOGetSampleRate:%f\n",oldRate)); + + if (oldRate != sampleRate){ + + PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate)); + asioError = ASIOSetSampleRate( sampleRate ); + /* Set sample rate */ + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOSetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + PA_DEBUG(("after ASIOSetSampleRate(%f)\n",sampleRate)); + } + else + { + PA_DEBUG(("No Need to change SR\n")); + } + + + /* + IMPLEMENT ME: + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + */ + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ){ + PA_DEBUG(("OpenStream invalid flags!!\n")); + return paInvalidFlag; /* unexpected platform specific flag */ + } + + + stream = (PaAsioStream*)PaUtil_AllocateMemory( sizeof(PaAsioStream) ); + if( !stream ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR5\n")); + goto error; + } + + stream->completedBuffersPlayedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( stream->completedBuffersPlayedEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("OpenStream ERROR6\n")); + goto error; + } + completedBuffersPlayedEventInited = 1; + + + stream->asioBufferInfos = 0; /* for deallocation in error */ + stream->asioChannelInfos = 0; /* for deallocation in error */ + stream->bufferPtrs = 0; /* for deallocation in error */ + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &asioHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &asioHostApi->blockingStreamInterface, streamCallback, userData ); + } + + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + stream->asioBufferInfos = (ASIOBufferInfo*)PaUtil_AllocateMemory( + sizeof(ASIOBufferInfo) * (inputChannelCount + outputChannelCount) ); + if( !stream->asioBufferInfos ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR7\n")); + goto error; + } + + + for( i=0; i < inputChannelCount; ++i ) + { + ASIOBufferInfo *info = &stream->asioBufferInfos[i]; + + info->isInput = ASIOTrue; + + if( inputChannelSelectors ){ + // inputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = inputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + + info->buffers[0] = info->buffers[1] = 0; + } + + for( i=0; i < outputChannelCount; ++i ){ + ASIOBufferInfo *info = &stream->asioBufferInfos[inputChannelCount+i]; + + info->isInput = ASIOFalse; + + if( outputChannelSelectors ){ + // outputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = outputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + + info->buffers[0] = info->buffers[1] = 0; + } + + + framesPerHostBuffer = SelectHostBufferSize( + (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) + ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), + driverInfo ); + + + PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); + + asioError = ASIOCreateBuffers( stream->asioBufferInfos, + inputChannelCount+outputChannelCount, + framesPerHostBuffer, &asioCallbacks_ ); + + if( asioError != ASE_OK + && framesPerHostBuffer != (unsigned long)driverInfo->bufferPreferredSize ) + { + PA_DEBUG(("ERROR: ASIOCreateBuffers: %s\n", PaAsio_GetAsioErrorText(asioError) )); + /* + Some buggy drivers (like the Hoontech DSP24) give incorrect + [min, preferred, max] values They should work with the preferred size + value, thus if Pa_ASIO_CreateBuffers fails with the hostBufferSize + computed in SelectHostBufferSize, we try again with the preferred size. + */ + + framesPerHostBuffer = driverInfo->bufferPreferredSize; + + PA_DEBUG(("PaAsioOpenStream: CORRECTED framesPerHostBuffer :%d\n", framesPerHostBuffer)); + + ASIOError asioError2 = ASIOCreateBuffers( stream->asioBufferInfos, + inputChannelCount+outputChannelCount, + framesPerHostBuffer, &asioCallbacks_ ); + if( asioError2 == ASE_OK ) + asioError = ASE_OK; + } + + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR9\n")); + goto error; + } + + asioBuffersCreated = 1; + + stream->asioChannelInfos = (ASIOChannelInfo*)PaUtil_AllocateMemory( + sizeof(ASIOChannelInfo) * (inputChannelCount + outputChannelCount) ); + if( !stream->asioChannelInfos ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR10\n")); + goto error; + } + + for( i=0; i < inputChannelCount + outputChannelCount; ++i ) + { + stream->asioChannelInfos[i].channel = stream->asioBufferInfos[i].channelNum; + stream->asioChannelInfos[i].isInput = stream->asioBufferInfos[i].isInput; + asioError = ASIOGetChannelInfo( &stream->asioChannelInfos[i] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR11\n")); + goto error; + } + } + + stream->bufferPtrs = (void**)PaUtil_AllocateMemory( + 2 * sizeof(void*) * (inputChannelCount + outputChannelCount) ); + if( !stream->bufferPtrs ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR12\n")); + goto error; + } + + if( inputChannelCount > 0 ) + { + stream->inputBufferPtrs[0] = stream-> bufferPtrs; + stream->inputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount]; + + for( i=0; iinputBufferPtrs[0][i] = stream->asioBufferInfos[i].buffers[0]; + stream->inputBufferPtrs[1][i] = stream->asioBufferInfos[i].buffers[1]; + } + } + else + { + stream->inputBufferPtrs[0] = 0; + stream->inputBufferPtrs[1] = 0; + } + + if( outputChannelCount > 0 ) + { + stream->outputBufferPtrs[0] = &stream->bufferPtrs[inputChannelCount*2]; + stream->outputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount*2 + outputChannelCount]; + + for( i=0; ioutputBufferPtrs[0][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[0]; + stream->outputBufferPtrs[1][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[1]; + } + } + else + { + stream->outputBufferPtrs[0] = 0; + stream->outputBufferPtrs[1] = 0; + } + + if( inputChannelCount > 0 ) + { + /* FIXME: assume all channels use the same type for now */ + ASIOSampleType inputType = stream->asioChannelInfos[0].type; + + PA_DEBUG(("ASIO Input type:%d",inputType)); + AsioSampleTypeLOG(inputType); + hostInputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( inputType ); + + SelectAsioToPaConverter( inputType, &stream->inputBufferConverter, &stream->inputShift ); + } + else + { + hostInputSampleFormat = 0; + stream->inputBufferConverter = 0; + } + + if( outputChannelCount > 0 ) + { + /* FIXME: assume all channels use the same type for now */ + ASIOSampleType outputType = stream->asioChannelInfos[inputChannelCount].type; + + PA_DEBUG(("ASIO Output type:%d",outputType)); + AsioSampleTypeLOG(outputType); + hostOutputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( outputType ); + + SelectPaToAsioConverter( outputType, &stream->outputBufferConverter, &stream->outputShift ); + } + else + { + hostOutputSampleFormat = 0; + stream->outputBufferConverter = 0; + } + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); + goto error; + } + + + ASIOGetLatencies( &stream->inputLatency, &stream->outputLatency ); + + stream->streamRepresentation.streamInfo.inputLatency = + (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) + + stream->inputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.outputLatency = + (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) + + stream->outputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + // the code below prints the ASIO latency which doesn't include the + // buffer processor latency. it reports the added latency separately + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->inputLatency, + (long)((stream->inputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + + PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->outputLatency, + (long)((stream->outputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + + stream->asioHostApi = asioHostApi; + stream->framesPerHostCallback = framesPerHostBuffer; + + stream->inputChannelCount = inputChannelCount; + stream->outputChannelCount = outputChannelCount; + stream->postOutput = driverInfo->postOutput; + stream->isActive = 0; + + asioHostApi->openAsioDeviceIndex = asioDeviceIndex; + + *s = (PaStream*)stream; + + return result; + +error: + PA_DEBUG(("goto errored\n")); + if( stream ) + { + if( completedBuffersPlayedEventInited ) + CloseHandle( stream->completedBuffersPlayedEvent ); + + if( stream->asioBufferInfos ) + PaUtil_FreeMemory( stream->asioBufferInfos ); + + if( stream->asioChannelInfos ) + PaUtil_FreeMemory( stream->asioChannelInfos ); + + if( stream->bufferPtrs ) + PaUtil_FreeMemory( stream->bufferPtrs ); + + PaUtil_FreeMemory( stream ); + } + + if( asioBuffersCreated ) + ASIODisposeBuffers(); + + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + + /* + IMPLEMENT ME: + - additional stream closing + cleanup + */ + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + stream->asioHostApi->openAsioDeviceIndex = paNoDevice; + + CloseHandle( stream->completedBuffersPlayedEvent ); + + PaUtil_FreeMemory( stream->asioBufferInfos ); + PaUtil_FreeMemory( stream->asioChannelInfos ); + PaUtil_FreeMemory( stream->bufferPtrs ); + PaUtil_FreeMemory( stream ); + + ASIODisposeBuffers(); + ASIOExit(); + + return result; +} + + +static void bufferSwitch(long index, ASIOBool directProcess) +{ +//TAKEN FROM THE ASIO SDK + + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that + // you take care about thread synchronization. This is omitted here for + // simplicity. + + // as this is a "back door" into the bufferSwitchTimeInfo a timeInfo needs + // to be created though it will only set the timeInfo.samplePosition and + // timeInfo.systemTime fields and the according flags + + ASIOTime timeInfo; + memset( &timeInfo, 0, sizeof (timeInfo) ); + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + if( ASIOGetSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK) + timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid; + + // Call the real callback + bufferSwitchTimeInfo( &timeInfo, index, directProcess ); +} + + +// conversion from 64 bit ASIOSample/ASIOTimeStamp to double float +#if NATIVE_INT64 + #define ASIO64toDouble(a) (a) +#else + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) +#endif + +static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool directProcess ) +{ + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that + // you take care about thread synchronization. + + + /* The SDK says the following about the directProcess flag: + suggests to the host whether it should immediately start processing + (directProcess == ASIOTrue), or whether its process should be deferred + because the call comes from a very low level (for instance, a high level + priority interrupt), and direct processing would cause timing instabilities for + the rest of the system. If in doubt, directProcess should be set to ASIOFalse. + + We just ignore directProcess. This could cause incompatibilities with + drivers which really don't want the audio processing to occur in this + callback, but none have been identified yet. + */ + + (void) directProcess; /* suppress unused parameter warning */ + +#if 0 + // store the timeInfo for later use + asioDriverInfo.tInfo = *timeInfo; + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + + if (timeInfo->timeInfo.flags & kSystemTimeValid) + asioDriverInfo.nanoSeconds = ASIO64toDouble(timeInfo->timeInfo.systemTime); + else + asioDriverInfo.nanoSeconds = 0; + + if (timeInfo->timeInfo.flags & kSamplePositionValid) + asioDriverInfo.samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); + else + asioDriverInfo.samples = 0; + + if (timeInfo->timeCode.flags & kTcValid) + asioDriverInfo.tcSamples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); + else + asioDriverInfo.tcSamples = 0; + + // get the system reference time + asioDriverInfo.sysRefTime = get_sys_reference_time(); +#endif + +#if 0 + // a few debug messages for the Windows device driver developer + // tells you the time when driver got its interrupt and the delay until the app receives + // the event notification. + static double last_samples = 0; + char tmp[128]; + sprintf (tmp, "diff: %d / %d ms / %d ms / %d samples \n", asioDriverInfo.sysRefTime - (long)(asioDriverInfo.nanoSeconds / 1000000.0), asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)(asioDriverInfo.samples - last_samples)); + OutputDebugString (tmp); + last_samples = asioDriverInfo.samples; +#endif + + + if( !theAsioStream ) + return 0L; + + // Keep sample position + // FIXME: asioDriverInfo.pahsc_NumFramesDone = timeInfo->timeInfo.samplePosition.lo; + + + // protect against reentrancy + if( PaAsio_AtomicIncrement(&theAsioStream->reenterCount) ) + { + theAsioStream->reenterError++; + //DBUG(("bufferSwitchTimeInfo : reentrancy detection = %d\n", asioDriverInfo.reenterError)); + return 0L; + } + + int buffersDone = 0; + + do + { + if( buffersDone > 0 ) + { + // this is a reentered buffer, we missed processing it on time + // set the input overflow and output underflow flags as appropriate + + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; + } + else + { + if( theAsioStream->zeroOutput ) + { + ZeroOutputBuffers( theAsioStream, index ); + + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place + if( theAsioStream->postOutput ) + ASIOOutputReady(); + + if( theAsioStream->stopProcessing ) + { + if( theAsioStream->stopPlayoutCount < 2 ) + { + ++theAsioStream->stopPlayoutCount; + if( theAsioStream->stopPlayoutCount == 2 ) + { + theAsioStream->isActive = 0; + if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 ) + theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData ); + theAsioStream->streamFinishedCallbackCalled = true; + SetEvent( theAsioStream->completedBuffersPlayedEvent ); + } + } + } + } + else + { + +#if 0 +// test code to try to detect slip conditions... these may work on some systems +// but neither of them work on the RME Digi96 + +// check that sample delta matches buffer size (otherwise we must have skipped +// a buffer. +static double last_samples = -512; +double samples; +//if( timeInfo->timeCode.flags & kTcValid ) +// samples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); +//else + samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); +int delta = samples - last_samples; +//printf( "%d\n", delta); +last_samples = samples; + +if( delta > theAsioStream->framesPerHostCallback ) +{ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; +} + +// check that the buffer index is not the previous index (which would indicate +// that a buffer was skipped. +static int previousIndex = 1; +if( index == previousIndex ) +{ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; +} +previousIndex = index; +#endif + + int i; + + PaUtil_BeginCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer ); + + PaStreamCallbackTimeInfo paTimeInfo; + + // asio systemTime is supposed to be measured according to the same + // clock as timeGetTime + paTimeInfo.currentTime = (ASIO64toDouble( timeInfo->timeInfo.systemTime ) * .000000001); + + /* patch from Paul Boege */ + paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - + ((double)theAsioStream->inputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + + ((double)theAsioStream->outputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + /* old version is buggy because the buffer processor also adds in its latency to the time parameters + paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency; + paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency; + */ +#if 1 +// detect underflows by checking inter-callback time > 2 buffer period +static double previousTime = -1; +if( previousTime > 0 ){ + + double delta = paTimeInfo.currentTime - previousTime; + + if( delta >= 2. * (theAsioStream->framesPerHostCallback / theAsioStream->streamRepresentation.streamInfo.sampleRate) ){ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; + } +} +previousTime = paTimeInfo.currentTime; +#endif + + // note that the above input and output times do not need to be + // adjusted for the latency of the buffer processor -- the buffer + // processor handles that. + + if( theAsioStream->inputBufferConverter ) + { + for( i=0; iinputChannelCount; i++ ) + { + theAsioStream->inputBufferConverter( theAsioStream->inputBufferPtrs[index][i], + theAsioStream->inputShift, theAsioStream->framesPerHostCallback ); + } + } + + PaUtil_BeginBufferProcessing( &theAsioStream->bufferProcessor, &paTimeInfo, theAsioStream->callbackFlags ); + + /* reset status flags once they've been passed to the callback */ + theAsioStream->callbackFlags = 0; + + PaUtil_SetInputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ ); + for( i=0; iinputChannelCount; ++i ) + PaUtil_SetNonInterleavedInputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->inputBufferPtrs[index][i] ); + + PaUtil_SetOutputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ ); + for( i=0; ioutputChannelCount; ++i ) + PaUtil_SetNonInterleavedOutputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->outputBufferPtrs[index][i] ); + + int callbackResult; + if( theAsioStream->stopProcessing ) + callbackResult = paComplete; + else + callbackResult = paContinue; + unsigned long framesProcessed = PaUtil_EndBufferProcessing( &theAsioStream->bufferProcessor, &callbackResult ); + + if( theAsioStream->outputBufferConverter ) + { + for( i=0; ioutputChannelCount; i++ ) + { + theAsioStream->outputBufferConverter( theAsioStream->outputBufferPtrs[index][i], + theAsioStream->outputShift, theAsioStream->framesPerHostCallback ); + } + } + + PaUtil_EndCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer, framesProcessed ); + + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place + if( theAsioStream->postOutput ) + ASIOOutputReady(); + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + /* finish playback immediately */ + theAsioStream->isActive = 0; + if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 ) + theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData ); + theAsioStream->streamFinishedCallbackCalled = true; + SetEvent( theAsioStream->completedBuffersPlayedEvent ); + theAsioStream->zeroOutput = true; + } + else /* paComplete or other non-zero value indicating complete */ + { + /* Finish playback once currently queued audio has completed. */ + theAsioStream->stopProcessing = true; + + if( PaUtil_IsBufferProcessorOutputEmpty( &theAsioStream->bufferProcessor ) ) + { + theAsioStream->zeroOutput = true; + theAsioStream->stopPlayoutCount = 0; + } + } + } + } + + ++buffersDone; + }while( PaAsio_AtomicDecrement(&theAsioStream->reenterCount) >= 0 ); + + return 0L; +} + + +static void sampleRateChanged(ASIOSampleRate sRate) +{ + // TAKEN FROM THE ASIO SDK + // do whatever you need to do if the sample rate changed + // usually this only happens during external sync. + // Audio processing is not stopped by the driver, actual sample rate + // might not have even changed, maybe only the sample rate status of an + // AES/EBU or S/PDIF digital input at the audio device. + // You might have to update time/sample related conversion routines, etc. + + (void) sRate; /* unused parameter */ + PA_DEBUG( ("sampleRateChanged : %d \n", sRate)); +} + +static long asioMessages(long selector, long value, void* message, double* opt) +{ +// TAKEN FROM THE ASIO SDK + // currently the parameters "value", "message" and "opt" are not used. + long ret = 0; + + (void) message; /* unused parameters */ + (void) opt; + + PA_DEBUG( ("asioMessages : %d , %d \n", selector, value)); + + switch(selector) + { + case kAsioSelectorSupported: + if(value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // the following three were added for ASIO 2.0, you don't necessarily have to support them + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + + case kAsioBufferSizeChange: + //printf("kAsioBufferSizeChange \n"); + break; + + case kAsioResetRequest: + // defer the task and perform the reset of the driver during the next "safe" situation + // You cannot reset the driver right now, as this code is called from the driver. + // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction + // Afterwards you initialize the driver again. + + /*FIXME: commented the next line out */ + //asioDriverInfo.stopped; // In this sample the processing will just stop + ret = 1L; + break; + + case kAsioResyncRequest: + // This informs the application, that the driver encountered some non fatal data loss. + // It is used for synchronization purposes of different media. + // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the + // Windows Multimedia system, which could loose data because the Mutex was hold too long + // by another thread. + // However a driver can issue it in other situations, too. + ret = 1L; + break; + + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were latencies changed. + // Beware, it this does not mean that the buffer sizes have changed! + // You might need to update internal delay data. + ret = 1L; + //printf("kAsioLatenciesChanged \n"); + break; + + case kAsioEngineVersion: + // return the supported ASIO version of the host application + // If a host applications does not implement this selector, ASIO 1.0 is assumed + // by the driver + ret = 2L; + break; + + case kAsioSupportsTimeInfo: + // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback + // is supported. + // For compatibility with ASIO 1.0 drivers the host application should always support + // the "old" bufferSwitch method, too. + ret = 1; + break; + + case kAsioSupportsTimeCode: + // informs the driver wether application is interested in time code info. + // If an application does not need to know about time code, the driver has less work + // to do. + ret = 0; + break; + } + return ret; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + if( stream->outputChannelCount > 0 ) + { + ZeroOutputBuffers( stream, 0 ); + ZeroOutputBuffers( stream, 1 ); + } + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + stream->stopProcessing = false; + stream->zeroOutput = false; + + /* Reentrancy counter initialisation */ + stream->reenterCount = -1; + stream->reenterError = 0; + + stream->callbackFlags = 0; + + if( ResetEvent( stream->completedBuffersPlayedEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + if( result == paNoError ) + { + theAsioStream = stream; + asioError = ASIOStart(); + if( asioError == ASE_OK ) + { + stream->isActive = 1; + stream->streamFinishedCallbackCalled = false; + } + else + { + theAsioStream = 0; + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + } + + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + if( stream->isActive ) + { + stream->stopProcessing = true; + + /* wait for the stream to finish playing out enqueued buffers. + timeout after four times the stream latency. + + @todo should use a better time out value - if the user buffer + length is longer than the asio buffer size then that should + be taken into account. + */ + if( WaitForSingleObject( theAsioStream->completedBuffersPlayedEvent, + (DWORD)(stream->streamRepresentation.streamInfo.outputLatency * 1000. * 4.) ) + == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n" )); + } + } + + asioError = ASIOStop(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + + theAsioStream = 0; + stream->isActive = 0; + + if( !stream->streamFinishedCallbackCalled ) + { + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + stream->zeroOutput = true; + + asioError = ASIOStop(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + else + { + // make sure that the callback is not still in-flight when ASIOStop() + // returns. This has been observed to happen on the Hoontech DSP24 for + // example. + int count = 2000; // only wait for 2 seconds, rather than hanging. + while( theAsioStream->reenterCount != -1 && count > 0 ) + { + Sleep(1); + --count; + } + } + + /* it is questionable whether we should zero theAsioStream if ASIOStop() + returns an error, because the callback could still be active. We assume + not - this is based on the fact that ASIOStop is unlikely to fail + if the callback is running - it's more likely to fail because the + callback is not running. */ + + theAsioStream = 0; + stream->isActive = 0; + + if( !stream->streamFinishedCallbackCalled ) + { + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + //PaAsioStream *stream = (PaAsioStream*)s; + (void) s; /* unused parameter */ + return theAsioStream == 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + (void) s; /* unused parameter */ + return (double)timeGetTime() * .001; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + +PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + ASIODriverInfo asioDriverInfo; + ASIOError asioError; + int asioIsInitialized = 0; + PaAsioHostApiRepresentation *asioHostApi; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + /* + In theory we could proceed if the currently open device was the same + one for which the control panel was requested, however because the + window pointer is not available until this function is called we + currently need to call ASIOInit() again here, which of course can't be + done safely while a stream is open. + */ + + asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + if( asioHostApi->openAsioDeviceIndex != paNoDevice ) + { + result = paDeviceUnavailable; + goto error; + } + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( !loadAsioDriver( const_cast(asioDeviceInfo->commonDeviceInfo.name) ) ) + { + result = paUnanticipatedHostError; + goto error; + } + + /* CRUCIAL!!! */ + memset( &asioDriverInfo, 0, sizeof(ASIODriverInfo) ); + asioDriverInfo.asioVersion = 2; + asioDriverInfo.sysRef = systemSpecific; + asioError = ASIOInit( &asioDriverInfo ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + else + { + asioIsInitialized = 1; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOInit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); +PA_DEBUG(("asioVersion: ASIOInit(): %ld\n", asioDriverInfo.asioVersion )); +PA_DEBUG(("driverVersion: ASIOInit(): %ld\n", asioDriverInfo.driverVersion )); +PA_DEBUG(("Name: ASIOInit(): %s\n", asioDriverInfo.name )); +PA_DEBUG(("ErrorMessage: ASIOInit(): %s\n", asioDriverInfo.errorMessage )); + + asioError = ASIOControlPanel(); + if( asioError != ASE_OK ) + { + PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + + asioError = ASIOExit(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + asioIsInitialized = 0; + goto error; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + + return result; + +error: + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxInputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[channelIndex].name; + + return paNoError; + +error: + return result; +} + + +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxOutputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[ + asioDeviceInfo->commonDeviceInfo.maxInputChannels + channelIndex].name; + + return paNoError; + +error: + return result; +} diff --git a/portaudio-v19/pa_asio/pa_asio.h b/portaudio-v19/pa_asio/pa_asio.h new file mode 100644 index 000000000..573930572 --- /dev/null +++ b/portaudio-v19/pa_asio/pa_asio.h @@ -0,0 +1,122 @@ +#ifndef PA_ASIO_H +#define PA_ASIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * ASIO specific extensions + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** @file + @brief ASIO-specific PortAudio API extension header file. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve legal latency settings for the specificed device, in samples. + + @param device The global index of the device about which the query is being made. + @param minLatency A pointer to the location which will recieve the minimum latency value. + @param maxLatency A pointer to the location which will recieve the maximum latency value. + @param preferredLatency A pointer to the location which will recieve the preferred latency value. + @param granularity A pointer to the location which will recieve the granularity. This value + determines which values between minLatency and maxLatency are available. ie the step size, + if granularity is -1 then available latency settings are powers of two. + + @see ASIOGetBufferSize in the ASIO SDK. + + @todo This function should have a better name, any suggestions? +*/ +PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ); + + +/** Display the ASIO control panel for the specified device. + + @param device The global index of the device whose control panel is to be displayed. + @param systemSpecific On Windows, the calling application's main window handle, + on Macintosh this value should be zero. +*/ +PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ); + + + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +#define paAsioUseChannelSelectors (0x01) + +typedef struct PaAsioStreamInfo{ + unsigned long size; /**< sizeof(PaAsioStreamInfo) */ + PaHostApiTypeId hostApiType; /**< paASIO */ + unsigned long version; /**< 1 */ + + unsigned long flags; + + /* Support for opening only specific channels of an ASIO device. + If the paAsioUseChannelSelectors flag is set, channelSelectors is a + pointer to an array of integers specifying the device channels to use. + When used, the length of the channelSelectors array must match the + corresponding channelCount parameter to Pa_OpenStream() otherwise a + crash may result. + The values in the selectors array must specify channels within the + range of supported channels for the device or paInvalidChannelCount will + result. + */ + int *channelSelectors; +}PaAsioStreamInfo; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_ASIO_H */ diff --git a/portaudio-v19/pa_beos/PlaybackNode.cc b/portaudio-v19/pa_beos/PlaybackNode.cc new file mode 100644 index 000000000..a93b84120 --- /dev/null +++ b/portaudio-v19/pa_beos/PlaybackNode.cc @@ -0,0 +1,538 @@ +/* + * $Id: PlaybackNode.cc,v 1.1.1.1 2002/01/22 00:52:07 phil Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * --- + * + * Significant portions of this file are based on sample code from Be. The + * Be Sample Code Licence follows: + * + * Copyright 1991-1999, Be Incorporated. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + + +PaPlaybackNode::PaPlaybackNode(uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback* callback, void *user_data) : + BMediaNode("PortAudio input node"), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BMediaEventLooper(), + mAborted(false), + mRunning(false), + mBufferGroup(NULL), + mDownstreamLatency(0), + mStartTime(0), + mCallback(callback), + mUserData(user_data), + mFramesPerBuffer(frames_per_buffer) +{ + DBUG(("Constructor called.\n")); + + mPreferredFormat.type = B_MEDIA_RAW_AUDIO; + mPreferredFormat.u.raw_audio.channel_count = channels; + mPreferredFormat.u.raw_audio.frame_rate = frame_rate; + mPreferredFormat.u.raw_audio.byte_order = + (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + mPreferredFormat.u.raw_audio.buffer_size = + media_raw_audio_format::wildcard.buffer_size; + + mOutput.destination = media_destination::null; + mOutput.format = mPreferredFormat; + + /* The amount of time it takes for this node to produce a buffer when + * asked. Essentially, it is how long the user's callback takes to run. + * We set this to be the length of the sound data each buffer of the + * requested size can hold. */ + //mInternalLatency = (bigtime_t)(1000000 * frames_per_buffer / frame_rate); + + /* ACK! it seems that the mixer (at least on my machine) demands that IT + * specify the buffer size, so for now I'll just make a generic guess here */ + mInternalLatency = 1000000 / 20; +} + + + +PaPlaybackNode::~PaPlaybackNode() +{ + DBUG(("Destructor called.\n")); + Quit(); /* Stop the BMediaEventLooper thread */ +} + + +/************************* + * + * Local methods + * + */ + +bool PaPlaybackNode::IsRunning() +{ + return mRunning; +} + + +PaTimestamp PaPlaybackNode::GetStreamTime() +{ + BTimeSource *timeSource = TimeSource(); + PaTimestamp time = (timeSource->Now() - mStartTime) * + mPreferredFormat.u.raw_audio.frame_rate / 1000000; + return time; +} + + +void PaPlaybackNode::SetSampleFormat(PaSampleFormat inFormat, + PaSampleFormat outFormat) +{ + uint32 beOutFormat; + + switch(outFormat) + { + case paFloat32: + beOutFormat = media_raw_audio_format::B_AUDIO_FLOAT; + mOutputSampleWidth = 4; + break; + + case paInt16: + beOutFormat = media_raw_audio_format::B_AUDIO_SHORT; + mOutputSampleWidth = 2; + break; + + case paInt32: + beOutFormat = media_raw_audio_format::B_AUDIO_INT; + mOutputSampleWidth = 4; + break; + + case paInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_CHAR; + mOutputSampleWidth = 1; + break; + + case paUInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_UCHAR; + mOutputSampleWidth = 1; + break; + + case paInt24: + case paPackedInt24: + case paCustomFormat: + DBUG(("Unsupported output format: %x\n", outFormat)); + break; + + default: + DBUG(("Unknown output format: %x\n", outFormat)); + } + + mPreferredFormat.u.raw_audio.format = beOutFormat; + mFramesPerBuffer * mPreferredFormat.u.raw_audio.channel_count * mOutputSampleWidth; +} + +BBuffer *PaPlaybackNode::FillNextBuffer(bigtime_t time) +{ + /* Get a buffer from the buffer group */ + BBuffer *buf = mBufferGroup->RequestBuffer( + mOutput.format.u.raw_audio.buffer_size, BufferDuration()); + unsigned long frames = mOutput.format.u.raw_audio.buffer_size / + mOutputSampleWidth / mOutput.format.u.raw_audio.channel_count; + bigtime_t start_time; + int ret; + + if( !buf ) + { + DBUG(("Unable to allocate a buffer\n")); + return NULL; + } + + start_time = mStartTime + + (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.frame_rate / + (double)mOutput.format.u.raw_audio.channel_count * + 1000000.0); + + /* Now call the user callback to get the data */ + ret = mCallback(NULL, /* Input buffer */ + buf->Data(), /* Output buffer */ + frames, /* Frames per buffer */ + mSamplesSent / mOutput.format.u.raw_audio.channel_count, /* timestamp */ + mUserData); + + if( ret ) + mAborted = true; + + media_header *hdr = buf->Header(); + + hdr->type = B_MEDIA_RAW_AUDIO; + hdr->size_used = mOutput.format.u.raw_audio.buffer_size; + hdr->time_source = TimeSource()->ID(); + hdr->start_time = start_time; + + return buf; +} + + + + +/************************* + * + * BMediaNode methods + * + */ + +BMediaAddOn *PaPlaybackNode::AddOn( int32 * ) const +{ + DBUG(("AddOn() called.\n")); + return NULL; /* we don't provide service to outside applications */ +} + + +status_t PaPlaybackNode::HandleMessage( int32 message, const void *data, + size_t size ) +{ + DBUG(("HandleMessage() called.\n")); + return B_ERROR; /* we don't define any custom messages */ +} + + + + +/************************* + * + * BMediaEventLooper methods + * + */ + +void PaPlaybackNode::NodeRegistered() +{ + DBUG(("NodeRegistered() called.\n")); + + /* Start the BMediaEventLooper thread */ + SetPriority(B_REAL_TIME_PRIORITY); + Run(); + + /* set up as much information about our output as we can */ + mOutput.source.port = ControlPort(); + mOutput.source.id = 0; + mOutput.node = Node(); + ::strcpy(mOutput.name, "PortAudio Playback"); +} + + +void PaPlaybackNode::HandleEvent( const media_timed_event *event, + bigtime_t lateness, bool realTimeEvent ) +{ + // DBUG(("HandleEvent() called.\n")); + status_t err; + + switch(event->type) + { + case BTimedEventQueue::B_START: + DBUG((" Handling a B_START event\n")); + if( RunState() != B_STARTED ) + { + mStartTime = event->event_time + EventLatency(); + mSamplesSent = 0; + mAborted = false; + mRunning = true; + media_timed_event firstEvent( mStartTime, + BTimedEventQueue::B_HANDLE_BUFFER ); + EventQueue()->AddEvent( firstEvent ); + } + break; + + case BTimedEventQueue::B_STOP: + DBUG((" Handling a B_STOP event\n")); + mRunning = false; + EventQueue()->FlushEvents( 0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER ); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: + //DBUG((" Handling a B_HANDLE_BUFFER event\n")); + + /* make sure we're started and connected */ + if( RunState() != BMediaEventLooper::B_STARTED || + mOutput.destination == media_destination::null ) + break; + + BBuffer *buffer = FillNextBuffer(event->event_time); + + /* make sure we weren't aborted while this routine was running. + * this can happen in one of two ways: either the callback returned + * nonzero (in which case mAborted is set in FillNextBuffer() ) or + * the client called AbortStream */ + if( mAborted ) + { + if( buffer ) + buffer->Recycle(); + Stop(0, true); + break; + } + + if( buffer ) + { + err = SendBuffer(buffer, mOutput.destination); + if( err != B_OK ) + buffer->Recycle(); + } + + mSamplesSent += mOutput.format.u.raw_audio.buffer_size / mOutputSampleWidth; + + /* Now schedule the next buffer event, so we can send another + * buffer when this one runs out. We calculate when it should + * happen by calculating when the data we just sent will finish + * playing. + * + * NOTE, however, that the event will actually get generated + * earlier than we specify, to account for the latency it will + * take to produce the buffer. It uses the latency value we + * specified in SetEventLatency() to determine just how early + * to generate it. */ + + /* totalPerformanceTime includes the time represented by the buffer + * we just sent */ + bigtime_t totalPerformanceTime = (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.channel_count / + (double)mOutput.format.u.raw_audio.frame_rate * 1000000.0); + + bigtime_t nextEventTime = mStartTime + totalPerformanceTime; + + media_timed_event nextBufferEvent(nextEventTime, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + + break; + + } +} + + + + +/************************* + * + * BBufferProducer methods + * + */ + +status_t PaPlaybackNode::FormatSuggestionRequested( media_type type, + int32 /*quality*/, media_format* format ) +{ + /* the caller wants to know this node's preferred format and provides + * a suggestion, asking if we support it */ + DBUG(("FormatSuggestionRequested() called.\n")); + + if(!format) + return B_BAD_VALUE; + + *format = mPreferredFormat; + + /* we only support raw audio (a wildcard is okay too) */ + if ( type == B_MEDIA_UNKNOWN_TYPE || type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatProposal( const media_source& output, + media_format* format ) +{ + /* This is similar to FormatSuggestionRequested(), but it is actually part + * of the negotiation process. We're given the opportunity to specify any + * properties that are wildcards (ie. properties that the other node doesn't + * care one way or another about) */ + DBUG(("FormatProposal() called.\n")); + + /* Make sure this proposal really applies to our output */ + if( output != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* We return two things: whether we support the proposed format, and our own + * preferred format */ + *format = mPreferredFormat; + + if( format->type == B_MEDIA_UNKNOWN_TYPE || format->type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ) +{ + /* we refuse to change formats, supporting only 1 */ + DBUG(("FormatChangeRequested() called.\n")); + + return B_ERROR; +} + + +status_t PaPlaybackNode::GetNextOutput( int32* cookie, media_output* out_output ) +{ + /* this is where we allow other to enumerate our outputs -- the cookie is + * an integer we can use to keep track of where we are in enumeration. */ + DBUG(("GetNextOutput() called.\n")); + + if( *cookie == 0 ) + { + *out_output = mOutput; + *cookie = 1; + return B_OK; + } + + return B_BAD_INDEX; +} + + +status_t PaPlaybackNode::DisposeOutputCookie( int32 cookie ) +{ + DBUG(("DisposeOutputCookie() called.\n")); + return B_OK; +} + + +void PaPlaybackNode::LateNoticeReceived( const media_source& what, + bigtime_t how_much, bigtime_t performance_time ) +{ + /* This function is called as notification that a buffer we sent wasn't + * received by the time we stamped it with -- it got there late. Basically, + * it means we underestimated our own latency, so we should increase it */ + DBUG(("LateNoticeReceived() called.\n")); + + if( what != mOutput.source ) + return; + + if( RunMode() == B_INCREASE_LATENCY ) + { + mInternalLatency += how_much; + SetEventLatency( mDownstreamLatency + mInternalLatency ); + DBUG(("Increasing latency to %Ld\n", mDownstreamLatency + mInternalLatency)); + } + else + DBUG(("I don't know what to do with this notice!")); +} + + +void PaPlaybackNode::EnableOutput( const media_source& what, bool enabled, + int32* ) +{ + DBUG(("EnableOutput() called.\n")); + /* stub -- we don't support this yet */ +} + + +status_t PaPlaybackNode::PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ) +{ + /* the final stage of format negotiations. here we _must_ make specific any + * remaining wildcards */ + DBUG(("PrepareToConnect() called.\n")); + + /* make sure this really refers to our source */ + if( what != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* make sure we're not already connected */ + if( mOutput.destination != media_destination::null ) + return B_MEDIA_ALREADY_CONNECTED; + + if( format->type != B_MEDIA_RAW_AUDIO ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.format != mPreferredFormat.u.raw_audio.format ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.buffer_size == + media_raw_audio_format::wildcard.buffer_size ) + { + DBUG(("We were left to decide buffer size: choosing 2048")); + format->u.raw_audio.buffer_size = 2048; + } + else + DBUG(("Using consumer specified buffer size of %lu.\n", + format->u.raw_audio.buffer_size)); + + /* Reserve the connection, return the information */ + mOutput.destination = where; + mOutput.format = *format; + *out_source = mOutput.source; + strncpy( out_name, mOutput.name, B_MEDIA_NAME_LENGTH ); + + return B_OK; +} + + +void PaPlaybackNode::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, char* io_name) +{ + DBUG(("Connect() called.\n")); + diff --git a/portaudio-v19/pa_beos/PlaybackNode.h b/portaudio-v19/pa_beos/PlaybackNode.h new file mode 100644 index 000000000..64ee5b3bc --- /dev/null +++ b/portaudio-v19/pa_beos/PlaybackNode.h @@ -0,0 +1,108 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "portaudio.h" + +class PaPlaybackNode : + public BBufferProducer, + public BMediaEventLooper +{ + +public: + PaPlaybackNode( uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback *callback, void *user_data ); + ~PaPlaybackNode(); + + + /* Local methods ******************************************/ + + BBuffer *FillNextBuffer(bigtime_t time); + void SetSampleFormat(PaSampleFormat inFormat, PaSampleFormat outFormat); + bool IsRunning(); + PaTimestamp GetStreamTime(); + + /* BMediaNode methods *************************************/ + + BMediaAddOn* AddOn( int32 * ) const; + status_t HandleMessage( int32 message, const void *data, size_t size ); + + /* BMediaEventLooper methods ******************************/ + + void HandleEvent( const media_timed_event *event, bigtime_t lateness, + bool realTimeEvent ); + void NodeRegistered(); + + /* BBufferProducer methods ********************************/ + + status_t FormatSuggestionRequested( media_type type, int32 quality, + media_format* format ); + status_t FormatProposal( const media_source& output, media_format* format ); + status_t FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ); + + status_t GetNextOutput( int32* cookie, media_output* out_output ); + status_t DisposeOutputCookie( int32 cookie ); + + void LateNoticeReceived( const media_source& what, bigtime_t how_much, + bigtime_t performance_time ); + void EnableOutput( const media_source& what, bool enabled, int32* _deprecated_ ); + + status_t PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ); + void Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, + char* io_name); + void Disconnect(const media_source& what, const media_destination& where); + + status_t SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup); + + bool mAborted; + +private: + media_output mOutput; + media_format mPreferredFormat; + uint32 mOutputSampleWidth, mFramesPerBuffer; + BBufferGroup *mBufferGroup; + bigtime_t mDownstreamLatency, mInternalLatency, mStartTime; + uint64 mSamplesSent; + PortAudioCallback *mCallback; + void *mUserData; + bool mRunning; + +}; + diff --git a/portaudio-v19/pa_beos/pa_beos_mk.cc b/portaudio-v19/pa_beos/pa_beos_mk.cc new file mode 100644 index 000000000..ff35cdc34 --- /dev/null +++ b/portaudio-v19/pa_beos/pa_beos_mk.cc @@ -0,0 +1,441 @@ +/* + * $Id: pa_beos_mk.cc,v 1.1.1.1 2002/01/22 00:52:09 phil Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "portaudio.h" +#include "pa_host.h" + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + +typedef struct PaHostSoundControl +{ + /* These members are common to all modes of operation */ + media_node pahsc_TimeSource; /* the sound card's DAC. */ + media_format pahsc_Format; + + /* These methods are specific to playing mode */ + media_node pahsc_OutputNode; /* output to the mixer */ + media_node pahsc_InputNode; /* reads data from user callback -- PA specific */ + + media_input pahsc_MixerInput; /* input jack on the soundcard's mixer. */ + media_output pahsc_PaOutput; /* output jack from the PA node */ + + PaPlaybackNode *pahsc_InputNodeInstance; + +} +PaHostSoundControl; + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + /* stub */ + return NULL; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + /* stub */ + return 1; +} + +/*************************************************************************/ +PaError PaHost_Init( void ) +{ + /* we have to create this in order to use BMediaRoster. I hope it doesn't + * cause problems */ + be_app = new BApplication("application/x-vnd.portaudio-app"); + + return paNoError; +} + +PaError PaHost_Term( void ) +{ + delete be_app; + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + DBUG(("IsRunning returning: %s\n", + pahsc->pahsc_InputNodeInstance->IsRunning() ? "true" : "false")); + + return (PaError)pahsc->pahsc_InputNodeInstance->IsRunning(); +} + +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + bigtime_t very_soon, start_latency; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + /* for some reason, err indicates an error (though nothing it wrong) + * when the DBUG macro in pa_lib.c is enabled. It's reproducably + * linked. Weird. */ + if( !roster /* || err != B_OK */ ) + { + DBUG(("No media server! err=%d, roster=%x\n", err, roster)); + return paHostError; + } + + /* tell the node when to start -- since there aren't any other nodes + * starting that we have to wait for, just tell it to start now + */ + + BTimeSource *timeSource = roster->MakeTimeSourceFor(pahsc->pahsc_TimeSource); + very_soon = timeSource->PerformanceTimeFor( BTimeSource::RealTime() ); + timeSource->Release(); + + /* Add the latency of starting the network of nodes */ + err = roster->GetStartLatencyFor( pahsc->pahsc_TimeSource, &start_latency ); + very_soon += start_latency; + + err = roster->StartNode( pahsc->pahsc_InputNode, very_soon ); + /* No need to start the mixer -- it's always running */ + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + BMediaRoster *roster = BMediaRoster::Roster(); + + if( !roster ) + { + DBUG(("No media roster!\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* this crashes, and I don't know why yet */ + // if( abort ) + // pahsc->pahsc_InputNodeInstance->mAborted = true; + + roster->StopNode(pahsc->pahsc_InputNode, 0, /* immediate = */ true); + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc; + + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + + if( !roster /* || err != B_OK */ ) + { + /* no media server! */ + DBUG(("No media server.\n")); + goto error; + } + + if ( past->past_NumInputChannels > 0 && past->past_NumOutputChannels > 0 ) + { + /* filter -- not implemented yet */ + goto error; + } + else if ( past->past_NumInputChannels > 0 ) + { + /* recorder -- not implemented yet */ + goto error; + } + else + { + /* player ****************************************************************/ + + status_t err; + int32 num; + + /* First we need to create the three components (like components in a stereo + * system). The mixer component is our interface to the sound card, data + * we write there will get played. The BePA_InputNode component is the node + * which represents communication with the PA client (it is what calls the + * client's callbacks). The time source component is the sound card's DAC, + * which allows us to slave the other components to it instead of the system + * clock. */ + err = roster->GetAudioMixer( &pahsc->pahsc_OutputNode ); + if( err != B_OK ) + { + DBUG(("Couldn't get default mixer.\n")); + goto error; + } + + err = roster->GetTimeSource( &pahsc->pahsc_TimeSource ); + if( err != B_OK ) + { + DBUG(("Couldn't get time source.\n")); + goto error; + } + + pahsc->pahsc_InputNodeInstance = new PaPlaybackNode(2, 44100, + past->past_FramesPerUserBuffer, past->past_Callback, past->past_UserData ); + pahsc->pahsc_InputNodeInstance->SetSampleFormat(0, + past->past_OutputSampleFormat); + err = roster->RegisterNode( pahsc->pahsc_InputNodeInstance ); + if( err != B_OK ) + { + DBUG(("Unable to register node.\n")); + goto error; + } + + roster->GetNodeFor( pahsc->pahsc_InputNodeInstance->Node().node, + &pahsc->pahsc_InputNode ); + if( err != B_OK ) + { + DBUG(("Unable to get input node.\n")); + goto error; + } + + /* Now we have three components (nodes) sitting next to each other. The + * next step is to look at them and find their inputs and outputs so we can + * wire them together. */ + err = roster->GetFreeInputsFor( pahsc->pahsc_OutputNode, + &pahsc->pahsc_MixerInput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get the mixer input.\n")); + goto error; + } + + err = roster->GetFreeOutputsFor( pahsc->pahsc_InputNode, + &pahsc->pahsc_PaOutput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get PortAudio output.\n")); + goto error; + } + + + /* We've found the input and output -- the final step is to run a wire + * between them so they are connected. */ + + /* try to make the mixer input adapt to what PA sends it */ + pahsc->pahsc_Format = pahsc->pahsc_PaOutput.format; + roster->Connect( pahsc->pahsc_PaOutput.source, + pahsc->pahsc_MixerInput.destination, &pahsc->pahsc_Format, + &pahsc->pahsc_PaOutput, &pahsc->pahsc_MixerInput ); + + + /* Actually, there's one final step -- tell them all to sync to the + * sound card's DAC */ + roster->SetTimeSourceFor( pahsc->pahsc_InputNode.node, + pahsc->pahsc_TimeSource.node ); + roster->SetTimeSourceFor( pahsc->pahsc_OutputNode.node, + pahsc->pahsc_TimeSource.node ); + + } + + return paNoError; + +error: + PaHost_CloseStream( past ); + return paHostError; +} + +/*************************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + + if( !roster ) + { + DBUG(("Couldn't get media roster\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* Disconnect all the connections we made when opening the stream */ + + roster->Disconnect(pahsc->pahsc_InputNode.node, pahsc->pahsc_PaOutput.source, + pahsc->pahsc_OutputNode.node, pahsc->pahsc_MixerInput.destination); + + DBUG(("Calling ReleaseNode()")); + roster->ReleaseNode(pahsc->pahsc_InputNode); + + /* deleting the node shouldn't be necessary -- it is reference counted, and will + * delete itself when its references drop to zero. the call to ReleaseNode() + * above should decrease its reference count */ + pahsc->pahsc_InputNodeInstance = NULL; + + return paNoError; +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + return pahsc->pahsc_InputNodeInstance->GetStreamTime(); +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + /* snooze() takes microseconds */ + snooze( msec * 1000 ); +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + * Memory will be set to zero. + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + /* BeOS supports non-pagable memory through pools -- a pool is an area + * of physical memory that is locked. It would be best to pre-allocate + * that pool and then hand out memory from it, but we don't know in + * advance how much we'll need. So for now, we'll allocate a pool + * for every request we get, storing a pointer to the pool at the + * beginning of the allocated memory */ + rtm_pool *pool; + void *addr; + long size = numBytes + sizeof(rtm_pool *); + static int counter = 0; + char pool_name[100]; + + /* Every pool needs a unique name. */ + sprintf(pool_name, "PaPoolNumber%d", counter++); + + if( rtm_create_pool( &pool, size, pool_name ) != B_OK ) + return 0; + + addr = rtm_alloc( pool, size ); + if( addr == NULL ) + return 0; + + memset( addr, 0, numBytes ); + *((rtm_pool **)addr) = pool; // store the pointer to the pool + addr = (rtm_pool **)addr + 1; // and return the next location in memory + + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + rtm_pool *pool; + + if( addr == NULL ) + return; + + addr = (rtm_pool **)addr - 1; + pool = *((rtm_pool **)addr); + + rtm_free( addr ); + rtm_delete_pool( pool ); +} diff --git a/portaudio-v19/pa_common/pa_allocation.c b/portaudio-v19/pa_common/pa_allocation.c new file mode 100644 index 000000000..98239c48f --- /dev/null +++ b/portaudio-v19/pa_common/pa_allocation.c @@ -0,0 +1,234 @@ +/* + * $Id$ + * Portable Audio I/O Library allocation group implementation + * memory allocation group for tracking allocation groups + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Allocation Group implementation. +*/ + + +#include "pa_allocation.h" +#include "pa_util.h" + + +/* + Maintain 3 singly linked lists... + linkBlocks: the buffers used to allocate the links + spareLinks: links available for use in the allocations list + allocations: the buffers currently allocated using PaUtil_ContextAllocateMemory() + + Link block size is doubled every time new links are allocated. +*/ + + +#define PA_INITIAL_LINK_COUNT_ 16 + +struct PaUtilAllocationGroupLink +{ + struct PaUtilAllocationGroupLink *next; + void *buffer; +}; + +/* + Allocate a block of links. The first link will have it's buffer member + pointing to the block, and it's next member set to . The remaining + links will have NULL buffer members, and each link will point to + the next link except the last, which will point to +*/ +static struct PaUtilAllocationGroupLink *AllocateLinks( long count, + struct PaUtilAllocationGroupLink *nextBlock, + struct PaUtilAllocationGroupLink *nextSpare ) +{ + struct PaUtilAllocationGroupLink *result; + int i; + + result = (struct PaUtilAllocationGroupLink *)PaUtil_AllocateMemory( + sizeof(struct PaUtilAllocationGroupLink) * count ); + if( result ) + { + /* the block link */ + result[0].buffer = result; + result[0].next = nextBlock; + + /* the spare links */ + for( i=1; ilinkCount = PA_INITIAL_LINK_COUNT_; + result->linkBlocks = &links[0]; + result->spareLinks = &links[1]; + result->allocations = 0; + } + else + { + PaUtil_FreeMemory( links ); + } + } + + return result; +} + + +void PaUtil_DestroyAllocationGroup( PaUtilAllocationGroup* group ) +{ + struct PaUtilAllocationGroupLink *current = group->linkBlocks; + struct PaUtilAllocationGroupLink *next; + + while( current ) + { + next = current->next; + PaUtil_FreeMemory( current->buffer ); + current = next; + } + + PaUtil_FreeMemory( group ); +} + + +void* PaUtil_GroupAllocateMemory( PaUtilAllocationGroup* group, long size ) +{ + struct PaUtilAllocationGroupLink *links, *link; + void *result = 0; + + /* allocate more links if necessary */ + if( !group->spareLinks ) + { + /* double the link count on each block allocation */ + links = AllocateLinks( group->linkCount, group->linkBlocks, group->spareLinks ); + if( links ) + { + group->linkCount += group->linkCount; + group->linkBlocks = &links[0]; + group->spareLinks = &links[1]; + } + } + + if( group->spareLinks ) + { + result = PaUtil_AllocateMemory( size ); + if( result ) + { + link = group->spareLinks; + group->spareLinks = link->next; + + link->buffer = result; + link->next = group->allocations; + + group->allocations = link; + } + } + + return result; +} + + +void PaUtil_GroupFreeMemory( PaUtilAllocationGroup* group, void *buffer ) +{ + struct PaUtilAllocationGroupLink *current = group->allocations; + struct PaUtilAllocationGroupLink *previous = 0; + + if( buffer == 0 ) + return; + + /* find the right link and remove it */ + while( current ) + { + if( current->buffer == buffer ) + { + if( previous ) + { + previous->next = current->next; + } + else + { + group->allocations = current->next; + } + + current->buffer = 0; + current->next = group->spareLinks; + group->spareLinks = current; + + break; + } + + previous = current; + current = current->next; + } + + PaUtil_FreeMemory( buffer ); /* free the memory whether we found it in the list or not */ +} + + +void PaUtil_FreeAllAllocations( PaUtilAllocationGroup* group ) +{ + struct PaUtilAllocationGroupLink *current = group->allocations; + struct PaUtilAllocationGroupLink *previous = 0; + + /* free all buffers in the allocations list */ + while( current ) + { + PaUtil_FreeMemory( current->buffer ); + current->buffer = 0; + + previous = current; + current = current->next; + } + + /* link the former allocations list onto the front of the spareLinks list */ + if( previous ) + { + previous->next = group->spareLinks; + group->spareLinks = group->allocations; + group->allocations = 0; + } +} + diff --git a/portaudio-v19/pa_common/pa_allocation.h b/portaudio-v19/pa_common/pa_allocation.h new file mode 100644 index 000000000..5bcfe75a6 --- /dev/null +++ b/portaudio-v19/pa_common/pa_allocation.h @@ -0,0 +1,95 @@ +#ifndef PA_ALLOCATION_H +#define PA_ALLOCATION_H +/* + * $Id$ + * Portable Audio I/O Library allocation context header + * memory allocation context for tracking allocation groups + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Allocation Group prototypes. An Allocation Group makes it easy to + allocate multiple blocks of memory and free them all simultanously. + + An allocation group is useful for keeping track of multiple blocks + of memory which are allocated at the same time (such as during initialization) + and need to be deallocated at the same time. The allocation group maintains + a list of allocated blocks, and can deallocate them all simultaneously which + can be usefull for cleaning up after a partially initialized object fails. + + The allocation group implementation is built on top of the lower + level allocation functions defined in pa_util.h +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct +{ + long linkCount; + struct PaUtilAllocationGroupLink *linkBlocks; + struct PaUtilAllocationGroupLink *spareLinks; + struct PaUtilAllocationGroupLink *allocations; +}PaUtilAllocationGroup; + + + +/** Create an allocation group. +*/ +PaUtilAllocationGroup* PaUtil_CreateAllocationGroup( void ); + +/** Destroy an allocation group, but not the memory allocated through the group. +*/ +void PaUtil_DestroyAllocationGroup( PaUtilAllocationGroup* group ); + +/** Allocate a block of memory though an allocation group. +*/ +void* PaUtil_GroupAllocateMemory( PaUtilAllocationGroup* group, long size ); + +/** Free a block of memory that was previously allocated though an allocation + group. Calling this function is a relatively time consuming operation. + Under normal circumstances clients should call PaUtil_FreeAllAllocations to + free all allocated blocks simultaneously. + @see PaUtil_FreeAllAllocations +*/ +void PaUtil_GroupFreeMemory( PaUtilAllocationGroup* group, void *buffer ); + +/** Free all blocks of memory which have been allocated through the allocation + group. This function doesn't destroy the group itself. +*/ +void PaUtil_FreeAllAllocations( PaUtilAllocationGroup* group ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_ALLOCATION_H */ diff --git a/portaudio-v19/pa_common/pa_converters.c b/portaudio-v19/pa_common/pa_converters.c new file mode 100644 index 000000000..39f83e481 --- /dev/null +++ b/portaudio-v19/pa_common/pa_converters.c @@ -0,0 +1,1926 @@ +/* + * $Id$ + * Portable Audio I/O Library sample conversion mechanism + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Conversion functions implementations. + + If the C9x function lrintf() is available, define PA_USE_C99_LRINTF to use it + + @todo Consider whether functions which dither but don't clip should exist, + V18 automatically enabled clipping whenever dithering was selected. Perhaps + we should do the same. + + @todo implement the converters marked IMPLEMENT ME: Float32_To_UInt8_Dither, + Float32_To_UInt8_Clip, Float32_To_UInt8_DitherClip, Int32_To_Int24_Dither, + Int32_To_UInt8_Dither, Int24_To_Int16_Dither, Int24_To_Int8_Dither, + Int24_To_UInt8_Dither, Int16_To_Int8_Dither, Int16_To_UInt8_Dither, + + @todo review the converters marked REVIEW: Float32_To_Int32, + Float32_To_Int32_Dither, Float32_To_Int32_Clip, Float32_To_Int32_DitherClip, + Int32_To_Int16_Dither, Int32_To_Int8_Dither, Int16_To_Int32 +*/ + + +#include "pa_converters.h" +#include "pa_dither.h" +#include "pa_endianness.h" +#include "pa_types.h" + + +PaSampleFormat PaUtil_SelectClosestAvailableFormat( + PaSampleFormat availableFormats, PaSampleFormat format ) +{ + PaSampleFormat result; + + format &= ~paNonInterleaved; + availableFormats &= ~paNonInterleaved; + + if( (format & availableFormats) == 0 ) + { + /* NOTE: this code depends on the sample format constants being in + descending order of quality - ie best quality is 0 + FIXME: should write an assert which checks that all of the + known constants conform to that requirement. + */ + + if( format != 0x01 ) + { + /* scan for better formats */ + result = format; + do + { + result >>= 1; + } + while( (result & availableFormats) == 0 && result != 0 ); + } + else + { + result = 0; + } + + if( result == 0 ){ + /* scan for worse formats */ + result = format; + do + { + result <<= 1; + } + while( (result & availableFormats) == 0 && result != paCustomFormat ); + + if( (result & availableFormats) == 0 ) + result = paSampleFormatNotSupported; + } + + }else{ + result = format; + } + + return result; +} + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_FORMAT_( format, float32, int32, int24, int16, int8, uint8 ) \ + switch( format & ~paNonInterleaved ){ \ + case paFloat32: \ + float32 \ + case paInt32: \ + int32 \ + case paInt24: \ + int24 \ + case paInt16: \ + int16 \ + case paInt8: \ + int8 \ + case paUInt8: \ + uint8 \ + default: return 0; \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_CONVERTER_DITHER_CLIP_( flags, source, destination ) \ + if( flags & paClipOff ){ /* no clip */ \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _Dither; \ + } \ + }else{ /* clip */ \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination ## _Clip; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _DitherClip; \ + } \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_CONVERTER_DITHER_( flags, source, destination ) \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _Dither; \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_USE_CONVERTER_( source, destination )\ + return paConverters. source ## _To_ ## destination; + +/* -------------------------------------------------------------------------- */ + +#define PA_UNITY_CONVERSION_( wordlength )\ + return paConverters. Copy_ ## wordlength ## _To_ ## wordlength; + +/* -------------------------------------------------------------------------- */ + +PaUtilConverter* PaUtil_SelectConverter( PaSampleFormat sourceFormat, + PaSampleFormat destinationFormat, PaStreamFlags flags ) +{ + PA_SELECT_FORMAT_( sourceFormat, + /* paFloat32: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_UNITY_CONVERSION_( 32 ), + /* paInt32: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int32 ), + /* paInt24: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, UInt8 ) + ), + /* paInt32: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int32, Float32 ), + /* paInt32: */ PA_UNITY_CONVERSION_( 32 ), + /* paInt24: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, UInt8 ) + ), + /* paInt24: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int24, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int24, Int32 ), + /* paInt24: */ PA_UNITY_CONVERSION_( 24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, UInt8 ) + ), + /* paInt16: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int16, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int16, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( Int16, Int24 ), + /* paInt16: */ PA_UNITY_CONVERSION_( 16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int16, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int16, UInt8 ) + ), + /* paInt8: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int8, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int8, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( Int8, Int24 ), + /* paInt16: */ PA_USE_CONVERTER_( Int8, Int16 ), + /* paInt8: */ PA_UNITY_CONVERSION_( 8 ), + /* paUInt8: */ PA_USE_CONVERTER_( Int8, UInt8 ) + ), + /* paUInt8: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( UInt8, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( UInt8, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( UInt8, Int24 ), + /* paInt16: */ PA_USE_CONVERTER_( UInt8, Int16 ), + /* paInt8: */ PA_USE_CONVERTER_( UInt8, Int8 ), + /* paUInt8: */ PA_UNITY_CONVERSION_( 8 ) + ) + ) +} + +/* -------------------------------------------------------------------------- */ + +#ifdef PA_NO_STANDARD_CONVERTERS + +/* -------------------------------------------------------------------------- */ + +PaUtilConverterTable paConverters = { + 0, /* PaUtilConverter *Float32_To_Int32; */ + 0, /* PaUtilConverter *Float32_To_Int32_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int32_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int32_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int24; */ + 0, /* PaUtilConverter *Float32_To_Int24_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int24_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int24_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int16; */ + 0, /* PaUtilConverter *Float32_To_Int16_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int16_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int16_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int8; */ + 0, /* PaUtilConverter *Float32_To_Int8_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int8_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int8_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_UInt8; */ + 0, /* PaUtilConverter *Float32_To_UInt8_Dither; */ + 0, /* PaUtilConverter *Float32_To_UInt8_Clip; */ + 0, /* PaUtilConverter *Float32_To_UInt8_DitherClip; */ + + 0, /* PaUtilConverter *Int32_To_Float32; */ + 0, /* PaUtilConverter *Int32_To_Int24; */ + 0, /* PaUtilConverter *Int32_To_Int24_Dither; */ + 0, /* PaUtilConverter *Int32_To_Int16; */ + 0, /* PaUtilConverter *Int32_To_Int16_Dither; */ + 0, /* PaUtilConverter *Int32_To_Int8; */ + 0, /* PaUtilConverter *Int32_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int32_To_UInt8; */ + 0, /* PaUtilConverter *Int32_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int24_To_Float32; */ + 0, /* PaUtilConverter *Int24_To_Int32; */ + 0, /* PaUtilConverter *Int24_To_Int16; */ + 0, /* PaUtilConverter *Int24_To_Int16_Dither; */ + 0, /* PaUtilConverter *Int24_To_Int8; */ + 0, /* PaUtilConverter *Int24_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int24_To_UInt8; */ + 0, /* PaUtilConverter *Int24_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int16_To_Float32; */ + 0, /* PaUtilConverter *Int16_To_Int32; */ + 0, /* PaUtilConverter *Int16_To_Int24; */ + 0, /* PaUtilConverter *Int16_To_Int8; */ + 0, /* PaUtilConverter *Int16_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int16_To_UInt8; */ + 0, /* PaUtilConverter *Int16_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int8_To_Float32; */ + 0, /* PaUtilConverter *Int8_To_Int32; */ + 0, /* PaUtilConverter *Int8_To_Int24 */ + 0, /* PaUtilConverter *Int8_To_Int16; */ + 0, /* PaUtilConverter *Int8_To_UInt8; */ + + 0, /* PaUtilConverter *UInt8_To_Float32; */ + 0, /* PaUtilConverter *UInt8_To_Int32; */ + 0, /* PaUtilConverter *UInt8_To_Int24; */ + 0, /* PaUtilConverter *UInt8_To_Int16; */ + 0, /* PaUtilConverter *UInt8_To_Int8; */ + + 0, /* PaUtilConverter *Copy_8_To_8; */ + 0, /* PaUtilConverter *Copy_16_To_16; */ + 0, /* PaUtilConverter *Copy_24_To_24; */ + 0 /* PaUtilConverter *Copy_32_To_32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#else /* PA_NO_STANDARD_CONVERTERS is not defined */ + +/* -------------------------------------------------------------------------- */ + +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } + + +static const float const_1_div_128_ = 1.0f / 128.0f; /* 8 bit multiplier */ + +static const float const_1_div_32768_ = 1.0f / 32768.f; /* 16 bit multiplier */ + +static const double const_1_div_2147483648_ = 1.0 / 2147483648.0; /* 32 bit multiplier */ + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float scaled = *src * 0x7FFFFFFF; + *dest = lrintf(scaled-0.5f); +#else + double scaled = *src * 0x7FFFFFFF; + *dest = (PaInt32) scaled; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = ((float)*src * (2147483646.0f)) + dither; + *dest = lrintf(dithered - 0.5f); +#else + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + *dest = (PaInt32) dithered; +#endif + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648.f, 2147483647.f ); + *dest = lrintf(scaled-0.5f); +#else + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + *dest = (PaInt32) scaled; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = ((float)*src * (2147483646.0f)) + dither; + PA_CLIP_( dithered, -2147483648.f, 2147483647.f ); + *dest = lrintf(dithered-0.5f); +#else + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + *dest = (PaInt32) dithered; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + PaInt32 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * 0x7FFFFFFF; + temp = (PaInt32) scaled; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + PaInt32 temp; + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + + temp = (PaInt32) dithered; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + PaInt32 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + temp = (PaInt32) scaled; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + PaInt32 temp; + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + + temp = (PaInt32) dithered; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { +#ifdef PA_USE_C99_LRINTF + float tempf = (*src * (32767.0f)) ; + *dest = lrintf(tempf-0.5f); +#else + short samp = (short) (*src * (32767.0f)); + *dest = samp; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (32766.0f)) + dither; + +#ifdef PA_USE_C99_LRINTF + *dest = lrintf(dithered-0.5f); +#else + *dest = (PaInt16) dithered; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { +#ifdef PA_USE_C99_LRINTF + long samp = lrintf((*src * (32767.0f)) -0.5f); +#else + long samp = (PaInt32) (*src * (32767.0f)); +#endif + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (PaInt16) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (32766.0f)) + dither; + PaInt32 samp = (PaInt32) dithered; + PA_CLIP_( samp, -0x8000, 0x7FFF ); +#ifdef PA_USE_C99_LRINTF + *dest = lrintf(samp-0.5f); +#else + *dest = (PaInt16) samp; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + signed char samp = (signed char) (*src * (127.0f)); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (126.0f)) + dither; + PaInt32 samp = (PaInt32) dithered; + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + PaInt32 samp = (PaInt32)(*src * (127.0f)); + PA_CLIP_( samp, -0x80, 0x7F ); + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (126.0f)) + dither; + PaInt32 samp = (PaInt32) dithered; + PA_CLIP_( samp, -0x80, 0x7F ); + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + unsigned char samp = (unsigned char)(128 + ((unsigned char) (*src * (127.0f)))); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (float) ((double)*src * const_1_div_2147483648_); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(*src >> 8); + dest[1] = (unsigned char)(*src >> 16); + dest[2] = (unsigned char)(*src >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(*src >> 24); + dest[1] = (unsigned char)(*src >> 16); + dest[2] = (unsigned char)(*src >> 8); +#endif + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int24_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (PaInt16) ((*src) >> 16); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + PaInt32 dither; + + while( count-- ) + { + /* REVIEW */ + dither = PaUtil_Generate16BitTriangularDither( ditherGenerator ); + *dest = (PaInt16) ((((*src)>>1) + dither) >> 15); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (signed char) ((*src) >> 24); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + PaInt32 dither; + + while( count-- ) + { + /* REVIEW */ + dither = PaUtil_Generate16BitTriangularDither( ditherGenerator ); + *dest = (signed char) ((((*src)>>1) + dither) >> 23); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(((*src) >> 24) + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt32 *src = (PaInt32*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + PaInt32 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + temp = (((long)src[0]) << 8); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 24); +#elif defined(PA_BIG_ENDIAN) + temp = (((long)src[0]) << 24); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 8); +#endif + + *dest = (float) ((double)temp * const_1_div_2147483648_); + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + PaInt32 *dest = (PaInt32*) destinationBuffer; + PaInt32 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + temp = (((long)src[0]) << 8); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 24); +#elif defined(PA_BIG_ENDIAN) + temp = (((long)src[0]) << 24); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 8); +#endif + + *dest = temp; + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + + PaInt16 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + temp = (((PaInt16)src[1])); + temp = temp | (PaInt16)(((PaInt16)src[2]) << 8); +#elif defined(PA_BIG_ENDIAN) + /* src[2] is discarded */ + temp = (PaInt16)(((PaInt16)src[0]) << 8); + temp = temp | (((PaInt16)src[1])); +#endif + + *dest = temp; + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + /* src[1] is discarded */ + *dest = src[2]; +#elif defined(PA_BIG_ENDIAN) + /* src[2] is discarded */ + /* src[1] is discarded */ + *dest = src[0]; +#endif + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + /* src[1] is discarded */ + *dest = (unsigned char)(src[2] + 128); +#elif defined(PA_BIG_ENDIAN) + *dest = (unsigned char)(src[0] + 128); + /* src[1] is discarded */ + /* src[2] is discarded */ +#endif + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = *src * const_1_div_32768_; /* FIXME: i'm concerned about this being asymetrical with float->int16 -rb */ + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW: we should consider something like + (*src << 16) | (*src & 0xFFFF) + */ + + *dest = *src << 16; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*) sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + PaInt16 temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + temp = *src; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = (unsigned char)(temp); + dest[2] = (unsigned char)(temp >> 8); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp); + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed char)((*src) >> 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(((*src) >> 8) + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaInt16 *src = (PaInt16*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = *src * const_1_div_128_; + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (*src) << 24; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = 0; + dest[2] = (*src); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (*src); + dest[1] = 0; + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (PaInt16)((*src) << 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(*src + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = (*src - 128) * const_1_div_128_; + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + PaInt32 *dest = (PaInt32*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (*src - 128) << 24; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void) ditherGenerator; /* unused parameters */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = 0; + dest[2] = (unsigned char)(*src - 128); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(*src - 128); + dest[1] = 0; + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + PaInt16 *dest = (PaInt16*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (PaInt16)((*src - 128) << 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed char)(*src - 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_8_To_8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_16_To_16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaUint16 *src = (PaUint16 *)sourceBuffer; + PaUint16 *dest = (PaUint16 *)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_24_To_24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + + src += sourceStride * 3; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_32_To_32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaUint32 *dest = (PaUint32 *)destinationBuffer; + PaUint32 *src = (PaUint32 *)sourceBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +PaUtilConverterTable paConverters = { + Float32_To_Int32, /* PaUtilConverter *Float32_To_Int32; */ + Float32_To_Int32_Dither, /* PaUtilConverter *Float32_To_Int32_Dither; */ + Float32_To_Int32_Clip, /* PaUtilConverter *Float32_To_Int32_Clip; */ + Float32_To_Int32_DitherClip, /* PaUtilConverter *Float32_To_Int32_DitherClip; */ + + Float32_To_Int24, /* PaUtilConverter *Float32_To_Int24; */ + Float32_To_Int24_Dither, /* PaUtilConverter *Float32_To_Int24_Dither; */ + Float32_To_Int24_Clip, /* PaUtilConverter *Float32_To_Int24_Clip; */ + Float32_To_Int24_DitherClip, /* PaUtilConverter *Float32_To_Int24_DitherClip; */ + + Float32_To_Int16, /* PaUtilConverter *Float32_To_Int16; */ + Float32_To_Int16_Dither, /* PaUtilConverter *Float32_To_Int16_Dither; */ + Float32_To_Int16_Clip, /* PaUtilConverter *Float32_To_Int16_Clip; */ + Float32_To_Int16_DitherClip, /* PaUtilConverter *Float32_To_Int16_DitherClip; */ + + Float32_To_Int8, /* PaUtilConverter *Float32_To_Int8; */ + Float32_To_Int8_Dither, /* PaUtilConverter *Float32_To_Int8_Dither; */ + Float32_To_Int8_Clip, /* PaUtilConverter *Float32_To_Int8_Clip; */ + Float32_To_Int8_DitherClip, /* PaUtilConverter *Float32_To_Int8_DitherClip; */ + + Float32_To_UInt8, /* PaUtilConverter *Float32_To_UInt8; */ + Float32_To_UInt8_Dither, /* PaUtilConverter *Float32_To_UInt8_Dither; */ + Float32_To_UInt8_Clip, /* PaUtilConverter *Float32_To_UInt8_Clip; */ + Float32_To_UInt8_DitherClip, /* PaUtilConverter *Float32_To_UInt8_DitherClip; */ + + Int32_To_Float32, /* PaUtilConverter *Int32_To_Float32; */ + Int32_To_Int24, /* PaUtilConverter *Int32_To_Int24; */ + Int32_To_Int24_Dither, /* PaUtilConverter *Int32_To_Int24_Dither; */ + Int32_To_Int16, /* PaUtilConverter *Int32_To_Int16; */ + Int32_To_Int16_Dither, /* PaUtilConverter *Int32_To_Int16_Dither; */ + Int32_To_Int8, /* PaUtilConverter *Int32_To_Int8; */ + Int32_To_Int8_Dither, /* PaUtilConverter *Int32_To_Int8_Dither; */ + Int32_To_UInt8, /* PaUtilConverter *Int32_To_UInt8; */ + Int32_To_UInt8_Dither, /* PaUtilConverter *Int32_To_UInt8_Dither; */ + + Int24_To_Float32, /* PaUtilConverter *Int24_To_Float32; */ + Int24_To_Int32, /* PaUtilConverter *Int24_To_Int32; */ + Int24_To_Int16, /* PaUtilConverter *Int24_To_Int16; */ + Int24_To_Int16_Dither, /* PaUtilConverter *Int24_To_Int16_Dither; */ + Int24_To_Int8, /* PaUtilConverter *Int24_To_Int8; */ + Int24_To_Int8_Dither, /* PaUtilConverter *Int24_To_Int8_Dither; */ + Int24_To_UInt8, /* PaUtilConverter *Int24_To_UInt8; */ + Int24_To_UInt8_Dither, /* PaUtilConverter *Int24_To_UInt8_Dither; */ + + Int16_To_Float32, /* PaUtilConverter *Int16_To_Float32; */ + Int16_To_Int32, /* PaUtilConverter *Int16_To_Int32; */ + Int16_To_Int24, /* PaUtilConverter *Int16_To_Int24; */ + Int16_To_Int8, /* PaUtilConverter *Int16_To_Int8; */ + Int16_To_Int8_Dither, /* PaUtilConverter *Int16_To_Int8_Dither; */ + Int16_To_UInt8, /* PaUtilConverter *Int16_To_UInt8; */ + Int16_To_UInt8_Dither, /* PaUtilConverter *Int16_To_UInt8_Dither; */ + + Int8_To_Float32, /* PaUtilConverter *Int8_To_Float32; */ + Int8_To_Int32, /* PaUtilConverter *Int8_To_Int32; */ + Int8_To_Int24, /* PaUtilConverter *Int8_To_Int24 */ + Int8_To_Int16, /* PaUtilConverter *Int8_To_Int16; */ + Int8_To_UInt8, /* PaUtilConverter *Int8_To_UInt8; */ + + UInt8_To_Float32, /* PaUtilConverter *UInt8_To_Float32; */ + UInt8_To_Int32, /* PaUtilConverter *UInt8_To_Int32; */ + UInt8_To_Int24, /* PaUtilConverter *UInt8_To_Int24; */ + UInt8_To_Int16, /* PaUtilConverter *UInt8_To_Int16; */ + UInt8_To_Int8, /* PaUtilConverter *UInt8_To_Int8; */ + + Copy_8_To_8, /* PaUtilConverter *Copy_8_To_8; */ + Copy_16_To_16, /* PaUtilConverter *Copy_16_To_16; */ + Copy_24_To_24, /* PaUtilConverter *Copy_24_To_24; */ + Copy_32_To_32 /* PaUtilConverter *Copy_32_To_32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#endif /* PA_NO_STANDARD_CONVERTERS */ + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroer* PaUtil_SelectZeroer( PaSampleFormat destinationFormat ) +{ + switch( destinationFormat & ~paNonInterleaved ){ + case paFloat32: + return paZeroers.Zero32; + case paInt32: + return paZeroers.Zero32; + case paInt24: + return paZeroers.Zero24; + case paInt16: + return paZeroers.Zero16; + case paInt8: + return paZeroers.Zero8; + case paUInt8: + return paZeroers.ZeroU8; + default: return 0; + } +} + +/* -------------------------------------------------------------------------- */ + +#ifdef PA_NO_STANDARD_ZEROERS + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroerTable paZeroers = { + 0, /* PaUtilZeroer *ZeroU8; */ + 0, /* PaUtilZeroer *Zero8; */ + 0, /* PaUtilZeroer *Zero16; */ + 0, /* PaUtilZeroer *Zero24; */ + 0, /* PaUtilZeroer *Zero32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#else /* PA_NO_STANDARD_ZEROERS is not defined */ + +/* -------------------------------------------------------------------------- */ + +static void ZeroU8( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + *dest = 128; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero8( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero16( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + PaUint16 *dest = (PaUint16 *)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero24( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero32( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + PaUint32 *dest = (PaUint32 *)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroerTable paZeroers = { + ZeroU8, /* PaUtilZeroer *ZeroU8; */ + Zero8, /* PaUtilZeroer *Zero8; */ + Zero16, /* PaUtilZeroer *Zero16; */ + Zero24, /* PaUtilZeroer *Zero24; */ + Zero32, /* PaUtilZeroer *Zero32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#endif /* PA_NO_STANDARD_ZEROERS */ diff --git a/portaudio-v19/pa_common/pa_converters.h b/portaudio-v19/pa_common/pa_converters.h new file mode 100644 index 000000000..1d83c990b --- /dev/null +++ b/portaudio-v19/pa_common/pa_converters.h @@ -0,0 +1,254 @@ +#ifndef PA_CONVERTERS_H +#define PA_CONVERTERS_H +/* + * $Id$ + * Portable Audio I/O Library sample conversion mechanism + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Conversion functions used to convert buffers of samples from one + format to another. +*/ + + +#include "portaudio.h" /* for PaSampleFormat */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +struct PaUtilTriangularDitherGenerator; + + +/** Choose an available sample format which is most appropriate for + representing the requested format. If the requested format is not available + higher quality formats are considered before lower quality formates. + @param availableFormats A variable containing the logical OR of all available + formats. + @param format The desired format. + @return The most appropriate available format for representing the requested + format. +*/ +PaSampleFormat PaUtil_SelectClosestAvailableFormat( + PaSampleFormat availableFormats, PaSampleFormat format ); + + +/* high level conversions functions for use by implementations */ + + +/** The generic sample converter prototype. Sample converters convert count + samples from sourceBuffer to destinationBuffer. The actual type of the data + pointed to by these parameters varys for different converter functions. + @param destinationBuffer A pointer to the first sample of the destination. + @param destinationStride An offset between successive destination samples + expressed in samples (not bytes.) It may be negative. + @param sourceBuffer A pointer to the first sample of the source. + @param sourceStride An offset between successive source samples + expressed in samples (not bytes.) It may be negative. + @param count The number of samples to convert. + @param ditherState State information used to calculate dither. Converters + that do not perform dithering will ignore this parameter, in which case + NULL or invalid dither state may be passed. +*/ +typedef void PaUtilConverter( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ); + + +/** Find a sample converter function for the given source and destinations + formats and flags (clip and dither.) + @return + A pointer to a PaUtilConverter which will perform the requested + conversion, or NULL if the given format conversion is not supported. + For conversions where clipping or dithering is not necessary, the + clip and dither flags are ignored and a non-clipping or dithering + version is returned. + If the source and destination formats are the same, a function which + copies data of the appropriate size will be returned. +*/ +PaUtilConverter* PaUtil_SelectConverter( PaSampleFormat sourceFormat, + PaSampleFormat destinationFormat, PaStreamFlags flags ); + + +/** The generic buffer zeroer prototype. Buffer zeroers copy count zeros to + destinationBuffer. The actual type of the data pointed to varys for + different zeroer functions. + @param destinationBuffer A pointer to the first sample of the destination. + @param destinationStride An offset between successive destination samples + expressed in samples (not bytes.) It may be negative. + @param count The number of samples to zero. +*/ +typedef void PaUtilZeroer( + void *destinationBuffer, signed int destinationStride, unsigned int count ); + + +/** Find a buffer zeroer function for the given destination format. + @return + A pointer to a PaUtilZeroer which will perform the requested + zeroing. +*/ +PaUtilZeroer* PaUtil_SelectZeroer( PaSampleFormat destinationFormat ); + +/*----------------------------------------------------------------------------*/ +/* low level functions and data structures which may be used for + substituting conversion functions */ + + +/** The type used to store all sample conversion functions. + @see paConverters; +*/ +typedef struct{ + PaUtilConverter *Float32_To_Int32; + PaUtilConverter *Float32_To_Int32_Dither; + PaUtilConverter *Float32_To_Int32_Clip; + PaUtilConverter *Float32_To_Int32_DitherClip; + + PaUtilConverter *Float32_To_Int24; + PaUtilConverter *Float32_To_Int24_Dither; + PaUtilConverter *Float32_To_Int24_Clip; + PaUtilConverter *Float32_To_Int24_DitherClip; + + PaUtilConverter *Float32_To_Int16; + PaUtilConverter *Float32_To_Int16_Dither; + PaUtilConverter *Float32_To_Int16_Clip; + PaUtilConverter *Float32_To_Int16_DitherClip; + + PaUtilConverter *Float32_To_Int8; + PaUtilConverter *Float32_To_Int8_Dither; + PaUtilConverter *Float32_To_Int8_Clip; + PaUtilConverter *Float32_To_Int8_DitherClip; + + PaUtilConverter *Float32_To_UInt8; + PaUtilConverter *Float32_To_UInt8_Dither; + PaUtilConverter *Float32_To_UInt8_Clip; + PaUtilConverter *Float32_To_UInt8_DitherClip; + + PaUtilConverter *Int32_To_Float32; + PaUtilConverter *Int32_To_Int24; + PaUtilConverter *Int32_To_Int24_Dither; + PaUtilConverter *Int32_To_Int16; + PaUtilConverter *Int32_To_Int16_Dither; + PaUtilConverter *Int32_To_Int8; + PaUtilConverter *Int32_To_Int8_Dither; + PaUtilConverter *Int32_To_UInt8; + PaUtilConverter *Int32_To_UInt8_Dither; + + PaUtilConverter *Int24_To_Float32; + PaUtilConverter *Int24_To_Int32; + PaUtilConverter *Int24_To_Int16; + PaUtilConverter *Int24_To_Int16_Dither; + PaUtilConverter *Int24_To_Int8; + PaUtilConverter *Int24_To_Int8_Dither; + PaUtilConverter *Int24_To_UInt8; + PaUtilConverter *Int24_To_UInt8_Dither; + + PaUtilConverter *Int16_To_Float32; + PaUtilConverter *Int16_To_Int32; + PaUtilConverter *Int16_To_Int24; + PaUtilConverter *Int16_To_Int8; + PaUtilConverter *Int16_To_Int8_Dither; + PaUtilConverter *Int16_To_UInt8; + PaUtilConverter *Int16_To_UInt8_Dither; + + PaUtilConverter *Int8_To_Float32; + PaUtilConverter *Int8_To_Int32; + PaUtilConverter *Int8_To_Int24; + PaUtilConverter *Int8_To_Int16; + PaUtilConverter *Int8_To_UInt8; + + PaUtilConverter *UInt8_To_Float32; + PaUtilConverter *UInt8_To_Int32; + PaUtilConverter *UInt8_To_Int24; + PaUtilConverter *UInt8_To_Int16; + PaUtilConverter *UInt8_To_Int8; + + PaUtilConverter *Copy_8_To_8; /* copy without any conversion */ + PaUtilConverter *Copy_16_To_16; /* copy without any conversion */ + PaUtilConverter *Copy_24_To_24; /* copy without any conversion */ + PaUtilConverter *Copy_32_To_32; /* copy without any conversion */ +} PaUtilConverterTable; + + +/** A table of pointers to all required converter functions. + PaUtil_SelectConverter() uses this table to lookup the appropriate + conversion functions. The fields of this structure are initialized + with default conversion functions. Fields may be NULL, indicating that + no conversion function is available. User code may substitue optimised + conversion functions by assigning different function pointers to + these fields. + + @note + If the PA_NO_STANDARD_CONVERTERS preprocessor variable is defined, + PortAudio's standard converters will not be compiled, and all fields + of this structure will be initialized to NULL. In such cases, users + should supply their own conversion functions if the require PortAudio + to open a stream that requires sample conversion. + + @see PaUtilConverterTable, PaUtilConverter, PaUtil_SelectConverter +*/ +extern PaUtilConverterTable paConverters; + + +/** The type used to store all buffer zeroing functions. + @see paZeroers; +*/ +typedef struct{ + PaUtilZeroer *ZeroU8; /* unsigned 8 bit, zero == 128 */ + PaUtilZeroer *Zero8; + PaUtilZeroer *Zero16; + PaUtilZeroer *Zero24; + PaUtilZeroer *Zero32; +} PaUtilZeroerTable; + + +/** A table of pointers to all required zeroer functions. + PaUtil_SelectZeroer() uses this table to lookup the appropriate + conversion functions. The fields of this structure are initialized + with default conversion functions. User code may substitue optimised + conversion functions by assigning different function pointers to + these fields. + + @note + If the PA_NO_STANDARD_ZEROERS preprocessor variable is defined, + PortAudio's standard zeroers will not be compiled, and all fields + of this structure will be initialized to NULL. In such cases, users + should supply their own zeroing functions for the sample sizes which + they intend to use. + + @see PaUtilZeroerTable, PaUtilZeroer, PaUtil_SelectZeroer +*/ +extern PaUtilZeroerTable paZeroers; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_CONVERTERS_H */ diff --git a/portaudio-v19/pa_common/pa_cpuload.c b/portaudio-v19/pa_common/pa_cpuload.c new file mode 100644 index 000000000..ee2147dc3 --- /dev/null +++ b/portaudio-v19/pa_common/pa_cpuload.c @@ -0,0 +1,96 @@ +/* + * $Id$ + * Portable Audio I/O Library CPU Load measurement functions + * Portable CPU load measurement facility. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Functions to assist in measuring the CPU utilization of a callback + stream. Used to implement the Pa_GetStreamCpuLoad() function. + + @todo Dynamically calculate the coefficients used to smooth the CPU Load + Measurements over time to provide a uniform characterisation of CPU Load + independent of rate at which PaUtil_BeginCpuLoadMeasurement / + PaUtil_EndCpuLoadMeasurement are called. +*/ + + +#include "pa_cpuload.h" + +#include + +#include "pa_util.h" /* for PaUtil_GetTime() */ + + +void PaUtil_InitializeCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer, double sampleRate ) +{ + assert( sampleRate > 0 ); + + measurer->samplingPeriod = 1. / sampleRate; + measurer->averageLoad = 0.; +} + +void PaUtil_ResetCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer ) +{ + measurer->averageLoad = 0.; +} + +void PaUtil_BeginCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer ) +{ + measurer->measurementStartTime = PaUtil_GetTime(); +} + + +void PaUtil_EndCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer, unsigned long framesProcessed ) +{ + double measurementEndTime, secondsFor100Percent, measuredLoad; + + if( framesProcessed > 0 ){ + measurementEndTime = PaUtil_GetTime(); + + assert( framesProcessed > 0 ); + secondsFor100Percent = framesProcessed * measurer->samplingPeriod; + + measuredLoad = (measurementEndTime - measurer->measurementStartTime) / secondsFor100Percent; + + /* Low pass filter the calculated CPU load to reduce jitter using a simple IIR low pass filter. */ + /** FIXME @todo these coefficients shouldn't be hardwired */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + measurer->averageLoad = (LOWPASS_COEFFICIENT_0 * measurer->averageLoad) + + (LOWPASS_COEFFICIENT_1 * measuredLoad); + } +} + + +double PaUtil_GetCpuLoad( PaUtilCpuLoadMeasurer* measurer ) +{ + return measurer->averageLoad; +} diff --git a/portaudio-v19/pa_common/pa_cpuload.h b/portaudio-v19/pa_common/pa_cpuload.h new file mode 100644 index 000000000..131ad10bd --- /dev/null +++ b/portaudio-v19/pa_common/pa_cpuload.h @@ -0,0 +1,63 @@ +#ifndef PA_CPULOAD_H +#define PA_CPULOAD_H +/* + * $Id$ + * Portable Audio I/O Library CPU Load measurement functions + * Portable CPU load measurement facility. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Functions to assist in measuring the CPU utilization of a callback + stream. Used to implement the Pa_GetStreamCpuLoad() function. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct { + double samplingPeriod; + double measurementStartTime; + double averageLoad; +} PaUtilCpuLoadMeasurer; /**< @todo need better name than measurer */ + +void PaUtil_InitializeCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer, double sampleRate ); +void PaUtil_BeginCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer ); +void PaUtil_EndCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer, unsigned long framesProcessed ); +void PaUtil_ResetCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer ); +double PaUtil_GetCpuLoad( PaUtilCpuLoadMeasurer* measurer ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_CPULOAD_H */ diff --git a/portaudio-v19/pa_common/pa_dither.c b/portaudio-v19/pa_common/pa_dither.c new file mode 100644 index 000000000..d44a060cd --- /dev/null +++ b/portaudio-v19/pa_common/pa_dither.c @@ -0,0 +1,204 @@ +/* + * $Id$ + * Portable Audio I/O Library triangular dither generator + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Functions for generating dither noise +*/ + + +#include "pa_dither.h" +#include "pa_types.h" + +#define PA_DITHER_BITS_ (15) + + +void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *state ) +{ + state->previous = 0; + state->randSeed1 = 22222; + state->randSeed2 = 5555555; +} + + +signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state ) +{ + signed long current, highPass; + + /* Generate two random numbers. */ + state->randSeed1 = (state->randSeed1 * 196314165) + 907633515; + state->randSeed2 = (state->randSeed2 * 196314165) + 907633515; + + /* Generate triangular distribution about 0. + * Shift before adding to prevent overflow which would skew the distribution. + * Also shift an extra bit for the high pass filter. + */ +#define DITHER_SHIFT_ ((SIZEOF_LONG*8 - PA_DITHER_BITS_) + 1) + current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) + + (((signed long)state->randSeed2)>>DITHER_SHIFT_); + + /* High pass filter to reduce audibility. */ + highPass = current - state->previous; + state->previous = current; + return highPass; +} + + +/* Multiply by PA_FLOAT_DITHER_SCALE_ to get a float between -2.0 and +1.99999 */ +#define PA_FLOAT_DITHER_SCALE_ (1.0f / ((1<randSeed1 = (state->randSeed1 * 196314165) + 907633515; + state->randSeed2 = (state->randSeed2 * 196314165) + 907633515; + + /* Generate triangular distribution about 0. + * Shift before adding to prevent overflow which would skew the distribution. + * Also shift an extra bit for the high pass filter. + */ +#define DITHER_SHIFT_ ((SIZEOF_LONG*8 - PA_DITHER_BITS_) + 1) + current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) + + (((signed long)state->randSeed2)>>DITHER_SHIFT_); + + /* High pass filter to reduce audibility. */ + highPass = current - state->previous; + state->previous = current; + return ((float)highPass) * const_float_dither_scale_; +} + + +/* +The following alternate dither algorithms (from musicdsp.org) could be +considered +*/ + +/*Noise shaped dither (March 2000) +------------------- + +This is a simple implementation of highpass triangular-PDF dither with +2nd-order noise shaping, for use when truncating floating point audio +data to fixed point. + +The noise shaping lowers the noise floor by 11dB below 5kHz (@ 44100Hz +sample rate) compared to triangular-PDF dither. The code below assumes +input data is in the range +1 to -1 and doesn't check for overloads! + +To save time when generating dither for multiple channels you can do +things like this: r3=(r1 & 0x7F)<<8; instead of calling rand() again. + + + + int r1, r2; //rectangular-PDF random numbers + float s1, s2; //error feedback buffers + float s = 0.5f; //set to 0.0f for no noise shaping + float w = pow(2.0,bits-1); //word length (usually bits=16) + float wi= 1.0f/w; + float d = wi / RAND_MAX; //dither amplitude (2 lsb) + float o = wi * 0.5f; //remove dc offset + float in, tmp; + int out; + + +//for each sample... + + r2=r1; //can make HP-TRI dither by + r1=rand(); //subtracting previous rand() + + in += s * (s1 + s1 - s2); //error feedback + tmp = in + o + d * (float)(r1 - r2); //dc offset and dither + + out = (int)(w * tmp); //truncate downwards + if(tmp<0.0f) out--; //this is faster than floor() + + s2 = s1; + s1 = in - wi * (float)out; //error + + + +-- +paul.kellett@maxim.abel.co.uk +http://www.maxim.abel.co.uk +*/ + + +/* +16-to-8-bit first-order dither + +Type : First order error feedforward dithering code +References : Posted by Jon Watte + +Notes : +This is about as simple a dithering algorithm as you can implement, but it's +likely to sound better than just truncating to N bits. + +Note that you might not want to carry forward the full difference for infinity. +It's probably likely that the worst performance hit comes from the saturation +conditionals, which can be avoided with appropriate instructions on many DSPs +and integer SIMD type instructions, or CMOV. + +Last, if sound quality is paramount (such as when going from > 16 bits to 16 +bits) you probably want to use a higher-order dither function found elsewhere +on this site. + + +Code : +// This code will down-convert and dither a 16-bit signed short +// mono signal into an 8-bit unsigned char signal, using a first +// order forward-feeding error term dither. + +#define uchar unsigned char + +void dither_one_channel_16_to_8( short * input, uchar * output, int count, int * memory ) +{ + int m = *memory; + while( count-- > 0 ) { + int i = *input++; + i += m; + int j = i + 32768 - 128; + uchar o; + if( j < 0 ) { + o = 0; + } + else if( j > 65535 ) { + o = 255; + } + else { + o = (uchar)((j>>8)&0xff); + } + m = ((j-32768+128)-i); + *output++ = o; + } + *memory = m; +} +*/ diff --git a/portaudio-v19/pa_common/pa_dither.h b/portaudio-v19/pa_common/pa_dither.h new file mode 100644 index 000000000..148db275f --- /dev/null +++ b/portaudio-v19/pa_common/pa_dither.h @@ -0,0 +1,91 @@ +#ifndef PA_DITHER_H +#define PA_DITHER_H +/* + * $Id$ + * Portable Audio I/O Library triangular dither generator + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Functions for generating dither noise +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** @brief State needed to generate a dither signal */ +typedef struct PaUtilTriangularDitherGenerator{ + unsigned long previous; + unsigned long randSeed1; + unsigned long randSeed2; +} PaUtilTriangularDitherGenerator; + + +/** @brief Initialize dither state */ +void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *ditherState ); + + +/** + @brief Calculate 2 LSB dither signal with a triangular distribution. + Ranged for adding to a 1 bit right-shifted 32 bit integer + prior to >>15. eg: +

    +    signed long in = *
    +    signed long dither = PaUtil_Generate16BitTriangularDither( ditherState );
    +    signed short out = (signed short)(((in>>1) + dither) >> 15);
    +
    + @return + A signed long with a range of +32767 to -32768 +*/ +signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); + + +/** + @brief Calculate 2 LSB dither signal with a triangular distribution. + Ranged for adding to a pre-scaled float. +
    +    float in = *
    +    float dither = PaUtil_GenerateFloatTriangularDither( ditherState );
    +    // use smaller scaler to prevent overflow when we add the dither
    +    signed short out = (signed short)(in*(32766.0f) + dither );
    +
    + @return + A float with a range of -2.0 to +1.99999. +*/ +float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_DITHER_H */ diff --git a/portaudio-v19/pa_common/pa_endianness.h b/portaudio-v19/pa_common/pa_endianness.h new file mode 100644 index 000000000..3a573929b --- /dev/null +++ b/portaudio-v19/pa_common/pa_endianness.h @@ -0,0 +1,111 @@ +#ifndef PA_ENDIANNESS_H +#define PA_ENDIANNESS_H +/* + * $Id$ + * Portable Audio I/O Library current platform endianness macros + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Configure endianness symbols for the target processor. + + Arrange for either the PA_LITTLE_ENDIAN or PA_BIG_ENDIAN preprocessor symbols + to be defined. The one that is defined reflects the endianness of the target + platform and may be used to implement conditional compilation of byte-order + dependent code. + + If either PA_LITTLE_ENDIAN or PA_BIG_ENDIAN is defined already, then no attempt + is made to override that setting. This may be useful if you have a better way + of determining the platform's endianness. The autoconf mechanism uses this for + example. + + A PA_VALIDATE_ENDIANNESS macro is provided to compare the compile time + and runtime endiannes and raise an assertion if they don't match. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#if defined(PA_LITTLE_ENDIAN) || defined(PA_BIG_ENDIAN) + /* endianness define has been set externally, such as by autoconf */ + + #if defined(PA_LITTLE_ENDIAN) && defined(PA_BIG_ENDIAN) + #error both PA_LITTLE_ENDIAN and PA_BIG_ENDIAN have been defined externally to pa_endianness.h - only one endianness at a time please + #endif + +#else + /* endianness define has not been set externally */ + + /* set PA_LITTLE_ENDIAN or PA_BIG_ENDIAN by testing well known platform specific defines */ + + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + + #define PA_LITTLE_ENDIAN /* win32, assume intel byte order */ + + #else + +#endif + + #if !defined(PA_LITTLE_ENDIAN) && !defined(PA_BIG_ENDIAN) + /* + If the following error is raised, you either need to modify the code above + to automatically determine the endianness from other symbols defined on your + platform, or define either PA_LITTLE_ENDIAN or PA_BIG_ENDIAN externally. + */ + #error pa_endianness.h was unable to automatically determine the endianness of the target platform + #endif + +#endif + +/* PA_VALIDATE_ENDIANNESS compares the compile time and runtime endianness, + and raises an assertion if they don't match. must be included in + the context in which this macro is used. +*/ +#if defined(PA_LITTLE_ENDIAN) + #define PA_VALIDATE_ENDIANNESS \ + { \ + const long nativeOne = 1; \ + assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 1 ); \ + } +#elif defined(PA_BIG_ENDIAN) + #define PA_VALIDATE_ENDIANNESS \ + { \ + const long nativeOne = 1; \ + assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 0 ); \ + } +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_ENDIANNESS_H */ diff --git a/portaudio-v19/pa_common/pa_front.c b/portaudio-v19/pa_common/pa_front.c new file mode 100644 index 000000000..f330008a7 --- /dev/null +++ b/portaudio-v19/pa_common/pa_front.c @@ -0,0 +1,1978 @@ +/* + * $Id$ + * Portable Audio I/O Library Multi-Host API front end + * Validate function parameters and manage multiple host APIs. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* doxygen index page */ +/** @mainpage + +PortAudio is an open-source cross-platform ‘C’ library for audio input +and output. It is designed to simplify the porting of audio applications +between various platforms, and also to simplify the development of audio +software in general by hiding the complexities of device interfacing. + +See the PortAudio website for further information http://www.portaudio.com/ + +This documentation pertains to PortAudio V19, API version 2.0 which is +currently under development. API version 2.0 differs in a number of ways from +previous versions, please consult the enhancement proposals for further details: +http://www.portaudio.com/docs/proposals/index.html + +This documentation is under construction. Things you might be interested in +include: + +- The PortAudio API 2.0, as documented in portaudio.h + +- The TODO List + +Feel free to pick an item off TODO list and fix/implement it. You may want to +enquire about status on the PortAudio mailing list first. +*/ + + +/** @file + @brief Implements public PortAudio API, checks some errors, forwards to + host API implementations. + + Implements the functions defined in the PortAudio API, checks for + some parameter and state inconsistencies and forwards API requests to + specific Host API implementations (via the interface declared in + pa_hostapi.h), and Streams (via the interface declared in pa_stream.h). + + This file handles initialization and termination of Host API + implementations via initializers stored in the paHostApiInitializers + global variable. + + Some utility functions declared in pa_util.h are implemented in this file. + + All PortAudio API functions can be conditionally compiled with logging code. + To compile with logging, define the PA_LOG_API_CALLS precompiler symbol. + + @todo Consider adding host API specific error text in Pa_GetErrorText() for + paUnanticipatedHostError + + @todo Consider adding a new error code for when (inputParameters == NULL) + && (outputParameters == NULL) + + @todo review whether Pa_CloseStream() should call the interface's + CloseStream function if aborting the stream returns an error code. + + @todo Create new error codes if a NULL buffer pointer, or a + zero frame count is passed to Pa_ReadStream or Pa_WriteStream. +*/ + + +#include +#include +#include +#include +#include /* needed by PA_VALIDATE_ENDIANNESS */ + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_endianness.h" +#include "pa_types.h" +#include "pa_hostapi.h" +#include "pa_stream.h" + +#include "pa_trace.h" + + +#define PA_VERSION_ 1899 +#define PA_VERSION_TEXT_ "PortAudio V19-devel" + + + +/* #define PA_LOG_API_CALLS */ + +/* + The basic format for log messages is described below. If you need to + add any log messages, please follow this format. + + Function entry (void function): + + "FunctionName called.\n" + + Function entry (non void function): + + "FunctionName called:\n" + "\tParam1Type param1: param1Value\n" + "\tParam2Type param2: param2Value\n" (etc...) + + + Function exit (no return value): + + "FunctionName returned.\n" + + Function exit (simple return value): + + "FunctionName returned:\n" + "\tReturnType: returnValue\n\n" + + If the return type is an error code, the error text is displayed in () + + If the return type is not an error code, but has taken a special value + because an error occurred, then the reason for the error is shown in [] + + If the return type is a struct ptr, the struct is dumped. + + See the code below for examples +*/ + + +int Pa_GetVersion( void ) +{ + return PA_VERSION_; +} + + +const char* Pa_GetVersionText( void ) +{ + return PA_VERSION_TEXT_; +} + + + +#define PA_LAST_HOST_ERROR_TEXT_LENGTH_ 1024 + +static char lastHostErrorText_[ PA_LAST_HOST_ERROR_TEXT_LENGTH_ + 1 ] = {0}; + +static PaHostErrorInfo lastHostErrorInfo_ = { (PaHostApiTypeId)-1, 0, lastHostErrorText_ }; + + +void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode, + const char *errorText ) +{ + lastHostErrorInfo_.hostApiType = hostApiType; + lastHostErrorInfo_.errorCode = errorCode; + + strncpy( lastHostErrorText_, errorText, PA_LAST_HOST_ERROR_TEXT_LENGTH_ ); +} + + +void PaUtil_DebugPrint( const char *format, ... ) +{ + va_list ap; + + va_start( ap, format ); + vfprintf( stderr, format, ap ); + va_end( ap ); + + fflush( stderr ); +} + + +static PaUtilHostApiRepresentation **hostApis_ = 0; +static int hostApisCount_ = 0; +static int initializationCount_ = 0; +static int deviceCount_ = 0; + +PaUtilStreamRepresentation *firstOpenStream_ = NULL; + + +#define PA_IS_INITIALISED_ (initializationCount_ != 0) + + +static int CountHostApiInitializers( void ) +{ + int result = 0; + + while( paHostApiInitializers[ result ] != 0 ) + ++result; + return result; +} + + +static void TerminateHostApis( void ) +{ + /* terminate in reverse order from initialization */ + + while( hostApisCount_ > 0 ) + { + --hostApisCount_; + hostApis_[hostApisCount_]->Terminate( hostApis_[hostApisCount_] ); + } + hostApisCount_ = 0; + deviceCount_ = 0; + + if( hostApis_ != 0 ) + PaUtil_FreeMemory( hostApis_ ); + hostApis_ = 0; +} + + +static PaError InitializeHostApis( void ) +{ + PaError result = paNoError; + int i, initializerCount, baseDeviceIndex; + + initializerCount = CountHostApiInitializers(); + + hostApis_ = (PaUtilHostApiRepresentation**)PaUtil_AllocateMemory( + sizeof(PaUtilHostApiRepresentation*) * initializerCount ); + if( !hostApis_ ) + { + result = paInsufficientMemory; + goto error; + } + + hostApisCount_ = 0; + deviceCount_ = 0; + baseDeviceIndex = 0; + + for( i=0; i< initializerCount; ++i ) + { + hostApis_[hostApisCount_] = NULL; + result = paHostApiInitializers[i]( &hostApis_[hostApisCount_], hostApisCount_ ); + if( result != paNoError ) + goto error; + + if( hostApis_[hostApisCount_] ) + { + + hostApis_[hostApisCount_]->privatePaFrontInfo.baseDeviceIndex = baseDeviceIndex; + + if( hostApis_[hostApisCount_]->info.defaultInputDevice != paNoDevice ) + hostApis_[hostApisCount_]->info.defaultInputDevice += baseDeviceIndex; + + if( hostApis_[hostApisCount_]->info.defaultOutputDevice != paNoDevice ) + hostApis_[hostApisCount_]->info.defaultOutputDevice += baseDeviceIndex; + + baseDeviceIndex += hostApis_[hostApisCount_]->info.deviceCount; + deviceCount_ += hostApis_[hostApisCount_]->info.deviceCount; + + ++hostApisCount_; + } + } + + return result; + +error: + TerminateHostApis(); + return result; +} + + +/* + FindHostApi() finds the index of the host api to which + belongs and returns it. if is + non-null, the host specific device index is returned in it. + returns -1 if is out of range. + +*/ +static int FindHostApi( PaDeviceIndex device, int *hostSpecificDeviceIndex ) +{ + int i=0; + + if( !PA_IS_INITIALISED_ ) + return -1; + + if( device < 0 ) + return -1; + + while( i < hostApisCount_ + && device >= hostApis_[i]->info.deviceCount ) + { + + device -= hostApis_[i]->info.deviceCount; + ++i; + } + + if( i >= hostApisCount_ ) + return -1; + + if( hostSpecificDeviceIndex ) + *hostSpecificDeviceIndex = device; + + return i; +} + + +static void AddOpenStream( PaStream* stream ) +{ + ((PaUtilStreamRepresentation*)stream)->nextOpenStream = firstOpenStream_; + firstOpenStream_ = (PaUtilStreamRepresentation*)stream; +} + + +static void RemoveOpenStream( PaStream* stream ) +{ + PaUtilStreamRepresentation *previous = NULL; + PaUtilStreamRepresentation *current = firstOpenStream_; + + while( current != NULL ) + { + if( ((PaStream*)current) == stream ) + { + if( previous == NULL ) + { + firstOpenStream_ = current->nextOpenStream; + } + else + { + previous->nextOpenStream = current->nextOpenStream; + } + return; + } + else + { + previous = current; + current = current->nextOpenStream; + } + } +} + + +static void CloseOpenStreams( void ) +{ + /* we call Pa_CloseStream() here to ensure that the same destruction + logic is used for automatically closed streams */ + + while( firstOpenStream_ != NULL ) + Pa_CloseStream( firstOpenStream_ ); +} + + +PaError Pa_Initialize( void ) +{ + PaError result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint( "Pa_Initialize called.\n" ); +#endif + + if( PA_IS_INITIALISED_ ) + { + ++initializationCount_; + result = paNoError; + } + else + { + PA_VALIDATE_TYPE_SIZES; + PA_VALIDATE_ENDIANNESS; + + PaUtil_InitializeClock(); + PaUtil_ResetTraceMessages(); + + result = InitializeHostApis(); + if( result == paNoError ) + ++initializationCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint( "Pa_Initialize returned:\n" ); + PaUtil_DebugPrint( "\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_Terminate( void ) +{ + PaError result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_Terminate called.\n" ); +#endif + + if( PA_IS_INITIALISED_ ) + { + if( --initializationCount_ == 0 ) + { + CloseOpenStreams(); + + TerminateHostApis(); + + PaUtil_DumpTraceMessages(); + } + result = paNoError; + } + else + { + result= paNotInitialized; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_Terminate returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ) +{ + return &lastHostErrorInfo_; +} + + +const char *Pa_GetErrorText( PaError errorCode ) +{ + const char *result; + + switch( errorCode ) + { + case paNoError: result = "Success"; break; + case paNotInitialized: result = "PortAudio not initialized"; break; + /** @todo could catenate the last host error text to result in the case of paUnanticipatedHostError */ + case paUnanticipatedHostError: result = "Unanticipated host error"; break; + case paInvalidChannelCount: result = "Invalid number of channels"; break; + case paInvalidSampleRate: result = "Invalid sample rate"; break; + case paInvalidDevice: result = "Invalid device"; break; + case paInvalidFlag: result = "Invalid flag"; break; + case paSampleFormatNotSupported: result = "Sample format not supported"; break; + case paBadIODeviceCombination: result = "Illegal combination of I/O devices"; break; + case paInsufficientMemory: result = "Insufficient memory"; break; + case paBufferTooBig: result = "Buffer too big"; break; + case paBufferTooSmall: result = "Buffer too small"; break; + case paNullCallback: result = "No callback routine specified"; break; + case paBadStreamPtr: result = "Invalid stream pointer"; break; + case paTimedOut: result = "Wait timed out"; break; + case paInternalError: result = "Internal PortAudio error"; break; + case paDeviceUnavailable: result = "Device unavailable"; break; + case paIncompatibleHostApiSpecificStreamInfo: result = "Incompatible host API specific stream info"; break; + case paStreamIsStopped: result = "Stream is stopped"; break; + case paStreamIsNotStopped: result = "Stream is not stopped"; break; + case paInputOverflowed: result = "Input overflowed"; break; + case paOutputUnderflowed: result = "Output underflowed"; break; + case paHostApiNotFound: result = "Host API not found"; break; + case paInvalidHostApi: result = "Invalid host API"; break; + case paCanNotReadFromACallbackStream: result = "Can't read from a callback stream"; break; + case paCanNotWriteToACallbackStream: result = "Can't write to a callback stream"; break; + case paCanNotReadFromAnOutputOnlyStream: result = "Can't read from an output only stream"; break; + case paCanNotWriteToAnInputOnlyStream: result = "Can't write to an input only stream"; break; + default: result = "Illegal error number"; break; + } + return result; +} + + +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ) +{ + PaHostApiIndex result; + int i; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiTypeIdToHostApiIndex called:\n" ); + PaUtil_DebugPrint("\tPaHostApiTypeId type: %d\n", type ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paHostApiNotFound; + + for( i=0; i < hostApisCount_; ++i ) + { + if( hostApis_[i]->info.type == type ) + { + result = i; + break; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiTypeIdToHostApiIndex returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaError PaUtil_GetHostApiRepresentation( struct PaUtilHostApiRepresentation **hostApi, + PaHostApiTypeId type ) +{ + PaError result; + int i; + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paHostApiNotFound; + + for( i=0; i < hostApisCount_; ++i ) + { + if( hostApis_[i]->info.type == type ) + { + *hostApi = hostApis_[i]; + result = paNoError; + break; + } + } + } + + return result; +} + + +PaError PaUtil_DeviceIndexToHostApiDeviceIndex( + PaDeviceIndex *hostApiDevice, PaDeviceIndex device, struct PaUtilHostApiRepresentation *hostApi ) +{ + PaError result; + PaDeviceIndex x; + + x = device - hostApi->privatePaFrontInfo.baseDeviceIndex; + + if( x < 0 || x >= hostApi->info.deviceCount ) + { + result = paInvalidDevice; + } + else + { + *hostApiDevice = x; + result = paNoError; + } + + return result; +} + + +PaHostApiIndex Pa_GetHostApiCount( void ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiCount called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = hostApisCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiCount returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex %d\n\n", result ); +#endif + + return result; +} + + +PaHostApiIndex Pa_GetDefaultHostApi( void ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultHostApi called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paDefaultHostApiIndex; + + /* internal consistency check: make sure that the default host api + index is within range */ + + if( result < 0 || result >= hostApisCount_ ) + { + result = paInternalError; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultHostApi returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex %d\n\n", result ); +#endif + + return result; +} + + +const PaHostApiInfo* Pa_GetHostApiInfo( PaHostApiIndex hostApi ) +{ + PaHostApiInfo *info; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo called:\n" ); + PaUtil_DebugPrint("\tPaHostApiIndex hostApi: %d\n", hostApi ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + info = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: NULL [ PortAudio not initialized ]\n\n" ); +#endif + + } + else if( hostApi < 0 || hostApi >= hostApisCount_ ) + { + info = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: NULL [ hostApi out of range ]\n\n" ); +#endif + + } + else + { + info = &hostApis_[hostApi]->info; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: 0x%p\n", info ); + PaUtil_DebugPrint("\t{" ); + PaUtil_DebugPrint("\t\tint structVersion: %d\n", info->structVersion ); + PaUtil_DebugPrint("\t\tPaHostApiTypeId type: %d\n", info->type ); + PaUtil_DebugPrint("\t\tconst char *name: %s\n\n", info->name ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return info; +} + + +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, int hostApiDeviceIndex ) +{ + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiDeviceIndexToPaDeviceIndex called:\n" ); + PaUtil_DebugPrint("\tPaHostApiIndex hostApi: %d\n", hostApi ); + PaUtil_DebugPrint("\tint hostApiDeviceIndex: %d\n", hostApiDeviceIndex ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + if( hostApi < 0 || hostApi >= hostApisCount_ ) + { + result = paInvalidHostApi; + } + else + { + if( hostApiDeviceIndex < 0 || + hostApiDeviceIndex >= hostApis_[hostApi]->info.deviceCount ) + { + result = paInvalidDevice; + } + else + { + result = hostApis_[hostApi]->privatePaFrontInfo.baseDeviceIndex + hostApiDeviceIndex; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiDeviceIndexToPaDeviceIndex returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDeviceCount( void ) +{ + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceCount called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = deviceCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceCount returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDefaultInputDevice( void ) +{ + PaHostApiIndex hostApi; + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultInputDevice called.\n" ); +#endif + + hostApi = Pa_GetDefaultHostApi(); + if( hostApi < 0 ) + { + result = paNoDevice; + } + else + { + result = hostApis_[hostApi]->info.defaultInputDevice; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultInputDevice returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDefaultOutputDevice( void ) +{ + PaHostApiIndex hostApi; + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultOutputDevice called.\n" ); +#endif + + hostApi = Pa_GetDefaultHostApi(); + if( hostApi < 0 ) + { + result = paNoDevice; + } + else + { + result = hostApis_[hostApi]->info.defaultOutputDevice; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultOutputDevice returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) +{ + int hostSpecificDeviceIndex; + int hostApiIndex = FindHostApi( device, &hostSpecificDeviceIndex ); + PaDeviceInfo *result; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo called:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex device: %d\n", device ); +#endif + + if( hostApiIndex < 0 ) + { + result = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceInfo* NULL [ invalid device index ]\n\n" ); +#endif + + } + else + { + result = hostApis_[hostApiIndex]->deviceInfos[ hostSpecificDeviceIndex ]; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceInfo*: 0x%p:\n", result ); + PaUtil_DebugPrint("\t{\n" ); + + PaUtil_DebugPrint("\t\tint structVersion: %d\n", result->structVersion ); + PaUtil_DebugPrint("\t\tconst char *name: %s\n", result->name ); + PaUtil_DebugPrint("\t\tPaHostApiIndex hostApi: %d\n", result->hostApi ); + PaUtil_DebugPrint("\t\tint maxInputChannels: %d\n", result->maxInputChannels ); + PaUtil_DebugPrint("\t\tint maxOutputChannels: %d\n", result->maxOutputChannels ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return result; +} + + +/* + SampleFormatIsValid() returns 1 if sampleFormat is a sample format + defined in portaudio.h, or 0 otherwise. +*/ +static int SampleFormatIsValid( PaSampleFormat format ) +{ + switch( format & ~paNonInterleaved ) + { + case paFloat32: return 1; + case paInt16: return 1; + case paInt32: return 1; + case paInt24: return 1; + case paInt8: return 1; + case paUInt8: return 1; + case paCustomFormat: return 1; + default: return 0; + } +} + +/* + NOTE: make sure this validation list is kept syncronised with the one in + pa_hostapi.h + + ValidateOpenStreamParameters() checks that parameters to Pa_OpenStream() + conform to the expected values as described below. This function is + also designed to be used with the proposed Pa_IsFormatSupported() function. + + There are basically two types of validation that could be performed: + Generic conformance validation, and device capability mismatch + validation. This function performs only generic conformance validation. + Validation that would require knowledge of device capabilities is + not performed because of potentially complex relationships between + combinations of parameters - for example, even if the sampleRate + seems ok, it might not be for a duplex stream - we have no way of + checking this in an API-neutral way, so we don't try. + + On success the function returns PaNoError and fills in hostApi, + hostApiInputDeviceID, and hostApiOutputDeviceID fields. On failure + the function returns an error code indicating the first encountered + parameter error. + + + If ValidateOpenStreamParameters() returns paNoError, the following + assertions are guaranteed to be true. + + - at least one of inputParameters & outputParmeters is valid (not NULL) + + - if inputParameters & outputParameters are both valid, that + inputParameters->device & outputParameters->device both use the same host api + + PaDeviceIndex inputParameters->device + - is within range (0 to Pa_GetDeviceCount-1) Or: + - is paUseHostApiSpecificDeviceSpecification and + inputParameters->hostApiSpecificStreamInfo is non-NULL and refers + to a valid host api + + int inputParameters->channelCount + - if inputParameters->device is not paUseHostApiSpecificDeviceSpecification, channelCount is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat inputParameters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *inputParameters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the input device's host Api + + PaDeviceIndex outputParmeters->device + - is within range (0 to Pa_GetDeviceCount-1) + + int outputParmeters->channelCount + - if inputDevice is valid, channelCount is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat outputParmeters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *outputParmeters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the output device's host Api + + double sampleRate + - is not an 'absurd' rate (less than 1000. or greater than 200000.) + - sampleRate is NOT validated against device capabilities + + PaStreamFlags streamFlags + - unused platform neutral flags are zero + - paNeverDropInput is only used for full-duplex callback streams with + variable buffer size (paFramesPerBufferUnspecified) +*/ +static PaError ValidateOpenStreamParameters( + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + PaUtilHostApiRepresentation **hostApi, + PaDeviceIndex *hostApiInputDevice, + PaDeviceIndex *hostApiOutputDevice ) +{ + int inputHostApiIndex = -1, /* Surpress uninitialised var warnings: compiler does */ + outputHostApiIndex = -1; /* not see that if inputParameters and outputParame- */ + /* ters are both nonzero, these indices are set. */ + + if( (inputParameters == NULL) && (outputParameters == NULL) ) + { + return paInvalidDevice; /** @todo should be a new error code "invalid device parameters" or something */ + } + else + { + if( inputParameters == NULL ) + { + *hostApiInputDevice = paNoDevice; + } + else if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + if( inputParameters->hostApiSpecificStreamInfo ) + { + inputHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( + ((PaUtilHostApiSpecificStreamInfoHeader*)inputParameters->hostApiSpecificStreamInfo)->hostApiType ); + + if( inputHostApiIndex != -1 ) + { + *hostApiInputDevice = paUseHostApiSpecificDeviceSpecification; + *hostApi = hostApis_[inputHostApiIndex]; + } + else + { + return paInvalidDevice; + } + } + else + { + return paInvalidDevice; + } + } + else + { + if( inputParameters->device < 0 || inputParameters->device >= deviceCount_ ) + return paInvalidDevice; + + inputHostApiIndex = FindHostApi( inputParameters->device, hostApiInputDevice ); + if( inputHostApiIndex < 0 ) + return paInternalError; + + *hostApi = hostApis_[inputHostApiIndex]; + + if( inputParameters->channelCount <= 0 ) + return paInvalidChannelCount; + + if( !SampleFormatIsValid( inputParameters->sampleFormat ) ) + return paSampleFormatNotSupported; + + if( inputParameters->hostApiSpecificStreamInfo != NULL ) + { + if( ((PaUtilHostApiSpecificStreamInfoHeader*)inputParameters->hostApiSpecificStreamInfo)->hostApiType + != (*hostApi)->info.type ) + return paIncompatibleHostApiSpecificStreamInfo; + } + } + + if( outputParameters == NULL ) + { + *hostApiOutputDevice = paNoDevice; + } + else if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + if( outputParameters->hostApiSpecificStreamInfo ) + { + outputHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( + ((PaUtilHostApiSpecificStreamInfoHeader*)outputParameters->hostApiSpecificStreamInfo)->hostApiType ); + + if( outputHostApiIndex != -1 ) + { + *hostApiOutputDevice = paUseHostApiSpecificDeviceSpecification; + *hostApi = hostApis_[outputHostApiIndex]; + } + else + { + return paInvalidDevice; + } + } + else + { + return paInvalidDevice; + } + } + else + { + if( outputParameters->device < 0 || outputParameters->device >= deviceCount_ ) + return paInvalidDevice; + + outputHostApiIndex = FindHostApi( outputParameters->device, hostApiOutputDevice ); + if( outputHostApiIndex < 0 ) + return paInternalError; + + *hostApi = hostApis_[outputHostApiIndex]; + + if( outputParameters->channelCount <= 0 ) + return paInvalidChannelCount; + + if( !SampleFormatIsValid( outputParameters->sampleFormat ) ) + return paSampleFormatNotSupported; + + if( outputParameters->hostApiSpecificStreamInfo != NULL ) + { + if( ((PaUtilHostApiSpecificStreamInfoHeader*)outputParameters->hostApiSpecificStreamInfo)->hostApiType + != (*hostApi)->info.type ) + return paIncompatibleHostApiSpecificStreamInfo; + } + } + + if( (inputParameters != NULL) && (outputParameters != NULL) ) + { + /* ensure that both devices use the same API */ + if( inputHostApiIndex != outputHostApiIndex ) + return paBadIODeviceCombination; + } + } + + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + return paInvalidSampleRate; + + if( ((streamFlags & ~paPlatformSpecificFlags) & ~(paClipOff | paDitherOff | paNeverDropInput | paPrimeOutputBuffersUsingStreamCallback ) ) != 0 ) + return paInvalidFlag; + + if( streamFlags & paNeverDropInput ) + { + /* must be a callback stream */ + if( !streamCallback ) + return paInvalidFlag; + + /* must be a full duplex stream */ + if( (inputParameters == NULL) || (outputParameters == NULL) ) + return paInvalidFlag; + + /* must use paFramesPerBufferUnspecified */ + if( framesPerBuffer != paFramesPerBufferUnspecified ) + return paInvalidFlag; + } + + return paNoError; +} + + +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiInputDevice, hostApiOutputDevice; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported called:\n" ); + + if( inputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: 0x%p\n", inputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex inputParameters->device: %d\n", inputParameters->device ); + PaUtil_DebugPrint("\tint inputParameters->channelCount: %d\n", inputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat inputParameters->sampleFormat: %d\n", inputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime inputParameters->suggestedLatency: %f\n", inputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *inputParameters->hostApiSpecificStreamInfo: 0x%p\n", inputParameters->hostApiSpecificStreamInfo ); + } + + if( outputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: 0x%p\n", outputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex outputParameters->device: %d\n", outputParameters->device ); + PaUtil_DebugPrint("\tint outputParameters->channelCount: %d\n", outputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat outputParameters->sampleFormat: %d\n", outputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime outputParameters->suggestedLatency: %f\n", outputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *outputParameters->hostApiSpecificStreamInfo: 0x%p\n", outputParameters->hostApiSpecificStreamInfo ); + } + + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + result = ValidateOpenStreamParameters( inputParameters, + outputParameters, + sampleRate, 0, paNoFlag, 0, + &hostApi, + &hostApiInputDevice, + &hostApiOutputDevice ); + if( result != paNoError ) + { +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + + if( inputParameters ) + { + hostApiInputParameters.device = hostApiInputDevice; + hostApiInputParameters.channelCount = inputParameters->channelCount; + hostApiInputParameters.sampleFormat = inputParameters->sampleFormat; + hostApiInputParameters.suggestedLatency = inputParameters->suggestedLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = inputParameters->hostApiSpecificStreamInfo; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputParameters ) + { + hostApiOutputParameters.device = hostApiOutputDevice; + hostApiOutputParameters.channelCount = outputParameters->channelCount; + hostApiOutputParameters.sampleFormat = outputParameters->sampleFormat; + hostApiOutputParameters.suggestedLatency = outputParameters->suggestedLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = outputParameters->hostApiSpecificStreamInfo; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + result = hostApi->IsFormatSupported( hostApi, + hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + if( result == paFormatIsSupported ) + PaUtil_DebugPrint("\tPaError: 0 [ paFormatIsSupported ]\n\n" ); + else + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiInputDevice, hostApiOutputDevice; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream called:\n" ); + PaUtil_DebugPrint("\tPaStream** stream: 0x%p\n", stream ); + + if( inputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: 0x%p\n", inputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex inputParameters->device: %d\n", inputParameters->device ); + PaUtil_DebugPrint("\tint inputParameters->channelCount: %d\n", inputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat inputParameters->sampleFormat: %d\n", inputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime inputParameters->suggestedLatency: %f\n", inputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *inputParameters->hostApiSpecificStreamInfo: 0x%p\n", inputParameters->hostApiSpecificStreamInfo ); + } + + if( outputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: 0x%p\n", outputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex outputParameters->device: %d\n", outputParameters->device ); + PaUtil_DebugPrint("\tint outputParameters->channelCount: %d\n", outputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat outputParameters->sampleFormat: %d\n", outputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime outputParameters->suggestedLatency: %f\n", outputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *outputParameters->hostApiSpecificStreamInfo: 0x%p\n", outputParameters->hostApiSpecificStreamInfo ); + } + + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); + PaUtil_DebugPrint("\tunsigned long framesPerBuffer: %d\n", framesPerBuffer ); + PaUtil_DebugPrint("\tPaStreamFlags streamFlags: 0x%x\n", streamFlags ); + PaUtil_DebugPrint("\tPaStreamCallback *streamCallback: 0x%p\n", streamCallback ); + PaUtil_DebugPrint("\tvoid *userData: 0x%p\n", userData ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + /* Check for parameter errors. + NOTE: make sure this validation list is kept syncronised with the one + in pa_hostapi.h + */ + + if( stream == NULL ) + { + result = paBadStreamPtr; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + result = ValidateOpenStreamParameters( inputParameters, + outputParameters, + sampleRate, framesPerBuffer, + streamFlags, streamCallback, + &hostApi, + &hostApiInputDevice, + &hostApiOutputDevice ); + if( result != paNoError ) + { +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + + if( inputParameters ) + { + hostApiInputParameters.device = hostApiInputDevice; + hostApiInputParameters.channelCount = inputParameters->channelCount; + hostApiInputParameters.sampleFormat = inputParameters->sampleFormat; + hostApiInputParameters.suggestedLatency = inputParameters->suggestedLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = inputParameters->hostApiSpecificStreamInfo; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputParameters ) + { + hostApiOutputParameters.device = hostApiOutputDevice; + hostApiOutputParameters.channelCount = outputParameters->channelCount; + hostApiOutputParameters.sampleFormat = outputParameters->sampleFormat; + hostApiOutputParameters.suggestedLatency = outputParameters->suggestedLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = outputParameters->hostApiSpecificStreamInfo; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + result = hostApi->OpenStream( hostApi, stream, + hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate, framesPerBuffer, streamFlags, streamCallback, userData ); + + if( result == paNoError ) + AddOpenStream( *stream ); + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): 0x%p\n", *stream ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_OpenDefaultStream( PaStream** stream, + int inputChannelCount, + int outputChannelCount, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenDefaultStream called:\n" ); + PaUtil_DebugPrint("\tPaStream** stream: 0x%p\n", stream ); + PaUtil_DebugPrint("\tint inputChannelCount: %d\n", inputChannelCount ); + PaUtil_DebugPrint("\tint outputChannelCount: %d\n", outputChannelCount ); + PaUtil_DebugPrint("\tPaSampleFormat sampleFormat: %d\n", sampleFormat ); + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); + PaUtil_DebugPrint("\tunsigned long framesPerBuffer: %d\n", framesPerBuffer ); + PaUtil_DebugPrint("\tPaStreamCallback *streamCallback: 0x%p\n", streamCallback ); + PaUtil_DebugPrint("\tvoid *userData: 0x%p\n", userData ); +#endif + + + if( inputChannelCount > 0 ) + { + hostApiInputParameters.device = Pa_GetDefaultInputDevice(); + hostApiInputParameters.channelCount = inputChannelCount; + hostApiInputParameters.sampleFormat = sampleFormat; + /* defaultHighInputLatency is used below instead of + defaultLowInputLatency because it is more important for the default + stream to work reliably than it is for it to work with the lowest + latency. + */ + hostApiInputParameters.suggestedLatency = + Pa_GetDeviceInfo( hostApiInputParameters.device )->defaultHighInputLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = NULL; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputChannelCount > 0 ) + { + hostApiOutputParameters.device = Pa_GetDefaultOutputDevice(); + hostApiOutputParameters.channelCount = outputChannelCount; + hostApiOutputParameters.sampleFormat = sampleFormat; + /* defaultHighOutputLatency is used below instead of + defaultLowOutputLatency because it is more important for the default + stream to work reliably than it is for it to work with the lowest + latency. + */ + hostApiOutputParameters.suggestedLatency = + Pa_GetDeviceInfo( hostApiOutputParameters.device )->defaultHighOutputLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = NULL; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + + result = Pa_OpenStream( + stream, hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate, framesPerBuffer, paNoFlag, streamCallback, userData ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenDefaultStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): 0x%p", *stream ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError PaUtil_ValidateStreamPointer( PaStream* stream ) +{ + if( !PA_IS_INITIALISED_ ) return paNotInitialized; + + if( stream == NULL ) return paBadStreamPtr; + + if( ((PaUtilStreamRepresentation*)stream)->magic != PA_STREAM_MAGIC ) + return paBadStreamPtr; + + return paNoError; +} + + +PaError Pa_CloseStream( PaStream* stream ) +{ + PaUtilStreamInterface *interface; + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_CloseStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + /* always remove the open stream from our list, even if this function + eventually returns an error. Otherwise CloseOpenStreams() will + get stuck in an infinite loop */ + RemoveOpenStream( stream ); /* be sure to call this _before_ closing the stream */ + + if( result == paNoError ) + { + interface = PA_STREAM_INTERFACE(stream); + + /* abort the stream if it isn't stopped */ + result = interface->IsStopped( stream ); + if( result == 1 ) + result = paNoError; + else if( result == 0 ) + result = interface->Abort( stream ); + + if( result == paNoError ) /** @todo REVIEW: shouldn't we close anyway? */ + result = interface->Close( stream ); + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_CloseStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_SetStreamFinishedCallback called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); + PaUtil_DebugPrint("\tPaStreamFinishedCallback* streamFinishedCallback: 0x%p\n", streamFinishedCallback ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = paStreamIsNotStopped ; + } + if( result == 1 ) + { + PA_STREAM_REP( stream )->streamFinishedCallback = streamFinishedCallback; + result = paNoError; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_SetStreamFinishedCallback returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; + +} + + +PaError Pa_StartStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StartStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = paStreamIsNotStopped ; + } + else if( result == 1 ) + { + result = PA_STREAM_INTERFACE(stream)->Start( stream ); + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StartStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_StopStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StopStream called\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Stop( stream ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StopStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_AbortStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_AbortStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Abort( stream ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_AbortStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_IsStreamStopped( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamStopped called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamStopped returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_IsStreamActive( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamActive called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + result = PA_STREAM_INTERFACE(stream)->IsActive( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamActive returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + const PaStreamInfo *result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo returned:\n" ); + PaUtil_DebugPrint("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n\n", result, error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = &PA_STREAM_REP( stream )->streamInfo; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo returned:\n" ); + PaUtil_DebugPrint("\tconst PaStreamInfo*: 0x%p:\n", result ); + PaUtil_DebugPrint("\t{" ); + + PaUtil_DebugPrint("\t\tint structVersion: %d\n", result->structVersion ); + PaUtil_DebugPrint("\t\tPaTime inputLatency: %f\n", result->inputLatency ); + PaUtil_DebugPrint("\t\tPaTime outputLatency: %f\n", result->outputLatency ); + PaUtil_DebugPrint("\t\tdouble sampleRate: %f\n", result->sampleRate ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return result; +} + + +PaTime Pa_GetStreamTime( PaStream *stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + PaTime result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime returned:\n" ); + PaUtil_DebugPrint("\tPaTime: 0 [PaError error:%d ( %s )]\n\n", result, error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetTime( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime returned:\n" ); + PaUtil_DebugPrint("\tPaTime: %g\n\n", result ); +#endif + + } + + return result; +} + + +double Pa_GetStreamCpuLoad( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + double result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + + result = 0.0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad returned:\n" ); + PaUtil_DebugPrint("\tdouble: 0.0 [PaError error: %d ( %s )]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetCpuLoad( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad returned:\n" ); + PaUtil_DebugPrint("\tdouble: %g\n\n", result ); +#endif + + } + + return result; +} + + +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_ReadStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + if( frames == 0 ) + { + /* XXX: Should we not allow the implementation to signal any overflow condition? */ + result = paNoError; + } + else if( buffer == 0 ) + { + result = paBadBufferPtr; + } + else + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Read( stream, buffer, frames ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_ReadStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_WriteStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + if( frames == 0 ) + { + /* XXX: Should we not allow the implementation to signal any underflow condition? */ + result = paNoError; + } + else if( buffer == 0 ) + { + result = paBadBufferPtr; + } + else + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Write( stream, buffer, frames ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_WriteStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + +signed long Pa_GetStreamReadAvailable( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + signed long result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable returned:\n" ); + PaUtil_DebugPrint("\tunsigned long: 0 [ PaError error: %d ( %s ) ]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetReadAvailable( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + } + + return result; +} + + +signed long Pa_GetStreamWriteAvailable( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + signed long result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable returned:\n" ); + PaUtil_DebugPrint("\tunsigned long: 0 [ PaError error: %d ( %s ) ]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetWriteAvailable( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + } + + return result; +} + + +PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetSampleSize called:\n" ); + PaUtil_DebugPrint("\tPaSampleFormat format: %d\n", format ); +#endif + + switch( format & ~paNonInterleaved ) + { + + case paUInt8: + case paInt8: + result = 1; + break; + + case paInt16: + result = 2; + break; + + case paInt24: + result = 3; + break; + + case paFloat32: + case paInt32: + result = 4; + break; + + default: + result = paSampleFormatNotSupported; + break; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetSampleSize returned:\n" ); + if( result > 0 ) + PaUtil_DebugPrint("\tint: %d\n\n", result ); + else + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return (PaError) result; +} + diff --git a/portaudio-v19/pa_common/pa_hostapi.h b/portaudio-v19/pa_common/pa_hostapi.h new file mode 100644 index 000000000..fec4e4871 --- /dev/null +++ b/portaudio-v19/pa_common/pa_hostapi.h @@ -0,0 +1,244 @@ +#ifndef PA_HOSTAPI_H +#define PA_HOSTAPI_H +/* + * $Id$ + * Portable Audio I/O Library + * host api representation + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + host APIs. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** **FOR THE USE OF pa_front.c ONLY** + Do NOT use fields in this structure, they my change at any time. + Use functions defined in pa_util.h if you think you need functionality + which can be derived from here. +*/ +typedef struct PaUtilPrivatePaFrontHostApiInfo { + + + unsigned long baseDeviceIndex; +}PaUtilPrivatePaFrontHostApiInfo; + + +/** The common header for all data structures whose pointers are passed through + the hostApiSpecificStreamInfo field of the PaStreamParameters structure. + Note that in order to keep the public PortAudio interface clean, this structure + is not used explicitly when declaring hostApiSpecificStreamInfo data structures. + However, some code in pa_front depends on the first 3 members being equivalent + with this structure. + @see PaStreamParameters +*/ +typedef struct PaUtilHostApiSpecificStreamInfoHeader +{ + unsigned long size; /**< size of whole structure including this header */ + PaHostApiTypeId hostApiType; /**< host API for which this data is intended */ + unsigned long version; /**< structure version */ +} PaUtilHostApiSpecificStreamInfoHeader; + + + +/** A structure representing the interface to a host API. Contains both + concrete data and pointers to functions which implement the interface. +*/ +typedef struct PaUtilHostApiRepresentation { + PaUtilPrivatePaFrontHostApiInfo privatePaFrontInfo; + + /** The host api implementation should populate the info field. In the + case of info.defaultInputDevice and info.defaultOutputDevice the + values stored should be 0 based indices within the host api's own + device index range (0 to deviceCount). These values will be converted + to global device indices by pa_front after PaUtilHostApiInitializer() + returns. + */ + PaHostApiInfo info; + + PaDeviceInfo** deviceInfos; + + /** + (*Terminate)() is guaranteed to be called with a valid + parameter, which was previously returned from the same implementation's + initializer. + */ + void (*Terminate)( struct PaUtilHostApiRepresentation *hostApi ); + + /** + The inputParameters and outputParameters pointers should not be saved + as they will not remain valid after OpenStream is called. + + + The following guarantees are made about parameters to (*OpenStream)(): + + [NOTE: the following list up to *END PA FRONT VALIDATIONS* should be + kept in sync with the one for ValidateOpenStreamParameters and + Pa_OpenStream in pa_front.c] + + PaHostApiRepresentation *hostApi + - is valid for this implementation + + PaStream** stream + - is non-null + + - at least one of inputParameters & outputParmeters is valid (not NULL) + + - if inputParameters & outputParmeters are both valid, that + inputParameters->device & outputParmeters->device both use the same host api + + PaDeviceIndex inputParameters->device + - is within range (0 to Pa_CountDevices-1) Or: + - is paUseHostApiSpecificDeviceSpecification and + inputParameters->hostApiSpecificStreamInfo is non-NULL and refers + to a valid host api + + int inputParameters->numChannels + - if inputParameters->device is not paUseHostApiSpecificDeviceSpecification, numInputChannels is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat inputParameters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *inputParameters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the input device's host Api + + PaDeviceIndex outputParmeters->device + - is within range (0 to Pa_CountDevices-1) + + int outputParmeters->numChannels + - if inputDevice is valid, numInputChannels is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat outputParmeters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *outputParmeters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the output device's host Api + + double sampleRate + - is not an 'absurd' rate (less than 1000. or greater than 200000.) + - sampleRate is NOT validated against device capabilities + + PaStreamFlags streamFlags + - unused platform neutral flags are zero + - paNeverDropInput is only used for full-duplex callback streams + with variable buffer size (paFramesPerBufferUnspecified) + + [*END PA FRONT VALIDATIONS*] + + + The following validations MUST be performed by (*OpenStream)(): + + - check that input device can support numInputChannels + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if inputStreamInfo is supplied, validate its contents, + or return an error if no inputStreamInfo is expected + + - check that output device can support numOutputChannels + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if outputStreamInfo is supplied, validate its contents, + or return an error if no outputStreamInfo is expected + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if necessary + + - validate inputLatency and outputLatency + + - validate any platform specific flags, if flags are supplied they + must be valid. + */ + PaError (*OpenStream)( struct PaUtilHostApiRepresentation *hostApi, + PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerCallback, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + + PaError (*IsFormatSupported)( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +} PaUtilHostApiRepresentation; + + +/** Prototype for the initialization function which must be implemented by every + host API. + + @see paHostApiInitializers +*/ +typedef PaError PaUtilHostApiInitializer( PaUtilHostApiRepresentation**, PaHostApiIndex ); + + +/** paHostApiInitializers is a NULL-terminated array of host API initialization + functions. These functions are called by pa_front to initialize the host APIs + when the client calls Pa_Initialize(). + + There is a platform specific file which defines paHostApiInitializers for that + platform, pa_win/pa_win_hostapis.c contains the Win32 definitions for example. +*/ +extern PaUtilHostApiInitializer *paHostApiInitializers[]; + + +/** The index of the default host API in the paHostApiInitializers array. + + There is a platform specific file which defines paDefaultHostApiIndex for that + platform, see pa_win/pa_win_hostapis.c for example. +*/ +extern int paDefaultHostApiIndex; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_HOSTAPI_H */ diff --git a/portaudio-v19/pa_common/pa_process.c b/portaudio-v19/pa_common/pa_process.c new file mode 100644 index 000000000..837bbef35 --- /dev/null +++ b/portaudio-v19/pa_common/pa_process.c @@ -0,0 +1,1763 @@ +/* + * $Id$ + * Portable Audio I/O Library + * streamCallback <-> host buffer processing adapter + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Buffer Processor implementation. + + The code in this file is not optimised yet - although it's not clear that + it needs to be. there may appear to be redundancies + that could be factored into common functions, but the redundanceis are left + intentionally as each appearance may have different optimisation possibilities. + + The optimisations which are planned involve only converting data in-place + where possible, rather than copying to the temp buffer(s). + + Note that in the extreme case of being able to convert in-place, and there + being no conversion necessary there should be some code which short-circuits + the operation. + + @todo Consider cache tilings for intereave<->deinterleave. + + @todo implement timeInfo->currentTime int PaUtil_BeginBufferProcessing() + + @todo specify and implement some kind of logical policy for handling the + underflow and overflow stream flags when the underflow/overflow overlaps + multiple user buffers/callbacks. + + @todo provide support for priming the buffers with data from the callback. + The client interface is now implemented through PaUtil_SetNoInput() + which sets bp->hostInputChannels[0][0].data to zero. However this is + currently only implemented in NonAdaptingProcess(). It shouldn't be + needed for AdaptingInputOnlyProcess() (no priming should ever be + requested for AdaptingInputOnlyProcess()). + Not sure if additional work should be required to make it work with + AdaptingOutputOnlyProcess, but it definitely is required for + AdaptingProcess. + + @todo implement PaUtil_SetNoOutput for AdaptingProcess + + @todo don't allocate temp buffers for blocking streams unless they are + needed. At the moment they are needed, but perhaps for host APIs + where the implementation passes a buffer to the host they could be + used. +*/ + + +#include +#include /* memset() */ + +#include "pa_process.h" +#include "pa_util.h" + + +#define PA_FRAMES_PER_TEMP_BUFFER_WHEN_HOST_BUFFER_SIZE_IS_UNKNOWN_ 1024 + +#define PA_MIN_( a, b ) ( ((a)<(b)) ? (a) : (b) ) + + +/* greatest common divisor - PGCD in French */ +static unsigned long GCD( unsigned long a, unsigned long b ) +{ + return (b==0) ? a : GCD( b, a%b); +} + +/* least common multiple - PPCM in French */ +static unsigned long LCM( unsigned long a, unsigned long b ) +{ + return (a*b) / GCD(a,b); +} + +#define PA_MAX_( a, b ) (((a) > (b)) ? (a) : (b)) + +static unsigned long CalculateFrameShift( unsigned long M, unsigned long N ) +{ + unsigned long result = 0; + unsigned long i; + unsigned long lcm; + + assert( M > 0 ); + assert( N > 0 ); + + lcm = LCM( M, N ); + for( i = M; i < lcm; i += M ) + result = PA_MAX_( result, i % N ); + + return result; +} + + +PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp, + int inputChannelCount, PaSampleFormat userInputSampleFormat, + PaSampleFormat hostInputSampleFormat, + int outputChannelCount, PaSampleFormat userOutputSampleFormat, + PaSampleFormat hostOutputSampleFormat, + double sampleRate, + PaStreamFlags streamFlags, + unsigned long framesPerUserBuffer, + unsigned long framesPerHostBuffer, + PaUtilHostBufferSizeMode hostBufferSizeMode, + PaStreamCallback *streamCallback, void *userData ) +{ + PaError result = paNoError; + PaError bytesPerSample; + unsigned long tempInputBufferSize, tempOutputBufferSize; + + if( streamFlags & paNeverDropInput ) + { + /* paNeverDropInput is only valid for full-duplex callback streams, with an unspecified number of frames per buffer. */ + if( !streamCallback || !(inputChannelCount > 0 && outputChannelCount > 0) || + framesPerUserBuffer != paFramesPerBufferUnspecified ) + return paInvalidFlag; + } + + /* initialize buffer ptrs to zero so they can be freed if necessary in error */ + bp->tempInputBuffer = 0; + bp->tempInputBufferPtrs = 0; + bp->tempOutputBuffer = 0; + bp->tempOutputBufferPtrs = 0; + + bp->framesPerUserBuffer = framesPerUserBuffer; + bp->framesPerHostBuffer = framesPerHostBuffer; + + bp->inputChannelCount = inputChannelCount; + bp->outputChannelCount = outputChannelCount; + + bp->hostBufferSizeMode = hostBufferSizeMode; + + bp->hostInputChannels[0] = bp->hostInputChannels[1] = 0; + bp->hostOutputChannels[0] = bp->hostOutputChannels[1] = 0; + + if( framesPerUserBuffer == 0 ) /* streamCallback will accept any buffer size */ + { + bp->useNonAdaptingProcess = 1; + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + + if( hostBufferSizeMode == paUtilFixedHostBufferSize + || hostBufferSizeMode == paUtilBoundedHostBufferSize ) + { + bp->framesPerTempBuffer = framesPerHostBuffer; + } + else /* unknown host buffer size */ + { + bp->framesPerTempBuffer = PA_FRAMES_PER_TEMP_BUFFER_WHEN_HOST_BUFFER_SIZE_IS_UNKNOWN_; + } + } + else + { + bp->framesPerTempBuffer = framesPerUserBuffer; + + if( hostBufferSizeMode == paUtilFixedHostBufferSize + && framesPerHostBuffer % framesPerUserBuffer == 0 ) + { + bp->useNonAdaptingProcess = 1; + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + } + else + { + bp->useNonAdaptingProcess = 0; + + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + /* full duplex */ + if( hostBufferSizeMode == paUtilFixedHostBufferSize ) + { + unsigned long frameShift = + CalculateFrameShift( framesPerHostBuffer, framesPerUserBuffer ); + + if( framesPerUserBuffer > framesPerHostBuffer ) + { + bp->initialFramesInTempInputBuffer = frameShift; + bp->initialFramesInTempOutputBuffer = 0; + } + else + { + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = frameShift; + } + } + else /* variable host buffer size, add framesPerUserBuffer latency */ + { + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = framesPerUserBuffer; + } + } + else + { + /* half duplex */ + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + } + } + } + + + bp->framesInTempInputBuffer = bp->initialFramesInTempInputBuffer; + bp->framesInTempOutputBuffer = bp->initialFramesInTempOutputBuffer; + + + if( inputChannelCount > 0 ) + { + bytesPerSample = Pa_GetSampleSize( hostInputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerHostInputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bytesPerSample = Pa_GetSampleSize( userInputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerUserInputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bp->inputConverter = + PaUtil_SelectConverter( hostInputSampleFormat, userInputSampleFormat, streamFlags ); + + bp->inputZeroer = PaUtil_SelectZeroer( hostInputSampleFormat ); + + bp->userInputIsInterleaved = (userInputSampleFormat & paNonInterleaved)?0:1; + + + tempInputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserInputSample * inputChannelCount; + + bp->tempInputBuffer = PaUtil_AllocateMemory( tempInputBufferSize ); + if( bp->tempInputBuffer == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + if( bp->framesInTempInputBuffer > 0 ) + memset( bp->tempInputBuffer, 0, tempInputBufferSize ); + + if( userInputSampleFormat & paNonInterleaved ) + { + bp->tempInputBufferPtrs = + (void **)PaUtil_AllocateMemory( sizeof(void*)*inputChannelCount ); + if( bp->tempInputBufferPtrs == 0 ) + { + result = paInsufficientMemory; + goto error; + } + } + + bp->hostInputChannels[0] = (PaUtilChannelDescriptor*) + PaUtil_AllocateMemory( sizeof(PaUtilChannelDescriptor) * inputChannelCount * 2); + if( bp->hostInputChannels[0] == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + bp->hostInputChannels[1] = &bp->hostInputChannels[0][inputChannelCount]; + } + + if( outputChannelCount > 0 ) + { + bytesPerSample = Pa_GetSampleSize( hostOutputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerHostOutputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bytesPerSample = Pa_GetSampleSize( userOutputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerUserOutputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bp->outputConverter = + PaUtil_SelectConverter( userOutputSampleFormat, hostOutputSampleFormat, streamFlags ); + + bp->outputZeroer = PaUtil_SelectZeroer( hostOutputSampleFormat ); + + bp->userOutputIsInterleaved = (userOutputSampleFormat & paNonInterleaved)?0:1; + + tempOutputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserOutputSample * outputChannelCount; + + bp->tempOutputBuffer = PaUtil_AllocateMemory( tempOutputBufferSize ); + if( bp->tempOutputBuffer == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + if( bp->framesInTempOutputBuffer > 0 ) + memset( bp->tempOutputBuffer, 0, tempOutputBufferSize ); + + if( userOutputSampleFormat & paNonInterleaved ) + { + bp->tempOutputBufferPtrs = + (void **)PaUtil_AllocateMemory( sizeof(void*)*outputChannelCount ); + if( bp->tempOutputBufferPtrs == 0 ) + { + result = paInsufficientMemory; + goto error; + } + } + + bp->hostOutputChannels[0] = (PaUtilChannelDescriptor*) + PaUtil_AllocateMemory( sizeof(PaUtilChannelDescriptor)*outputChannelCount * 2 ); + if( bp->hostOutputChannels[0] == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + bp->hostOutputChannels[1] = &bp->hostOutputChannels[0][outputChannelCount]; + } + + PaUtil_InitializeTriangularDitherState( &bp->ditherGenerator ); + + bp->samplePeriod = 1. / sampleRate; + + bp->streamCallback = streamCallback; + bp->userData = userData; + + return result; + +error: + if( bp->tempInputBuffer ) + PaUtil_FreeMemory( bp->tempInputBuffer ); + + if( bp->tempInputBufferPtrs ) + PaUtil_FreeMemory( bp->tempInputBufferPtrs ); + + if( bp->hostInputChannels[0] ) + PaUtil_FreeMemory( bp->hostInputChannels[0] ); + + if( bp->tempOutputBuffer ) + PaUtil_FreeMemory( bp->tempOutputBuffer ); + + if( bp->tempOutputBufferPtrs ) + PaUtil_FreeMemory( bp->tempOutputBufferPtrs ); + + if( bp->hostOutputChannels[0] ) + PaUtil_FreeMemory( bp->hostOutputChannels[0] ); + + return result; +} + + +void PaUtil_TerminateBufferProcessor( PaUtilBufferProcessor* bp ) +{ + if( bp->tempInputBuffer ) + PaUtil_FreeMemory( bp->tempInputBuffer ); + + if( bp->tempInputBufferPtrs ) + PaUtil_FreeMemory( bp->tempInputBufferPtrs ); + + if( bp->hostInputChannels[0] ) + PaUtil_FreeMemory( bp->hostInputChannels[0] ); + + if( bp->tempOutputBuffer ) + PaUtil_FreeMemory( bp->tempOutputBuffer ); + + if( bp->tempOutputBufferPtrs ) + PaUtil_FreeMemory( bp->tempOutputBufferPtrs ); + + if( bp->hostOutputChannels[0] ) + PaUtil_FreeMemory( bp->hostOutputChannels[0] ); +} + + +void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bp ) +{ + unsigned long tempInputBufferSize, tempOutputBufferSize; + + bp->framesInTempInputBuffer = bp->initialFramesInTempInputBuffer; + bp->framesInTempOutputBuffer = bp->initialFramesInTempOutputBuffer; + + if( bp->framesInTempInputBuffer > 0 ) + { + tempInputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserInputSample * bp->inputChannelCount; + memset( bp->tempInputBuffer, 0, tempInputBufferSize ); + } + + if( bp->framesInTempOutputBuffer > 0 ) + { + tempOutputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserOutputSample * bp->outputChannelCount; + memset( bp->tempOutputBuffer, 0, tempOutputBufferSize ); + } +} + + +unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bp ) +{ + return bp->initialFramesInTempInputBuffer; +} + + +unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bp ) +{ + return bp->initialFramesInTempOutputBuffer; +} + + +void PaUtil_SetInputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + if( frameCount == 0 ) + bp->hostInputFrameCount[0] = bp->framesPerHostBuffer; + else + bp->hostInputFrameCount[0] = frameCount; +} + + +void PaUtil_SetNoInput( PaUtilBufferProcessor* bp ) +{ + assert( bp->inputChannelCount > 0 ); + + bp->hostInputChannels[0][0].data = 0; +} + + +void PaUtil_SetInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[0][channel].data = data; + bp->hostInputChannels[0][channel].stride = stride; +} + + +void PaUtil_SetInterleavedInputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->inputChannelCount; + + assert( firstChannel < bp->inputChannelCount ); + assert( firstChannel + channelCount <= bp->inputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostInputChannels[0][channel+i].data = p; + p += bp->bytesPerHostInputSample; + bp->hostInputChannels[0][channel+i].stride = channelCount; + } +} + + +void PaUtil_SetNonInterleavedInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[0][channel].data = data; + bp->hostInputChannels[0][channel].stride = 1; +} + + +void PaUtil_Set2ndInputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + bp->hostInputFrameCount[1] = frameCount; +} + + +void PaUtil_Set2ndInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[1][channel].data = data; + bp->hostInputChannels[1][channel].stride = stride; +} + + +void PaUtil_Set2ndInterleavedInputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->inputChannelCount; + + assert( firstChannel < bp->inputChannelCount ); + assert( firstChannel + channelCount <= bp->inputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostInputChannels[1][channel+i].data = p; + p += bp->bytesPerHostInputSample; + bp->hostInputChannels[1][channel+i].stride = channelCount; + } +} + + +void PaUtil_Set2ndNonInterleavedInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[1][channel].data = data; + bp->hostInputChannels[1][channel].stride = 1; +} + + +void PaUtil_SetOutputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + if( frameCount == 0 ) + bp->hostOutputFrameCount[0] = bp->framesPerHostBuffer; + else + bp->hostOutputFrameCount[0] = frameCount; +} + + +void PaUtil_SetNoOutput( PaUtilBufferProcessor* bp ) +{ + assert( bp->outputChannelCount > 0 ); + + bp->hostOutputChannels[0][0].data = 0; +} + + +void PaUtil_SetOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->outputChannelCount ); + assert( data != NULL ); + + bp->hostOutputChannels[0][channel].data = data; + bp->hostOutputChannels[0][channel].stride = stride; +} + + +void PaUtil_SetInterleavedOutputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->outputChannelCount; + + assert( firstChannel < bp->outputChannelCount ); + assert( firstChannel + channelCount <= bp->outputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + PaUtil_SetOutputChannel( bp, channel + i, p, channelCount ); + p += bp->bytesPerHostOutputSample; + } +} + + +void PaUtil_SetNonInterleavedOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->outputChannelCount ); + + PaUtil_SetOutputChannel( bp, channel, data, 1 ); +} + + +void PaUtil_Set2ndOutputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + bp->hostOutputFrameCount[1] = frameCount; +} + + +void PaUtil_Set2ndOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->outputChannelCount ); + assert( data != NULL ); + + bp->hostOutputChannels[1][channel].data = data; + bp->hostOutputChannels[1][channel].stride = stride; +} + + +void PaUtil_Set2ndInterleavedOutputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->outputChannelCount; + + assert( firstChannel < bp->outputChannelCount ); + assert( firstChannel + channelCount <= bp->outputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + PaUtil_Set2ndOutputChannel( bp, channel + i, p, channelCount ); + p += bp->bytesPerHostOutputSample; + } +} + + +void PaUtil_Set2ndNonInterleavedOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->outputChannelCount ); + + PaUtil_Set2ndOutputChannel( bp, channel, data, 1 ); +} + + +void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bp, + PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags callbackStatusFlags ) +{ + bp->timeInfo = timeInfo; + + /* the first streamCallback will be called to process samples which are + currently in the input buffer before the ones starting at the timeInfo time */ + + bp->timeInfo->inputBufferAdcTime -= bp->framesInTempInputBuffer * bp->samplePeriod; + + bp->timeInfo->currentTime = 0; /** FIXME: @todo time info currentTime not implemented */ + + /* the first streamCallback will be called to generate samples which will be + outputted after the frames currently in the output buffer have been + outputted. */ + bp->timeInfo->outputBufferDacTime += bp->framesInTempOutputBuffer * bp->samplePeriod; + + bp->callbackStatusFlags = callbackStatusFlags; + + bp->hostInputFrameCount[1] = 0; + bp->hostOutputFrameCount[1] = 0; +} + + +/* + NonAdaptingProcess() is a simple buffer copying adaptor that can handle + both full and half duplex copies. It processes framesToProcess frames, + broken into blocks bp->framesPerTempBuffer long. + This routine can be used when the streamCallback doesn't care what length + the buffers are, or when framesToProcess is an integer multiple of + bp->framesPerTempBuffer, in which case streamCallback will always be called + with bp->framesPerTempBuffer samples. +*/ +static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostInputChannels, + PaUtilChannelDescriptor *hostOutputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *srcBytePtr, *destBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + + if( *streamCallbackResult == paContinue ) + { + do + { + frameCount = PA_MIN_( bp->framesPerTempBuffer, framesToGo ); + + /* configure user input buffer and convert input data (host -> user) */ + if( bp->inputChannelCount == 0 ) + { + /* no input */ + userInput = 0; + } + else /* there are input channels */ + { + /* + could use more elaborate logic here and sometimes process + buffers in-place. + */ + + destBytePtr = (unsigned char *)bp->tempInputBuffer; + + if( bp->userInputIsInterleaved ) + { + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + destSampleStrideSamples = 1; + destChannelStrideBytes = frameCount * bp->bytesPerUserInputSample; + + /* setup non-interleaved ptrs */ + for( i=0; iinputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->bytesPerUserInputSample * frameCount; + } + + userInput = bp->tempInputBufferPtrs; + } + + if( !bp->hostInputChannels[0][0].data ) + { + /* no input was supplied (see PaUtil_SetNoInput), so + zero the input buffer */ + + for( i=0; iinputChannelCount; ++i ) + { + bp->inputZeroer( destBytePtr, destSampleStrideSamples, frameCount ); + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + } + } + else + { + for( i=0; iinputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + } + } + + /* configure user output buffer */ + if( bp->outputChannelCount == 0 ) + { + /* no output */ + userOutput = 0; + } + else /* there are output channels */ + { + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->bytesPerUserOutputSample * frameCount; + } + + userOutput = bp->tempOutputBufferPtrs; + } + } + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + frameCount, bp->timeInfo, bp->callbackStatusFlags, bp->userData ); + + if( *streamCallbackResult == paAbort ) + { + /* callback returned paAbort, don't advance framesProcessed + and framesToGo, they will be handled below */ + } + else + { + bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod; + bp->timeInfo->outputBufferDacTime += frameCount * bp->samplePeriod; + + /* convert output data (user -> host) */ + + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) + { + /* + could use more elaborate logic here and sometimes process + buffers in-place. + */ + + srcBytePtr = (unsigned char *)bp->tempOutputBuffer; + + if( bp->userOutputIsInterleaved ) + { + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcSampleStrideSamples = 1; + srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample; + } + + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + } + } + while( framesToGo > 0 && *streamCallbackResult == paContinue ); + } + + if( framesToGo > 0 ) + { + /* zero any remaining frames output. There will only be remaining frames + if the callback has returned paComplete or paAbort */ + + frameCount = framesToGo; + + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) + { + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + } + + return framesProcessed; +} + + +/* + AdaptingInputOnlyProcess() is a half duplex input buffer processor. It + converts data from the input buffers into the temporary input buffer, + when the temporary input buffer is full, it calls the streamCallback. +*/ +static unsigned long AdaptingInputOnlyProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostInputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *destBytePtr; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + userOutput = 0; + + do + { + frameCount = ( bp->framesInTempInputBuffer + framesToGo > bp->framesPerUserBuffer ) + ? ( bp->framesPerUserBuffer - bp->framesInTempInputBuffer ) + : framesToGo; + + /* convert frameCount samples into temp buffer */ + + if( bp->userInputIsInterleaved ) + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->inputChannelCount * + bp->framesInTempInputBuffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->framesInTempInputBuffer; + + destSampleStrideSamples = 1; + destChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + + /* setup non-interleaved ptrs */ + for( i=0; iinputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->bytesPerUserInputSample * bp->framesPerUserBuffer; + } + + userInput = bp->tempInputBufferPtrs; + } + + for( i=0; iinputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + bp->framesInTempInputBuffer += frameCount; + + if( bp->framesInTempInputBuffer == bp->framesPerUserBuffer ) + { + /** + @todo (non-critical optimisation) + The conditional below implements the continue/complete/abort mechanism + simply by continuing on iterating through the input buffer, but not + passing the data to the callback. With care, the outer loop could be + terminated earlier, thus some unneeded conversion cycles would be + saved. + */ + if( *streamCallbackResult == paContinue ) + { + bp->timeInfo->outputBufferDacTime = 0; + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod; + } + + bp->framesInTempInputBuffer = 0; + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + }while( framesToGo > 0 ); + + return framesProcessed; +} + + +/* + AdaptingOutputOnlyProcess() is a half duplex output buffer processor. + It converts data from the temporary output buffer, to the output buffers, + when the temporary output buffer is empty, it calls the streamCallback. +*/ +static unsigned long AdaptingOutputOnlyProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostOutputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *srcBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + do + { + if( bp->framesInTempOutputBuffer == 0 && *streamCallbackResult == paContinue ) + { + userInput = 0; + + /* setup userOutput */ + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + userOutput = bp->tempOutputBufferPtrs; + } + + bp->timeInfo->inputBufferAdcTime = 0; + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + if( *streamCallbackResult == paAbort ) + { + /* if the callback returned paAbort, we disregard its output */ + } + else + { + bp->timeInfo->outputBufferDacTime += bp->framesPerUserBuffer * bp->samplePeriod; + + bp->framesInTempOutputBuffer = bp->framesPerUserBuffer; + } + } + + if( bp->framesInTempOutputBuffer > 0 ) + { + /* convert frameCount frames from user buffer to host buffer */ + + frameCount = PA_MIN_( bp->framesInTempOutputBuffer, framesToGo ); + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * bp->outputChannelCount * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = 1; + srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + bp->framesInTempOutputBuffer -= frameCount; + } + else + { + /* no more user data is available because the callback has returned + paComplete or paAbort. Fill the remainder of the host buffer + with zeros. + */ + + frameCount = framesToGo; + + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + + }while( framesToGo > 0 ); + + return framesProcessed; +} + +/* CopyTempOutputBuffersToHostOutputBuffers is called from AdaptingProcess to copy frames from + tempOutputBuffer to hostOutputChannels. This includes data conversion + and interleaving. +*/ +static void CopyTempOutputBuffersToHostOutputBuffers( PaUtilBufferProcessor *bp) +{ + unsigned long maxFramesToCopy; + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int frameCount; + unsigned char *srcBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + /* copy frames from user to host output buffers */ + while( bp->framesInTempOutputBuffer > 0 && + ((bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) > 0) ) + { + maxFramesToCopy = bp->framesInTempOutputBuffer; + + /* select the output buffer set (1st or 2nd) */ + if( bp->hostOutputFrameCount[0] > 0 ) + { + hostOutputChannels = bp->hostOutputChannels[0]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[0], maxFramesToCopy ); + } + else + { + hostOutputChannels = bp->hostOutputChannels[1]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[1], maxFramesToCopy ); + } + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * bp->outputChannelCount * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = 1; + srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + for( i=0; ioutputChannelCount; ++i ) + { + assert( hostOutputChannels[i].data != NULL ); + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + if( bp->hostOutputFrameCount[0] > 0 ) + bp->hostOutputFrameCount[0] -= frameCount; + else + bp->hostOutputFrameCount[1] -= frameCount; + + bp->framesInTempOutputBuffer -= frameCount; + } +} + +/* + AdaptingProcess is a full duplex adapting buffer processor. It converts + data from the temporary output buffer into the host output buffers, then + from the host input buffers into the temporary input buffers. Calling the + streamCallback when necessary. + When processPartialUserBuffers is 0, all available input data will be + consumed and all available output space will be filled. When + processPartialUserBuffers is non-zero, as many full user buffers + as possible will be processed, but partial buffers will not be consumed. +*/ +static unsigned long AdaptingProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, int processPartialUserBuffers ) +{ + void *userInput, *userOutput; + unsigned long framesProcessed = 0; + unsigned long framesAvailable; + unsigned long endProcessingMinFrameCount; + unsigned long maxFramesToCopy; + PaUtilChannelDescriptor *hostInputChannels, *hostOutputChannels; + unsigned int frameCount; + unsigned char *destBytePtr; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i, j; + + + framesAvailable = bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1];/* this is assumed to be the same as the output buffer's frame count */ + + if( processPartialUserBuffers ) + endProcessingMinFrameCount = 0; + else + endProcessingMinFrameCount = (bp->framesPerUserBuffer - 1); + + /* Fill host output with remaining frames in user output (tempOutputBuffer) */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); + + while( framesAvailable > endProcessingMinFrameCount ) + { + + if( bp->framesInTempOutputBuffer == 0 && *streamCallbackResult != paContinue ) + { + /* the callback will not be called any more, so zero what remains + of the host output buffers */ + + for( i=0; i<2; ++i ) + { + frameCount = bp->hostOutputFrameCount[i]; + if( frameCount > 0 ) + { + hostOutputChannels = bp->hostOutputChannels[i]; + + for( j=0; joutputChannelCount; ++j ) + { + bp->outputZeroer( hostOutputChannels[j].data, + hostOutputChannels[j].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[j].data = ((unsigned char*)hostOutputChannels[j].data) + + frameCount * hostOutputChannels[j].stride * bp->bytesPerHostOutputSample; + } + bp->hostOutputFrameCount[i] = 0; + } + } + } + + + /* copy frames from host to user input buffers */ + while( bp->framesInTempInputBuffer < bp->framesPerUserBuffer && + ((bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1]) > 0) ) + { + maxFramesToCopy = bp->framesPerUserBuffer - bp->framesInTempInputBuffer; + + /* select the input buffer set (1st or 2nd) */ + if( bp->hostInputFrameCount[0] > 0 ) + { + hostInputChannels = bp->hostInputChannels[0]; + frameCount = PA_MIN_( bp->hostInputFrameCount[0], maxFramesToCopy ); + } + else + { + hostInputChannels = bp->hostInputChannels[1]; + frameCount = PA_MIN_( bp->hostInputFrameCount[1], maxFramesToCopy ); + } + + /* configure conversion destination pointers */ + if( bp->userInputIsInterleaved ) + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->inputChannelCount * + bp->framesInTempInputBuffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + } + else /* user input is not interleaved */ + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->framesInTempInputBuffer; + + destSampleStrideSamples = 1; + destChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + } + + for( i=0; iinputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + if( bp->hostInputFrameCount[0] > 0 ) + bp->hostInputFrameCount[0] -= frameCount; + else + bp->hostInputFrameCount[1] -= frameCount; + + bp->framesInTempInputBuffer += frameCount; + + /* update framesAvailable and framesProcessed based on input consumed + unless something is very wrong this will also correspond to the + amount of output generated */ + framesAvailable -= frameCount; + framesProcessed += frameCount; + } + + /* call streamCallback */ + if( bp->framesInTempInputBuffer == bp->framesPerUserBuffer && + bp->framesInTempOutputBuffer == 0 ) + { + if( *streamCallbackResult == paContinue ) + { + /* setup userInput */ + if( bp->userInputIsInterleaved ) + { + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + for( i = 0; i < bp->inputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + } + + userInput = bp->tempInputBufferPtrs; + } + + /* setup userOutput */ + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + userOutput = bp->tempOutputBufferPtrs; + } + + /* call streamCallback */ + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + bp->timeInfo->inputBufferAdcTime += bp->framesPerUserBuffer * bp->samplePeriod; + bp->timeInfo->outputBufferDacTime += bp->framesPerUserBuffer * bp->samplePeriod; + + bp->framesInTempInputBuffer = 0; + + if( *streamCallbackResult == paAbort ) + bp->framesInTempOutputBuffer = 0; + else + bp->framesInTempOutputBuffer = bp->framesPerUserBuffer; + } + else + { + /* paComplete or paAbort has already been called. */ + + bp->framesInTempInputBuffer = 0; + } + } + + /* copy frames from user (tempOutputBuffer) to host output buffers (hostOutputChannels) + Means to process the user output provided by the callback. Has to be called after + each callback. */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); + + } + + return framesProcessed; +} + + +unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bp, int *streamCallbackResult ) +{ + unsigned long framesToProcess, framesToGo; + unsigned long framesProcessed = 0; + + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 + && bp->hostInputChannels[0][0].data /* input was supplied (see PaUtil_SetNoInput) */ + && bp->hostOutputChannels[0][0].data /* output was supplied (see PaUtil_SetNoOutput) */ ) + { + assert( (bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1]) == + (bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) ); + } + + assert( *streamCallbackResult == paContinue + || *streamCallbackResult == paComplete + || *streamCallbackResult == paAbort ); /* don't forget to pass in a valid callback result value */ + + if( bp->useNonAdaptingProcess ) + { + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 ) + { + /* full duplex non-adapting process, splice buffers if they are + different lengths */ + + framesToGo = bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]; /* relies on assert above for input/output equivalence */ + + do{ + unsigned long noInputInputFrameCount; + unsigned long *hostInputFrameCount; + PaUtilChannelDescriptor *hostInputChannels; + unsigned long noOutputOutputFrameCount; + unsigned long *hostOutputFrameCount; + PaUtilChannelDescriptor *hostOutputChannels; + unsigned long framesProcessedThisIteration; + + if( !bp->hostInputChannels[0][0].data ) + { + /* no input was supplied (see PaUtil_SetNoInput) + NonAdaptingProcess knows how to deal with this + */ + noInputInputFrameCount = framesToGo; + hostInputFrameCount = &noInputInputFrameCount; + hostInputChannels = 0; + } + else if( bp->hostInputFrameCount[0] != 0 ) + { + hostInputFrameCount = &bp->hostInputFrameCount[0]; + hostInputChannels = bp->hostInputChannels[0]; + } + else + { + hostInputFrameCount = &bp->hostInputFrameCount[1]; + hostInputChannels = bp->hostInputChannels[1]; + } + + if( !bp->hostOutputChannels[0][0].data ) + { + /* no output was supplied (see PaUtil_SetNoOutput) + NonAdaptingProcess knows how to deal with this + */ + noOutputOutputFrameCount = framesToGo; + hostOutputFrameCount = &noOutputOutputFrameCount; + hostOutputChannels = 0; + } + if( bp->hostOutputFrameCount[0] != 0 ) + { + hostOutputFrameCount = &bp->hostOutputFrameCount[0]; + hostOutputChannels = bp->hostOutputChannels[0]; + } + else + { + hostOutputFrameCount = &bp->hostOutputFrameCount[1]; + hostOutputChannels = bp->hostOutputChannels[1]; + } + + framesToProcess = PA_MIN_( *hostInputFrameCount, + *hostOutputFrameCount ); + + assert( framesToProcess != 0 ); + + framesProcessedThisIteration = NonAdaptingProcess( bp, streamCallbackResult, + hostInputChannels, hostOutputChannels, + framesToProcess ); + + *hostInputFrameCount -= framesProcessedThisIteration; + *hostOutputFrameCount -= framesProcessedThisIteration; + + framesProcessed += framesProcessedThisIteration; + framesToGo -= framesProcessedThisIteration; + + }while( framesToGo > 0 ); + } + else + { + /* half duplex non-adapting process, just process 1st and 2nd buffer */ + /* process first buffer */ + + framesToProcess = (bp->inputChannelCount != 0) + ? bp->hostInputFrameCount[0] + : bp->hostOutputFrameCount[0]; + + framesProcessed = NonAdaptingProcess( bp, streamCallbackResult, + bp->hostInputChannels[0], bp->hostOutputChannels[0], + framesToProcess ); + + /* process second buffer if provided */ + + framesToProcess = (bp->inputChannelCount != 0) + ? bp->hostInputFrameCount[1] + : bp->hostOutputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += NonAdaptingProcess( bp, streamCallbackResult, + bp->hostInputChannels[1], bp->hostOutputChannels[1], + framesToProcess ); + } + } + } + else /* block adaption necessary*/ + { + + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 ) + { + /* full duplex */ + + if( bp->hostBufferSizeMode == paUtilVariableHostBufferSizePartialUsageAllowed ) + { + framesProcessed = AdaptingProcess( bp, streamCallbackResult, + 0 /* dont process partial user buffers */ ); + } + else + { + framesProcessed = AdaptingProcess( bp, streamCallbackResult, + 1 /* process partial user buffers */ ); + } + } + else if( bp->inputChannelCount != 0 ) + { + /* input only */ + framesToProcess = bp->hostInputFrameCount[0]; + + framesProcessed = AdaptingInputOnlyProcess( bp, streamCallbackResult, + bp->hostInputChannels[0], framesToProcess ); + + framesToProcess = bp->hostInputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += AdaptingInputOnlyProcess( bp, streamCallbackResult, + bp->hostInputChannels[1], framesToProcess ); + } + } + else + { + /* output only */ + framesToProcess = bp->hostOutputFrameCount[0]; + + framesProcessed = AdaptingOutputOnlyProcess( bp, streamCallbackResult, + bp->hostOutputChannels[0], framesToProcess ); + + framesToProcess = bp->hostOutputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += AdaptingOutputOnlyProcess( bp, streamCallbackResult, + bp->hostOutputChannels[1], framesToProcess ); + } + } + } + + return framesProcessed; +} + + +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bp ) +{ + return (bp->framesInTempOutputBuffer) ? 0 : 1; +} + + +unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bp, + void **buffer, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostInputChannels; + unsigned int framesToCopy; + unsigned char *destBytePtr; + void **nonInterleavedDestPtrs; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + hostInputChannels = bp->hostInputChannels[0]; + framesToCopy = PA_MIN_( bp->hostInputFrameCount[0], frameCount ); + + if( bp->userInputIsInterleaved ) + { + destBytePtr = (unsigned char*)*buffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + + for( i=0; iinputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + framesToCopy, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + /* advance callers dest pointer (buffer) */ + *buffer = ((unsigned char *)*buffer) + + framesToCopy * bp->inputChannelCount * bp->bytesPerUserInputSample; + } + else + { + /* user input is not interleaved */ + + nonInterleavedDestPtrs = (void**)*buffer; + + destSampleStrideSamples = 1; + + for( i=0; iinputChannelCount; ++i ) + { + destBytePtr = (unsigned char*)nonInterleavedDestPtrs[i]; + + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + framesToCopy, &bp->ditherGenerator ); + + /* advance callers dest pointer (nonInterleavedDestPtrs[i]) */ + destBytePtr += bp->bytesPerUserInputSample * framesToCopy; + nonInterleavedDestPtrs[i] = destBytePtr; + + /* advance dest ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + } + + bp->hostInputFrameCount[0] -= framesToCopy; + + return framesToCopy; +} + +unsigned long PaUtil_CopyOutput( PaUtilBufferProcessor* bp, + const void ** buffer, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int framesToCopy; + unsigned char *srcBytePtr; + void **nonInterleavedSrcPtrs; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + hostOutputChannels = bp->hostOutputChannels[0]; + framesToCopy = PA_MIN_( bp->hostOutputFrameCount[0], frameCount ); + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = (unsigned char*)*buffer; + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + framesToCopy, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToCopy * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + /* advance callers source pointer (buffer) */ + *buffer = ((unsigned char *)*buffer) + + framesToCopy * bp->outputChannelCount * bp->bytesPerUserOutputSample; + + } + else + { + /* user output is not interleaved */ + + nonInterleavedSrcPtrs = (void**)*buffer; + + srcSampleStrideSamples = 1; + + for( i=0; ioutputChannelCount; ++i ) + { + srcBytePtr = (unsigned char*)nonInterleavedSrcPtrs[i]; + + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + framesToCopy, &bp->ditherGenerator ); + + + /* advance callers source pointer (nonInterleavedSrcPtrs[i]) */ + srcBytePtr += bp->bytesPerUserOutputSample * framesToCopy; + nonInterleavedSrcPtrs[i] = srcBytePtr; + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToCopy * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + bp->hostOutputFrameCount[0] += framesToCopy; + + return framesToCopy; +} + + +unsigned long PaUtil_ZeroOutput( PaUtilBufferProcessor* bp, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int framesToZero; + unsigned int i; + + hostOutputChannels = bp->hostOutputChannels[0]; + framesToZero = PA_MIN_( bp->hostOutputFrameCount[0], frameCount ); + + for( i=0; ioutputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + framesToZero ); + + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToZero * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + bp->hostOutputFrameCount[0] += framesToZero; + + return framesToZero; +} diff --git a/portaudio-v19/pa_common/pa_process.h b/portaudio-v19/pa_common/pa_process.h new file mode 100644 index 000000000..8d13d903a --- /dev/null +++ b/portaudio-v19/pa_common/pa_process.h @@ -0,0 +1,741 @@ +#ifndef PA_PROCESS_H +#define PA_PROCESS_H +/* + * $Id$ + * Portable Audio I/O Library callback buffer processing adapters + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Buffer Processor prototypes. A Buffer Processor performs buffer length + adaption, coordinates sample format conversion, and interleaves/deinterleaves + channels. + +

    Overview

    + + The "Buffer Processor" (PaUtilBufferProcessor) manages conversion of audio + data from host buffers to user buffers and back again. Where required, the + buffer processor takes care of converting between host and user sample formats, + interleaving and deinterleaving multichannel buffers, and adapting between host + and user buffers with different lengths. The buffer processor may be used with + full and half duplex streams, for both callback streams and blocking read/write + streams. + + One of the important capabilities provided by the buffer processor is + the ability to adapt between user and host buffer sizes of different lengths + with minimum latency. Although this task is relatively easy to perform when + the host buffer size is an integer multiple of the user buffer size, the + problem is more complicated when this is not the case - especially for + full-duplex callback streams. Where necessary the adaption is implemented by + internally buffering some input and/or output data. The buffer adation + algorithm used by the buffer processor was originally implemented by + Stephan Letz for the ASIO version of PortAudio, and is described in his + Callback_adaption_.pdf which is included in the distribution. + + The buffer processor performs sample conversion using the functions provided + by pa_converters.c. + + The following sections provide an overview of how to use the buffer processor. + Interested readers are advised to consult the host API implementations for + examples of buffer processor usage. + + +

    Initialization, resetting and termination

    + + When a stream is opened, the buffer processor should be initialized using + PaUtil_InitializeBufferProcessor. This function initializes internal state + and allocates temporary buffers as neccesary according to the supplied + configuration parameters. Some of the parameters correspond to those requested + by the user in their call to Pa_OpenStream(), others reflect the requirements + of the host API implementation - they indicate host buffer sizes, formats, + and the type of buffering which the Host API uses. The buffer processor should + be initialized for callback streams and blocking read/write streams. + + Call PaUtil_ResetBufferProcessor to clear any sample data which is present + in the buffer processor before starting to use it (for example when + Pa_StartStream is called). + + When the buffer processor is no longer used call + PaUtil_TerminateBufferProcessor. + + +

    Using the buffer processor for a callback stream

    + + The buffer processor's role in a callback stream is to take host input buffers + process them with the stream callback, and fill host output buffers. For a + full duplex stream, the buffer processor handles input and output simultaneously + due to the requirements of the minimum-latency buffer adation algorithm. + + When a host buffer becomes available, the implementation should call + the buffer processor to process the buffer. The buffer processor calls the + stream callback to consume and/or produce audio data as necessary. The buffer + processor will convert sample formats, interleave/deinterleave channels, + and slice or chunk the data to the appropriate buffer lengths according to + the requirements of the stream callback and the host API. + + To process a host buffer (or a pair of host buffers for a full-duplex stream) + use the following calling sequence: + + -# Call PaUtil_BeginBufferProcessing + -# For a stream which takes input: + - Call PaUtil_SetInputFrameCount with the number of frames in the host input + buffer. + - Call one of the following functions one or more times to tell the + buffer processor about the host input buffer(s): PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + - If the available host data is split accross two buffers (for example a + data range at the end of a circular buffer and another range at the + beginning of the circular buffer), also call + PaUtil_Set2ndInputFrameCount, PaUtil_Set2ndInputChannel, + PaUtil_Set2ndInterleavedInputChannels, + PaUtil_Set2ndNonInterleavedInputChannel as necessary to tell the buffer + processor about the second buffer. + -# For a stream which generates output: + - Call PaUtil_SetOutputFrameCount with the number of frames in the host + output buffer. + - Call one of the following functions one or more times to tell the + buffer processor about the host output buffer(s): PaUtil_SetOutputChannel, + PaUtil_SetInterleavedOutputChannels, PaUtil_SetNonInterleavedOutputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + - If the available host output buffer space is split accross two buffers + (for example a data range at the end of a circular buffer and another + range at the beginning of the circular buffer), call + PaUtil_Set2ndOutputFrameCount, PaUtil_Set2ndOutputChannel, + PaUtil_Set2ndInterleavedOutputChannels, + PaUtil_Set2ndNonInterleavedOutputChannel as necessary to tell the buffer + processor about the second buffer. + -# Call PaUtil_EndBufferProcessing, this function performs the actual data + conversion and processing. + + +

    Using the buffer processor for a blocking read/write stream

    + + Blocking read/write streams use the buffer processor to convert and copy user + output data to a host buffer, and to convert and copy host input data to + the user's buffer. The buffer processor does not perform any buffer adaption. + When using the buffer processor in a blocking read/write stream the input and + output conversion are performed separately by the PaUtil_CopyInput and + PaUtil_CopyOutput functions. + + To copy data from a host input buffer to the buffer(s) which the user supplies + to Pa_ReadStream, use the following calling sequence. + + - Repeat the following three steps until the user buffer(s) have been filled + with samples from the host input buffers: + -# Call PaUtil_SetInputFrameCount with the number of frames in the host + input buffer. + -# Call one of the following functions one or more times to tell the + buffer processor about the host input buffer(s): PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + -# Call PaUtil_CopyInput with the user buffer pointer (or a copy of the + array of buffer pointers for a non-interleaved stream) passed to + Pa_ReadStream, along with the number of frames in the user buffer(s). + Be careful to pass a copy of the user buffer pointers to + PaUtil_CopyInput because PaUtil_CopyInput advances the pointers to + the start of the next region to copy. + - PaUtil_CopyInput will not copy more data than is available in the + host buffer(s), so the above steps need to be repeated until the user + buffer(s) are full. + + + To copy data to the host output buffer from the user buffers(s) supplied + to Pa_WriteStream use the following calling sequence. + + - Repeat the following three steps until all frames from the user buffer(s) + have been copied to the host API: + -# Call PaUtil_SetOutputFrameCount with the number of frames in the host + output buffer. + -# Call one of the following functions one or more times to tell the + buffer processor about the host output buffer(s): PaUtil_SetOutputChannel, + PaUtil_SetInterleavedOutputChannels, PaUtil_SetNonInterleavedOutputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + -# Call PaUtil_CopyOutput with the user buffer pointer (or a copy of the + array of buffer pointers for a non-interleaved stream) passed to + Pa_WriteStream, along with the number of frames in the user buffer(s). + Be careful to pass a copy of the user buffer pointers to + PaUtil_CopyOutput because PaUtil_CopyOutput advances the pointers to + the start of the next region to copy. + - PaUtil_CopyOutput will not copy more data than fits in the host buffer(s), + so the above steps need to be repeated until all user data is copied. +*/ + + +#include "portaudio.h" +#include "pa_converters.h" +#include "pa_dither.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** @brief Mode flag passed to PaUtil_InitializeBufferProcessor indicating the type + of buffering that the host API uses. + + The mode used depends on whether the host API or the implementation manages + the buffers, and how these buffers are used (scatter gather, circular buffer). +*/ +typedef enum { +/** The host buffer size is a fixed known size. */ + paUtilFixedHostBufferSize, + +/** The host buffer size may vary, but has a known maximum size. */ + paUtilBoundedHostBufferSize, + +/** Nothing is known about the host buffer size. */ + paUtilUnknownHostBufferSize, + +/** The host buffer size varies, and the client does not require the buffer + processor to consume all of the input and fill all of the output buffer. This + is useful when the implementation has access to the host API's circular buffer + and only needs to consume/fill some of it, not necessarily all of it, with each + call to the buffer processor. This is the only mode where + PaUtil_EndBufferProcessing() may not consume the whole buffer. +*/ + paUtilVariableHostBufferSizePartialUsageAllowed +}PaUtilHostBufferSizeMode; + + +/** @brief An auxilliary data structure used internally by the buffer processor + to represent host input and output buffers. */ +typedef struct PaUtilChannelDescriptor{ + void *data; + unsigned int stride; /**< stride in samples, not bytes */ +}PaUtilChannelDescriptor; + + +/** @brief The main buffer processor data structure. + + Allocate one of these, initialize it with PaUtil_InitializeBufferProcessor + and terminate it with PaUtil_TerminateBufferProcessor. +*/ +typedef struct { + unsigned long framesPerUserBuffer; + unsigned long framesPerHostBuffer; + + PaUtilHostBufferSizeMode hostBufferSizeMode; + int useNonAdaptingProcess; + unsigned long framesPerTempBuffer; + + unsigned int inputChannelCount; + unsigned int bytesPerHostInputSample; + unsigned int bytesPerUserInputSample; + int userInputIsInterleaved; + PaUtilConverter *inputConverter; + PaUtilZeroer *inputZeroer; + + unsigned int outputChannelCount; + unsigned int bytesPerHostOutputSample; + unsigned int bytesPerUserOutputSample; + int userOutputIsInterleaved; + PaUtilConverter *outputConverter; + PaUtilZeroer *outputZeroer; + + unsigned long initialFramesInTempInputBuffer; + unsigned long initialFramesInTempOutputBuffer; + + void *tempInputBuffer; /**< used for slips, block adaption, and conversion. */ + void **tempInputBufferPtrs; /**< storage for non-interleaved buffer pointers, NULL for interleaved user input */ + unsigned long framesInTempInputBuffer; /**< frames remaining in input buffer from previous adaption iteration */ + + void *tempOutputBuffer; /**< used for slips, block adaption, and conversion. */ + void **tempOutputBufferPtrs; /**< storage for non-interleaved buffer pointers, NULL for interleaved user output */ + unsigned long framesInTempOutputBuffer; /**< frames remaining in input buffer from previous adaption iteration */ + + PaStreamCallbackTimeInfo *timeInfo; + + PaStreamCallbackFlags callbackStatusFlags; + + unsigned long hostInputFrameCount[2]; + PaUtilChannelDescriptor *hostInputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex output processing. + hostInputChannels[i].data is NULL when the caller + calls PaUtil_SetNoInput() + */ + unsigned long hostOutputFrameCount[2]; + PaUtilChannelDescriptor *hostOutputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex input processing. + hostOutputChannels[i].data is NULL when the caller + calls PaUtil_SetNoOutput() + */ + + PaUtilTriangularDitherGenerator ditherGenerator; + + double samplePeriod; + + PaStreamCallback *streamCallback; + void *userData; +} PaUtilBufferProcessor; + + +/** @name Initialization, termination, resetting and info */ +/*@{*/ + +/** Initialize a buffer processor's representation stored in a + PaUtilBufferProcessor structure. Be sure to call + PaUtil_TerminateBufferProcessor after finishing with a buffer processor. + + @param bufferProcessor The buffer processor structure to initialize. + + @param inputChannelCount The number of input channels as passed to + Pa_OpenStream or 0 for an output-only stream. + + @param userInputSampleFormat Format of user input samples, as passed to + Pa_OpenStream. This parameter is ignored for ouput-only streams. + + @param hostInputSampleFormat Format of host input samples. This parameter is + ignored for output-only streams. See note about host buffer interleave below. + + @param outputChannelCount The number of output channels as passed to + Pa_OpenStream or 0 for an input-only stream. + + @param userOutputSampleFormat Format of user output samples, as passed to + Pa_OpenStream. This parameter is ignored for input-only streams. + + @param hostOutputSampleFormat Format of host output samples. This parameter is + ignored for input-only streams. See note about host buffer interleave below. + + @param sampleRate Sample rate of the stream. The more accurate this is the + better - it is used for updating time stamps when adapting buffers. + + @param streamFlags Stream flags as passed to Pa_OpenStream, this parameter is + used for selecting special sample conversion options such as clipping and + dithering. + + @param framesPerUserBuffer Number of frames per user buffer, as requested + by the framesPerBuffer parameter to Pa_OpenStream. This parameter may be + zero to indicate that the user will accept any (and varying) buffer sizes. + + @param framesPerHostBuffer Specifies the number of frames per host buffer + for the fixed buffer size mode, and the maximum number of frames + per host buffer for the bounded host buffer size mode. It is ignored for + the other modes. + + @param hostBufferSizeMode A mode flag indicating the size variability of + host buffers that will be passed to the buffer processor. See + PaUtilHostBufferSizeMode for further details. + + @param streamCallback The user stream callback passed to Pa_OpenStream. + + @param userData The user data field passed to Pa_OpenStream. + + @note The interleave flag is ignored for host buffer formats. Host + interleave is determined by the use of different SetInput and SetOutput + functions. + + @return An error code indicating whether the initialization was successful. + If the error code is not PaNoError, the buffer processor was not initialized + and should not be used. + + @see Pa_OpenStream, PaUtilHostBufferSizeMode, PaUtil_TerminateBufferProcessor +*/ +PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bufferProcessor, + int inputChannelCount, PaSampleFormat userInputSampleFormat, + PaSampleFormat hostInputSampleFormat, + int outputChannelCount, PaSampleFormat userOutputSampleFormat, + PaSampleFormat hostOutputSampleFormat, + double sampleRate, + PaStreamFlags streamFlags, + unsigned long framesPerUserBuffer, /* 0 indicates don't care */ + unsigned long framesPerHostBuffer, + PaUtilHostBufferSizeMode hostBufferSizeMode, + PaStreamCallback *streamCallback, void *userData ); + + +/** Terminate a buffer processor's representation. Deallocates any temporary + buffers allocated by PaUtil_InitializeBufferProcessor. + + @param bufferProcessor The buffer processor structure to terminate. + + @see PaUtil_InitializeBufferProcessor. +*/ +void PaUtil_TerminateBufferProcessor( PaUtilBufferProcessor* bufferProcessor ); + + +/** Clear any internally buffered data. If you call + PaUtil_InitializeBufferProcessor in your OpenStream routine, make sure you + call PaUtil_ResetBufferProcessor in your StartStream call. + + @param bufferProcessor The buffer processor to reset. +*/ +void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bufferProcessor ); + + +/** Retrieve the input latency of a buffer processor. + + @param bufferProcessor The buffer processor examine. + + @return The input latency introduced by the buffer processor, in frames. + + @see PaUtil_GetBufferProcessorOutputLatency +*/ +unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bufferProcessor ); + +/** Retrieve the output latency of a buffer processor. + + @param bufferProcessor The buffer processor examine. + + @return The output latency introduced by the buffer processor, in frames. + + @see PaUtil_GetBufferProcessorInputLatency +*/ +unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bufferProcessor ); + +/*@}*/ + + +/** @name Host buffer pointer configuration + + Functions to set host input and output buffers, used by both callback streams + and blocking read/write streams. +*/ +/*@{*/ + + +/** Set the number of frames in the input host buffer(s) specified by the + PaUtil_Set*InputChannel functions. + + @param bufferProcessor The buffer processor. + + @param frameCount The number of host input frames. A 0 frameCount indicates to + use the framesPerHostBuffer value passed to PaUtil_InitializeBufferProcessor. + + @see PaUtil_SetNoInput, PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel +*/ +void PaUtil_SetInputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/** Indicate that no input is avalable. This function should be used when + priming the output of a full-duplex stream opened with the + paPrimeOutputBuffersUsingStreamCallback flag. Note that it is not necessary + to call this or any othe PaUtil_Set*Input* functions for ouput-only streams. + + @param bufferProcessor The buffer processor. +*/ +void PaUtil_SetNoInput( PaUtilBufferProcessor* bufferProcessor ); + + +/** Provide the buffer processor with a pointer to a host input channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. + @param stride The stride from one sample to the next, in samples. For + interleaved host buffers, the stride will usually be the same as the number of + channels in the buffer. +*/ +void PaUtil_SetInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + + +/** Provide the buffer processor with a pointer to an number of interleaved + host input channels. + + @param bufferProcessor The buffer processor. + @param firstChannel The first channel number. + @param data The buffer. + @param channelCount The number of interleaved channels in the buffer. If + channelCount is zero, the number of channels specified to + PaUtil_InitializeBufferProcessor will be used. +*/ +void PaUtil_SetInterleavedInputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + + +/** Provide the buffer processor with a pointer to one non-interleaved host + output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. +*/ +void PaUtil_SetNonInterleavedInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInputFrameCount +*/ +void PaUtil_Set2ndInputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInputChannel +*/ +void PaUtil_Set2ndInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInterleavedInputChannels +*/ +void PaUtil_Set2ndInterleavedInputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetNonInterleavedInputChannel +*/ +void PaUtil_Set2ndNonInterleavedInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Set the number of frames in the output host buffer(s) specified by the + PaUtil_Set*OutputChannel functions. + + @param bufferProcessor The buffer processor. + + @param frameCount The number of host output frames. A 0 frameCount indicates to + use the framesPerHostBuffer value passed to PaUtil_InitializeBufferProcessor. + + @see PaUtil_SetOutputChannel, PaUtil_SetInterleavedOutputChannels, + PaUtil_SetNonInterleavedOutputChannel +*/ +void PaUtil_SetOutputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/** Indicate that the output will be discarded. This function should be used + when implementing the paNeverDropInput mode for full duplex streams. + + @param bufferProcessor The buffer processor. +*/ +void PaUtil_SetNoOutput( PaUtilBufferProcessor* bufferProcessor ); + + +/** Provide the buffer processor with a pointer to a host output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. + @param stride The stride from one sample to the next, in samples. For + interleaved host buffers, the stride will usually be the same as the number of + channels in the buffer. +*/ +void PaUtil_SetOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + + +/** Provide the buffer processor with a pointer to a number of interleaved + host output channels. + + @param bufferProcessor The buffer processor. + @param firstChannel The first channel number. + @param data The buffer. + @param channelCount The number of interleaved channels in the buffer. If + channelCount is zero, the number of channels specified to + PaUtil_InitializeBufferProcessor will be used. +*/ +void PaUtil_SetInterleavedOutputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + + +/** Provide the buffer processor with a pointer to one non-interleaved host + output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. +*/ +void PaUtil_SetNonInterleavedOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetOutputFrameCount +*/ +void PaUtil_Set2ndOutputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetOutputChannel +*/ +void PaUtil_Set2ndOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetInterleavedOutputChannels +*/ +void PaUtil_Set2ndInterleavedOutputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetNonInterleavedOutputChannel +*/ +void PaUtil_Set2ndNonInterleavedOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + +/*@}*/ + + +/** @name Buffer processing functions for callback streams +*/ +/*@{*/ + +/** Commence processing a host buffer (or a pair of host buffers in the + full-duplex case) for a callback stream. + + @param bufferProcessor The buffer processor. + + @param timeInfo Timing information for the first sample of the host + buffer(s). This information may be adjusted when buffer adaption is being + performed. + + @param callbackStatusFlags Flags indicating whether underruns and overruns + have occurred since the last time the buffer processor was called. +*/ +void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bufferProcessor, + PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags callbackStatusFlags ); + + +/** Finish processing a host buffer (or a pair of host buffers in the + full-duplex case) for a callback stream. + + @param bufferProcessor The buffer processor. + + @param callbackResult On input, indicates a previous callback result, and on + exit, the result of the user stream callback, if it is called. + On entry callbackResult should contain one of { paContinue, paComplete, or + paAbort}. If paComplete is passed, the stream callback will not be called + but any audio that was generated by previous stream callbacks will be copied + to the output buffer(s). You can check whether the buffer processor's internal + buffer is empty by calling PaUtil_IsBufferProcessorOutputEmpty. + + If the stream callback is called its result is stored in *callbackResult. If + the stream callback returns paComplete or paAbort, all output buffers will be + full of valid data - some of which may be zeros to acount for data that + wasn't generated by the terminating callback. + + @return The number of frames processed. This usually corresponds to the + number of frames specified by the PaUtil_Set*FrameCount functions, exept in + the paUtilVariableHostBufferSizePartialUsageAllowed buffer size mode when a + smaller value may be returned. +*/ +unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bufferProcessor, + int *callbackResult ); + + +/** Determine whether any callback generated output remains in the bufffer + processor's internal buffers. This method may be used to determine when to + continue calling PaUtil_EndBufferProcessing() after the callback has returned + a callbackResult of paComplete. + + @param bufferProcessor The buffer processor. + + @return Returns non-zero when callback generated output remains in the internal + buffer and zero (0) when there internal buffer contains no callback generated + data. +*/ +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bufferProcessor ); + +/*@}*/ + + +/** @name Buffer processing functions for blocking read/write streams +*/ +/*@{*/ + +/** Copy samples from host input channels set up by the PaUtil_Set*InputChannels + functions to a user supplied buffer. This function is intended for use with + blocking read/write streams. Copies the minimum of the number of + user frames (specified by the frameCount parameter) and the number of available + host frames (specified in a previous call to SetInputFrameCount()). + + @param bufferProcessor The buffer processor. + + @param buffer A pointer to the user buffer pointer, or a pointer to a pointer + to an array of user buffer pointers for a non-interleaved stream. It is + important that this parameter points to a copy of the user buffer pointers, + not to the actual user buffer pointers, because this function updates the + pointers before returning. + + @param frameCount The number of frames of data in the buffer(s) pointed to by + the buffer parameter. + + @return The number of frames copied. The buffer pointer(s) pointed to by the + buffer parameter are advanced to point to the frame(s) following the last one + filled. +*/ +unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bufferProcessor, + void **buffer, unsigned long frameCount ); + + +/* Copy samples from a user supplied buffer to host output channels set up by + the PaUtil_Set*OutputChannels functions. This function is intended for use with + blocking read/write streams. Copies the minimum of the number of + user frames (specified by the frameCount parameter) and the number of + host frames (specified in a previous call to SetOutputFrameCount()). + + @param bufferProcessor The buffer processor. + + @param buffer A pointer to the user buffer pointer, or a pointer to a pointer + to an array of user buffer pointers for a non-interleaved stream. It is + important that this parameter points to a copy of the user buffer pointers, + not to the actual user buffer pointers, because this function updates the + pointers before returning. + + @param frameCount The number of frames of data in the buffer(s) pointed to by + the buffer parameter. + + @return The number of frames copied. The buffer pointer(s) pointed to by the + buffer parameter are advanced to point to the frame(s) following the last one + copied. +*/ +unsigned long PaUtil_CopyOutput( PaUtilBufferProcessor* bufferProcessor, + const void ** buffer, unsigned long frameCount ); + + +/* Zero samples in host output channels set up by the PaUtil_Set*OutputChannels + functions. This function is useful for flushing streams. + Zeros the minimum of frameCount and the number of host frames specified in a + previous call to SetOutputFrameCount(). + + @param bufferProcessor The buffer processor. + + @param frameCount The maximum number of frames to zero. + + @return The number of frames zeroed. +*/ +unsigned long PaUtil_ZeroOutput( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/*@}*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_PROCESS_H */ diff --git a/portaudio-v19/pa_common/pa_skeleton.c b/portaudio-v19/pa_common/pa_skeleton.c new file mode 100644 index 000000000..51f95c8a3 --- /dev/null +++ b/portaudio-v19/pa_common/pa_skeleton.c @@ -0,0 +1,807 @@ +/* + * $Id$ + * Portable Audio I/O Library skeleton implementation + * demonstrates how to use the common functions to implement support + * for a host API + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Skeleton implementation of support for a host API. + + @note This file is provided as a starting point for implementing support for + a new host API. IMPLEMENT ME comments are used to indicate functionality + which much be customised for each implementation. +*/ + + +#include /* strlen() */ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* IMPLEMENT ME: a macro like the following one should be used for reporting + host errors */ +#define PA_SKELETON_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) + +/* PaSkeletonHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ +} +PaSkeletonHostApiRepresentation; /* IMPLEMENT ME: rename this */ + + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaSkeletonHostApiRepresentation *skeletonHostApi; + PaDeviceInfo *deviceInfoArray; + + skeletonHostApi = (PaSkeletonHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaSkeletonHostApiRepresentation) ); + if( !skeletonHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + skeletonHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !skeletonHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &skeletonHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paInDevelopment; /* IMPLEMENT ME: change to correct type id */ + (*hostApi)->info.name = "skeleton implementation"; /* IMPLEMENT ME: change to correct name */ + + (*hostApi)->info.defaultInputDevice = paNoDevice; /* IMPLEMENT ME */ + (*hostApi)->info.defaultOutputDevice = paNoDevice; /* IMPLEMENT ME */ + + (*hostApi)->info.deviceCount = 0; + + deviceCount = 0; /* IMPLEMENT ME */ + + if( deviceCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + skeletonHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + skeletonHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; /* IMPLEMENT ME: allocate block and copy name eg: + deviceName = (char*)PaUtil_GroupAllocateMemory( skeletonHostApi->allocations, strlen(srcName) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, srcName ); + deviceInfo->name = deviceName; + */ + + deviceInfo->maxInputChannels = 0; /* IMPLEMENT ME */ + deviceInfo->maxOutputChannels = 0; /* IMPLEMENT ME */ + + deviceInfo->defaultLowInputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /* IMPLEMENT ME */ + + deviceInfo->defaultSampleRate = 0.; /* IMPLEMENT ME */ + + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &skeletonHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( skeletonHostApi ) + { + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSkeletonHostApiRepresentation *skeletonHostApi = (PaSkeletonHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from inputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + + /* suppress unused variable warnings */ + (void) sampleRate; + + return paFormatIsSupported; +} + +/* PaSkeletonStream - a stream data structure specifically for this implementation */ + +typedef struct PaSkeletonStream +{ /* IMPLEMENT ME: rename this */ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + /* IMPLEMENT ME: + - implementation specific data goes here + */ + unsigned long framesPerHostCallback; /* just an example */ +} +PaSkeletonStream; + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaSkeletonHostApiRepresentation *skeletonHostApi = (PaSkeletonHostApiRepresentation*)hostApi; + PaSkeletonStream *stream = 0; + unsigned long framesPerHostBuffer = framesPerBuffer; /* these may not be equivalent for all implementations */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */ + } + + /* + IMPLEMENT ME: + + ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() FIXME - checks needed? ) + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if possible / necessary + + - validate suggestedInputLatency and suggestedOutputLatency parameters, + use default values where necessary + */ + + + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + stream = (PaSkeletonStream*)PaUtil_AllocateMemory( sizeof(PaSkeletonStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + /* we assume a fixed host buffer size in this example, but the buffer processor + can also support bounded and unknown host buffer sizes by passing + paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of + paUtilFixedHostBufferSize below. */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + + /* + IMPLEMENT ME: initialise the following fields with estimated or actual + values. + */ + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + /* + IMPLEMENT ME: + - additional stream setup + opening + */ + + stream->framesPerHostCallback = framesPerHostBuffer; + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + +/* + ExampleHostProcessingLoop() illustrates the kind of processing which may + occur in a host implementation. + +*/ +static void ExampleHostProcessingLoop( void *inputBuffer, void *outputBuffer, void *userData ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)userData; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* IMPLEMENT ME */ + int callbackResult; + unsigned long framesProcessed; + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* + IMPLEMENT ME: + - generate timing information + - handle buffer slips + */ + + /* + If you need to byte swap or shift inputBuffer to convert it into a + portaudio format, do it here. + */ + + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, 0 /* IMPLEMENT ME: pass underflow/overflow flags when necessary */ ); + + /* + depending on whether the host buffers are interleaved, non-interleaved + or a mixture, you will want to call PaUtil_SetInterleaved*Channels(), + PaUtil_SetNonInterleaved*Channel() or PaUtil_Set*Channel() here. + */ + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + inputBuffer, + 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, /* first channel of outputBuffer is channel 0 */ + outputBuffer, + 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + + /* you must pass a valid value of callback result to PaUtil_EndBufferProcessing() + in general you would pass paContinue for normal operation, and + paComplete to drain the buffer processor's internal output buffer. + You can check whether the buffer processor's output buffer is empty + using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) + */ + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + + + /* + If you need to byte swap or shift outputBuffer to convert it to + host format, do it here. + */ + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + /* IMPLEMENT ME - finish playback immediately */ + + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + else + { + /* User callback has asked us to stop with paComplete or other non-zero value */ + + /* IMPLEMENT ME - finish playback once currently queued audio has completed */ + + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* + IMPLEMENT ME: + - additional stream closing + cleanup + */ + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + /* suppress unused function warning. the code in ExampleHostProcessingLoop or + something similar should be implemented to feed samples to and from the + host after StartStream() is called. + */ + (void) ExampleHostProcessingLoop; + + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + + + diff --git a/portaudio-v19/pa_common/pa_stream.c b/portaudio-v19/pa_common/pa_stream.c new file mode 100644 index 000000000..e820671bc --- /dev/null +++ b/portaudio-v19/pa_common/pa_stream.c @@ -0,0 +1,141 @@ +/* + * $Id$ + * Portable Audio I/O Library + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + streams. +*/ + + +#include "pa_stream.h" + + +void PaUtil_InitializeStreamInterface( PaUtilStreamInterface *streamInterface, + PaError (*Close)( PaStream* ), + PaError (*Start)( PaStream* ), + PaError (*Stop)( PaStream* ), + PaError (*Abort)( PaStream* ), + PaError (*IsStopped)( PaStream* ), + PaError (*IsActive)( PaStream* ), + PaTime (*GetTime)( PaStream* ), + double (*GetCpuLoad)( PaStream* ), + PaError (*Read)( PaStream*, void *, unsigned long ), + PaError (*Write)( PaStream*, const void *, unsigned long ), + signed long (*GetReadAvailable)( PaStream* ), + signed long (*GetWriteAvailable)( PaStream* ) ) +{ + streamInterface->Close = Close; + streamInterface->Start = Start; + streamInterface->Stop = Stop; + streamInterface->Abort = Abort; + streamInterface->IsStopped = IsStopped; + streamInterface->IsActive = IsActive; + streamInterface->GetTime = GetTime; + streamInterface->GetCpuLoad = GetCpuLoad; + streamInterface->Read = Read; + streamInterface->Write = Write; + streamInterface->GetReadAvailable = GetReadAvailable; + streamInterface->GetWriteAvailable = GetWriteAvailable; +} + + +void PaUtil_InitializeStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation, + PaUtilStreamInterface *streamInterface, + PaStreamCallback *streamCallback, + void *userData ) +{ + streamRepresentation->magic = PA_STREAM_MAGIC; + streamRepresentation->nextOpenStream = 0; + streamRepresentation->streamInterface = streamInterface; + streamRepresentation->streamCallback = streamCallback; + streamRepresentation->streamFinishedCallback = 0; + + streamRepresentation->userData = userData; + + streamRepresentation->streamInfo.inputLatency = 0.; + streamRepresentation->streamInfo.outputLatency = 0.; + streamRepresentation->streamInfo.sampleRate = 0.; +} + + +void PaUtil_TerminateStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation ) +{ + streamRepresentation->magic = 0; +} + + +PaError PaUtil_DummyRead( PaStream* stream, + void *buffer, + unsigned long frames ) +{ + (void)stream; /* unused parameter */ + (void)buffer; /* unused parameter */ + (void)frames; /* unused parameter */ + + return paCanNotReadFromACallbackStream; +} + + +PaError PaUtil_DummyWrite( PaStream* stream, + const void *buffer, + unsigned long frames ) +{ + (void)stream; /* unused parameter */ + (void)buffer; /* unused parameter */ + (void)frames; /* unused parameter */ + + return paCanNotWriteToACallbackStream; +} + + +signed long PaUtil_DummyGetReadAvailable( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return paCanNotReadFromACallbackStream; +} + + +signed long PaUtil_DummyGetWriteAvailable( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return paCanNotWriteToACallbackStream; +} + + +double PaUtil_DummyGetCpuLoad( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return 0.0; +} diff --git a/portaudio-v19/pa_common/pa_stream.h b/portaudio-v19/pa_common/pa_stream.h new file mode 100644 index 000000000..aaf89cf21 --- /dev/null +++ b/portaudio-v19/pa_common/pa_stream.h @@ -0,0 +1,196 @@ +#ifndef PA_STREAM_H +#define PA_STREAM_H +/* + * $Id$ + * Portable Audio I/O Library + * stream interface + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + streams. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#define PA_STREAM_MAGIC (0x18273645) + + +/** A structure representing an (abstract) interface to a host API. Contains + pointers to functions which implement the interface. + + All PaStreamInterface functions are guaranteed to be called with a non-null, + valid stream parameter. +*/ +typedef struct { + PaError (*Close)( PaStream* stream ); + PaError (*Start)( PaStream *stream ); + PaError (*Stop)( PaStream *stream ); + PaError (*Abort)( PaStream *stream ); + PaError (*IsStopped)( PaStream *stream ); + PaError (*IsActive)( PaStream *stream ); + PaTime (*GetTime)( PaStream *stream ); + double (*GetCpuLoad)( PaStream* stream ); + PaError (*Read)( PaStream* stream, void *buffer, unsigned long frames ); + PaError (*Write)( PaStream* stream, const void *buffer, unsigned long frames ); + signed long (*GetReadAvailable)( PaStream* stream ); + signed long (*GetWriteAvailable)( PaStream* stream ); +} PaUtilStreamInterface; + + +/** Initialize the fields of a PaUtilStreamInterface structure. +*/ +void PaUtil_InitializeStreamInterface( PaUtilStreamInterface *streamInterface, + PaError (*Close)( PaStream* ), + PaError (*Start)( PaStream* ), + PaError (*Stop)( PaStream* ), + PaError (*Abort)( PaStream* ), + PaError (*IsStopped)( PaStream* ), + PaError (*IsActive)( PaStream* ), + PaTime (*GetTime)( PaStream* ), + double (*GetCpuLoad)( PaStream* ), + PaError (*Read)( PaStream* stream, void *buffer, unsigned long frames ), + PaError (*Write)( PaStream* stream, const void *buffer, unsigned long frames ), + signed long (*GetReadAvailable)( PaStream* stream ), + signed long (*GetWriteAvailable)( PaStream* stream ) ); + + +/** Dummy Read function for use in interfaces to a callback based streams. + Pass to the Read parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +PaError PaUtil_DummyRead( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Dummy Write function for use in an interfaces to callback based streams. + Pass to the Write parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +PaError PaUtil_DummyWrite( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Dummy GetReadAvailable function for use in interfaces to callback based + streams. Pass to the GetReadAvailable parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +signed long PaUtil_DummyGetReadAvailable( PaStream* stream ); + + +/** Dummy GetWriteAvailable function for use in interfaces to callback based + streams. Pass to the GetWriteAvailable parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +signed long PaUtil_DummyGetWriteAvailable( PaStream* stream ); + + + +/** Dummy GetCpuLoad function for use in an interface to a read/write stream. + Pass to the GetCpuLoad parameter of PaUtil_InitializeStreamInterface. + @return Returns 0. +*/ +double PaUtil_DummyGetCpuLoad( PaStream* stream ); + + +/** Non host specific data for a stream. This data is used by pa_front to + forward to the appropriate functions in the streamInterface structure. +*/ +typedef struct PaUtilStreamRepresentation { + unsigned long magic; /**< set to PA_STREAM_MAGIC */ + struct PaUtilStreamRepresentation *nextOpenStream; /**< field used by multi-api code */ + PaUtilStreamInterface *streamInterface; + PaStreamCallback *streamCallback; + PaStreamFinishedCallback *streamFinishedCallback; + void *userData; + PaStreamInfo streamInfo; +} PaUtilStreamRepresentation; + + +/** Initialize a PaUtilStreamRepresentation structure. + + @see PaUtil_InitializeStreamRepresentation +*/ +void PaUtil_InitializeStreamRepresentation( + PaUtilStreamRepresentation *streamRepresentation, + PaUtilStreamInterface *streamInterface, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Clean up a PaUtilStreamRepresentation structure previously initialized + by a call to PaUtil_InitializeStreamRepresentation. + + @see PaUtil_InitializeStreamRepresentation +*/ +void PaUtil_TerminateStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation ); + + +/** Check that the stream pointer is valid. + + @return Returns paNoError if the stream pointer appears to be OK, otherwise + returns an error indicating the cause of failure. +*/ +PaError PaUtil_ValidateStreamPointer( PaStream *stream ); + + +/** Cast an opaque stream pointer into a pointer to a PaUtilStreamRepresentation. + + @see PaUtilStreamRepresentation +*/ +#define PA_STREAM_REP( stream )\ + ((PaUtilStreamRepresentation*) (stream) ) + + +/** Cast an opaque stream pointer into a pointer to a PaUtilStreamInterface. + + @see PaUtilStreamRepresentation, PaUtilStreamInterface +*/ +#define PA_STREAM_INTERFACE( stream )\ + PA_STREAM_REP( (stream) )->streamInterface + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_STREAM_H */ diff --git a/portaudio-v19/pa_common/pa_trace.c b/portaudio-v19/pa_common/pa_trace.c new file mode 100644 index 000000000..6619d423a --- /dev/null +++ b/portaudio-v19/pa_common/pa_trace.c @@ -0,0 +1,88 @@ +/* + * $Id$ + * Portable Audio I/O Library Trace Facility + * Store trace information in real-time for later printing. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Event trace mechanism for debugging. +*/ + + +#include +#include +#include +#include "pa_trace.h" + +#if PA_TRACE_REALTIME_EVENTS + +static char *traceTextArray[PA_MAX_TRACE_RECORDS]; +static int traceIntArray[PA_MAX_TRACE_RECORDS]; +static int traceIndex = 0; +static int traceBlock = 0; + +/*********************************************************************/ +void PaUtil_ResetTraceMessages() +{ + traceIndex = 0; +} + +/*********************************************************************/ +void PaUtil_DumpTraceMessages() +{ + int i; + int messageCount = (traceIndex < PA_MAX_TRACE_RECORDS) ? traceIndex : PA_MAX_TRACE_RECORDS; + + printf("DumpTraceMessages: traceIndex = %d\n", traceIndex ); + for( i=0; i must be included in the + context in which this macro is used. +*/ +#define PA_VALIDATE_TYPE_SIZES \ + { \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaUint16 ) == 2 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaInt16 ) == 2 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaUint32 ) == 4 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaInt32 ) == 4 ); \ + } + + +#endif /* PA_TYPES_H */ diff --git a/portaudio-v19/pa_common/pa_util.h b/portaudio-v19/pa_common/pa_util.h new file mode 100644 index 000000000..ab8dbe046 --- /dev/null +++ b/portaudio-v19/pa_common/pa_util.h @@ -0,0 +1,167 @@ +#ifndef PA_UTIL_H +#define PA_UTIL_H +/* + * $Id$ + * Portable Audio I/O Library implementation utilities header + * common implementation utilities and interfaces + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Prototypes for utility functions used by PortAudio implementations. + + @todo Document and adhere to the alignment guarantees provided by + PaUtil_AllocateMemory(). +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +struct PaUtilHostApiRepresentation; + + +/** Retrieve a specific host API representation. This function can be used + by implementations to retrieve a pointer to their representation in + host api specific extension functions which aren't passed a rep pointer + by pa_front.c. + + @param hostApi A pointer to a host API represenation pointer. Apon success + this will receive the requested representation pointer. + + @param type A valid host API type identifier. + + @returns An error code. If the result is PaNoError then a pointer to the + requested host API representation will be stored in *hostApi. If the host API + specified by type is not found, this function returns paHostApiNotFound. +*/ +PaError PaUtil_GetHostApiRepresentation( struct PaUtilHostApiRepresentation **hostApi, + PaHostApiTypeId type ); + + +/** Convert a PortAudio device index into a host API specific device index. + @param hostApiDevice Pointer to a device index, on success this will recieve the + converted device index value. + @param device The PortAudio device index to convert. + @param hostApi The host api which the index should be converted for. + + @returns On success returns PaNoError and places the converted index in the + hostApiDevice parameter. +*/ +PaError PaUtil_DeviceIndexToHostApiDeviceIndex( + PaDeviceIndex *hostApiDevice, PaDeviceIndex device, + struct PaUtilHostApiRepresentation *hostApi ); + + +/** Set the host error information returned by Pa_GetLastHostErrorInfo. This + function and the paUnanticipatedHostError error code should be used as a + last resort. Implementors should use existing PA error codes where possible, + or nominate new ones. Note that at it is always better to use + PaUtil_SetLastHostErrorInfo() and paUnanticipatedHostError than to return an + ambiguous or inaccurate PaError code. + + @param hostApiType The host API which encountered the error (ie of the caller) + + @param errorCode The error code returned by the native API function. + + @param errorText A string describing the error. PaUtil_SetLastHostErrorInfo + makes a copy of the string, so it is not necessary for the pointer to remain + valid after the call to PaUtil_SetLastHostErrorInfo() returns. + +*/ +void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode, + const char *errorText ); + + + +/** PA_DEBUG() provides a simple debug message printing facility. The macro + passes it's argument to a printf-like function called PaUtil_DebugPrint() + which prints to stderr and always flushes the stream after printing. + Because preprocessor macros cannot directly accept variable length argument + lists, calls to the macro must include an additional set of parenthesis, eg: + PA_DEBUG(("errorno: %d", 1001 )); +*/ + +void PaUtil_DebugPrint( const char *format, ... ); + +#ifdef PA_ENABLE_DEBUG_OUTPUT +#define PA_DEBUG(x) PaUtil_DebugPrint x ; +#else +#define PA_DEBUG(x) +#endif + + +/* the following functions are implemented in a platform platform specific + .c file +*/ + +/** Allocate size bytes, guaranteed to be aligned to a FIXME byte boundary */ +void *PaUtil_AllocateMemory( long size ); + + +/** Realease block if non-NULL. block may be NULL */ +void PaUtil_FreeMemory( void *block ); + + +/** Return the number of currently allocated blocks. This function can be + used for detecting memory leaks. + + @note Allocations will only be tracked if PA_TRACK_MEMORY is #defined. If + it isn't, this function will always return 0. +*/ +int PaUtil_CountCurrentlyAllocatedBlocks( void ); + + +/** Initialize the clock used by PaUtil_GetTime(). Call this before calling + PaUtil_GetTime. + + @see PaUtil_GetTime +*/ +void PaUtil_InitializeClock( void ); + + +/** Return the system time in seconds. Used to implement CPU load functions + + @see PaUtil_InitializeClock +*/ +double PaUtil_GetTime( void ); + + +/* void Pa_Sleep( long msec ); must also be implemented in per-platform .c file */ + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_UTIL_H */ diff --git a/portaudio-v19/pa_common/portaudio.h b/portaudio-v19/pa_common/portaudio.h new file mode 100644 index 000000000..2b257399b --- /dev/null +++ b/portaudio-v19/pa_common/portaudio.h @@ -0,0 +1,1124 @@ + +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief The PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, /**< @todo review error code name */ + paCanNotWriteToACallbackStream, /**< @todo review error code name */ + paCanNotReadFromAnOutputOnlyStream, /**< @todo review error code name */ + paCanNotWriteToAnInputOnlyStream, /**< @todo review error code name */ + paIncompatibleStreamHostApi, + paBadBufferPtr +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initialises internal data structures and prepares underlying + host APIs for use. This function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_DeviceCount-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_DeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The the default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The the default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asyncronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constaining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the defualt host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
    + set PA_RECOMMENDED_OUTPUT_DEVICE=1
    +
    + The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds that can be used + for syncronisation. The type is used for the outTime argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that a multichannel buffer is passed + as a set of non-interleaved pointers. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /* Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /* Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + should round the suggestedLatency up to the next practial value - ie to + provide an equal or higher latency than suggestedLatency wherever possibe. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; + PaTime currentTime; + PaTime outputBufferDacTime; +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, + paComplete=1, + paAbort=2 +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + @param input and @param output are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo The time in seconds when the first sample of the input + buffer was received at the audio input, the time in seconds when the first + sample of the output buffer will begin being played at the audio output, and + the time in seconds when the stream callback was called. + See also Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilise this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will recieve an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behaviour of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output dvice. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Determine the current time for the stream according to the same clock used + to generate buffer timestamps. This time may be used for syncronising other + events to the audio stream, for example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurrs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/portaudio-v19/pa_dll_switch/PaDllEntry.h b/portaudio-v19/pa_dll_switch/PaDllEntry.h new file mode 100644 index 000000000..e070054b3 --- /dev/null +++ b/portaudio-v19/pa_dll_switch/PaDllEntry.h @@ -0,0 +1,184 @@ + +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio DLL Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +// changed by zplane.developement in order to generate a DLL + +#ifndef __PADLLENTRY_HEADER_INCLUDED__ + +#define __PADLLENTRY_HEADER_INCLUDED__ + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + + +typedef double PaTimestamp; + + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable default clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +extern PaError (__cdecl* Pa_Initialize)( void ); + + + +extern PaError (__cdecl* Pa_Terminate)( void ); + + +extern long (__cdecl* Pa_GetHostError)( void ); + + +extern const char* (__cdecl* Pa_GetErrorText)( PaError ); + + + +extern int (__cdecl* Pa_CountDevices)(void); + +extern PaDeviceID (__cdecl* Pa_GetDefaultInputDeviceID)( void ); + +extern PaDeviceID (__cdecl* Pa_GetDefaultOutputDeviceID)( void ); + + +extern const PaDeviceInfo* (__cdecl* Pa_GetDeviceInfo)( PaDeviceID); + + + +extern PaError (__cdecl* Pa_OpenStream)( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * ); + + + +extern PaError (__cdecl* Pa_OpenDefaultStream)( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + + +extern PaError (__cdecl* Pa_CloseStream)( PortAudioStream* ); + + +extern PaError (__cdecl* Pa_StartStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_StopStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_AbortStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_StreamActive)( PortAudioStream *stream ); + +extern PaTimestamp (__cdecl* Pa_StreamTime)( PortAudioStream *stream ); + +extern double (__cdecl* Pa_GetCPULoad)( PortAudioStream* stream ); + +extern int (__cdecl* Pa_GetMinNumBuffers)( int framesPerBuffer, double sampleRate ); + +extern void (__cdecl* Pa_Sleep)( long msec ); + +extern PaError (__cdecl* Pa_GetSampleSize)( PaSampleFormat format ); + +#endif // __PADLLENTRY_HEADER_INCLUDED__ + diff --git a/portaudio-v19/pa_dll_switch/letter_from_tim_010817.txt b/portaudio-v19/pa_dll_switch/letter_from_tim_010817.txt new file mode 100644 index 0000000000000000000000000000000000000000..a535cd1dae6afccc58d304b141166f7f7600dbd5 GIT binary patch literal 1176 zcmZuxTT9$P6z=oV{oit8R;E*@}X#()!Rhna!MR#+}TBxv1M;-|u9#rL+*p zkaPPkXL1okH>0gP+tGy!ZREH`ADhkT>}T(^=gRP@x(mpji@Uw+ukuPbiHD4lA7y`q>E^cUhmO)S(cP( za4U;D09O>SYBC-uVX`k6BwO>hkw??RUw$=lQD{8et zNgaw12@N&H*F&fe7KJg9;l_F(*-6Z#__^lTXL3QC+w=SR^>tPE{Vs5V!IHB8wvisS zD-2rf2L=KOG^I$e98{$7B_KCaV;!eWeMvl|--6@0a2&nDNdr(3<5tg|`|NXELjYxc z?aI*eiugH4U4!h<<8OczN)~$HpF)W>;{}!5&_n;cok$fAAybCLdI2d?=(r6}u%aO! zGBwPOng|J?wN9%_sk^~F4F|PDw?9yu5aA!!E9;kH!Q4h{&<{~SKWgh_fOxFCp|y$( zH2|#0GBiMsL_Cx6_${1?QoKhC5V(?jTMrB&H@5KW9i?^3+@T^vjzpbJN#=@HuaHny z(UJHsUkw!SrV>vE5gueq2fQ+UqXzN-=EKb{-E4|BUSTOkU5qh-li!Bjj5g~~2A)G> zfzG%axC@39q!9Ub=9?HcB+(1s1y*m^kk{-DNedzRK42B3?aGHIOw@U|QkUY^a7$z| zRF-|yehol=sT3%{L8Y?olp2lXO)ocJ{qqM<>i5s!kzMFJ_F1*TK}+jxLN7`=R&;PO un(nK~{^b3H795@RxK>T@GkP~YsSd`~@!^!lCp0;%#uCxb|A`!`H@^T5#E2*W literal 0 HcmV?d00001 diff --git a/portaudio-v19/pa_dll_switch/loadPA_DLL.cpp b/portaudio-v19/pa_dll_switch/loadPA_DLL.cpp new file mode 100644 index 000000000..043eda878 --- /dev/null +++ b/portaudio-v19/pa_dll_switch/loadPA_DLL.cpp @@ -0,0 +1,203 @@ +////////////////////////////////////////////////////////////////////////// + + +HINSTANCE pPaDll; + +/* + the function pointers to the PortAudio DLLs +*/ + +PaError (__cdecl* Pa_Initialize)( void ); + + + +PaError (__cdecl* Pa_Terminate)( void ); + + +long (__cdecl* Pa_GetHostError)( void ); + + +const char* (__cdecl* Pa_GetErrorText)( PaError ); + + +int (__cdecl* Pa_CountDevices)(void); + +PaDeviceID (__cdecl* Pa_GetDefaultInputDeviceID)( void ); + +PaDeviceID (__cdecl* Pa_GetDefaultOutputDeviceID)( void ); + + +const PaDeviceInfo* (__cdecl* Pa_GetDeviceInfo)( PaDeviceID); + + + +PaError (__cdecl* Pa_OpenStream)( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * ); + + + +PaError (__cdecl* Pa_OpenDefaultStream)( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + + +PaError (__cdecl* Pa_CloseStream)( PortAudioStream* ); + + +PaError (__cdecl* Pa_StartStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_StopStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_AbortStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_StreamActive)( PortAudioStream *stream ); + +PaTimestamp (__cdecl* Pa_StreamTime)( PortAudioStream *stream ); + +double (__cdecl* Pa_GetCPULoad)( PortAudioStream* stream ); + +int (__cdecl* Pa_GetMinNumBuffers)( int framesPerBuffer, double sampleRate ); + +void (__cdecl* Pa_Sleep)( long msec ); + +PaError (__cdecl* Pa_GetSampleSize)( PaSampleFormat format ); + + +////////////////////////////////////////////////////////////////////////// + +... + +ZERROR AudioEngine::DirectXSupport(ZBOOL bSupDX) +{ + if (bSupDX) + if (CheckForDirectXSupport()) + bSupportDirectX = _TRUE; + else + return _NO_SOUND; + else + bSupportDirectX = _FALSE; + return _NO_ERROR; +} + + + +ZBOOL AudioEngine::CheckForDirectXSupport() +{ + HMODULE pTestDXLib; + FARPROC pFunctionality; + + pTestDXLib=LoadLibrary("DSOUND"); + if (pTestDXLib!=NULL) // check if there is a DirectSound + { + pFunctionality = GetProcAddress(pTestDXLib, (char*) 7); + if (pFunctionality!=NULL) + { + FreeLibrary(pTestDXLib); + return _TRUE; + } + else + { + FreeLibrary(pTestDXLib); + return _FALSE; + } + } + else + return _FALSE; +} + + +ZERROR AudioEngine::LoadPALib() +{ +#ifdef _DEBUG + if (bSupportDirectX) + pPaDll = LoadLibrary("PA_DXD"); + else + pPaDll = LoadLibrary("PA_MMED"); +#else + if (bSupportDirectX) + pPaDll = LoadLibrary("PA_DX"); + else + pPaDll = LoadLibrary("PA_MME"); +#endif + if (pPaDll!=NULL) + { + + Pa_Initialize = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_Initialize"); + Pa_Terminate = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_Terminate"); + Pa_GetHostError = (long (__cdecl* )( void )) GetProcAddress(pPaDll,"Pa_GetHostError"); + Pa_GetErrorText = (const char* (__cdecl* )( PaError )) GetProcAddress(pPaDll,"Pa_GetErrorText"); + Pa_CountDevices = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_CountDevices"); + Pa_GetDefaultInputDeviceID = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_GetDefaultInputDeviceID"); + Pa_GetDefaultOutputDeviceID = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_GetDefaultOutputDeviceID"); + Pa_GetDeviceInfo = (const PaDeviceInfo* (__cdecl* )( PaDeviceID)) GetProcAddress(pPaDll,"Pa_GetDeviceInfo"); + Pa_OpenStream = ( PaError (__cdecl* )( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * )) GetProcAddress(pPaDll,"Pa_OpenStream"); + + Pa_OpenDefaultStream = (PaError (__cdecl* )( PortAudioStream** , + int , + int , + PaSampleFormat , + double , + unsigned long , + unsigned long , + PortAudioCallback *, + void * )) GetProcAddress(pPaDll,"Pa_OpenDefaultStream"); + Pa_CloseStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_CloseStream"); + Pa_StartStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_StartStream"); + Pa_StopStream = (PaError (__cdecl* )( PortAudioStream* ))GetProcAddress(pPaDll,"Pa_StopStream"); + Pa_AbortStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_AbortStream"); + Pa_StreamActive = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_StreamActive"); + Pa_StreamTime = (PaTimestamp (__cdecl* )( PortAudioStream *))GetProcAddress(pPaDll,"Pa_StreamTime"); + Pa_GetCPULoad = (double (__cdecl* )( PortAudioStream* ))GetProcAddress(pPaDll,"Pa_GetCPULoad"); + Pa_GetMinNumBuffers = (int (__cdecl* )( int , double )) GetProcAddress(pPaDll,"Pa_GetMinNumBuffers"); + Pa_Sleep = (void (__cdecl* )( long )) GetProcAddress(pPaDll,"Pa_Sleep"); + Pa_GetSampleSize = (PaError (__cdecl* )( PaSampleFormat )) GetProcAddress(pPaDll,"Pa_GetSampleSize"); + + return _NO_ERROR; + } + else + return _DLL_NOT_FOUND; +} + +ZERROR AudioEngine::UnLoadPALib() +{ + if (pPaDll!=NULL) + FreeLibrary(pPaDll); + return _NO_ERROR; +} + +... diff --git a/portaudio-v19/pa_dll_switch/pa_lib.c b/portaudio-v19/pa_dll_switch/pa_lib.c new file mode 100644 index 000000000..86601592c --- /dev/null +++ b/portaudio-v19/pa_dll_switch/pa_lib.c @@ -0,0 +1,827 @@ +/* + * Portable Audio I/O Library + * Host Independant Layer + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification History: + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC +*/ + +#include +#include +#include +#include + +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifdef _WIN32 +#ifndef __MWERKS__ +#include +#endif /* __MWERKS__ */ +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/* The reason we might NOT want to validate the rate before opening the stream + * is because many DirectSound drivers lie about the rates they actually support. + */ +#define PA_VALIDATE_RATE (0) /* If true validate sample rate against driver info. */ + +/* +O- maybe not allocate past_InputBuffer and past_OutputBuffer if not needed for conversion +*/ + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +static int gInitCount = 0; /* Count number of times Pa_Initialize() called to allow nesting and overlapping. */ + +static PaError Pa_KillStream( PortAudioStream *stream, int abort ); + +/***********************************************************************/ +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, int numRates, double frameRate ) +{ + double err, minErr = allowableError; + int i, bestFit = -1; + + for( i=0; inumSampleRates == -1 ) + { + /* Is it out of range? */ + if( (requestedFrameRate < pdi->sampleRates[0]) || + (requestedFrameRate > pdi->sampleRates[1]) ) + { + return paInvalidSampleRate; + } + + *closestFrameRatePtr = requestedFrameRate; + } + else + { + bestRateIndex = PaHost_FindClosestTableEntry( 1.0, pdi->sampleRates, pdi->numSampleRates, requestedFrameRate ); + if( bestRateIndex < 0 ) return paInvalidSampleRate; + *closestFrameRatePtr = pdi->sampleRates[bestRateIndex]; + } + return paNoError; +} + +/*************************************************************************/ +DLL_API PaError Pa_OpenStream( + PortAudioStream** streamPtrPtr, + PaDeviceID inputDeviceID, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDeviceID, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + unsigned long streamFlags, + PortAudioCallback *callback, + void *userData ) +{ + internalPortAudioStream *past = NULL; + PaError result = paNoError; + int bitsPerInputSample; + int bitsPerOutputSample; + /* Print passed parameters. */ + DBUG(("Pa_OpenStream( %p, %d, %d, %d, %p, /* input */ \n", + streamPtrPtr, inputDeviceID, numInputChannels, + inputSampleFormat, inputDriverInfo )); + DBUG((" %d, %d, %d, %p, /* output */\n", + outputDeviceID, numOutputChannels, + outputSampleFormat, outputDriverInfo )); + DBUG((" %g, %d, %d, 0x%x, , %p )\n", + sampleRate, framesPerBuffer, numberOfBuffers, + streamFlags, userData )); + + /* Check for parameter errors. */ + if( (streamFlags & ~(paClipOff | paDitherOff)) != 0 ) return paInvalidFlag; + if( streamPtrPtr == NULL ) return paBadStreamPtr; + if( inputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( outputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( (inputDeviceID < 0) && ( outputDeviceID < 0) ) return paInvalidDeviceId; + if( (outputDeviceID >= Pa_CountDevices()) || (inputDeviceID >= Pa_CountDevices()) ) return paInvalidDeviceId; + if( (numInputChannels <= 0) && ( numOutputChannels <= 0) ) return paInvalidChannelCount; + +#if SUPPORT_AUDIO_CAPTURE + if( inputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( inputSampleFormat ); + if( size < 0 ) return size; + bitsPerInputSample = 8 * size; + if( (numInputChannels <= 0) ) return paInvalidChannelCount; + } +#else + if( inputDeviceID >= 0 ) + { + return paInvalidChannelCount; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + else + { + if( numInputChannels > 0 ) return paInvalidChannelCount; + bitsPerInputSample = 0; + } + + if( outputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( outputSampleFormat ); + if( size < 0 ) return size; + bitsPerOutputSample = 8 * size; + if( (numOutputChannels <= 0) ) return paInvalidChannelCount; + } + else + { + if( numOutputChannels > 0 ) return paInvalidChannelCount; + bitsPerOutputSample = 0; + } + + if( callback == NULL ) return paNullCallback; + + /* Allocate and clear stream structure. */ + past = (internalPortAudioStream *) PaHost_AllocateFastMemory( sizeof(internalPortAudioStream) ); + if( past == NULL ) return paInsufficientMemory; + memset( past, 0, sizeof(internalPortAudioStream) ); + AddTraceMessage("Pa_OpenStream: past", (long) past ); + + past->past_Magic = PA_MAGIC; /* Set ID to catch bugs. */ + past->past_FramesPerUserBuffer = framesPerBuffer; + past->past_NumUserBuffers = numberOfBuffers; /* NOTE - PaHost_OpenStream() NMUST CHECK FOR ZERO! */ + past->past_Callback = callback; + past->past_UserData = userData; + past->past_OutputSampleFormat = outputSampleFormat; + past->past_InputSampleFormat = inputSampleFormat; + past->past_OutputDeviceID = outputDeviceID; + past->past_InputDeviceID = inputDeviceID; + past->past_NumInputChannels = numInputChannels; + past->past_NumOutputChannels = numOutputChannels; + past->past_Flags = streamFlags; + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + { + result = paInvalidSampleRate; + goto cleanup; + } + + /* Allocate buffers that may be used for format conversion from user to native buffers. */ + if( numInputChannels > 0 ) + { + +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( inputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + /* Allocate single Input buffer. */ + past->past_InputBufferSize = framesPerBuffer * numInputChannels * ((bitsPerInputSample+7) / 8); + past->past_InputBuffer = PaHost_AllocateFastMemory(past->past_InputBufferSize); + if( past->past_InputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_InputBuffer = NULL; + } + + /* Allocate single Output buffer. */ + if( numOutputChannels > 0 ) + { +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( outputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + past->past_OutputBufferSize = framesPerBuffer * numOutputChannels * ((bitsPerOutputSample+7) / 8); + past->past_OutputBuffer = PaHost_AllocateFastMemory(past->past_OutputBufferSize); + if( past->past_OutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_OutputBuffer = NULL; + } + + result = PaHost_OpenStream( past ); + if( result < 0 ) goto cleanup; + + *streamPtrPtr = (void *) past; + + return result; + +cleanup: + if( past != NULL ) Pa_CloseStream( past ); + *streamPtrPtr = NULL; + return result; +} + + +/*************************************************************************/ +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ) +{ + return Pa_OpenStream( + stream, + ((numInputChannels > 0) ? Pa_GetDefaultInputDeviceID() : paNoDevice), + numInputChannels, sampleFormat, NULL, + ((numOutputChannels > 0) ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + numOutputChannels, sampleFormat, NULL, + sampleRate, framesPerBuffer, numberOfBuffers, paNoFlag, callback, userData ); +} + +/*************************************************************************/ +DLL_API PaError Pa_CloseStream( PortAudioStream* stream) +{ + PaError result; + internalPortAudioStream *past; + + DBUG(("Pa_CloseStream()\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + Pa_AbortStream( past ); + result = PaHost_CloseStream( past ); + + if( past->past_InputBuffer ) PaHost_FreeFastMemory( past->past_InputBuffer, past->past_InputBufferSize ); + if( past->past_OutputBuffer ) PaHost_FreeFastMemory( past->past_OutputBuffer, past->past_OutputBufferSize ); + PaHost_FreeFastMemory( past, sizeof(internalPortAudioStream) ); + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StartStream( PortAudioStream *stream ) +{ + PaError result = paHostError; + internalPortAudioStream *past; + + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + past->past_FrameCount = 0.0; + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StartInput( past ); + DBUG(("Pa_StartStream: PaHost_StartInput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StartOutput( past ); + DBUG(("Pa_StartStream: PaHost_StartOutput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + result = PaHost_StartEngine( past ); + DBUG(("Pa_StartStream: PaHost_StartEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + + return paNoError; + +error: + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StopStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 0 ); +} + +/*************************************************************************/ +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 1 ); +} + +/*************************************************************************/ +static PaError Pa_KillStream( PortAudioStream *stream, int abort ) +{ + PaError result = paNoError; + internalPortAudioStream *past; + + DBUG(("Pa_StopStream().\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + if( (past->past_NumInputChannels > 0) || (past->past_NumOutputChannels > 0) ) + { + result = PaHost_StopEngine( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StopInput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopInput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StopOutput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopOutput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + +error: + past->past_Usage = 0; + past->past_IfLastExitValid = 0; + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ) +{ + internalPortAudioStream *past; + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return PaHost_StreamActive( past ); +} + +/*************************************************************************/ +DLL_API const char *Pa_GetErrorText( PaError errnum ) +{ + const char *msg; + + switch(errnum) + { + case paNoError: msg = "Success"; break; + case paHostError: msg = "Host error."; break; + case paInvalidChannelCount: msg = "Invalid number of channels."; break; + case paInvalidSampleRate: msg = "Invalid sample rate."; break; + case paInvalidDeviceId: msg = "Invalid device ID."; break; + case paInvalidFlag: msg = "Invalid flag."; break; + case paSampleFormatNotSupported: msg = "Sample format not supported"; break; + case paBadIODeviceCombination: msg = "Illegal combination of I/O devices."; break; + case paInsufficientMemory: msg = "Insufficient memory."; break; + case paBufferTooBig: msg = "Buffer too big."; break; + case paBufferTooSmall: msg = "Buffer too small."; break; + case paNullCallback: msg = "No callback routine specified."; break; + case paBadStreamPtr: msg = "Invalid stream pointer."; break; + case paTimedOut : msg = "Wait Timed Out."; break; + case paInternalError: msg = "Internal PortAudio Error."; break; + default: msg = "Illegal error number."; break; + } + return msg; +} + +/* + Get CPU Load as a fraction of total CPU time. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + The amount may vary depending on CPU load. + This function may be called from the callback function. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream) +{ + internalPortAudioStream *past; + if( stream == NULL ) return (double) paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return past->past_Usage; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* High pass filter to reduce audibility. */ + highPass = current - previous; + previous = current; + return highPass; +} + +/************************************************************************* +** Called by host code. +** Convert input from Int16, call user code, then convert output +** to Int16 format for native use. +** Assumes host native format is paInt16. +** Returns result from user callback. +*/ +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ) +{ + long temp; + long bytesEmpty = 0; + long bytesFilled = 0; + int userResult; + unsigned int i; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + /* Convert from native format to PA format. */ + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumInputChannels; + switch(past->past_InputSampleFormat) + { + + case paFloat32: + { + float *inBufPtr = (float *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8); + } + } + else + { + for( i=0; i> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (char)(temp >> 8); + } + } + break; + } + + case paUInt8: + { + /* Convert 16 bit data to 8 bit unsigned chars */ + unsigned char *inBufPtr = (unsigned char *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8)) + 0x80; + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (unsigned char)(temp + 0x80); + } + } + break; + } + + default: + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paInt16) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ + if( outputBuffer != NULL ) + { + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + switch(past->past_OutputSampleFormat) + { + case paFloat32: + { + float *outBufPtr = (float *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + if( past->past_Flags & paClipOff ) /* NOTHING */ + { + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt32: + { + int *outBufPtr = (int *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 16 ); + } + } + else + { + for( i=0; i> 1) + Pa_TriangularDither(); + temp = temp >> 15; + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt8: + { + char *outBufPtr = (char *) past->past_OutputBuffer; + for( i=0; ipast_OutputBuffer; + for( i=0; ipast_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + inputBuffer = nativeInputBuffer; // FIXME + } + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paFloat32) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ // FIXME + return userResult; +} + +/*************************************************************************/ +DLL_API PaError Pa_Initialize( void ) +{ + if( gInitCount++ > 0 ) return paNoError; + ResetTraceMessages(); + return PaHost_Init(); +} + +DLL_API PaError Pa_Terminate( void ) +{ + PaError result = paNoError; + + if( gInitCount == 0 ) return paNoError; + else if( --gInitCount == 0 ) + { + result = PaHost_Term(); + DumpTraceMessages(); + } + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int size; + switch(format ) + { + + case paUInt8: + case paInt8: + size = 1; + break; + + case paInt16: + size = 2; + break; + + case paPackedInt24: + size = 3; + break; + + case paFloat32: + case paInt32: + case paInt24: + size = 4; + break; + + default: + size = paSampleFormatNotSupported; + break; + } + return (PaError) size; +} + + diff --git a/portaudio-v19/pa_dll_switch/portaudio.h b/portaudio-v19/pa_dll_switch/portaudio.h new file mode 100644 index 000000000..9632521eb --- /dev/null +++ b/portaudio-v19/pa_dll_switch/portaudio.h @@ -0,0 +1,439 @@ +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +// added by zplane.developement in order to generate a DLL + +#if defined(PA_MME_EXPORTS) || defined(PA_DX_EXPORTS) +#define DLL_API __declspec( dllexport ) +#elif defined(_LIB) || defined(_STATIC_LINK) || defined(_STATIC_APP) +#define DLL_API +#else +#define DLL_API __declspec(dllexport) +#endif + + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. +*/ + +DLL_API PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. +*/ + +DLL_API PaError Pa_Terminate( void ); + +/* + Return host specific error. + This can be called after receiving a paHostError. +*/ +DLL_API long Pa_GetHostError( void ); + +/* + Translate the error number into a human readable message. +*/ +DLL_API const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all devices. + + The floating point representation uses +1.0 and -1.0 as the respective + maximum and minimum. + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. Device 0 is always the "default" + device and should support at least stereo in and out if that is available + on the taget platform _even_ if this involves kludging an input/output + device on platforms that usually separate input from output. Other platform + specific devices are specified by positive device ids. +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + + +DLL_API int Pa_CountDevices(); +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() + + Return the default device ID or paNoDevice if there is no devices. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "pa_devs". +*/ +DLL_API PaDeviceID Pa_GetDefaultInputDeviceID( void ); +DLL_API PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time useful for syncronisation. The type is used in the outTime + argument to the callback function and the result of Pa_StreamTime() +*/ + +typedef double PaTimestamp; + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + referring to the device specified by id. + If id is out of range the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must + not be manipulated or freed. The pointer is guaranteed to be valid until + between calls to Pa_Initialize() and Pa_Terminate(). +*/ + +DLL_API const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ); + +/* + PortAudioCallback is implemented by clients of the portable audio api. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a nonzero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + nonzero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + + [ suggestions? ] +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable defult clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + device input record for the device specified in the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the sound devices native formats (nativeSampleFormats in the device info + record) and additionally 16 and 32 bit integer and 32 bit floating point + formats. Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate for input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered + communication with the platform specific audio routines. This parameter is + provided only as a guide - and does not imply that an implementation must + use multibuffered i/o when reliable double buffering is available (such as + SndPlayDoubleBuffer() on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behavior of the + streaming process. Some flags may only be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Apon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a nonzero error code is returned (see + PAError above) and the value of stream is invalid. + +*/ + +DLL_API PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that + opens the default input and/or ouput devices. Most parameters have + identical meaning to their Pa_OpenStream() counterparts, with the following + exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened (same as passing paNoDevice in the device arguments to Pa_OpenStream() ) + + sampleFormat applies to both the input and output buffers. +*/ + +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. +*/ + +DLL_API PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + When Pa_StopStream() returns, all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. +*/ + +DLL_API PaError Pa_StartStream( PortAudioStream *stream ); + +DLL_API PaError Pa_StopStream( PortAudioStream *stream ); + +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one when the stream is playing audio, + zero when not playing, or a negative error number if the + stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. +*/ + +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time for the stream in samples. + This time may be used as a time reference (for example syncronising audio to + MIDI). +*/ + +DLL_API PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + The "CPU Load" is a fraction of total CPU time consumed by the + stream's audio processing. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Use Pa_GetMinNumBuffers() to determine minimum number of buffers required for + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. +*/ + +DLL_API int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely + on this for accurate musical timing. +*/ +DLL_API void Pa_Sleep( long msec ); + +/* + Return size in bytes of a single sample in a given PaSampleFormat + or paSampleFormatNotSupported. +*/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/portaudio-v19/pa_jack/pa_jack.c b/portaudio-v19/pa_jack/pa_jack.c new file mode 100644 index 000000000..17bf3d371 --- /dev/null +++ b/portaudio-v19/pa_jack/pa_jack.c @@ -0,0 +1,1714 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * JACK Implementation by Joshua Haberman + * + * Copyright (c) 2004 Stefan Westerfeld + * Copyright (c) 2004 Arve Knudsen + * Copyright (c) 2002 Joshua Haberman + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* EBUSY */ +#include /* sig_atomic_t */ +#include +#include + +#include +#include + +#include "pa_util.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_process.h" +#include "pa_allocation.h" +#include "pa_cpuload.h" +#include "../pablio/ringbuffer.c" + +static int aErr_; +static PaError paErr_; /* For use with ENSURE_PA */ +static pthread_t mainThread_; +static char *jackErr_ = NULL; + +#define STRINGIZE_HELPER(expr) #expr +#define STRINGIZE(expr) STRINGIZE_HELPER(expr) + +/* Check PaError */ +#define ENSURE_PA(expr) \ + do { \ + if( (paErr_ = (expr)) < paNoError ) \ + { \ + if( (paErr_) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paErr_; \ + goto error; \ + } \ + } while( 0 ) + +#define UNLESS(expr, code) \ + do { \ + if( (expr) == 0 ) \ + { \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ) + +#define ASSERT_CALL(expr, success) \ + aErr_ = (expr); \ + assert( aErr_ == success ); + +/* + * Functions that directly map to the PortAudio stream interface + */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +/*static PaTime GetStreamInputLatency( PaStream *stream );*/ +/*static PaTime GetStreamOutputLatency( PaStream *stream );*/ +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); + + +/* + * Data specific to this API + */ + +struct PaJackStream; + +typedef struct +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *deviceInfoMemory; + + jack_client_t *jack_client; + int jack_buffer_size; + PaHostApiIndex hostApiIndex; + + pthread_mutex_t mtx; + pthread_cond_t cond; + unsigned long inputBase, outputBase; + + /* For dealing with the process thread */ + volatile int xrun; /* Received xrun notification from JACK? */ + struct PaJackStream * volatile toAdd, * volatile toRemove; + struct PaJackStream *processQueue; + volatile sig_atomic_t jackIsDown; +} +PaJackHostApiRepresentation; + +/* PaJackStream - a stream data structure specifically for this implementation */ + +typedef struct PaJackStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilBufferProcessor bufferProcessor; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaJackHostApiRepresentation *hostApi; + + /* our input and output ports */ + jack_port_t **local_input_ports; + jack_port_t **local_output_ports; + + /* the input and output ports of the client we are connecting to */ + jack_port_t **remote_input_ports; + jack_port_t **remote_output_ports; + + int num_incoming_connections; + int num_outgoing_connections; + + jack_client_t *jack_client; + + /* The stream is running if it's still producing samples. + * The stream is active if samples it produced are still being heard. + */ + volatile sig_atomic_t is_running; + volatile sig_atomic_t is_active; + /* Used to signal processing thread that stream should start or stop, respectively */ + volatile sig_atomic_t doStart, doStop, doAbort; + + jack_nframes_t t0; + + PaUtilAllocationGroup *stream_memory; + + /* These are useful in the process callback */ + + int callbackResult; + int isSilenced; + int xrun; + + /* These are useful for the blocking API */ + + int isBlockingStream; + RingBuffer inFIFO; + RingBuffer outFIFO; + volatile sig_atomic_t data_available; + sem_t data_semaphore; + int bytesPerFrame; + int samplesPerFrame; + + struct PaJackStream *next; +} +PaJackStream; + +#define TRUE 1 +#define FALSE 0 + +/* + * Functions specific to this API + */ + +static int JackCallback( jack_nframes_t frames, void *userData ); + + +/* + * + * Implementation + * + */ + +/* ---- blocking emulation layer ---- */ + +/* Allocate buffer. */ +static PaError BlockingInitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ) +{ + long numBytes = numFrames * bytesPerFrame; + char *buffer = (char *) malloc( numBytes ); + if( buffer == NULL ) return paInsufficientMemory; + memset( buffer, 0, numBytes ); + return (PaError) RingBuffer_Init( rbuf, numBytes, buffer ); +} + +/* Free buffer. */ +static PaError BlockingTermFIFO( RingBuffer *rbuf ) +{ + if( rbuf->buffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +static int +BlockingCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct PaJackStream *stream = (PaJackStream *)userData; + long numBytes = stream->bytesPerFrame * framesPerBuffer; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &stream->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int numRead = RingBuffer_Read( &stream->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + memset( (char *)outputBuffer + numRead, 0, numBytes - numRead ); + } + + if( !stream->data_available ) + { + stream->data_available = 1; + sem_post( &stream->data_semaphore ); + } + return paContinue; +} + +static PaError +BlockingBegin( PaJackStream *stream, int minimum_buffer_size ) +{ + long doRead = 0; + long doWrite = 0; + PaError result = paNoError; + long numFrames; + + doRead = stream->local_input_ports != NULL; + doWrite = stream->local_output_ports != NULL; + /* */ + stream->samplesPerFrame = 2; + stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame; + /* */ + numFrames = 32; + while (numFrames < minimum_buffer_size) + numFrames *= 2; + + if( doRead ) + { + ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) ); + } + if( doWrite ) + { + long numBytes; + + ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) ); + + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &stream->outFIFO, numBytes ); + } + + stream->data_available = 0; + sem_init( &stream->data_semaphore, 0, 0 ); + +error: + return result; +} + +static void +BlockingEnd( PaJackStream *stream ) +{ + BlockingTermFIFO( &stream->inFIFO ); + BlockingTermFIFO( &stream->outFIFO ); + + sem_destroy( &stream->data_semaphore ); +} + +static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + + long bytesRead; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesRead = RingBuffer_Read( &stream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0 ) + { + /* see write for an explanation */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + long bytesWritten; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesWritten = RingBuffer_Write( &stream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0 ) + { + /* we use the following algorithm: + * (1) write data + * (2) if some data didn't fit into the ringbuffer, set data_available to 0 + * to indicate to the audio that if space becomes available, we want to know + * (3) retry to write data (because it might be that between (1) and (2) + * new space in the buffer became available) + * (4) if this failed, we are sure that the buffer is really empty and + * we will definitely receive a notification when it becomes available + * thus we can safely sleep + * + * if the algorithm bailed out in step (3) before, it leaks a count of 1 + * on the semaphore; however, it doesn't matter, because if we block in (4), + * we also do it in a loop + */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static signed long +BlockingGetStreamReadAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesFull = RingBuffer_GetReadAvailable( &stream->inFIFO ); + return bytesFull / stream->bytesPerFrame; +} + +static signed long +BlockingGetStreamWriteAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesEmpty = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + return bytesEmpty / stream->bytesPerFrame; +} + +static PaError +BlockingWaitEmpty( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + while( RingBuffer_GetReadAvailable( &stream->outFIFO ) > 0 ) + { + stream->data_available = 0; + sem_wait( &stream->data_semaphore ); + } + return 0; +} + +/* ---- jack driver ---- */ + +/* BuildDeviceList(): + * + * The process of determining a list of PortAudio "devices" from + * JACK's client/port system is fairly involved, so it is separated + * into its own routine. + */ + +static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) +{ + /* Utility macros for the repetitive process of allocating memory */ + + /* ... MALLOC: allocate memory as part of the device list + * allocation group */ +#define MALLOC(size) \ + (PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, (size) )) + + /* JACK has no concept of a device. To JACK, there are clients + * which have an arbitrary number of ports. To make this + * intelligible to PortAudio clients, we will group each JACK client + * into a device, and make each port of that client a channel */ + + PaError result = paNoError; + PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep; + + const char **jack_ports = NULL; + char **client_names = NULL; + char *regex_pattern = alloca( jack_client_name_size() + 3 ); + int port_index, client_index, i; + double globalSampleRate; + regex_t port_regex; + unsigned long numClients = 0, numPorts = 0; + char *tmp_client_name = alloca( jack_client_name_size() ); + + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + commonApi->info.deviceCount = 0; + + /* Parse the list of ports, using a regex to grab the client names */ + ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 ); + + /* since we are rebuilding the list of devices, free all memory + * associated with the previous list */ + PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory ); + + /* We can only retrieve the list of clients indirectly, by first + * asking for a list of all ports, then parsing the port names + * according to the client_name:port_name convention (which is + * enforced by jackd) + * A: If jack_get_ports returns NULL, there's nothing for us to do */ + UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", "", 0 )) && jack_ports[0], paNoError ); + /* Find number of ports */ + while( jack_ports[numPorts] ) + ++numPorts; + /* At least there will be one port per client :) */ + UNLESS( client_names = alloca( numPorts * sizeof (char *) ), paInsufficientMemory ); + + /* Build a list of clients from the list of ports */ + for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ ) + { + int client_seen = FALSE; + regmatch_t match_info; + const char *port = jack_ports[port_index]; + + /* extract the client name from the port name, using a regex + * that parses the clientname:portname syntax */ + UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError ); + assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size()); + memcpy( tmp_client_name, port + match_info.rm_so, + match_info.rm_eo - match_info.rm_so ); + tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0'; + + /* do we know about this port's client yet? */ + for( i = 0; i < numClients; i++ ) + { + if( strcmp( tmp_client_name, client_names[i] ) == 0 ) + client_seen = TRUE; + } + + if (client_seen) + continue; /* A: Nothing to see here, move along */ + + UNLESS( client_names[numClients] = (char*)MALLOC(strlen(tmp_client_name) + 1), paInsufficientMemory ); + + /* The alsa_pcm client should go in spot 0. If this + * is the alsa_pcm client AND we are NOT about to put + * it in spot 0 put it in spot 0 and move whatever + * was already in spot 0 to the end. */ + if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 ) + { + /* alsa_pcm goes in spot 0 */ + strcpy( client_names[ numClients ], client_names[0] ); + strcpy( client_names[0], tmp_client_name ); + } + else + { + /* put the new client at the end of the client list */ + strcpy( client_names[ numClients ], tmp_client_name ); + } + ++numClients; + } + + /* Now we have a list of clients, which will become the list of + * PortAudio devices. */ + + /* there is one global sample rate all clients must conform to */ + + globalSampleRate = jack_get_sample_rate( jackApi->jack_client ); + UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)MALLOC( sizeof(PaDeviceInfo*) * + numClients ), paInsufficientMemory ); + + assert( commonApi->info.deviceCount == 0 ); + + /* Create a PaDeviceInfo structure for every client */ + for( client_index = 0; client_index < numClients; client_index++ ) + { + PaDeviceInfo *curDevInfo; + const char **clientPorts = NULL; + + UNLESS( curDevInfo = (PaDeviceInfo*)MALLOC( sizeof(PaDeviceInfo) ), paInsufficientMemory ); + UNLESS( curDevInfo->name = (char*)MALLOC( strlen(client_names[client_index]) + 1 ), paInsufficientMemory ); + strcpy( (char *)curDevInfo->name, client_names[client_index] ); + + curDevInfo->structVersion = 2; + curDevInfo->hostApi = jackApi->hostApiIndex; + + /* JACK is very inflexible: there is one sample rate the whole + * system must run at, and all clients must speak IEEE float. */ + curDevInfo->defaultSampleRate = globalSampleRate; + + /* To determine how many input and output channels are available, + * we re-query jackd with more specific parameters. */ + + sprintf( regex_pattern, "%s:.*", client_names[client_index] ); + + /* ... what are your output ports (that we could input from)? */ + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, + NULL, JackPortIsOutput); + curDevInfo->maxInputChannels = 0; + curDevInfo->defaultLowInputLatency = 0.; + curDevInfo->defaultHighInputLatency = 0.; + if( clientPorts ) + { + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency = + jack_port_get_latency( p ) / globalSampleRate; + + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of output channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxInputChannels++; + } + free(clientPorts); + } + + /* ... what are your input ports (that we could output to)? */ + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, + NULL, JackPortIsInput); + curDevInfo->maxOutputChannels = 0; + curDevInfo->defaultLowOutputLatency = 0.; + curDevInfo->defaultHighOutputLatency = 0.; + if( clientPorts ) + { + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency = + jack_port_get_latency( p ) / globalSampleRate; + + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of input channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxOutputChannels++; + } + free(clientPorts); + } + + /* Add this client to the list of devices */ + commonApi->deviceInfos[client_index] = curDevInfo; + ++commonApi->info.deviceCount; + if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = client_index; + if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = client_index; + } + +error: + regfree( &port_regex ); + free( jack_ports ); + return result; +} +#undef MALLOC + +static void UpdateSampleRate( PaJackStream *stream, double sampleRate ) +{ + /* XXX: Maybe not the cleanest way of going about this? */ + stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; +} + +static void JackErrorCallback( const char *msg ) +{ + if( pthread_self() == mainThread_ ) + { + assert( msg ); + free( jackErr_ ); + jackErr_ = malloc( strlen( msg ) ); + sprintf( jackErr_, msg ); + } +} + +static void JackOnShutdown( void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + PaJackStream *stream = jackApi->processQueue; + + PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ )); + for( ; stream; stream = stream->next ) + { + stream->is_active = 0; + } + + /* Make sure that the main thread doesn't get stuck waiting on the condition */ + ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 ); + jackApi->jackIsDown = 1; + ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 ); + +} + +static int JackSrCb( jack_nframes_t nframes, void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + double sampleRate = (double)nframes; + PaJackStream *stream = jackApi->processQueue; + + /* Update all streams in process queue */ + PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate )); + for( ; stream; stream = stream->next ) + { + if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate ) + { + PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ )); + UpdateSampleRate( stream, sampleRate ); + } + } + + return 0; +} + +static int JackXRunCb(void *arg) { + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg; + assert( hostApi ); + hostApi->xrun = TRUE; + PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ )); + return 0; +} + +PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, + PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *jackHostApi; + int activated = 0; + char *clientName; + int written; + *hostApi = NULL; /* Initialize to NULL */ + + UNLESS( jackHostApi = (PaJackHostApiRepresentation*) + PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory ); + jackHostApi->deviceInfoMemory = NULL; + + mainThread_ = pthread_self(); + ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 ); + ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 ); + + /* Try to become a client of the JACK server. If we cannot do + * this, then this API cannot be used. */ + + clientName = alloca( jack_client_name_size() ); + written = snprintf( clientName, jack_client_name_size(), "PortAudio-%d", getpid() ); + assert( written < jack_client_name_size() ); + jackHostApi->jack_client = jack_client_new( clientName ); + if( jackHostApi->jack_client == NULL ) + { + /* the V19 development docs say that if an implementation + * detects that it cannot be used, it should return a NULL + * interface and paNoError */ + result = paNoError; + goto error; + } + + UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + jackHostApi->hostApiIndex = hostApiIndex; + + *hostApi = &jackHostApi->commonHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paJACK; + (*hostApi)->info.name = "JACK Audio Connection Kit"; + + /* Build a device list by querying the JACK server */ + + ENSURE_PA( BuildDeviceList( jackHostApi ) ); + + /* Register functions */ + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + BlockingReadStream, BlockingWriteStream, + BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable ); + + jackHostApi->inputBase = jackHostApi->outputBase = 0; + jackHostApi->xrun = 0; + jackHostApi->toAdd = jackHostApi->toRemove = NULL; + jackHostApi->processQueue = NULL; + jackHostApi->jackIsDown = 0; + + jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi ); + jack_set_error_function( JackErrorCallback ); + jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client ); + UNLESS( !jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError ); + activated = 1; + + return result; + +error: + if( activated ) + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + + if( jackHostApi ) + { + if( jackHostApi->jack_client ) + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); + + if( jackHostApi->deviceInfoMemory ) + { + PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory ); + PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory ); + } + + PaUtil_FreeMemory( jackHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; + + /* note: this automatically disconnects all ports, since a deactivated + * client is not allowed to have any ports connected */ + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + + ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 ); + ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 ); + + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); + + if( jackHostApi->deviceInfoMemory ) + { + PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory ); + PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory ); + } + + PaUtil_FreeMemory( jackHostApi ); + + free( jackErr_ ); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + The following check is not necessary for JACK. + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + /* check that the device supports sampleRate */ + +#define ABS(x) ( (x) > 0 ? (x) : -(x) ) + if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 ) + return paInvalidSampleRate; +#undef ABS + + return paFormatIsSupported; +} + +/* Basic stream initialization */ +static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels, + int numOutputChannels ) +{ + PaError result = paNoError; + assert( stream ); + + memset( stream, 0, sizeof (PaJackStream) ); + UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + stream->jack_client = hostApi->jack_client; + stream->hostApi = hostApi; + + if( numInputChannels > 0 ) + { + UNLESS( stream->local_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + UNLESS( stream->remote_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + } + if( numOutputChannels > 0 ) + { + UNLESS( stream->local_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + UNLESS( stream->remote_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + } + + stream->num_incoming_connections = numInputChannels; + stream->num_outgoing_connections = numOutputChannels; + +error: + return result; +} + +/*! + * Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and closes opened pcms. + */ +static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor ) +{ + int i; + assert( stream ); + + if( stream->isBlockingStream ) + BlockingEnd( stream ); + + for( i = 0; i < stream->num_incoming_connections; ++i ) + { + if( stream->local_input_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 ); + } + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + if( stream->local_output_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 ); + } + + if( terminateStreamRepresentation ) + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + if( terminateBufferProcessor ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + if( stream->stream_memory ) + { + PaUtil_FreeAllAllocations( stream->stream_memory ); + PaUtil_DestroyAllocationGroup( stream->stream_memory ); + } + PaUtil_FreeMemory( stream ); +} + +static PaError WaitCondition( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int err = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; + + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + /* XXX: Best enclose in loop, in case of spurious wakeups? */ + err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts ); + + /* Make sure we didn't time out */ + UNLESS( err != ETIMEDOUT, paTimedOut ); + UNLESS( !err, paInternalError ); + +error: + return result; +} + +static PaError AddStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + /* Add to queue of streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toAdd = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + + UNLESS( !hostApi->jackIsDown, paDeviceUnavailable ); + +error: + return result; +} + +/* Remove stream from processing queue */ +static PaError RemoveStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + + /* Add to queue over streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toRemove = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + +error: + return result; +} + +/* Add stream to processing queue */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; + PaJackStream *stream = NULL; + char *port_string = alloca( jack_port_name_size() ); + unsigned long regexSz = jack_client_name_size() + 3; + char *regex_pattern = alloca( regexSz ); + const char **jack_ports = NULL; + /* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */ + int i; + int inputChannelCount, outputChannelCount; + const double jackSr = jack_get_sample_rate( jackHostApi->jack_client ); + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int bpInitialized = 0, srInitialized = 0; /* Initialized buffer processor and stream representation? */ + unsigned long ofs; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 ) + { + streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback; + /*return paInvalidFlag;*/ /* This implementation does not support buffer priming */ + } + + if( framesPerBuffer != paFramesPerBufferUnspecified ) + { + /* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */ + /*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/ /* TODO: Add descriptive error code? */ + } + + /* Preliminary checks */ + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* ... check that the sample rate exactly matches the ONE acceptable rate + * A: This rate isn't necessarily constant though? */ + +#define ABS(x) ( (x) > 0 ? (x) : -(x) ) + if( ABS(sampleRate - jackSr) > 1 ) + return paInvalidSampleRate; +#undef ABS + + UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory ); + ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) ); + + /* the blocking emulation, if necessary */ + stream->isBlockingStream = !streamCallback; + if( stream->isBlockingStream ) + { + float latency = 0.001; /* 1ms is the absolute minimum we support */ + int minimum_buffer_frames = 0; + + if( inputParameters && inputParameters->suggestedLatency > latency ) + latency = inputParameters->suggestedLatency; + else if( outputParameters && outputParameters->suggestedLatency > latency ) + latency = outputParameters->suggestedLatency; + + /* the latency the user asked for indicates the minimum buffer size in frames */ + minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client )); + + /* we also need to be able to store at least three full jack buffers to avoid dropouts */ + if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames ) + minimum_buffer_frames = jackHostApi->jack_buffer_size * 3; + + /* setup blocking API data structures (FIXME: can fail) */ + BlockingBegin( stream, minimum_buffer_frames ); + + /* install our own callback for the blocking API */ + streamCallback = BlockingCallback; + userData = stream; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &jackHostApi->blockingStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &jackHostApi->callbackStreamInterface, streamCallback, userData ); + } + srInitialized = 1; + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr ); + + /* create the JACK ports. We cannot connect them until audio + * processing begins */ + + /* Register a unique set of ports for this stream + * TODO: Robust allocation of new port names */ + + ofs = jackHostApi->inputBase; + for( i = 0; i < inputChannelCount; i++ ) + { + snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i ); + UNLESS( stream->local_input_ports[i] = jack_port_register( + jackHostApi->jack_client, port_string, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory ); + } + jackHostApi->inputBase += inputChannelCount; + + ofs = jackHostApi->outputBase; + for( i = 0; i < outputChannelCount; i++ ) + { + snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i ); + UNLESS( stream->local_output_ports[i] = jack_port_register( + jackHostApi->jack_client, port_string, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory ); + } + jackHostApi->outputBase += outputChannelCount; + + /* look up the jack_port_t's for the remote ports. We could do + * this at stream start time, but doing it here ensures the + * name lookup only happens once. */ + + if( inputChannelCount > 0 ) + { + int err = 0; + + /* ... remote output ports (that we input from) */ + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsOutput ), paUnanticipatedHostError ); + for( i = 0; i < inputChannelCount && jack_ports[i]; i++ ) + { + if( (stream->remote_output_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == NULL ) + { + err = 1; + break; + } + } + free( jack_ports ); + UNLESS( !err, paInsufficientMemory ); + + /* Fewer ports than expected? */ + UNLESS( i == inputChannelCount, paInternalError ); + } + + if( outputChannelCount > 0 ) + { + int err = 0; + + /* ... remote input ports (that we output to) */ + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsInput ), paUnanticipatedHostError ); + for( i = 0; i < outputChannelCount && jack_ports[i]; i++ ) + { + if( (stream->remote_input_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == 0 ) + { + err = 1; + break; + } + } + free( jack_ports ); + UNLESS( !err , paInsufficientMemory ); + + /* Fewer ports than expected? */ + UNLESS( i == outputChannelCount, paInternalError ); + } + + ENSURE_PA( PaUtil_InitializeBufferProcessor( + &stream->bufferProcessor, + inputChannelCount, + inputSampleFormat, + paFloat32, /* hostInputSampleFormat */ + outputChannelCount, + outputSampleFormat, + paFloat32, /* hostOutputSampleFormat */ + jackSr, + streamFlags, + framesPerBuffer, + 0, /* Ignored */ + paUtilUnknownHostBufferSize, /* Buffer size may vary on JACK's discretion */ + streamCallback, + userData ) ); + bpInitialized = 1; + + if( stream->num_incoming_connections > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor )) / sampleRate; + if( stream->num_outgoing_connections > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor )) / sampleRate; + + stream->streamRepresentation.streamInfo.sampleRate = jackSr; + stream->t0 = jack_frame_time( jackHostApi->jack_client ); /* A: Time should run from Pa_OpenStream */ + + ENSURE_PA( AddStream( stream ) ); /* Add to queue over opened streams */ + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + CleanUpStream( stream, srInitialized, bpInitialized ); + + return result; +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream*)s; + + /* Remove this stream from the processing queue */ + ENSURE_PA( RemoveStream( stream ) ); + +error: + CleanUpStream( stream, 1, 1 ); + return result; +} + +static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames ) +{ + PaError result = paNoError; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + int chn; + int framesProcessed; + const double sr = jack_get_sample_rate( stream->jack_client ); /* Shouldn't change during the process callback */ + PaStreamCallbackFlags cbFlags = 0; + + /* If the user has returned !paContinue from the callback we'll want to flush the internal buffers, + * when these are empty we can finally mark the stream as inactive */ + if( stream->callbackResult != paContinue && + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + { + stream->is_active = 0; + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ )); + + goto end; + } + + timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr; + if( stream->num_incoming_connections > 0 ) + timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] ) + / sr; + if( stream->num_outgoing_connections > 0 ) + timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] ) + / sr; + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + if( stream->xrun ) + { + /* XXX: Any way to tell which of these occurred? */ + cbFlags = paOutputUnderflow | paInputOverflow; + stream->xrun = FALSE; + } + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + + if( stream->num_incoming_connections > 0 ) + PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames ); + if( stream->num_outgoing_connections > 0 ) + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames ); + + for( chn = 0; chn < stream->num_incoming_connections; chn++ ) + { + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) + jack_port_get_buffer( stream->local_input_ports[chn], + frames ); + + PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, + chn, + channel_buf ); + } + + for( chn = 0; chn < stream->num_outgoing_connections; chn++ ) + { + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) + jack_port_get_buffer( stream->local_output_ports[chn], + frames ); + + PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, + chn, + channel_buf ); + } + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &stream->callbackResult ); + /* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */ + assert( framesProcessed == frames ); + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + +end: + return result; +} + +/* Alter the processing queue if necessary */ +static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int queueModified = 0; + const double jackSr = jack_get_sample_rate( hostApi->jack_client ); + int err; + + if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 ) + { + assert( err == EBUSY ); + return paNoError; + } + + if( hostApi->toAdd ) + { + if( hostApi->processQueue ) + { + PaJackStream *node = hostApi->processQueue; + /* Advance to end of queue */ + while( node->next ) + node = node->next; + + node->next = hostApi->toAdd; + } + else + hostApi->processQueue = (PaJackStream *)hostApi->toAdd; + + /* If necessary, update stream state */ + if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr ) + UpdateSampleRate( hostApi->toAdd, jackSr ); + + hostApi->toAdd = NULL; + queueModified = 1; + } + if( hostApi->toRemove ) + { + int removed = 0; + PaJackStream *node = hostApi->processQueue, *prev = NULL; + assert( hostApi->processQueue ); + + while( node ) + { + if( node == hostApi->toRemove ) + { + if( prev ) + prev->next = node->next; + else + hostApi->processQueue = (PaJackStream *)node->next; + + removed = 1; + break; + } + + prev = node; + node = node->next; + } + UNLESS( removed, paInternalError ); + hostApi->toRemove = NULL; + PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ )); + queueModified = 1; + } + + if( queueModified ) + { + /* Signal that we've done what was asked of us */ + ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 ); + } + +error: + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + + return result; +} + +static int JackCallback( jack_nframes_t frames, void *userData ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData; + PaJackStream *stream = NULL; + int xrun = hostApi->xrun; + hostApi->xrun = 0; + + assert( hostApi ); + + ENSURE_PA( UpdateQueue( hostApi ) ); + + /* Process each stream */ + stream = hostApi->processQueue; + for( ; stream; stream = stream->next ) + { + if( xrun ) /* Don't override if already set */ + stream->xrun = 1; + + /* See if this stream is to be started */ + if( stream->doStart ) + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + if( stream->doStart ) /* Could potentially change before obtaining the lock */ + { + stream->is_active = 1; + stream->doStart = 0; + PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ )); + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + stream->callbackResult = paContinue; + stream->isSilenced = 0; + } + + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + else if( stream->doStop || stream->doAbort ) /* Should we stop/abort stream? */ + { + if( stream->callbackResult == paContinue ) /* Ok, make it stop */ + { + PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ )); + stream->callbackResult = stream->doStop ? paComplete : paAbort; + } + } + + if( stream->is_active ) + ENSURE_PA( RealProcess( stream, frames ) ); + /* If we have just entered inactive state, silence output */ + if( !stream->is_active && !stream->isSilenced ) + { + int i; + + /* Silence buffer after entering inactive state */ + PA_DEBUG(( "Silencing the output\n" )); + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames ); + memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames ); + } + + stream->isSilenced = 1; + } + + if( stream->doStop || stream->doAbort ) + { + /* See if RealProcess has acted on the request */ + if( !stream->is_active ) /* Ok, signal to the main thread that we've carried out the operation */ + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + stream->doStop = stream->doAbort = 0; + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + } + } + + return 0; +error: + return -1; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream*)s; + int i; + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* connect the ports */ + + /* NOTE: I would rather use jack_port_connect which uses jack_port_t's + * instead of port names, but it is not implemented yet. */ + if( stream->num_incoming_connections > 0 ) + { + for( i = 0; i < stream->num_incoming_connections; i++ ) + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ) == 0, paUnanticipatedHostError ); + } + + if( stream->num_outgoing_connections > 0 ) + { + for( i = 0; i < stream->num_outgoing_connections; i++ ) + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ) == 0, paUnanticipatedHostError ); + } + + stream->xrun = FALSE; + + /* Enable processing */ + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + stream->doStart = 1; + + /* Wait for stream to be started */ + result = WaitCondition( stream->hostApi ); + /* + do + { + err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts ); + } while( !stream->is_active && !err ); + */ + if( result != paNoError ) /* Something went wrong, call off the stream start */ + { + stream->doStart = 0; + stream->is_active = 0; /* Cancel any processing */ + } + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + + ENSURE_PA( result ); + + stream->is_running = TRUE; + PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ )); + +error: + return result; +} + +static PaError RealStop( PaJackStream *stream, int abort ) +{ + PaError result = paNoError; + int i; + + if( stream->isBlockingStream ) + BlockingWaitEmpty ( stream ); + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + if( abort ) + stream->doAbort = 1; + else + stream->doStop = 1; + + /* Wait for stream to be stopped */ + result = WaitCondition( stream->hostApi ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + ENSURE_PA( result ); + + UNLESS( !stream->is_active, paInternalError ); + + PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ )); + +error: + stream->is_running = FALSE; + + /* Disconnect ports belonging to this stream */ + + if( !stream->hostApi->jackIsDown ) /* XXX: Well? */ + { + if( stream->num_incoming_connections > 0 ) + { + for( i = 0; i < stream->num_incoming_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ), paUnanticipatedHostError ); + } + if( stream->num_outgoing_connections > 0 ) + { + for( i = 0; i < stream->num_outgoing_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ), paUnanticipatedHostError ); + } + } + + return result; +} + +static PaError StopStream( PaStream *s ) +{ + assert(s); + return RealStop( (PaJackStream *)s, 0 ); +} + +static PaError AbortStream( PaStream *s ) +{ + assert(s); + return RealStop( (PaJackStream *)s, 1 ); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return !stream->is_running; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return stream->is_active; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + + /* A: Is this relevant?? --> TODO: what if we're recording-only? */ + return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client ); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} diff --git a/portaudio-v19/pa_linux_alsa/pa_linux_alsa.c b/portaudio-v19/pa_linux_alsa/pa_linux_alsa.c new file mode 100644 index 000000000..dce232e36 --- /dev/null +++ b/portaudio-v19/pa_linux_alsa/pa_linux_alsa.c @@ -0,0 +1,3590 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * ALSA implementation by Joshua Haberman and Arve Knudsen + * + * Copyright (c) 2002 Joshua Haberman + * Copyright (c) 2005 Arve Knudsen + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API +#include +#undef ALSA_PCM_NEW_HW_PARAMS_API +#undef ALSA_PCM_NEW_SW_PARAMS_API + +#include +#include /* strlen() */ +#include +#include +#include +#include +#include +#include +#include /* For sig_atomic_t */ + +#include "portaudio.h" +#include "pa_util.h" +#include "../pa_unix/pa_unix_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_linux_alsa.h" + +/* Check return value of ALSA function, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (aErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() != callbackThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \ + } \ + PaUtil_DebugPrint( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" ); \ + if( (code) == paUnanticipatedHostError ) \ + PA_DEBUG(( "Host error description: %s\n", snd_strerror( aErr_ ) )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); + +#define ENSURE_SYSTEM_(expr, success) \ + do { \ + if( UNLIKELY( (aErr_ = (expr)) != success ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( pthread_self() != callbackThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, aErr_, strerror( aErr_ ) ); \ + } \ + PaUtil_DebugPrint( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" ); \ + result = paUnanticipatedHostError; \ + goto error; \ + } \ + } while( 0 ); + +#define ASSERT_CALL_(expr, success) \ + aErr_ = (expr); \ + assert( success == aErr_ ); + +static int aErr_; /* Used with ENSURE_ */ +static pthread_t callbackThread_; + +typedef enum +{ + StreamDirection_In, + StreamDirection_Out +} StreamDirection; + +/* Threading utility struct */ +typedef struct PaAlsaThreading +{ + pthread_t watchdogThread; + pthread_t callbackThread; + int watchdogRunning; + int rtSched; + int rtPrio; + int useWatchdog; + unsigned long throttledSleepTime; + volatile PaTime callbackTime; + volatile PaTime callbackCpuTime; + PaUtilCpuLoadMeasurer *cpuLoadMeasurer; +} PaAlsaThreading; + +typedef struct +{ + PaSampleFormat hostSampleFormat; + unsigned long framesPerBuffer; + int numUserChannels, numHostChannels; + int userInterleaved, hostInterleaved; + + snd_pcm_t *pcm; + snd_pcm_uframes_t bufferSize; + snd_pcm_format_t nativeFormat; + unsigned int nfds; + int ready; /* Marked ready from poll */ + void **userBuffers; + snd_pcm_uframes_t offset; + StreamDirection streamDir; + + snd_pcm_channel_area_t *channelAreas; /* Needed for channel adaption */ +} PaAlsaStreamComponent; + +/* Implementation specific stream structure */ +typedef struct PaAlsaStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + PaAlsaThreading threading; + + unsigned long framesPerUserBuffer, maxFramesPerHostBuffer; + + int primeBuffers; + int callbackMode; /* bool: are we running in callback mode? */ + int pcmsSynced; /* Have we successfully synced pcms */ + + /* the callback thread uses these to poll the sound device(s), waiting + * for data to be ready/available */ + struct pollfd* pfds; + int pollTimeout; + + /* Used in communication between threads */ + volatile sig_atomic_t callback_finished; /* bool: are we in the "callback finished" state? */ + volatile sig_atomic_t callbackAbort; /* Drop frames? */ + volatile sig_atomic_t callbackStop; /* Signal a stop */ + volatile sig_atomic_t isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */ + pthread_mutex_t stateMtx; /* Used to synchronize access to stream state */ + pthread_mutex_t startMtx; /* Used to synchronize stream start in callback mode */ + pthread_cond_t startCond; /* Wait untill audio is started in callback thread */ + + int neverDropInput; + + PaTime underrun; + PaTime overrun; + + PaAlsaStreamComponent capture, playback; +} +PaAlsaStream; + +/* PaAlsaHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaAlsaHostApiRepresentation +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaAlsaHostApiRepresentation; + +typedef struct PaAlsaDeviceInfo +{ + PaDeviceInfo commonDeviceInfo; + char *alsaName; + int isPlug; + int minInputChannels; + int minOutputChannels; +} +PaAlsaDeviceInfo; + +/* Threading utilities */ + +static void InitializeThreading( PaAlsaThreading *th, PaUtilCpuLoadMeasurer *clm ) +{ + th->watchdogRunning = 0; + th->rtSched = 0; + th->callbackTime = 0; + th->callbackCpuTime = 0; + th->useWatchdog = 1; + th->throttledSleepTime = 0; + th->cpuLoadMeasurer = clm; + + th->rtPrio = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2 + + sched_get_priority_min( SCHED_FIFO ); +} + +static PaError KillCallbackThread( PaAlsaThreading *th, int wait, PaError *exitResult, PaError *watchdogExitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + if( watchdogExitResult ) + *watchdogExitResult = paNoError; + + if( th->watchdogRunning ) + { + pthread_cancel( th->watchdogThread ); + ENSURE_SYSTEM_( pthread_join( th->watchdogThread, &pret ), 0 ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( watchdogExitResult ) + *watchdogExitResult = *(PaError *) pret; + free( pret ); + } + } + + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + /* TODO: Make join time out */ + if( !wait ) + { + PA_DEBUG(( "%s: Canceling thread %d\n", __FUNCTION__, th->callbackThread )); + pthread_cancel( th->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + } + PA_DEBUG(( "%s: Joining thread %d\n", __FUNCTION__, th->callbackThread )); + ENSURE_SYSTEM_( pthread_join( th->callbackThread, &pret ), 0 ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + +error: + return result; +} + +/** Lock a pthread_mutex_t. + * + * @concern ThreadCancellation We're disabling thread cancellation while the thread is holding a lock, so mutexes are + * properly unlocked at termination time. + */ +static PaError LockMutex( pthread_mutex_t *mtx ) +{ + PaError result = paNoError; + int oldState; + + ENSURE_SYSTEM_( pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldState ), 0 ); + ENSURE_SYSTEM_( pthread_mutex_lock( mtx ), 0 ); + +error: + return result; +} + +/** Unlock a pthread_mutex_t. + * + * @concern ThreadCancellation Thread cancellation is enabled again after the mutex is properly unlocked. + */ +static PaError UnlockMutex( pthread_mutex_t *mtx ) +{ + PaError result = paNoError; + int oldState; + + ENSURE_SYSTEM_( pthread_mutex_unlock( mtx ), 0 ); + ENSURE_SYSTEM_( pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldState ), 0 ); + +error: + return result; +} + +static void OnWatchdogExit( void *userData ) +{ + PaAlsaThreading *th = (PaAlsaThreading *) userData; + struct sched_param spm = { 0 }; + assert( th ); + + ASSERT_CALL_( pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ), 0 ); /* Lower before exiting */ + PA_DEBUG(( "Watchdog exiting\n" )); +} + +static PaError BoostPriority( PaAlsaThreading *th ) +{ + PaError result = paNoError; + struct sched_param spm = { 0 }; + spm.sched_priority = th->rtPrio; + + assert( th ); + + if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) + { + PA_UNLESS( errno == EPERM, paInternalError ); /* Lack permission to raise priority */ + PA_DEBUG(( "Failed bumping priority\n" )); + result = 0; + } + else + result = 1; /* Success */ +error: + return result; +} + +static void *WatchdogFunc( void *userData ) +{ + PaError result = paNoError, *pres = NULL; + int err; + PaAlsaThreading *th = (PaAlsaThreading *) userData; + unsigned intervalMsec = 500; + const PaTime maxSeconds = 3.; /* Max seconds between callbacks */ + PaTime timeThen = PaUtil_GetTime(), timeNow, timeElapsed, cpuTimeThen, cpuTimeNow, cpuTimeElapsed; + double cpuLoad, avgCpuLoad = 0.; + int throttled = 0; + + assert( th ); + + /* Execute OnWatchdogExit when exiting */ + pthread_cleanup_push( &OnWatchdogExit, th ); + + /* Boost priority of callback thread */ + PA_ENSURE( result = BoostPriority( th ) ); + if( !result ) + { + /* Boost failed, might as well exit */ + pthread_exit( NULL ); + } + + cpuTimeThen = th->callbackCpuTime; + { + int policy; + struct sched_param spm = { 0 }; + pthread_getschedparam( pthread_self(), &policy, &spm ); + PA_DEBUG(( "%s: Watchdog priority is %d\n", __FUNCTION__, spm.sched_priority )); + } + + while( 1 ) + { + double lowpassCoeff = 0.9, lowpassCoeff1 = 0.99999 - lowpassCoeff; + + /* Test before and after in case whatever underlying sleep call isn't interrupted by pthread_cancel */ + pthread_testcancel(); + Pa_Sleep( intervalMsec ); + pthread_testcancel(); + + if( PaUtil_GetTime() - th->callbackTime > maxSeconds ) + { + PA_DEBUG(( "Watchdog: Terminating callback thread\n" )); + /* Tell thread to terminate */ + err = pthread_kill( th->callbackThread, SIGKILL ); + pthread_exit( NULL ); + } + + PA_DEBUG(( "%s: PortAudio reports CPU load: %g\n", __FUNCTION__, PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) )); + + /* Check if we should throttle, or unthrottle :P */ + cpuTimeNow = th->callbackCpuTime; + cpuTimeElapsed = cpuTimeNow - cpuTimeThen; + cpuTimeThen = cpuTimeNow; + + timeNow = PaUtil_GetTime(); + timeElapsed = timeNow - timeThen; + timeThen = timeNow; + cpuLoad = cpuTimeElapsed / timeElapsed; + avgCpuLoad = avgCpuLoad * lowpassCoeff + cpuLoad * lowpassCoeff1; + /* + if( throttled ) + PA_DEBUG(( "Watchdog: CPU load: %g, %g\n", avgCpuLoad, cpuTimeElapsed )); + */ + if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) > .925 ) + { + static int policy; + static struct sched_param spm = { 0 }; + static const struct sched_param defaultSpm = { 0 }; + PA_DEBUG(( "%s: Throttling audio thread, priority %d\n", __FUNCTION__, spm.sched_priority )); + + pthread_getschedparam( th->callbackThread, &policy, &spm ); + if( !pthread_setschedparam( th->callbackThread, SCHED_OTHER, &defaultSpm ) ) + { + throttled = 1; + } + else + PA_DEBUG(( "Watchdog: Couldn't lower priority of audio thread: %s\n", strerror( errno ) )); + + /* Give other processes a go, before raising priority again */ + PA_DEBUG(( "%s: Watchdog sleeping for %lu msecs before unthrottling\n", __FUNCTION__, th->throttledSleepTime )); + Pa_Sleep( th->throttledSleepTime ); + + /* Reset callback priority */ + if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) + { + PA_DEBUG(( "%s: Couldn't raise priority of audio thread: %s\n", __FUNCTION__, strerror( errno ) )); + } + + if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) >= .99 ) + intervalMsec = 50; + else + intervalMsec = 100; + + /* + lowpassCoeff = .97; + lowpassCoeff1 = .99999 - lowpassCoeff; + */ + } + else if( throttled && avgCpuLoad < .8 ) + { + intervalMsec = 500; + throttled = 0; + + /* + lowpassCoeff = .9; + lowpassCoeff1 = .99999 - lowpassCoeff; + */ + } + } + + pthread_cleanup_pop( 1 ); /* Execute cleanup on exit */ + +error: + /* Shouldn't get here in the normal case */ + + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + pthread_exit( pres ); +} + +static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*callbackThreadFunc)( void * ), PaStream *s ) +{ + PaError result = paNoError; + pthread_attr_t attr; + int started = 0; + +#if defined _POSIX_MEMLOCK && (_POSIX_MEMLOCK != -1) + if( th->rtSched ) + { + if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 ) + { + int savedErrno = errno; /* In case errno gets overwritten */ + assert( savedErrno != EINVAL ); /* Most likely a programmer error */ + PA_UNLESS( (savedErrno == EPERM), paInternalError ); + PA_DEBUG(( "%s: Failed locking memory\n", __FUNCTION__ )); + } + else + PA_DEBUG(( "%s: Successfully locked memory\n", __FUNCTION__ )); + } +#endif + + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); + /* Priority relative to other processes */ + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + + PA_UNLESS( !pthread_create( &th->callbackThread, &attr, callbackThreadFunc, s ), paInternalError ); + started = 1; + + if( th->rtSched ) + { + if( th->useWatchdog ) + { + int err; + struct sched_param wdSpm = { 0 }; + /* Launch watchdog, watchdog sets callback thread priority */ + int prio = PA_MIN( th->rtPrio + 4, sched_get_priority_max( SCHED_FIFO ) ); + wdSpm.sched_priority = prio; + + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); + PA_UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError ); + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError ); + if( (err = pthread_create( &th->watchdogThread, &attr, &WatchdogFunc, th )) ) + { + PA_UNLESS( err == EPERM, paInternalError ); + /* Permission error, go on without realtime privileges */ + PA_DEBUG(( "Failed bumping priority\n" )); + } + else + { + int policy; + th->watchdogRunning = 1; + ENSURE_SYSTEM_( pthread_getschedparam( th->watchdogThread, &policy, &wdSpm ), 0 ); + /* Check if priority is right, policy could potentially differ from SCHED_FIFO (but that's alright) */ + if( wdSpm.sched_priority != prio ) + { + PA_DEBUG(( "Watchdog priority not set correctly (%d)\n", wdSpm.sched_priority )); + PA_ENSURE( paInternalError ); + } + } + } + else + PA_ENSURE( BoostPriority( th ) ); + } + +end: + return result; +error: + if( started ) + KillCallbackThread( th, 0, NULL, NULL ); + + goto end; +} + +static void CallbackUpdate( PaAlsaThreading *th ) +{ + th->callbackTime = PaUtil_GetTime(); + th->callbackCpuTime = PaUtil_GetCpuLoad( th->cpuLoadMeasurer ); +} + +/* prototypes for functions declared in this file */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ); +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ); + +/* Callback prototypes */ +static void *CallbackThreadFunc( void *userData ); + +/* Blocking prototypes */ +static signed long GetStreamReadAvailable( PaStream* s ); +static signed long GetStreamWriteAvailable( PaStream* s ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); + + +static const PaAlsaDeviceInfo *GetDeviceInfo( const PaUtilHostApiRepresentation *hostApi, int device ) +{ + return (const PaAlsaDeviceInfo *)hostApi->deviceInfos[device]; +} + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = NULL; + + PA_UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory( + sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory ); + PA_UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + alsaHostApi->hostApiIndex = hostApiIndex; + + *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paALSA; + (*hostApi)->info.name = "ALSA"; + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PA_ENSURE( BuildDeviceList( alsaHostApi ) ); + + PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &alsaHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); + + return result; + +error: + if( alsaHostApi ) + { + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + } + + return result; +} + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + assert( hostApi ); + + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + snd_config_update_free_global(); +} + +/*! Determine max channels and default latencies. + * + * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for + * traits like max channels, suitable default latencies and default sample rate. Upon error, max channels is set to zero, + * and a suitable result returned. The device is closed before returning. + */ +static PaError GropeDevice( snd_pcm_t *pcm, int *minChannels, int *maxChannels, double *defaultLowLatency, + double *defaultHighLatency, double *defaultSampleRate, int isPlug ) +{ + PaError result = paNoError; + snd_pcm_hw_params_t *hwParams; + snd_pcm_uframes_t lowLatency = 512, highLatency = 2048; + unsigned int minChans, maxChans; + double defaultSr = *defaultSampleRate; + + assert( pcm ); + + ENSURE_( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError ); + + snd_pcm_hw_params_alloca( &hwParams ); + snd_pcm_hw_params_any( pcm, hwParams ); + + if( defaultSr >= 0 ) + { + /* Could be that the device opened in one mode supports samplerates that the other mode wont have, + * so try again .. */ + if( SetApproximateSampleRate( pcm, hwParams, defaultSr ) < 0 ) + { + defaultSr = -1.; + PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ )); + } + } + + if( defaultSr < 0. ) /* Default sample rate not set */ + { + unsigned int sampleRate = 44100; /* Will contain approximate rate returned by alsa-lib */ + ENSURE_( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError ); + ENSURE_( GetExactSampleRate( hwParams, &defaultSr ), paUnanticipatedHostError ); + } + + ENSURE_( snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError ); + assert( maxChans <= INT_MAX ); + assert( maxChans > 0 ); /* Weird linking issue could cause wrong version of ALSA symbols to be called, + resulting in zeroed values */ + + /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */ + if( isPlug && maxChans > 128 ) + { + maxChans = 128; + PA_DEBUG(( "%s: Limiting number of plugin channels to %u\n", __FUNCTION__, maxChans )); + } + + /* TWEAKME: + * + * Giving values for default min and max latency is not + * straightforward. Here are our objectives: + * + * * for low latency, we want to give the lowest value + * that will work reliably. This varies based on the + * sound card, kernel, CPU, etc. I think it is better + * to give sub-optimal latency than to give a number + * too low and cause dropouts. My conservative + * estimate at this point is to base it on 4096-sample + * latency at 44.1 kHz, which gives a latency of 23ms. + * * for high latency we want to give a large enough + * value that dropouts are basically impossible. This + * doesn't really require as much tweaking, since + * providing too large a number will just cause us to + * select the nearest setting that will work at stream + * config time. + */ + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError ); + + /* Have to reset hwParams, to set new buffer size */ + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError ); + + *minChannels = (int)minChans; + *maxChannels = (int)maxChans; + *defaultSampleRate = defaultSr; + *defaultLowLatency = (double) lowLatency / *defaultSampleRate; + *defaultHighLatency = (double) highLatency / *defaultSampleRate; + +end: + snd_pcm_close( pcm ); + return result; + +error: + goto end; +} + +/* Initialize device info with invalid values (maxInputChannels and maxOutputChannels are set to zero since these indicate + * wether input/output is available) */ +static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo ) +{ + deviceInfo->structVersion = -1; + deviceInfo->name = NULL; + deviceInfo->hostApi = -1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->defaultLowInputLatency = -1.; + deviceInfo->defaultLowOutputLatency = -1.; + deviceInfo->defaultHighInputLatency = -1.; + deviceInfo->defaultHighOutputLatency = -1.; + deviceInfo->defaultSampleRate = -1.; +} + +/* Helper struct */ +typedef struct +{ + char *alsaName; + char *name; + int isPlug; + int hasPlayback; + int hasCapture; +} DeviceNames; + +static PaError PaAlsa_StrDup( PaAlsaHostApiRepresentation *alsaApi, + char **dst, + const char *src) +{ + PaError result = paNoError; + int len = strlen( src ) + 1; + + /* PA_DEBUG(("PaStrDup %s %d\n", src, len)); */ + + PA_UNLESS( *dst = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), + paInsufficientMemory ); + strncpy( *dst, src, len ); + +error: + return result; +} + +/* Disregard standard plugins + * XXX: Might want to make the "default" plugin available, if we can make it work + */ +static int IgnorePlugin( const char *pluginId ) +{ +#define numIgnored 10 + static const char *ignoredPlugins[numIgnored] = {"hw", "plughw", "plug", "default", "dsnoop", "dmix", "tee", + "file", "null", "shm"}; + int i; + + for( i = 0; i < numIgnored; ++i ) + { + if( !strcmp( pluginId, ignoredPlugins[i] ) ) + { + return 1; + } + } + + return 0; +} + +/* Build PaDeviceInfo list, ignore devices for which we cannot determine capabilities (possibly busy, sigh) */ +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) +{ + PaUtilHostApiRepresentation *commonApi = &alsaApi->commonHostApiRep; + PaAlsaDeviceInfo *deviceInfoArray; + int cardIdx = -1, devIdx = 0; + snd_ctl_card_info_t *cardInfo; + PaError result = paNoError; + size_t numDeviceNames = 0, maxDeviceNames = 1, i; + DeviceNames *deviceNames = NULL; + snd_config_t *topNode = NULL; + snd_pcm_info_t *pcmInfo; + int res; + int blocking = SND_PCM_NONBLOCK; + char alsaCardName[50]; + if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) ) + blocking = 0; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* count the devices by enumerating all the card numbers */ + + /* snd_card_next() modifies the integer passed to it to be: + * the index of the first card if the parameter is -1 + * the index of the next card if the parameter is the index of a card + * -1 if there are no more cards + * + * The function itself returns 0 if it succeeded. */ + cardIdx = -1; + snd_ctl_card_info_alloca( &cardInfo ); + snd_pcm_info_alloca( &pcmInfo ); + while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) + { + char *cardName; + int devIdx = -1; + snd_ctl_t *ctl; + char buf[50]; + + snprintf( alsaCardName, sizeof (alsaCardName), "hw:%d", cardIdx ); + + /* Acquire name of card */ + if( snd_ctl_open( &ctl, alsaCardName, 0 ) < 0 ) + continue; /* Unable to open card :( */ + snd_ctl_card_info( ctl, cardInfo ); + + PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, snd_ctl_card_info_get_name( cardInfo )) ); + + while( snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 ) + { + char *alsaDeviceName, *deviceName; + size_t len; + int hasPlayback = 0, hasCapture = 0; + snprintf( buf, sizeof (buf), "%s:%d,%d", "hw", cardIdx, devIdx ); + + /* Obtain info about this particular device */ + snd_pcm_info_set_device( pcmInfo, devIdx ); + snd_pcm_info_set_subdevice( pcmInfo, 0 ); + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasCapture = 1; + + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasPlayback = 1; + + if( !hasPlayback && !hasCapture ) + { + continue; /* Error */ + } + + /* The length of the string written by snprintf plus terminating 0 */ + len = snprintf( NULL, 0, "%s: %s (%s)", cardName, snd_pcm_info_get_name( pcmInfo ), buf ) + 1; + PA_UNLESS( deviceName = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), + paInsufficientMemory ); + snprintf( deviceName, len, "%s: %s (%s)", cardName, + snd_pcm_info_get_name( pcmInfo ), buf ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + PA_ENSURE( PaAlsa_StrDup( alsaApi, &alsaDeviceName, buf ) ); + + deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; + deviceNames[ numDeviceNames - 1 ].name = deviceName; + deviceNames[ numDeviceNames - 1 ].isPlug = 0; + deviceNames[ numDeviceNames - 1 ].hasPlayback = hasPlayback; + deviceNames[ numDeviceNames - 1 ].hasCapture = hasCapture; + } + snd_ctl_close( ctl ); + } + + /* Iterate over plugin devices */ + if( NULL == snd_config ) + { + /* snd_config_update is called implicitly by some functions, if this hasn't happened snd_config will be NULL (bleh) */ + ENSURE_( snd_config_update(), paUnanticipatedHostError ); + PA_DEBUG(( "Updating snd_config\n" )); + } + assert( snd_config ); + if( (res = snd_config_search( snd_config, "pcm", &topNode )) >= 0 ) + { + snd_config_iterator_t i, next; + + snd_config_for_each( i, next, topNode ) + { + const char *tpStr = NULL, *idStr = NULL; + char *alsaDeviceName, *deviceName; + snd_config_t *n = snd_config_iterator_entry( i ), *tp = NULL; + if( snd_config_get_type( n ) != SND_CONFIG_TYPE_COMPOUND ) + continue; + + ENSURE_( snd_config_search( n, "type", &tp ), paUnanticipatedHostError ); + ENSURE_( snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError ); + + ENSURE_( snd_config_get_id( n, &idStr ), paUnanticipatedHostError ); + if( IgnorePlugin( idStr ) ) + { + PA_DEBUG(( "%s: Ignoring ALSA plugin device %s of type %s\n", __FUNCTION__, idStr, tpStr )); + continue; + } + + PA_DEBUG(( "%s: Found plugin %s of type %s\n", __FUNCTION__, idStr, tpStr )); + + PA_UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 6 ), paInsufficientMemory ); + strcpy( alsaDeviceName, idStr ); + PA_UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 1 ), paInsufficientMemory ); + strcpy( deviceName, idStr ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + deviceNames[numDeviceNames - 1].alsaName = alsaDeviceName; + deviceNames[numDeviceNames - 1].name = deviceName; + deviceNames[numDeviceNames - 1].isPlug = 1; + deviceNames[numDeviceNames - 1].hasPlayback = 1; + deviceNames[numDeviceNames - 1].hasCapture = 1; + } + } + else + PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, snd_strerror( res ) )); + + /* allocate deviceInfo memory based on the number of devices */ + PA_UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo*) * (numDeviceNames) ), paInsufficientMemory ); + + /* allocate all device info structs in a contiguous block */ + PA_UNLESS( deviceInfoArray = (PaAlsaDeviceInfo*)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaAlsaDeviceInfo) * numDeviceNames ), paInsufficientMemory ); + + /* Loop over list of cards, filling in info, if a device is deemed unavailable (can't get name), + * it's ignored. + */ + /* while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) */ + for( i = 0, devIdx = 0; i < numDeviceNames; ++i ) + { + snd_pcm_t *pcm; + PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[devIdx]; + PaDeviceInfo *commonDeviceInfo = &deviceInfo->commonDeviceInfo; + + /* Zero fields */ + InitializeDeviceInfo( commonDeviceInfo ); + + /* to determine device capabilities, we must open the device and query the + * hardware parameter configuration space */ + + /* Query capture */ + if( deviceNames[i].hasCapture && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &deviceInfo->minInputChannels, &commonDeviceInfo->maxInputChannels, + &commonDeviceInfo->defaultLowInputLatency, &commonDeviceInfo->defaultHighInputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) + continue; /* Error */ + } + + /* Query playback */ + if( deviceNames[i].hasPlayback && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &deviceInfo->minOutputChannels, &commonDeviceInfo->maxOutputChannels, + &commonDeviceInfo->defaultLowOutputLatency, &commonDeviceInfo->defaultHighOutputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) + continue; /* Error */ + } + + commonDeviceInfo->structVersion = 2; + commonDeviceInfo->hostApi = alsaApi->hostApiIndex; + commonDeviceInfo->name = deviceNames[i].name; + deviceInfo->alsaName = deviceNames[i].alsaName; + deviceInfo->isPlug = deviceNames[i].isPlug; + + /* A: Storing pointer to PaAlsaDeviceInfo object as pointer to PaDeviceInfo object. + * Should now be safe to add device info, unless the device supports neither capture nor playback + */ + if( commonDeviceInfo->maxInputChannels > 0 || commonDeviceInfo->maxOutputChannels > 0 ) + { + if( commonApi->info.defaultInputDevice == paNoDevice && commonDeviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = devIdx; + if( commonApi->info.defaultOutputDevice == paNoDevice && commonDeviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = devIdx; + + commonApi->deviceInfos[devIdx++] = (PaDeviceInfo *) deviceInfo; + } + } + free( deviceNames ); + + commonApi->info.deviceCount = devIdx; /* Number of successfully queried devices */ + +end: + return result; + +error: + /* No particular action */ + goto end; +} + +/* Check against known device capabilities */ +static PaError ValidateParameters( const PaStreamParameters *parameters, PaUtilHostApiRepresentation *hostApi, StreamDirection mode ) +{ + PaError result = paNoError; + int maxChans; + const PaAlsaDeviceInfo *deviceInfo = NULL; + assert( parameters ); + + if( parameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( parameters->device < hostApi->info.deviceCount ); + PA_UNLESS( parameters->hostApiSpecificStreamInfo == NULL, paBadIODeviceCombination ); + deviceInfo = GetDeviceInfo( hostApi, parameters->device ); + } + else + { + const PaAlsaStreamInfo *streamInfo = parameters->hostApiSpecificStreamInfo; + + PA_UNLESS( parameters->device == paUseHostApiSpecificDeviceSpecification, paInvalidDevice ); + PA_UNLESS( streamInfo->size == sizeof (PaAlsaStreamInfo) && streamInfo->version == 1, + paIncompatibleHostApiSpecificStreamInfo ); + PA_UNLESS( streamInfo->deviceString != NULL, paInvalidDevice ); + + /* Skip further checking */ + return paNoError; + } + + assert( deviceInfo ); + assert( parameters->hostApiSpecificStreamInfo == NULL ); + maxChans = (StreamDirection_In == mode ? deviceInfo->commonDeviceInfo.maxInputChannels : + deviceInfo->commonDeviceInfo.maxOutputChannels); + PA_UNLESS( parameters->channelCount <= maxChans, paInvalidChannelCount ); + +error: + return result; +} + +/* Given an open stream, what sample formats are available? */ +static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm ) +{ + PaSampleFormat available = 0; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + snd_pcm_hw_params_any( pcm, hwParams ); + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0) + available |= paFloat32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0) + available |= paInt32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24 ) >= 0) + available |= paInt24; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0) + available |= paInt16; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0) + available |= paUInt8; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0) + available |= paInt8; + + return available; +} + +static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat ) +{ + switch( paFormat ) + { + case paFloat32: + return SND_PCM_FORMAT_FLOAT; + + case paInt16: + return SND_PCM_FORMAT_S16; + + case paInt24: + return SND_PCM_FORMAT_S24; + + case paInt32: + return SND_PCM_FORMAT_S32; + + case paInt8: + return SND_PCM_FORMAT_S8; + + case paUInt8: + return SND_PCM_FORMAT_U8; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +/** Open an ALSA pcm handle. + * + * The device to be open can be specified in a custom PaAlsaStreamInfo struct, or it will be a device number. In case of a + * device number, it maybe specified through an env variable (PA_ALSA_PLUGHW) that we should open the corresponding plugin + * device. + */ +static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *params, StreamDirection + streamDir, snd_pcm_t **pcm ) +{ + PaError result = paNoError; + int ret; + const char *deviceName = alloca( 50 ); + const PaAlsaDeviceInfo *deviceInfo = NULL; + PaAlsaStreamInfo *streamInfo = (PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo; + + if( !streamInfo ) + { + int usePlug = 0; + deviceInfo = GetDeviceInfo( hostApi, params->device ); + + /* If device name starts with hw: and PA_ALSA_PLUGHW is 1, we open the plughw device instead */ + if( !strncmp( "hw:", deviceInfo->alsaName, 3 ) && getenv( "PA_ALSA_PLUGHW" ) ) + usePlug = atoi( getenv( "PA_ALSA_PLUGHW" ) ); + if( usePlug ) + snprintf( (char *) deviceName, 50, "plug%s", deviceInfo->alsaName ); + else + deviceName = deviceInfo->alsaName; + } + else + deviceName = streamInfo->deviceString; + + PA_DEBUG(( "%s: Opening device %s\n", __FUNCTION__, deviceName )); + if( (ret = snd_pcm_open( pcm, deviceName, streamDir == StreamDirection_In ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK )) < 0 ) + { + /* Not to be closed */ + *pcm = NULL; + ENSURE_( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination ); + } + ENSURE_( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError ); + +end: + return result; + +error: + goto end; +} + +static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *parameters, + double sampleRate, StreamDirection streamDir ) +{ + PaError result = paNoError; + snd_pcm_t *pcm = NULL; + PaSampleFormat availableFormats; + /* We are able to adapt to a number of channels less than what the device supports */ + unsigned int numHostChannels; + PaSampleFormat hostFormat; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + if( !parameters->hostApiSpecificStreamInfo ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, parameters->device ); + numHostChannels = PA_MAX( parameters->channelCount, StreamDirection_In == streamDir ? + devInfo->minInputChannels : devInfo->minOutputChannels ); + } + else + numHostChannels = parameters->channelCount; + + PA_ENSURE( AlsaOpen( hostApi, parameters, streamDir, &pcm ) ); + + snd_pcm_hw_params_any( pcm, hwParams ); + + if( SetApproximateSampleRate( pcm, hwParams, sampleRate ) < 0 ) + { + result = paInvalidSampleRate; + goto error; + } + + if( snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 ) + { + result = paInvalidChannelCount; + goto error; + } + + /* See if we can find a best possible match */ + availableFormats = GetAvailableFormats( pcm ); + PA_ENSURE( hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) ); + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError ); + + { + /* It happens that this call fails because the device is busy */ + int ret = 0; + if( (ret = snd_pcm_hw_params( pcm, hwParams )) < 0) + { + ENSURE_( ret, ret == -EBUSY ? paDeviceUnavailable : paUnanticipatedHostError ); + } + } + +end: + if( pcm ) + { + snd_pcm_close( pcm ); + } + return result; + +error: + goto end; +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaError result = paFormatIsSupported; + + if( inputParameters ) + { + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + + if( outputParameters ) + { + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + if( inputChannelCount ) + { + if( (result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In )) + != paNoError ) + goto error; + } + if ( outputChannelCount ) + { + if( (result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out )) + != paNoError ) + goto error; + } + + return paFormatIsSupported; + +error: + return result; +} + +static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, PaAlsaHostApiRepresentation *alsaApi, + const PaStreamParameters *params, StreamDirection streamDir, int callbackMode ) +{ + PaError result = paNoError; + PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat; + assert( params->channelCount > 0 ); + + /* Make sure things have an initial value */ + memset( self, 0, sizeof (PaAlsaStreamComponent) ); + + if( NULL == params->hostApiSpecificStreamInfo ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( &alsaApi->commonHostApiRep, params->device ); + self->numHostChannels = PA_MAX( params->channelCount, StreamDirection_In == streamDir ? devInfo->minInputChannels + : devInfo->minOutputChannels ); + } + else + { + /* We're blissfully unaware of the minimum channelCount */ + self->numHostChannels = params->channelCount; + } + + PA_ENSURE( AlsaOpen( &alsaApi->commonHostApiRep, params, streamDir, &self->pcm ) ); + self->nfds = snd_pcm_poll_descriptors_count( self->pcm ); + hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat ); + + self->hostSampleFormat = hostSampleFormat; + self->nativeFormat = Pa2AlsaFormat( hostSampleFormat ); + self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved); + self->numUserChannels = params->channelCount; + self->streamDir = streamDir; + + if( !callbackMode && !self->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( self->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * self->numUserChannels ), + paInsufficientMemory ); + } + +error: + return result; +} + +static void PaAlsaStreamComponent_Terminate( PaAlsaStreamComponent *self ) +{ + snd_pcm_close( self->pcm ); + if( self->userBuffers ) + PaUtil_FreeMemory( self->userBuffers ); +} + +int nearbyint_(float value) { + if( value - (int)value > .5 ) + return (int)ceil( value ); + return (int)floor( value ); +} + +/** Initiate configuration, preparing for determining a period size suitable for both capture and playback components. + * + */ +static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *self, const PaStreamParameters *params, + int primeBuffers, snd_pcm_hw_params_t *hwParams, double *sampleRate ) +{ + /* Configuration consists of setting all of ALSA's parameters. + * These parameters come in two flavors: hardware parameters + * and software paramters. Hardware parameters will affect + * the way the device is initialized, software parameters + * affect the way ALSA interacts with me, the user-level client. + */ + + PaError result = paNoError; + snd_pcm_access_t accessMode, alternateAccessMode; + int dir = 0; + snd_pcm_t *pcm = self->pcm; + double sr = *sampleRate; + unsigned int minPeriods = 2; + + /* self->framesPerBuffer = framesPerHostBuffer; */ + + /* ... fill up the configuration space with all possibile + * combinations of parameters this device will accept */ + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + /* I think there should be at least 2 periods (even though ALSA doesn't appear to enforce this) */ + dir = 0; + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError ); + + if( self->userInterleaved ) + { + accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + } + else + { + accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + } + /* If requested access mode fails, try alternate mode */ + if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) + { + ENSURE_( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError ); + /* Flip mode */ + self->hostInterleaved = !self->userInterleaved; + } + + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError ); + + ENSURE_( SetApproximateSampleRate( pcm, hwParams, sr ), paInvalidSampleRate ); + ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError ); + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( *sampleRate - sr ) / *sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), paInvalidChannelCount ); + + *sampleRate = sr; + +end: + return result; + +error: + /* No particular action */ + goto end; +} + +static PaError PaAlsaStreamComponent_FinishConfigure( PaAlsaStreamComponent *self, snd_pcm_hw_params_t* hwParams, + const PaStreamParameters *params, int primeBuffers, double sampleRate, PaTime* returnedLatency ) +{ + PaError result = paNoError; + snd_pcm_sw_params_t* swParams; + snd_pcm_uframes_t bufSz = 0; + *returnedLatency = -1.; + + snd_pcm_sw_params_alloca( &swParams ); + + bufSz = (params->suggestedLatency * sampleRate) + self->framesPerBuffer; /* One period does not count as latency */ + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( self->pcm, hwParams, &bufSz ), paUnanticipatedHostError ); + + /* Set the parameters! */ + ENSURE_( snd_pcm_hw_params( self->pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size( hwParams, &self->bufferSize ), paUnanticipatedHostError ); + /* Latency in seconds, one period is not counted as latency */ + *returnedLatency = (self->bufferSize - self->framesPerBuffer) / sampleRate; + + /* Now software parameters... */ + ENSURE_( snd_pcm_sw_params_current( self->pcm, swParams ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_sw_params_set_start_threshold( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_stop_threshold( self->pcm, swParams, self->bufferSize ), paUnanticipatedHostError ); + + /* Silence buffer in the case of underrun */ + if( !primeBuffers ) /* XXX: Make sense? */ + { + snd_pcm_uframes_t boundary; + ENSURE_( snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_threshold( self->pcm, swParams, 0 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_size( self->pcm, swParams, boundary ), paUnanticipatedHostError ); + } + + ENSURE_( snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); + + /* Set the parameters! */ + ENSURE_( snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError ); + +error: + return result; +} + +static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiRepresentation *alsaApi, const PaStreamParameters *inParams, + const PaStreamParameters *outParams, double sampleRate, unsigned long framesPerUserBuffer, PaStreamCallback callback, + PaStreamFlags streamFlags, void *userData ) +{ + PaError result = paNoError; + assert( self ); + + memset( self, 0, sizeof (PaAlsaStream) ); + + if( NULL != callback ) + { + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->callbackStreamInterface, + callback, userData ); + self->callbackMode = 1; + } + else + { + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->blockingStreamInterface, + NULL, userData ); + } + + self->framesPerUserBuffer = framesPerUserBuffer; + self->neverDropInput = streamFlags & paNeverDropInput; + /* XXX: Ignore paPrimeOutputBuffersUsingStreamCallback untill buffer priming is fully supported in pa_process.c */ + /* + if( outParams & streamFlags & paPrimeOutputBuffersUsingStreamCallback ) + self->primeBuffers = 1; + */ + memset( &self->capture, 0, sizeof (PaAlsaStreamComponent) ); + memset( &self->playback, 0, sizeof (PaAlsaStreamComponent) ); + if( inParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->capture, alsaApi, inParams, StreamDirection_In, NULL != callback ) ); + if( outParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->playback, alsaApi, outParams, StreamDirection_Out, NULL != callback ) ); + + assert( self->capture.nfds || self->playback.nfds ); + + PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( (self->capture.nfds + + self->playback.nfds) * sizeof (struct pollfd) ), paInsufficientMemory ); + + PaUtil_InitializeCpuLoadMeasurer( &self->cpuLoadMeasurer, sampleRate ); + InitializeThreading( &self->threading, &self->cpuLoadMeasurer ); + ASSERT_CALL_( pthread_mutex_init( &self->stateMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_mutex_init( &self->startMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_cond_init( &self->startCond, NULL ), 0 ); + +error: + return result; +} + +/** Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and terminates individual StreamComponents. + */ +static void PaAlsaStream_Terminate( PaAlsaStream *self ) +{ + assert( self ); + + if( self->capture.pcm ) + { + PaAlsaStreamComponent_Terminate( &self->capture ); + } + if( self->playback.pcm ) + { + PaAlsaStreamComponent_Terminate( &self->playback ); + } + + PaUtil_FreeMemory( self->pfds ); + ASSERT_CALL_( pthread_mutex_destroy( &self->stateMtx ), 0 ); + ASSERT_CALL_( pthread_mutex_destroy( &self->startMtx ), 0 ); + ASSERT_CALL_( pthread_cond_destroy( &self->startCond ), 0 ); + + PaUtil_FreeMemory( self ); +} + +/** Calculate polling timeout + * + * @param frames Time to wait + * @return Polling timeout in milliseconds + */ +static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frames ) +{ + assert( stream->streamRepresentation.streamInfo.sampleRate > 0.0 ); + /* Period in msecs, rounded up */ + return (int)ceil( 1000 * frames / stream->streamRepresentation.streamInfo.sampleRate ); +} + +static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamComponent* self, const PaStreamParameters* params, + unsigned long framesPerUserBuffer, double sampleRate, snd_pcm_hw_params_t* hwParams ) +{ + PaError result = paNoError; + unsigned long bufferSize = params->suggestedLatency * sampleRate, framesPerHostBuffer; + int dir = 0; + + { + snd_pcm_uframes_t tmp; + snd_pcm_hw_params_get_buffer_size_min(hwParams, &tmp); + bufferSize = PA_MAX(bufferSize, tmp); + snd_pcm_hw_params_get_buffer_size_max(hwParams, &tmp); + bufferSize = PA_MIN(bufferSize, tmp); + } + + assert(bufferSize > 0); + + if( framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* Preferably the host buffer size should be a multiple of the user buffer size */ + + if( bufferSize > framesPerUserBuffer ) + { + snd_pcm_uframes_t remainder = bufferSize % framesPerUserBuffer; + if( remainder > framesPerUserBuffer / 2. ) + bufferSize += framesPerUserBuffer - remainder; + else + bufferSize -= remainder; + + assert( bufferSize % framesPerUserBuffer == 0 ); + } + else if( framesPerUserBuffer % bufferSize != 0 ) + { + /* Find a good compromise between user specified latency and buffer size */ + if( bufferSize > framesPerUserBuffer * .75 ) + { + bufferSize = framesPerUserBuffer; + } + else + { + snd_pcm_uframes_t newSz = framesPerUserBuffer; + while( newSz / 2 >= bufferSize ) + { + if( framesPerUserBuffer % (newSz / 2) != 0 ) + { + /* No use dividing any further */ + break; + } + newSz /= 2; + } + bufferSize = newSz; + } + + assert( framesPerUserBuffer % bufferSize == 0 ); + } + } + + /* Using 5 as a base number of periods, we try to approximate the suggested latency (+1 period), + finding a combination of period/buffer size which best fits these constraints */ + { + unsigned numPeriods = 4, maxPeriods = 0; + /* It may be that the device only supports 2 periods for instance */ + dir = 0; + ENSURE_( snd_pcm_hw_params_get_periods_max( hwParams, &maxPeriods, &dir ), paUnanticipatedHostError ); + assert( maxPeriods > 1 ); + /* One period is not counted as latency */ + maxPeriods -= 1; + numPeriods = PA_MIN( maxPeriods, numPeriods ); + + if( framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + framesPerHostBuffer = framesPerUserBuffer; + if( framesPerHostBuffer < bufferSize ) + { + while( bufferSize / framesPerHostBuffer > numPeriods ) + { + framesPerHostBuffer *= 2; + } + } + else + { + while( bufferSize / framesPerHostBuffer < numPeriods ) + { + if( framesPerUserBuffer % (framesPerHostBuffer / 2) != 0 ) + { + /* Can't be divided any further */ + break; + } + framesPerHostBuffer /= 2; + } + } + + if( framesPerHostBuffer < framesPerUserBuffer ) + { + assert( framesPerUserBuffer % framesPerHostBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 ) + framesPerHostBuffer *= 2; + else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 ) + framesPerHostBuffer /= 2; + } + } + else + { + assert( framesPerHostBuffer % framesPerUserBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer += framesPerUserBuffer; + else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer -= framesPerUserBuffer; + } + } + } + else + { + framesPerHostBuffer = bufferSize / numPeriods; + } + } + + assert( framesPerHostBuffer > 0 ); + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( self->pcm, hwParams, &framesPerHostBuffer, &dir ), paUnanticipatedHostError ); + self->framesPerBuffer = framesPerHostBuffer; + +error: + return result; +} + +/* We need to determine how many frames per host buffer (period) to use. Our + * goals are to provide the best possible performance, but also to + * honor the requested latency settings as closely as we can. Therefore this + * decision is based on: + * + * - the period sizes that playback and/or capture support. The + * host buffer size has to be one of these. + * - the number of periods that playback and/or capture support. + * + * We want to make period_size*(num_periods-1) to be as close as possible + * to latency*rate for both playback and capture. + * + * This method will determine suitable period sizes for capture and playback handles, and report the maximum number of + * frames per host buffer. The latter is relevant, in case we should be so unfortunate that the period size differs + * between capture and playback. If this should happen, the stream's hostBufferSizeMode attribute will be set to + * paUtilBoundedHostBufferSize, because the best we can do is limit the size of individual host buffers to the upper + * bound. The size of host buffers scheduled for processing should only matter if the user has specified a buffer size, + * but when he/she does we must strive for an optimal configuration. By default we'll opt for a fixed host buffer size, + * which should be fine if the period size is the same for capture and playback. In general, if there is a specified user + * buffer size, this method tries it best to determine a period size which is a multiple of the user buffer size. + * + * The framesPerBuffer attributes of the individual capture and playback components of the stream are set to corresponding + * values determined here. Since these should be reported as + * + * This is one of those blocks of code that will just take a lot of + * refinement to be any good. + * + * In the full-duplex case it is possible that the routine was unable + * to find a number of frames per buffer acceptable to both devices + * TODO: Implement an algorithm to find the value closest to acceptance + * by both devices, to minimize difference between period sizes? + */ +static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double sampleRate, const PaStreamParameters* inputParameters, + const PaStreamParameters* outputParameters, unsigned long framesPerUserBuffer, snd_pcm_hw_params_t* hwParamsCapture, + snd_pcm_hw_params_t* hwParamsPlayback, PaUtilHostBufferSizeMode* hostBufferSizeMode ) +{ + PaError result = paNoError; + unsigned long framesPerHostBuffer = 0; + int dir = 0; + + if( self->capture.pcm && self->playback.pcm ) + { + if( framesPerUserBuffer == paFramesPerBufferUnspecified ) + { + snd_pcm_uframes_t desiredLatency, e, minPeriodSize, maxPeriodSize, optimalPeriodSize, periodSize, + minCapture, minPlayback, maxCapture, maxPlayback; + + /* Come up with a common desired latency */ + + dir = 0; + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError ); + dir = 0; + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError ); + dir = 0; + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError ); + dir = 0; + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError ); + minPeriodSize = PA_MAX( minPlayback, minCapture ); + maxPeriodSize = PA_MIN( maxPlayback, maxCapture ); + PA_UNLESS( minPeriodSize <= maxPeriodSize, paBadIODeviceCombination ); + + desiredLatency = (snd_pcm_uframes_t)(PA_MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency ) + * sampleRate); + /* Clamp desiredLatency */ + { + snd_pcm_uframes_t maxBufferSize; + snd_pcm_uframes_t maxBufferSizeCapture, maxBufferSizePlayback; + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &maxBufferSizeCapture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &maxBufferSizePlayback ), paUnanticipatedHostError ); + maxBufferSize = PA_MIN( maxBufferSizeCapture, maxBufferSizePlayback ); + + desiredLatency = PA_MIN( desiredLatency, maxBufferSize ); + } + + /* Find the closest power of 2 */ + e = ilogb( minPeriodSize ); + if( minPeriodSize & (minPeriodSize - 1) ) + e += 1; + periodSize = (snd_pcm_uframes_t)pow( 2, e ); + + while( periodSize <= maxPeriodSize ) + { + if( snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 && + snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 ) + break; /* Ok! */ + + periodSize *= 2; + } + + /* 4 periods considered optimal */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); + + /* Find the closest power of 2 */ + e = ilogb( optimalPeriodSize ); + if( optimalPeriodSize & (optimalPeriodSize - 1) ) + e += 1; + optimalPeriodSize = (snd_pcm_uframes_t)pow( 2, e ); + + while( optimalPeriodSize >= periodSize ) + { + if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 ) < 0 ) + continue; + if( snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, optimalPeriodSize, 0 ) >= 0 ) + break; + optimalPeriodSize /= 2; + } + if( optimalPeriodSize > periodSize ) + periodSize = optimalPeriodSize; + + if( periodSize <= maxPeriodSize ) + { + /* Looks good, the periodSize _should_ be acceptable by both devices */ + ENSURE_( snd_pcm_hw_params_set_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ), + paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ), + paUnanticipatedHostError ); + self->capture.framesPerBuffer = self->playback.framesPerBuffer = periodSize; + framesPerHostBuffer = periodSize; + } + else + { + /* Unable to find a common period size, oh well */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); + + self->capture.framesPerBuffer = optimalPeriodSize; + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( self->capture.pcm, hwParamsCapture, &self->capture.framesPerBuffer, &dir ), + paUnanticipatedHostError ); + self->playback.framesPerBuffer = optimalPeriodSize; + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( self->playback.pcm, hwParamsPlayback, &self->playback.framesPerBuffer, &dir ), + paUnanticipatedHostError ); + framesPerHostBuffer = PA_MAX( self->capture.framesPerBuffer, self->playback.framesPerBuffer ); + *hostBufferSizeMode = paUtilBoundedHostBufferSize; + } + } + else + { + /* We choose the simple route and determine a suitable number of frames per buffer for one component of + * the stream, then we hope that this will work for the other component too (it should!). + */ + + unsigned maxPeriods = 0; + PaAlsaStreamComponent* first = &self->capture, * second = &self->playback; + const PaStreamParameters* firstStreamParams = inputParameters; + snd_pcm_hw_params_t* firstHwParams = hwParamsCapture, * secondHwParams = hwParamsPlayback; + + dir = 0; + ENSURE_( snd_pcm_hw_params_get_periods_max( hwParamsPlayback, &maxPeriods, &dir ), paUnanticipatedHostError ); + if( maxPeriods < 4 ) + { + /* The playback component is tricker to get right, try that first */ + first = &self->playback; + second = &self->capture; + firstStreamParams = outputParameters; + firstHwParams = hwParamsPlayback; + secondHwParams = hwParamsCapture; + } + + PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( first, firstStreamParams, framesPerUserBuffer, + sampleRate, firstHwParams ) ); + + second->framesPerBuffer = first->framesPerBuffer; + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( second->pcm, secondHwParams, &second->framesPerBuffer, &dir ), + paUnanticipatedHostError ); + if( self->capture.framesPerBuffer == self->playback.framesPerBuffer ) + { + framesPerHostBuffer = self->capture.framesPerBuffer; + } + else + { + framesPerHostBuffer = PA_MAX( self->capture.framesPerBuffer, self->playback.framesPerBuffer ); + *hostBufferSizeMode = paUtilBoundedHostBufferSize; + } + } + } + else /* half-duplex is a slightly simpler case */ + { + if( self->capture.pcm ) + { + PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( &self->capture, inputParameters, framesPerUserBuffer, + sampleRate, hwParamsCapture) ); + framesPerHostBuffer = self->capture.framesPerBuffer; + } + else + { + assert( self->playback.pcm ); + PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( &self->playback, outputParameters, framesPerUserBuffer, + sampleRate, hwParamsPlayback ) ); + framesPerHostBuffer = self->playback.framesPerBuffer; + } + } + + PA_UNLESS( framesPerHostBuffer != 0, paInternalError ); + self->maxFramesPerHostBuffer = framesPerHostBuffer; + +error: + return result; +} + +/** Set up ALSA stream parameters. + * + */ +static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParameters *inParams, const PaStreamParameters* + outParams, double sampleRate, unsigned long framesPerUserBuffer, double* inputLatency, double* outputLatency, + PaUtilHostBufferSizeMode* hostBufferSizeMode ) +{ + PaError result = paNoError; + double realSr = sampleRate; + snd_pcm_hw_params_t* hwParamsCapture, * hwParamsPlayback; + + snd_pcm_hw_params_alloca( &hwParamsCapture ); + snd_pcm_hw_params_alloca( &hwParamsPlayback ); + + if( self->capture.pcm ) + PA_ENSURE( PaAlsaStreamComponent_InitialConfigure( &self->capture, inParams, self->primeBuffers, hwParamsCapture, + &realSr ) ); + if( self->playback.pcm ) + PA_ENSURE( PaAlsaStreamComponent_InitialConfigure( &self->playback, outParams, self->primeBuffers, hwParamsPlayback, + &realSr ) ); + + PA_ENSURE( PaAlsaStream_DetermineFramesPerBuffer( self, realSr, inParams, outParams, framesPerUserBuffer, + hwParamsCapture, hwParamsPlayback, hostBufferSizeMode ) ); + + if( self->capture.pcm ) + { + assert( self->capture.framesPerBuffer != 0 ); + PA_ENSURE( PaAlsaStreamComponent_FinishConfigure( &self->capture, hwParamsCapture, inParams, self->primeBuffers, realSr, + inputLatency ) ); + PA_DEBUG(( "%s: Capture period size: %lu, latency: %f\n", __FUNCTION__, self->capture.framesPerBuffer, *inputLatency )); + } + if( self->playback.pcm ) + { + assert( self->playback.framesPerBuffer != 0 ); + PA_ENSURE( PaAlsaStreamComponent_FinishConfigure( &self->playback, hwParamsPlayback, outParams, self->primeBuffers, realSr, + outputLatency ) ); + PA_DEBUG(( "%s: Playback period size: %lu, latency: %f\n", __FUNCTION__, self->playback.framesPerBuffer, *outputLatency )); + } + + /* Should be exact now */ + self->streamRepresentation.streamInfo.sampleRate = realSr; + + /* this will cause the two streams to automatically start/stop/prepare in sync. + * We only need to execute these operations on one of the pair. + * A: We don't want to do this on a blocking stream. + */ + if( self->callbackMode && self->capture.pcm && self->playback.pcm ) + { + int err = snd_pcm_link( self->capture.pcm, self->playback.pcm ); + if( err == 0 ) + self->pcmsSynced = 1; + else + PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, snd_strerror( err ) )); + } + + { + unsigned long minFramesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerBuffer : ULONG_MAX, + self->playback.pcm ? self->playback.framesPerBuffer : ULONG_MAX ); + self->pollTimeout = CalculatePollTimeout( self, minFramesPerHostBuffer ); /* Period in msecs, rounded up */ + + /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */ + self->threading.throttledSleepTime = (unsigned long) (minFramesPerHostBuffer / sampleRate / 4 * 1000); + } + + if( self->callbackMode ) + { + /* If the user expects a certain number of frames per callback we will either have to rely on block adaption + * (framesPerHostBuffer is not an integer multiple of framesPerBuffer) or we can simply align the number + * of host buffer frames with what the user specified */ + if( self->framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* self->alignFrames = 1; */ + + /* Unless the ratio between number of host and user buffer frames is an integer we will have to rely + * on block adaption */ + /* + if( framesPerHostBuffer % framesPerBuffer != 0 || (self->capture.pcm && self->playback.pcm && + self->capture.framesPerBuffer != self->playback.framesPerBuffer) ) + self->useBlockAdaption = 1; + else + self->alignFrames = 1; + */ + } + } + +error: + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback* callback, + void *userData ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + PaAlsaStream *stream = NULL; + PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int numInputChannels = 0, numOutputChannels = 0; + PaTime inputLatency, outputLatency; + PaUtilHostBufferSizeMode hostBufferSizeMode = paUtilFixedHostBufferSize; + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; + + if( inputParameters ) + { + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); + + numInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); + + numOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* XXX: Why do we support this anyway? */ + if( framesPerBuffer == paFramesPerBufferUnspecified && getenv( "PA_ALSA_PERIODSIZE" ) != NULL ) + { + PA_DEBUG(( "%s: Getting framesPerBuffer from environment\n", __FUNCTION__ )); + framesPerBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") ); + } + + PA_UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory ); + PA_ENSURE( PaAlsaStream_Initialize( stream, alsaHostApi, inputParameters, outputParameters, sampleRate, + framesPerBuffer, callback, streamFlags, userData ) ); + + PA_ENSURE( PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerBuffer, + &inputLatency, &outputLatency, &hostBufferSizeMode ) ); + if( stream->capture.pcm && stream->playback.pcm ) + { + /* In full-duplex mode, host buffers should be of a fixed size unless we were unable to match the period + * sizes of the two handles */ + if( hostBufferSizeMode == paUtilFixedHostBufferSize ) + { + PA_UNLESS( stream->capture.framesPerBuffer == stream->playback.framesPerBuffer, paInternalError ); + } + else + { + assert( hostBufferSizeMode == paUtilBoundedHostBufferSize ); + PA_UNLESS( stream->capture.framesPerBuffer != stream->playback.framesPerBuffer, paInternalError ); + } + } + else + { + if( stream->capture.pcm ) + { + PA_UNLESS( stream->maxFramesPerHostBuffer == stream->capture.framesPerBuffer, paInternalError ); + } + else + { + PA_UNLESS( stream->maxFramesPerHostBuffer == stream->playback.framesPerBuffer, paInternalError ); + } + } + hostInputSampleFormat = stream->capture.hostSampleFormat; + hostOutputSampleFormat = stream->playback.hostSampleFormat; + + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + numInputChannels, inputSampleFormat, hostInputSampleFormat, + numOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, stream->maxFramesPerHostBuffer, + hostBufferSizeMode, callback, userData ) ); + + /* Ok, buffer processor is initialized, now we can deduce it's latency */ + if( numInputChannels > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency( + &stream->bufferProcessor ); + if( numOutputChannels > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( + &stream->bufferProcessor ); + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + { + PA_DEBUG(( "%s: Stream in error, terminating\n", __FUNCTION__ )); + PaAlsaStream_Terminate( stream ); + } + + return result; +} + +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaAlsaStream_Terminate( stream ); + + return result; +} + +static void SilenceBuffer( PaAlsaStream *stream ) +{ + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frames = (snd_pcm_uframes_t)snd_pcm_avail_update( stream->playback.pcm ), offset; + + snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames ); + snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat ); + snd_pcm_mmap_commit( stream->playback.pcm, offset, frames ); +} + +/** Start/prepare pcm(s) for streaming. + * + * Depending on wether the stream is in callback or blocking mode, we will respectively start or simply + * prepare the playback pcm. If the buffer has _not_ been primed, we will in callback mode prepare and + * silence the buffer before starting playback. In blocking mode we simply prepare, as the playback will + * be started automatically as the user writes to output. + * + * The capture pcm, however, will simply be prepared and started. + * + * PaAlsaStream::startMtx makes sure access is synchronized (useful in callback mode) + */ +static PaError AlsaStart( PaAlsaStream *stream, int priming ) +{ + PaError result = paNoError; + + if( stream->playback.pcm ) + { + if( stream->callbackMode ) + { + if( !priming ) + { + /* Buffer isn't primed, so prepare and silence */ + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + SilenceBuffer( stream ); + } + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + } + else + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + } + if( stream->capture.pcm && !stream->pcmsSynced ) + { + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + /* For a blocking stream we want to start capture as well, since nothing will happen otherwise */ + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); + } + +end: + return result; +error: + goto end; +} + +/** Utility function for determining if pcms are in running state. + * + */ +static int IsRunning( PaAlsaStream *stream ) +{ + int result = 0; + + LockMutex( &stream->stateMtx ); + if( stream->capture.pcm ) + { + snd_pcm_state_t capture_state = snd_pcm_state( stream->capture.pcm ); + + if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN + || capture_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + + if( stream->playback.pcm ) + { + snd_pcm_state_t playback_state = snd_pcm_state( stream->playback.pcm ); + + if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN + || playback_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + +end: + ASSERT_CALL_( UnlockMutex( &stream->stateMtx ), paNoError ); + + return result; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + int streamStarted = 0; /* So we can know wether we need to take the stream down */ + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* Set now, so we can test for activity further down */ + stream->isActive = 1; + + if( stream->callbackMode ) + { + int res = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; + + PA_ENSURE( CreateCallbackThread( &stream->threading, &CallbackThreadFunc, stream ) ); + streamStarted = 1; + + /* Wait for stream to be started */ + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + + /* Since we'll be holding a lock on the startMtx (when not waiting on the condition), IsRunning won't be checking + * stream state at the same time as the callback thread affects it. We also check IsStreamActive, in the unlikely + * case the callback thread exits in the meantime (the stream will be considered inactive after the thread exits) */ + PA_ENSURE( LockMutex( &stream->startMtx ) ); + + /* Due to possible spurious wakeups, we enclose in a loop */ + while( !IsRunning( stream ) && IsStreamActive( s ) && !res ) + { + res = pthread_cond_timedwait( &stream->startCond, &stream->startMtx, &ts ); + } + PA_ENSURE( UnlockMutex( &stream->startMtx ) ); + + PA_UNLESS( !res || res == ETIMEDOUT, paInternalError ); + PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - pt )); + + if( res == ETIMEDOUT ) + { + PA_ENSURE( paTimedOut ); + } + } + else + { + PA_ENSURE( AlsaStart( stream, 0 ) ); + streamStarted = 1; + } + +end: + return result; +error: + if( streamStarted ) + AbortStream( stream ); + stream->isActive = 0; + + goto end; +} + +static PaError AlsaStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + if( abort ) + { + if( stream->playback.pcm ) + { + ENSURE_( snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError ); + } + if( stream->capture.pcm && !stream->pcmsSynced ) + { + ENSURE_( snd_pcm_drop( stream->capture.pcm ), paUnanticipatedHostError ); + } + + PA_DEBUG(( "%s: Dropped frames\n", __FUNCTION__ )); + } + else + { + if( stream->playback.pcm ) + { + ENSURE_( snd_pcm_nonblock( stream->playback.pcm, 0 ), paUnanticipatedHostError ); + if( snd_pcm_drain( stream->playback.pcm ) < 0 ) + { + PA_DEBUG(( "%s: Draining playback handle failed!\n", __FUNCTION__ )); + } + } + if( stream->capture.pcm && !stream->pcmsSynced ) + { + /* We don't need to retrieve any remaining frames */ + if( snd_pcm_drop( stream->capture.pcm ) < 0 ) + { + PA_DEBUG(( "%s: Draining capture handle failed!\n", __FUNCTION__ )); + } + } + } + +end: + return result; +error: + goto end; +} + +/** Stop or abort stream. + * + * If a stream is in callback mode we will have to inspect wether the background thread has + * finished, or we will have to take it out. In either case we join the thread before + * returning. In blocking mode, we simply tell ALSA to stop abruptly (abort) or finish + * buffers (drain) + * + * Stream will be considered inactive (!PaAlsaStream::isActive) after a call to this function + */ +static PaError RealStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + /* First deal with the callback thread, cancelling and/or joining + * it if necessary + */ + if( stream->callbackMode ) + { + PaError threadRes, watchdogRes; + stream->callbackAbort = abort; + + if( !abort ) + { + PA_DEBUG(( "Stopping callback\n" )); + stream->callbackStop = 1; + } + PA_ENSURE( KillCallbackThread( &stream->threading, !abort, &threadRes, &watchdogRes ) ); + if( threadRes != paNoError ) + PA_DEBUG(( "Callback thread returned: %d\n", threadRes )); + if( watchdogRes != paNoError ) + PA_DEBUG(( "Watchdog thread returned: %d\n", watchdogRes )); + + stream->callbackStop = 0; /* The deed is done */ + stream->callback_finished = 0; + } + else + { + PA_ENSURE( AlsaStop( stream, abort ) ); + } + + stream->isActive = 0; + +end: + return result; + +error: + goto end; +} + +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream *) s, 0 ); +} + +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream * ) s, 1 ); +} + +/** The stream is considered stopped before StartStream, or AFTER a call to Abort/StopStream (callback + * returning !paContinue is not considered) + * + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream *)s; + + /* callback_finished indicates we need to join callback thread (ie. in Abort/StopStream) */ + return !IsStreamActive( s ) && !stream->callback_finished; +} + +static PaError IsStreamActive( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + return stream->isActive; +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + snd_timestamp_t timestamp; + snd_pcm_status_t *status; + snd_pcm_status_alloca( &status ); + + /* TODO: what if we have both? does it really matter? */ + + /* TODO: if running in callback mode, this will mean + * libasound routines are being called from multiple threads. + * need to verify that libasound is thread-safe. */ + + if( stream->capture.pcm ) + { + snd_pcm_status( stream->capture.pcm, status ); + } + else if( stream->playback.pcm ) + { + snd_pcm_status( stream->playback.pcm, status ); + } + + snd_pcm_status_get_tstamp( status, ×tamp ); + return timestamp.tv_sec + (PaTime)timestamp.tv_usec / 1000000.0; +} + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ) +{ + unsigned long approx = (unsigned long) sampleRate; + int dir = 0; + double fraction = sampleRate - approx; + + assert( pcm && hwParams ); + + if( fraction > 0.0 ) + { + if( fraction > 0.5 ) + { + ++approx; + dir = -1; + } + else + dir = 1; + } + + return snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir ); +} + +/* Return exact sample rate in param sampleRate */ +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ) +{ + unsigned int num, den; + int err; + + assert( hwParams ); + + err = snd_pcm_hw_params_get_rate_numden( hwParams, &num, &den ); + *sampleRate = (double) num / den; + + return err; +} + +/* Utility functions for blocking/callback interfaces */ + +/* Atomic restart of stream (we don't want the intermediate state visible) */ +static PaError AlsaRestart( PaAlsaStream *stream ) +{ + PaError result = paNoError; + + PA_ENSURE( LockMutex( &stream->stateMtx ) ); + PA_ENSURE( AlsaStop( stream, 0 ) ); + PA_ENSURE( AlsaStart( stream, 0 ) ); + + PA_DEBUG(( "%s: Restarted audio\n", __FUNCTION__ )); + +error: + PA_ENSURE( UnlockMutex( &stream->stateMtx ) ); + + return result; +} + +/** Recover from xrun state. + * + */ +static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) +{ + PaError result = paNoError; + snd_pcm_status_t *st; + PaTime now = PaUtil_GetTime(); + snd_timestamp_t t; + + snd_pcm_status_alloca( &st ); + + if( self->playback.pcm ) + { + snd_pcm_status( self->playback.pcm, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + if( self->capture.pcm ) + { + snd_pcm_status( self->capture.pcm, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + + PA_ENSURE( AlsaRestart( self ) ); + +end: + return result; +error: + goto end; +} + +/** Decide if we should continue polling for specified direction, eventually adjust the poll timeout. + * + */ +static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamDir, int *pollTimeout, int *continuePoll ) +{ + PaError result = paNoError; + snd_pcm_sframes_t delay, margin; + int err; + const PaAlsaStreamComponent *component = NULL, *otherComponent = NULL; + + *continuePoll = 1; + + if( StreamDirection_In == streamDir ) + { + component = &stream->capture; + otherComponent = &stream->playback; + } + else + { + component = &stream->playback; + otherComponent = &stream->capture; + } + + /* ALSA docs say that negative delay should indicate xrun, but in my experience snd_pcm_delay returns -EPIPE */ + if( (err = snd_pcm_delay( otherComponent->pcm, &delay )) < 0 ) + { + if( err == -EPIPE ) + { + /* Xrun */ + *continuePoll = 0; + goto error; + } + + ENSURE_( err, paUnanticipatedHostError ); + } + + if( StreamDirection_Out == streamDir ) + { + /* Number of eligible frames before capture overrun */ + delay = otherComponent->bufferSize - delay; + } + margin = delay - otherComponent->framesPerBuffer / 2; + + if( margin < 0 ) + { + PA_DEBUG(( "%s: Stopping poll for %s\n", __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback" )); + *continuePoll = 0; + } + else if( margin < otherComponent->framesPerBuffer ) + { + *pollTimeout = CalculatePollTimeout( stream, margin ); + PA_DEBUG(( "%s: Trying to poll again for %s frames, pollTimeout: %d\n", + __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback", *pollTimeout )); + } + +error: + return result; +} + +/* Callback interface */ + +static void OnExit( void *data ) +{ + PaAlsaStream *stream = (PaAlsaStream *) data; + + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */ + PA_DEBUG(( "%s: Stopping ALSA handles\n", __FUNCTION__ )); + AlsaStop( stream, stream->callbackAbort ); + stream->callbackAbort = 0; /* Clear state */ + + PA_DEBUG(( "%s: Stoppage\n", __FUNCTION__ )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + stream->isActive = 0; +} + +static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *timeInfo ) +{ + snd_pcm_status_t *capture_status, *playback_status; + snd_timestamp_t capture_timestamp, playback_timestamp; + PaTime capture_time = 0., playback_time = 0.; + + snd_pcm_status_alloca( &capture_status ); + snd_pcm_status_alloca( &playback_status ); + + if( stream->capture.pcm ) + { + snd_pcm_sframes_t capture_delay; + + snd_pcm_status( stream->capture.pcm, capture_status ); + snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); + + capture_time = capture_timestamp.tv_sec + + ((PaTime)capture_timestamp.tv_usec / 1000000.0); + timeInfo->currentTime = capture_time; + + capture_delay = snd_pcm_status_get_delay( capture_status ); + timeInfo->inputBufferAdcTime = timeInfo->currentTime - + (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + if( stream->playback.pcm ) + { + snd_pcm_sframes_t playback_delay; + + snd_pcm_status( stream->playback.pcm, playback_status ); + snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); + + playback_time = playback_timestamp.tv_sec + + ((PaTime)playback_timestamp.tv_usec / 1000000.0); + + if( stream->capture.pcm ) /* Full duplex */ + { + /* Hmm, we have both a playback and a capture timestamp. + * Hopefully they are the same... */ + if( fabs( capture_time - playback_time ) > 0.01 ) + PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time))); + } + else + timeInfo->currentTime = playback_time; + + playback_delay = snd_pcm_status_get_delay( playback_status ); + timeInfo->outputBufferDacTime = timeInfo->currentTime + + (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; + } +} + +/** Called after buffer processing is finished. + * + * A number of mmapped frames is committed, it is possible that an xrun has occurred in the meantime. + * + * @param numFrames The number of frames that has been processed + * @param xrun Return whether an xrun has occurred + */ +static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun ) +{ + PaError result = paNoError; + int res; + + /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed + * afterwards + */ + if( !self->ready ) + goto end; + + res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + if( res == -EPIPE || res == -ESTRPIPE ) + { + *xrun = 1; + } + else + { + ENSURE_( res, paUnanticipatedHostError ); + } + +end: +error: + return result; +} + +/* Extract buffer from channel area */ +static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset ) +{ + return (unsigned char *) area->addr + (area->first + offset * area->step) / 8; +} + +/** Do necessary adaption between user and host channels. + * + @concern ChannelAdaption Adapting between user and host channels can involve silencing unused channels and + duplicating mono information if host outputs come in pairs. + */ +static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, int numFrames ) +{ + PaError result = paNoError; + unsigned char *p; + int i; + int unusedChans = self->numHostChannels - self->numUserChannels; + unsigned char *src, *dst; + int convertMono = (self->numHostChannels % 2) == 0 && (self->numUserChannels % 2) != 0; + + assert( StreamDirection_Out == self->streamDir ); + + if( self->hostInterleaved ) + { + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset ); + + /* Start after the last user channel */ + p = buffer + self->numUserChannels * swidth; + + if( convertMono ) + { + /* Convert the last user channel into stereo pair */ + src = buffer + (self->numUserChannels - 1) * swidth; + for( i = 0; i < numFrames; ++i ) + { + dst = src + swidth; + memcpy( dst, src, swidth ); + src += self->numHostChannels * swidth; + } + + /* Don't touch the channel we just wrote to */ + p += swidth; + --unusedChans; + } + + if( unusedChans > 0 ) + { + /* Silence unused output channels */ + for( i = 0; i < numFrames; ++i ) + { + memset( p, 0, swidth * unusedChans ); + p += self->numHostChannels * swidth; + } + } + } + else + { + /* We extract the last user channel */ + if( convertMono ) + { + ENSURE_( snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas + + (self->numUserChannels - 1), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError ); + --unusedChans; + } + if( unusedChans > 0 ) + { + snd_pcm_areas_silence( self->channelAreas + (self->numHostChannels - unusedChans), self->offset, unusedChans, numFrames, + self->nativeFormat ); + } + } + +error: + return result; +} + +static PaError PaAlsaStream_EndProcessing( PaAlsaStream *self, unsigned long numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + int xrun = 0; + + if( self->capture.pcm ) + { + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->capture, numFrames, &xrun ) ); + } + if( self->playback.pcm ) + { + if( self->playback.numHostChannels > self->playback.numUserChannels ) + PA_ENSURE( PaAlsaStreamComponent_DoChannelAdaption( &self->playback, &self->bufferProcessor, numFrames ) ); + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->playback, numFrames, &xrun ) ); + } + +error: + *xrunOccurred = xrun; + return result; +} + +/** Update the number of available frames. + * + */ +static PaError PaAlsaStreamComponent_GetAvailableFrames( PaAlsaStreamComponent *self, unsigned long *numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + snd_pcm_sframes_t framesAvail = snd_pcm_avail_update( self->pcm ); + *xrunOccurred = 0; + + if( -EPIPE == framesAvail ) + { + *xrunOccurred = 1; + framesAvail = 0; + } + else + ENSURE_( framesAvail, paUnanticipatedHostError ); + + *numFrames = framesAvail; + +error: + return result; +} + +/** Fill in pollfd objects. + */ +static PaError PaAlsaStreamComponent_BeginPolling( PaAlsaStreamComponent* self, struct pollfd* pfds ) +{ + PaError result = paNoError; + int ret = snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds ); + (void)ret; /* Prevent unused variable warning if asserts are turned off */ + assert( ret == self->nfds ); + + self->ready = 0; + + return result; +} + +/** Examine results from poll(). + * + * @param pfds pollfds to inspect + * @param shouldPoll Should we continue to poll + * @param xrun Has an xrun occurred + */ +static PaError PaAlsaStreamComponent_EndPolling( PaAlsaStreamComponent* self, struct pollfd* pfds, int* shouldPoll, int* xrun ) +{ + PaError result = paNoError; + unsigned short revents; + + ENSURE_( snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError ); + if( revents != 0 ) + { + if( revents & POLLERR ) + { + *xrun = 1; + } + else + self->ready = 1; + + *shouldPoll = 0; + } + +error: + return result; +} + +/** Return the number of available frames for this stream. + * + * @concern FullDuplex The minimum available for the two directions is calculated, it might be desirable to ignore + * one direction however (not marked ready from poll), so this is controlled by queryCapture and queryPlayback. + * + * @param queryCapture Check available for capture + * @param queryPlayback Check available for playback + * @param available The returned number of frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_GetAvailableFrames( PaAlsaStream *self, int queryCapture, int queryPlayback, unsigned long + *available, int *xrunOccurred ) +{ + PaError result = paNoError; + unsigned long captureFrames, playbackFrames; + *xrunOccurred = 0; + + assert( queryCapture || queryPlayback ); + + if( queryCapture ) + { + assert( self->capture.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->capture, &captureFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; + } + if( queryPlayback ) + { + assert( self->playback.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->playback, &playbackFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; + } + + if( queryCapture && queryPlayback ) + { + *available = PA_MIN( captureFrames, playbackFrames ); + /*PA_DEBUG(("capture: %lu, playback: %lu, combined: %lu\n", captureFrames, playbackFrames, *available));*/ + } + else if( queryCapture ) + { + *available = captureFrames; + } + else + { + *available = playbackFrames; + } + +end: +error: + return result; +} + +/** Wait for and report available buffer space from ALSA. + * + * Unless ALSA reports a minimum of frames available for I/O, we poll the ALSA filedescriptors for more. + * Both of these operations can uncover xrun conditions. + * + * @concern Xruns Both polling and querying available frames can report an xrun condition. + * + * @param framesAvail Return the number of available frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *framesAvail, int *xrunOccurred ) +{ + PaError result = paNoError; + int pollPlayback = self->playback.pcm != NULL, pollCapture = self->capture.pcm != NULL; + int pollTimeout = self->pollTimeout; + int xrun = 0; + + assert( self ); + assert( framesAvail ); + + if( !self->callbackMode ) + { + /* In blocking mode we will only wait if necessary */ + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, self->capture.pcm != NULL, self->playback.pcm != NULL, + framesAvail, &xrun ) ); + if( xrun ) + { + goto end; + } + + if( *framesAvail > 0 ) + { + /* Mark pcms ready from poll */ + if( self->capture.pcm ) + self->capture.ready = 1; + if( self->playback.pcm ) + self->playback.ready = 1; + + goto end; + } + } + + while( pollPlayback || pollCapture ) + { + int totalFds = 0; + struct pollfd *capturePfds = NULL, *playbackPfds = NULL; + + pthread_testcancel(); + + if( pollCapture ) + { + capturePfds = self->pfds; + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->capture, capturePfds ) ); + totalFds += self->capture.nfds; + } + if( pollPlayback ) + { + playbackPfds = self->pfds + (self->capture.pcm ? self->capture.nfds : 0); + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->playback, playbackPfds ) ); + totalFds += self->playback.nfds; + } + + if( poll( self->pfds, totalFds, pollTimeout ) < 0 ) + { + /* XXX: Depend on preprocessor condition? */ + if( errno == EINTR ) { /* gdb */ + continue; + } + + /* TODO: Add macro for checking system calls */ + PA_ENSURE( paInternalError ); + } + + /* check the return status of our pfds */ + if( pollCapture ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) ); + } + if( pollPlayback ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) ); + } + if( xrun ) + { + break; + } + + /* @concern FullDuplex If only one of two pcms is ready we may want to compromise between the two. + * If there is less than half a period's worth of samples left of frames in the other pcm's buffer we will + * stop polling. + */ + if( self->capture.pcm && self->playback.pcm ) + { + if( pollCapture && !pollPlayback ) + { + PA_ENSURE( ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture ) ); + } + else if( pollPlayback && !pollCapture ) + { + PA_ENSURE( ContinuePoll( self, StreamDirection_Out, &pollTimeout, &pollPlayback ) ); + } + } + } + + if( !xrun ) + { + /* Get the number of available frames for the pcms that are marked ready. + * @concern FullDuplex If only one direction is marked ready (from poll), the number of frames available for + * the other direction is returned. Output is normally preferred over capture however, so capture frames may be + * discarded to avoid overrun unless paNeverDropInput is specified. + */ + int captureReady = self->capture.pcm ? self->capture.ready : 0, + playbackReady = self->playback.pcm ? self->playback.ready : 0; + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, captureReady, playbackReady, framesAvail, &xrun ) ); + + if( self->capture.pcm && self->playback.pcm ) + { + if( !self->playback.ready && !self->neverDropInput ) + { + /* Drop input, a period's worth */ + assert( self->capture.ready ); + PaAlsaStreamComponent_EndProcessing( &self->capture, PA_MIN( self->capture.framesPerBuffer, + *framesAvail ), &xrun ); + *framesAvail = 0; + self->capture.ready = 0; + } + } + else if( self->capture.pcm ) + assert( self->capture.ready ); + else + assert( self->playback.ready ); + } + +end: +error: + if( xrun ) + { + /* Recover from the xrun state */ + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *framesAvail = 0; + } + else + { + if( 0 != *framesAvail ) + { + /* If we're reporting frames eligible for processing, one of the handles better be ready */ + PA_UNLESS( self->capture.ready || self->playback.ready, paInternalError ); + } + } + *xrunOccurred = xrun; + + return result; +} + +/** Register per-channel ALSA buffer information with buffer processor. + * + * Mmapped buffer space is acquired from ALSA, and registered with the buffer processor. Differences between the + * number of host and user channels is taken into account. + * + * @param numFrames On entrance the number of requested frames, on exit the number of contiguously accessible frames. + */ +static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, + unsigned long *numFrames, int *xrun ) +{ + PaError result = paNoError; + const snd_pcm_channel_area_t *areas, *area; + void (*setChannel)(PaUtilBufferProcessor *, unsigned int, void *, unsigned int) = + StreamDirection_In == self->streamDir ? PaUtil_SetInputChannel : PaUtil_SetOutputChannel; + unsigned char *buffer, *p; + int i; + unsigned long framesAvail; + + /* This _must_ be called before mmap_begin */ + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( self, &framesAvail, xrun ) ); + if( *xrun ) + { + *numFrames = 0; + goto end; + } + + ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + + if( self->hostInterleaved ) + { + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + + p = buffer = ExtractAddress( areas, self->offset ); + for( i = 0; i < self->numUserChannels; ++i ) + { + /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */ + setChannel( bp, i, p, self->numHostChannels ); + p += swidth; + } + } + else + { + for( i = 0; i < self->numUserChannels; ++i ) + { + area = areas + i; + buffer = ExtractAddress( area, self->offset ); + setChannel( bp, i, buffer, 1 ); + } + } + + /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ + self->channelAreas = (snd_pcm_channel_area_t *)areas; + +end: +error: + return result; +} + +/** Initiate buffer processing. + * + * ALSA buffers are registered with the PA buffer processor and the buffer size (in frames) set. + * + * @concern FullDuplex If both directions are being processed, the minimum amount of frames for the two directions is + * calculated. + * + * @param numFrames On entrance the number of available frames, on exit the number of received frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream *self, unsigned long *numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + unsigned long captureFrames = ULONG_MAX, playbackFrames = ULONG_MAX, commonFrames = 0; + int xrun = 0; + + if( *xrunOccurred ) + { + *numFrames = 0; + return result; + } + /* If we got here at least one of the pcm's should be marked ready */ + PA_UNLESS( self->capture.ready || self->playback.ready, paInternalError ); + + /* Extract per-channel ALSA buffer pointers and register them with the buffer processor. + * It is possible that a direction is not marked ready however, because it is out of sync with the other. + */ + if( self->capture.pcm && self->capture.ready ) + { + captureFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames, + &xrun ) ); + } + if( self->playback.pcm && self->playback.ready ) + { + playbackFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames, + &xrun ) ); + } + if( xrun ) + { + /* Nothing more to do */ + assert( 0 == commonFrames ); + goto end; + } + + commonFrames = PA_MIN( captureFrames, playbackFrames ); + /* assert( commonFrames <= *numFrames ); */ + if( commonFrames > *numFrames ) + { + /* Hmmm ... how come there are more frames available than we requested!? Blah. */ + PA_DEBUG(( "%s: Common available frames are reported to be more than number requested: %lu, %lu, callbackMode: %d\n", __FUNCTION__, + commonFrames, *numFrames, self->callbackMode )); + if( self->capture.pcm ) + { + PA_DEBUG(( "%s: captureFrames: %lu, capture.ready: %d\n", __FUNCTION__, captureFrames, self->capture.ready )); + } + if( self->playback.pcm ) + { + PA_DEBUG(( "%s: playbackFrames: %lu, playback.ready: %d\n", __FUNCTION__, playbackFrames, self->playback.ready )); + } + + commonFrames = 0; + goto end; + } + + /* Inform PortAudio of the number of frames we got. + * @concern FullDuplex We might be experiencing underflow in either end; if its an input underflow, we go on + * with output. If its output underflow however, depending on the paNeverDropInput flag, we may want to simply + * discard the excess input or call the callback with paOutputOverflow flagged. + */ + if( self->capture.pcm ) + { + if( self->capture.ready ) + { + PaUtil_SetInputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have input underflow */ + PaUtil_SetNoInput( &self->bufferProcessor ); + } + } + if( self->playback.pcm ) + { + if( self->playback.ready ) + { + PaUtil_SetOutputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have output underflow, but keeping input data (paNeverDropInput) */ + assert( self->neverDropInput ); + assert( self->capture.pcm != NULL ); + PA_DEBUG(( "%s: Setting output buffers to NULL\n", __FUNCTION__ )); + PaUtil_SetNoOutput( &self->bufferProcessor ); + } + } + +end: + *numFrames = commonFrames; +error: + if( xrun ) + { + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *numFrames = 0; + } + *xrunOccurred = xrun; + + return result; +} + +/** Callback thread's function. + * + * Roughly, the workflow can be described in the following way: The number of available frames that can be processed + * directly is obtained from ALSA, we then request as much directly accessible memory as possible within this amount + * from ALSA. The buffer memory is registered with the PA buffer processor and processing is carried out with + * PaUtil_EndBufferProcessing. Finally, the number of processed frames is reported to ALSA. The processing can + * happen in several iterations untill we have consumed the known number of available frames (or an xrun is detected). + */ +static void *CallbackThreadFunc( void *userData ) +{ + PaError result = paNoError, *pres = NULL; + PaAlsaStream *stream = (PaAlsaStream*) userData; + PaStreamCallbackTimeInfo timeInfo = {0, 0, 0}; + snd_pcm_sframes_t startThreshold = 0; + int callbackResult = paContinue; + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + int streamStarted = 0; + + assert( stream ); + + callbackThread_ = pthread_self(); + /* Execute OnExit when exiting */ + pthread_cleanup_push( &OnExit, stream ); + + /* Not implemented */ + assert( !stream->primeBuffers ); + + /* @concern StreamStart If the output is being primed the output pcm needs to be prepared, otherwise the + * stream is started immediately. The latter involves signaling the waiting main thread. + */ + if( stream->primeBuffers ) + { + snd_pcm_sframes_t avail; + + if( stream->playback.pcm ) + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + + /* We can't be certain that the whole ring buffer is available for priming, but there should be + * at least one period */ + avail = snd_pcm_avail_update( stream->playback.pcm ); + startThreshold = avail - (avail % stream->playback.framesPerBuffer); + assert( startThreshold >= stream->playback.framesPerBuffer ); + } + else + { + PA_ENSURE( LockMutex( &stream->startMtx ) ); + PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */ + ENSURE_SYSTEM_( pthread_cond_signal( &stream->startCond ), 0 ); + PA_ENSURE( UnlockMutex( &stream->startMtx ) ); + + streamStarted = 1; + } + + while( 1 ) + { + unsigned long framesAvail, framesGot; + int xrun = 0; + + pthread_testcancel(); + + /* @concern StreamStop if the main thread has requested a stop and the stream has not been effectively + * stopped we signal this condition by modifying callbackResult (we'll want to flush buffered output). + */ + if( stream->callbackStop && paContinue == callbackResult ) + { + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; + } + + if( paContinue != callbackResult ) + { + stream->callbackAbort = (paAbort == callbackResult); + if( stream->callbackAbort || + /** @concern BlockAdaption: Go on if adaption buffers are empty */ + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + goto end; + + PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ )); + /* There is still buffered output that needs to be processed */ + } + + /* Wait for data to become available, this comes down to polling the ALSA file descriptors untill we have + * a number of available frames. + */ + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + if( xrun ) + { + assert( 0 == framesAvail ); + continue; + + /* XXX: Report xruns to the user? A situation is conceivable where the callback is never invoked due + * to constant xruns, it might be desirable to notify the user of this. + */ + } + + /* Consume buffer space. Once we have a number of frames available for consumption we must retrieve the + * mmapped buffers from ALSA, this is contiguously accessible memory however, so we may receive smaller + * portions at a time than is available as a whole. Therefore we should be prepared to process several + * chunks successively. The buffers are passed to the PA buffer processor. + */ + while( framesAvail > 0 ) + { + xrun = 0; + + pthread_testcancel(); + + /** @concern Xruns Under/overflows are to be reported to the callback */ + if( stream->underrun > 0.0 ) + { + cbFlags |= paOutputUnderflow; + stream->underrun = 0.0; + } + if( stream->overrun > 0.0 ) + { + cbFlags |= paInputOverflow; + stream->overrun = 0.0; + } + if( stream->capture.pcm && stream->playback.pcm ) + { + /** @concern FullDuplex It's possible that only one direction is being processed to avoid an + * under- or overflow, this should be reported correspondingly */ + if( !stream->capture.ready ) + { + cbFlags |= paInputUnderflow; + PA_DEBUG(( "%s: Input underflow\n", __FUNCTION__ )); + } + else if( !stream->playback.ready ) + { + cbFlags |= paOutputOverflow; + PA_DEBUG(( "%s: Output overflow\n", __FUNCTION__ )); + } + } + + CallbackUpdate( &stream->threading ); + CalculateTimeInfo( stream, &timeInfo ); + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags ); + cbFlags = 0; + + /* CPU load measurement should include processing activivity external to the stream callback */ + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + framesGot = framesAvail; + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + /* Check the host buffer size against the buffer processor configuration */ + if( stream->bufferProcessor.hostBufferSizeMode == paUtilFixedHostBufferSize ) + { + /* We've committed to a fixed host buffer size, stick to that */ + framesGot = framesGot >= stream->maxFramesPerHostBuffer ? stream->maxFramesPerHostBuffer : 0; + } + else + { + /* We've committed to an upper bound on the size of host buffers */ + assert( stream->bufferProcessor.hostBufferSizeMode == paUtilBoundedHostBufferSize ); + framesGot = PA_MIN( framesGot, stream->maxFramesPerHostBuffer ); + } + framesAvail -= framesGot; + + if( framesGot > 0 ) + { + assert( !xrun ); + PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + } + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot ); + + if( framesGot == 0 ) + { + if( !xrun ) + { + PA_DEBUG(( "%s: Received less frames than reported from ALSA, framesAvail: %lu\n", __FUNCTION__, + framesAvail )); + } + + /* Go back to polling for more frames */ + break; + + } + + if( paContinue != callbackResult ) + break; + } + } + + /* Match pthread_cleanup_push */ + pthread_cleanup_pop( 1 ); + +end: + PA_DEBUG(( "%s: Thread %d exiting\n ", __FUNCTION__, pthread_self() )); + pthread_exit( pres ); + +error: + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + goto end; +} + +/* Blocking interface */ + +static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long framesGot, framesAvail; + void *userBuffer; + snd_pcm_t *save = stream->playback.pcm; + + assert( stream ); + + PA_UNLESS( stream->capture.pcm, paCanNotReadFromAnOutputOnlyStream ); + + /* Disregard playback */ + stream->playback.pcm = NULL; + + if( stream->overrun > 0. ) + { + result = paInputOverflowed; + stream->overrun = 0.0; + } + + if( stream->capture.userInterleaved ) + { + userBuffer = buffer; + } + else + { + /* Copy channels into local array */ + userBuffer = stream->capture.userBuffers; + memcpy( userBuffer, buffer, sizeof (void *) * stream->capture.numUserChannels ); + } + + /* Start stream if in prepared state */ + if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED ) + { + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); + } + + while( frames > 0 ) + { + int xrun = 0; + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); + + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } + } + +end: + stream->playback.pcm = save; + return result; +error: + goto end; +} + +static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frames ) +{ + PaError result = paNoError; + signed long err; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_uframes_t framesGot, framesAvail; + const void *userBuffer; + snd_pcm_t *save = stream->capture.pcm; + + assert( stream ); + + PA_UNLESS( stream->playback.pcm, paCanNotWriteToAnInputOnlyStream ); + + /* Disregard capture */ + stream->capture.pcm = NULL; + + if( stream->underrun > 0. ) + { + result = paOutputUnderflowed; + stream->underrun = 0.0; + } + + if( stream->playback.userInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->playback.userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback.numUserChannels ); + } + + while( frames > 0 ) + { + int xrun = 0; + snd_pcm_uframes_t hwAvail; + + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); + + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } + + /* Start stream after one period of samples worth */ + + /* Frames residing in buffer */ + PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); + framesAvail = err; + hwAvail = stream->playback.bufferSize - framesAvail; + + if( snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED && + hwAvail >= stream->playback.framesPerBuffer ) + { + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + } + } + +end: + stream->capture.pcm = save; + return result; +error: + goto end; +} + +/* Return frames available for reading. In the event of an overflow, the capture pcm will be restarted */ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long avail; + int xrun; + + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) + { + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) + PA_ENSURE( paInputOverflowed ); + } + + return (signed long)avail; + +error: + return result; +} + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long avail; + int xrun; + + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->playback, &avail, &xrun ) ); + if( xrun ) + { + snd_pcm_sframes_t savail; + + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + savail = snd_pcm_avail_update( stream->playback.pcm ); + + /* savail should not contain -EPIPE now, since PaAlsaStream_HandleXrun will only prepare the pcm */ + ENSURE_( savail, paUnanticipatedHostError ); + + avail = (unsigned long) savail; + } + + return (signed long)avail; + +error: + return result; +} + +/* Extensions */ + +/* Initialize host api specific structure */ +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ) +{ + info->size = sizeof (PaAlsaStreamInfo); + info->hostApiType = paALSA; + info->version = 1; + info->deviceString = NULL; +} + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.rtSched = enable; +} + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.useWatchdog = enable; +} diff --git a/portaudio-v19/pa_linux_alsa/pa_linux_alsa.h b/portaudio-v19/pa_linux_alsa/pa_linux_alsa.h new file mode 100644 index 000000000..b9d43c929 --- /dev/null +++ b/portaudio-v19/pa_linux_alsa/pa_linux_alsa.h @@ -0,0 +1,64 @@ +#ifndef PA_LINUX_ALSA_H +#define PA_LINUX_ALSA_H + +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * ALSA-specific extensions + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** @file + * ALSA-specific PortAudio API extension header file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PaAlsaStreamInfo +{ + unsigned long size; + PaHostApiTypeId hostApiType; + unsigned long version; + + const char *deviceString; +} +PaAlsaStreamInfo; + +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ); + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ); + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/portaudio-v19/pa_mac/pa_mac_hostapis.c b/portaudio-v19/pa_mac/pa_mac_hostapis.c new file mode 100644 index 000000000..f8306c302 --- /dev/null +++ b/portaudio-v19/pa_mac/pa_mac_hostapis.c @@ -0,0 +1,79 @@ +/* + * $Id$ + * Portable Audio I/O Library Windows initialization table + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file +Mac OS host API initialization function table. +*/ + + +#include "pa_hostapi.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacSm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +PaUtilHostApiInitializer *paHostApiInitializers[] = +{ +#ifdef PA_USE_COREAUDIO + PaMacCore_Initialize, +#endif + +#ifdef PA_USE_SM + PaMacSm_Initialize, +#endif + +#ifdef PA_USE_JACK + PaJack_Initialize, +#endif + +#ifdef PA_USE_ASIO + PaMacAsio_Initialize, +#endif + + PaSkeleton_Initialize, /* just for testing */ + + 0 /* NULL terminated array */ +}; + + +int paDefaultHostApiIndex = 0; diff --git a/portaudio-v19/pa_mac_core/notes.txt b/portaudio-v19/pa_mac_core/notes.txt new file mode 100644 index 000000000..4b9aa0f1d --- /dev/null +++ b/portaudio-v19/pa_mac_core/notes.txt @@ -0,0 +1,124 @@ +Notes on status of CoreAudio Implementation of PortAudio + +Document Last Updated December 9, 2005 + +There are currently two implementations of PortAudio for Mac Core Audio. + +The original is in pa_mac_core_old.c, and the newer one is in pa_mac_core_auhal.c. +Only pa_mac_core_auhal.c is currently developed and supported as it uses apple's +current core audio technology. To select one of the implementations, copy the +appropriate file to pa_mac_core.c, (eg. "cp pa_mac_core_auhal.c pa_mac_core.c") +then run configure and make as usual. + +---------------------------------------- + +Notes on Original/Default implementation: + +by Phil Burk and Darren Gibbs + +Last updated March 20, 2002 + +WHAT WORKS + +Output with very low latency, <10 msec. +Half duplex input or output. +Full duplex on the same CoreAudio device. +The paFLoat32, paInt16, paInt8, paUInt8 sample formats. +Pa_GetCPULoad() +Pa_StreamTime() + +KNOWN BUGS OR LIMITATIONS + +We do not yet support simultaneous input and output on different +devices. Note that some CoreAudio devices like the Roland UH30 look +like one device but are actually two different CoreAudio devices. The +Built-In audio is typically one CoreAudio device. + +Mono doesn't work. + +DEVICE MAPPING + +CoreAudio devices can support both input and output. But the sample +rates supported may be different. So we have map one or two PortAudio +device to each CoreAudio device depending on whether it supports +input, output or both. + +When we query devices, we first get a list of CoreAudio devices. Then +we scan the list and add a PortAudio device for each CoreAudio device +that supports input. Then we make a scan for output devices. + +------------------------------------------- + +Notes on Newer AUHAL implementation: + +by Bjorn Roche + +Last Updated December 9, 2005 + +Principle of Operation: + +This implementation uses AUHAL for audio I/O. To some extent, it also +operates at the "HAL" Layer, though this behavior can be limited by +platform specific flags (see pa_mac_core.h for details). The default +settings should be reasonable: they don't change the SR of the device and +don't cause interruptions if other devices are using the device. + +Major Software Elements Used: Apple's HAL AUs provide output SR +conversion transparently, however, only on output, so this +implementation uses AudioConverters to convert the sample rate on input. +A PortAudio ring buffer is used to buffer input when sample rate +conversion is required or when separate audio units are used for duplex +IO. Finally, a PortAudio buffer processor is used to convert formats and +provide additional buffers if needed. Internally, interleaved floating +point data streams are used exclusively - the audio unit converts from +the audio hardware's native format to interleaved float PCM and +PortAudio's Buffer processor is used for conversion to user formats. + +Simplex Input: Simplex input uses a single callback. If sample rate +conversion is required, a ring buffer and AudioConverter are used as +well. + +Simplex output: Simplex output uses a single callback. No ring buffer or +audio converter is used because AUHAL does its own output SR conversion. + +Duplex, one device (no SR conversion): When one device is used, a single +callback is used. This achieves very low latency. + +Duplex, separate devices or SR conversion: When SR conversion is +required, data must be buffered before it is converted and data is not +always available at the same times on input and output, so SR conversion +requires the same treatment as separate devices. The input callback +reads data and puts it in the ring buffer. The output callback reads the +data off the ring buffer, into an audio converter and finally to the +buffer processor. + + + +Known issues: + +- Latency: Latency settings are ignored except when doing I/O between different +devices. Still, latency should be very low. + +- Timing info. It reports on stream time, but I'm probably doing something +wrong since patest_sine_time often reports negative latency numbers. + +- xrun detection: The only xrun detection performed is when reading +and writing the ring buffer. There is probably more that can be done. + +- abort/stop issues: stopping a stream is always a complete operation, +but latency should be low enough to make the lack of a separate abort +unnecessary. Apple clarifies its AudioOutputUnitStop() call here: +http://lists.apple.com/archives/coreaudio-api/2005/Dec/msg00055.html + +- blocking interface: Not implemented. + +- multichannel: I don't have a multichannel IO box, so I didn't test it. +It should work though ;) I haven't done anything with "channel maps" +yet, so it's possible that more needs to be done. + +- sample rate conversion quality: the input audio converter is set to +the highest quality. I don't do anything about the output converter, and +I am not sure how AUHAL manages quality. + +- x86: I haven't tested it on an x86 Mac, but I tried to be aware of it, so +it should work without too much trouble. diff --git a/portaudio-v19/pa_mac_core/pa_mac_core.h b/portaudio-v19/pa_mac_core/pa_mac_core.h new file mode 100644 index 000000000..9dbe51824 --- /dev/null +++ b/portaudio-v19/pa_mac_core/pa_mac_core.h @@ -0,0 +1,64 @@ +/* + * Mac spcific flags for PA. + * portaudio.h should be included before this file. + */ + +/* + * A pointer to a paMacCoreStreamInfo may be passed as + * the hostApiSpecificStreamInfo in the PaStreamParameters struct + * when opening a stream. Use NULL, for the defaults. Note that for + * duplex streams, both infos should be the same or behaviour + * is undefined. + */ +typedef struct paMacCoreStreamInfo +{ + unsigned long size; /**< size of whole structure including this header */ + PaHostApiTypeId hostApiType;/**< host API for which this data is intended */ + unsigned long version; /**< structure version */ + unsigned long flags; /* flags to modify behaviour */ +} paMacCoreStreamInfo; + +/* Use this function to initialize a paMacCoreStreamInfo struct + using the requested flags. */ +void paSetupMacCoreStreamInfo( paMacCoreStreamInfo *data, unsigned long flags ) +{ + bzero( data, sizeof( paMacCoreStreamInfo ) ); + data->size = sizeof( paMacCoreStreamInfo ); + data->hostApiType = paCoreAudio; + data->version = 0x01; + data->flags = flags; +} + +/* + * Here is the struct that shoul + */ + +/* + * The following flags alter the behaviour of PA on the mac platform. + * they can be ORed together. These should work both for opening and + * checking a device. + */ +/* Allows PortAudio to change things like the device's frame size, + * which allows for much lower latency, but might disrupt the device + * if other programs are using it. */ +const unsigned long paMacCore_ChangeDeviceParameters = 0x01; + +/* In combination with the above flag, + * causes the stream opening to fail, unless the exact sample rates + * are supported by the device. */ +const unsigned long paMacCore_FailIfConversionRequired = 0x02; + + + +/* + * Here are some "preset" combinations of flags (above) to get to some + * common configurations. THIS IS OVERKILL, but if more flags are added + * it won't be. + */ +/*This is the default setting: do as much sample rate conversion as possible + * and as little mucking with the device as possible. */ +const unsigned long paMacCorePlayNice = 0x00; +/*This setting is tuned for pro audio apps. It allows SR conversion on input + and output, but it tries to set the appropriate SR on the device.*/ +const unsigned long paMacCorePro = 0x01; + diff --git a/portaudio-v19/pa_mac_core/pa_mac_core_auhal.c b/portaudio-v19/pa_mac_core/pa_mac_core_auhal.c new file mode 100644 index 000000000..c447fd5a3 --- /dev/null +++ b/portaudio-v19/pa_mac_core/pa_mac_core_auhal.c @@ -0,0 +1,1934 @@ +/* + * This is the AUHAL implementation of portaudio. Hopefully this will + * one day replace pa_mac_core. + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * Bjorn Roche and XO Audio LLC reserve no rights to this code. + * The maintainers of PortAudio may redistribute and modify the code and + * licenses as they deam appropriate. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + @file pa_mac_core + @author Bjorn Roche + @brief AUHAL implementation of PortAudio +*/ + +#include /* strlen(), memcmp() etc. */ + +#include +#include + + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "../pablio/ringbuffer.h" +#include "pa_mac_core.h" + +#ifndef MIN +#define MIN(a, b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a)<(b))?(b):(a)) +#endif + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define ERR(mac_error) PaMacCore_SetError(mac_error, __LINE__, 1 ) +#define WARNING(mac_error) PaMacCore_SetError(mac_error, __LINE__, 0 ) + +/* Help keep track of AUHAL element numbers */ +#define INPUT_ELEMENT (1) +#define OUTPUT_ELEMENT (0) + +/* Normal level of debugging */ +/* + */ +#define MAC_CORE_DEBUG +#ifdef MAC_CORE_DEBUG +# define DBUG(MSG) do { printf("||PaMacCore (AUHAL)|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define DBUG(MSG) +#endif + +/* Very verbose debugging */ +/* +#define MAC_CORE_VERBOSE_DEBUG + */ +#ifdef MAC_CORE_VERBOSE_DEBUG +# define VDBUG(MSG) do { printf("||PaMacCore|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define VDBUG(MSG) +#endif + +#define RING_BUFFER_ADVANCE_DENOMINATOR (4) + +/* Some utilities that sort of belong here, but were getting too unweildy */ +#include "pa_mac_core_utilities.c" +/* Special purpose ring buffer just for pa_mac_core input processing. */ +/* #include "pa_mac_core_input_ring_buffer.c" */ +#include "../pablio/ringbuffer.c" + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static OSStatus AudioIOProc( void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); +/* PaMacAUHAL - host api datastructure specific to this implementation */ +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + long devCount; + AudioDeviceID *devIds; /*array of all audio devices*/ + AudioDeviceID defaultIn; + AudioDeviceID defaultOut; +} +PaMacAUHAL; + +/* stream data structure specifically for this implementation */ +typedef struct PaMacCoreStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + /* implementation specific data goes here */ + bool bufferProcessorIsInitialized; + AudioUnit inputUnit; + AudioUnit outputUnit; + AudioDeviceID inputDevice; + AudioDeviceID outputDevice; + size_t userInChan; + size_t userOutChan; + size_t inputFramesPerBuffer; + size_t outputFramesPerBuffer; + /* We use this ring buffer when input and out devs are different. */ + RingBuffer inputRingBuffer; + /* We may need to do SR conversion on input. */ + AudioConverterRef inputSRConverter; + /* We need to preallocate an inputBuffer for reading data. */ + AudioBufferList inputAudioBufferList; + AudioTimeStamp startTime; + volatile bool isTimeSet; + volatile PaStreamCallbackFlags xrunFlags; + volatile enum { + STOPPED = 0, /* playback is completely stopped, + and the user has called StopStream(). */ + CALLBACK_STOPPED = 1, /* callback has requested stop, + but user has not yet called StopStream(). */ + STOPPING = 2, /* The stream is in the process of closing. + This state is just used internally; + externally it is indistinguishable from + ACTIVE.*/ + ACTIVE = 3 /* The stream is active and running. */ + } state; + double sampleRate; +} +PaMacCoreStream; + +static PaError OpenAndSetupOneAudioUnit( + const PaStreamParameters *inStreamParams, + const PaStreamParameters *outStreamParams, + const unsigned long requestedFramesPerBuffer, + unsigned long *actualInputFramesPerBuffer, + unsigned long *actualOutputFramesPerBuffer, + const PaMacAUHAL *auhalHostApi, + AudioUnit *audioUnit, + AudioConverterRef *srConverter, + AudioDeviceID *audioDevice, + const double sampleRate, + void *refCon ); + +/* for setting errors. */ +#define PA_AUHAL_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) + + + + +/*currently, this is only used in initialization, but it might be modified + to be used when the list of devices changes.*/ +static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) +{ + UInt32 size; + UInt32 propsize; + /* -- free any previous allocations -- */ + if( auhalHostApi->devIds ) + PaUtil_GroupFreeMemory(auhalHostApi->allocations, auhalHostApi->devIds); + auhalHostApi->devIds = NULL; + + /* -- figure out how many devices there are -- */ + AudioHardwareGetPropertyInfo( kAudioHardwarePropertyDevices, + &propsize, + NULL ); + auhalHostApi->devCount = propsize / sizeof( AudioDeviceID ); + + VDBUG( ( "Found %ld device(s).\n", auhalHostApi->devCount ) ); + + /* -- copy the device IDs -- */ + auhalHostApi->devIds = (AudioDeviceID *)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, + propsize ); + if( !auhalHostApi->devIds ) + return paInsufficientMemory; + AudioHardwareGetProperty( kAudioHardwarePropertyDevices, + &propsize, + auhalHostApi->devIds ); +#ifdef MAC_CORE_VERBOSE_DEBUG + { + int i; + for( i=0; idevCount; ++i ) + printf( "Device %d\t: %ld\n", i, auhalHostApi->devIds[i] ); + } +#endif + + size = sizeof(AudioDeviceID); + auhalHostApi->defaultIn = kAudioDeviceUnknown; + auhalHostApi->defaultOut = kAudioDeviceUnknown; + /* FEEDBACK: these calls could fail, in which case default in and out will + be unknown devices or could be undefined. Do I need to be more + rigorous here? */ + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, + &size, + &auhalHostApi->defaultIn); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, + &size, + &auhalHostApi->defaultOut); + VDBUG( ( "Default in : %ld\n", auhalHostApi->defaultIn ) ); + VDBUG( ( "Default out: %ld\n", auhalHostApi->defaultOut ) ); + + return paNoError; +} + +static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, + PaDeviceInfo *deviceInfo, + AudioDeviceID macCoreDeviceId, + int isInput) +{ + UInt32 propSize; + PaError err = paNoError; + UInt32 i; + int numChannels = 0; + AudioBufferList *buflist; + UInt32 frameLatency; + + /* Get the number of channels from the stream configuration. + Fail if we can't get this. */ + + err = ERR(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); + if (err) + return err; + + buflist = PaUtil_AllocateMemory(propSize); + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); + if (err) + return err; + + for (i = 0; i < buflist->mNumberBuffers; ++i) + numChannels += buflist->mBuffers[i].mNumberChannels; + + if (isInput) + deviceInfo->maxInputChannels = numChannels; + else + deviceInfo->maxOutputChannels = numChannels; + + /* Get the latency. Don't fail if we can't get this. */ + /* default to something reasonable */ + deviceInfo->defaultLowInputLatency = .01; + deviceInfo->defaultHighInputLatency = .01; + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); + if (!err) { + double secondLatency = frameLatency / deviceInfo->defaultSampleRate; + if (isInput) { + deviceInfo->defaultLowInputLatency = secondLatency; + deviceInfo->defaultHighInputLatency = secondLatency; + } + else { + deviceInfo->defaultLowOutputLatency = secondLatency; + deviceInfo->defaultHighOutputLatency = secondLatency; + } + } + return paNoError; +} + +static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi, + PaDeviceInfo *deviceInfo, + AudioDeviceID macCoreDeviceId, + PaHostApiIndex hostApiIndex ) +{ + Float64 sampleRate; + char *name; + PaError err = paNoError; + UInt32 propSize; + + memset(deviceInfo, 0, sizeof(deviceInfo)); + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + /* Get the device name. Fail if we can't get it. */ + err = ERR(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); + if (err) + return err; + + name = PaUtil_GroupAllocateMemory(auhalHostApi->allocations,propSize); + if ( !name ) + return paInsufficientMemory; + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); + if (err) + return err; + deviceInfo->name = name; + + /* Try to get the default sample rate. Don't fail if we can't get this. */ + propSize = sizeof(Float64); + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); + if (err) + deviceInfo->defaultSampleRate = 0.0; + else + deviceInfo->defaultSampleRate = sampleRate; + + /* Get the maximum number of input and output channels. Fail if we can't get this. */ + + err = GetChannelInfo(auhalHostApi, deviceInfo, macCoreDeviceId, 1); + if (err) + return err; + + err = GetChannelInfo(auhalHostApi, deviceInfo, macCoreDeviceId, 0); + if (err) + return err; + + return paNoError; +} + +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + PaMacAUHAL *auhalHostApi; + PaDeviceInfo *deviceInfoArray; + + auhalHostApi = (PaMacAUHAL*)PaUtil_AllocateMemory( sizeof(PaMacAUHAL) ); + if( !auhalHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + auhalHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !auhalHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + auhalHostApi->devIds = NULL; + auhalHostApi->devCount = 0; + + /* get the info we need about the devices */ + result = gatherDeviceInfo( auhalHostApi ); + if( result != paNoError ) + goto error; + + *hostApi = &auhalHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "Core Audio"; + + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + (*hostApi)->info.deviceCount = 0; + + if( auhalHostApi->devCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo*) * auhalHostApi->devCount); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo) * auhalHostApi->devCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < auhalHostApi->devCount; ++i ) + { + int err; + err = InitializeDeviceInfo( auhalHostApi, &deviceInfoArray[i], + auhalHostApi->devIds[i], + hostApiIndex ); + if (err == paNoError) + { /* copy some info and set the defaults */ + (*hostApi)->deviceInfos[(*hostApi)->info.deviceCount] = &deviceInfoArray[i]; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultIn) + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultOut) + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + (*hostApi)->info.deviceCount++; + } + else + { /* there was an error. we need to shift the devices down, so we ignore this one */ + int j; + auhalHostApi->devCount--; + for( j=i; jdevCount; ++j ) + auhalHostApi->devIds[j] = auhalHostApi->devIds[j+1]; + i--; + } + } + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &auhalHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &auhalHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); + + return result; + +error: + if( auhalHostApi ) + { + if( auhalHostApi->allocations ) + { + PaUtil_FreeAllAllocations( auhalHostApi->allocations ); + PaUtil_DestroyAllocationGroup( auhalHostApi->allocations ); + } + + PaUtil_FreeMemory( auhalHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + TODO: Double check that everything is handled by alloc group + */ + + if( auhalHostApi->allocations ) + { + PaUtil_FreeAllAllocations( auhalHostApi->allocations ); + PaUtil_DestroyAllocationGroup( auhalHostApi->allocations ); + } + + PaUtil_FreeMemory( auhalHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + /** These first checks are standard PA checks. We do some fancier checks + later. */ + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + } + else + { + outputChannelCount = 0; + } + + /* FEEDBACK */ + /* I think the only way to check a given format SR combo is */ + /* to try opening it. This could be disruptive, is that Okay? */ + /* The alternative is to just read off available sample rates, */ + /* but this will not work %100 of the time (eg, a device that */ + /* supports N output at one rate but only N/2 at a higher rate.)*/ + + /* The following code opens the device with the requested parameters to + see if it works. */ + { + PaError err; + PaStream *s; + err = OpenStream( hostApi, &s, inputParameters, outputParameters, + sampleRate, 1024, 0, (PaStreamCallback *)1, NULL ); + if( err != paNoError && err != paInvalidSampleRate ) + DBUG( ( "OpenStream @ %g returned: %d: %s\n", + (float) sampleRate, err, Pa_GetErrorText( err ) ) ); + if( err ) + return err; + err = CloseStream( s ); + if( err ) { + /* FEEDBACK: is this more serious? should we assert? */ + DBUG( ( "WARNING: could not close Stream. %d: %s\n", + err, Pa_GetErrorText( err ) ) ); + } + } + + return paFormatIsSupported; +} + +static PaError OpenAndSetupOneAudioUnit( + const PaStreamParameters *inStreamParams, + const PaStreamParameters *outStreamParams, + const unsigned long requestedFramesPerBuffer, + unsigned long *actualInputFramesPerBuffer, + unsigned long *actualOutputFramesPerBuffer, + const PaMacAUHAL *auhalHostApi, + AudioUnit *audioUnit, + AudioConverterRef *srConverter, + AudioDeviceID *audioDevice, + const double sampleRate, + void *refCon ) +{ + ComponentDescription desc; + Component comp; + /*An Apple TN suggests using CAStreamBasicDescription, but that is C++*/ + AudioStreamBasicDescription desiredFormat; + OSErr result = noErr; + PaError paResult = paNoError; + int line; + UInt32 callbackKey; + AURenderCallbackStruct rcbs; + unsigned long macInputStreamFlags = paMacCorePlayNice; + unsigned long macOutputStreamFlags = paMacCorePlayNice; + + /*change the flags here, if desired, for testing.*/ + /*macInputStreamInfo = macOutputStreamInfo = paMacCore_ChangeDeviceParameters ;*/ + + /* -- handle the degenerate case -- */ + if( !inStreamParams && !outStreamParams ) { + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return paNoError; + } + + /* -- get the user's api specific info, if they set any -- */ + if( inStreamParams && inStreamParams->hostApiSpecificStreamInfo ) + macInputStreamFlags= + ((paMacCoreStreamInfo*)inStreamParams->hostApiSpecificStreamInfo) + ->flags; + if( outStreamParams && outStreamParams->hostApiSpecificStreamInfo ) + macOutputStreamFlags= + ((paMacCoreStreamInfo*)outStreamParams->hostApiSpecificStreamInfo) + ->flags; + + /* + * The HAL AU is a Mac OS style "component". + * the first few steps deal with that. + * Later steps work on a combination of Mac OS + * components and the slightly lower level + * HAL. + */ + + /* -- describe the output type AudioUnit -- */ + /* Note: for the default AudioUnit, we could use the + * componentSubType value kAudioUnitSubType_DefaultOutput; + * but I don't think that's relevant here. + */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + /* -- find the component -- */ + comp = FindNextComponent( NULL, &desc ); + if( !comp ) + { + DBUG( ( "AUHAL component not found." ) ); + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return paUnanticipatedHostError; + } + /* -- open it -- */ + result = OpenAComponent( comp, audioUnit ); + if( result ) + { + DBUG( ( "Failed to open AUHAL component." ) ); + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return ERR( result ); + } + /* -- prepare a little error handling logic / hackery -- */ +#define ERR_WRAP(mac_err) do { result = mac_err ; line = __LINE__ ; if ( result != noErr ) goto error ; } while(0) + + /* -- if there is input, we have to explicitly enable input -- */ + if( inStreamParams ) + { + UInt32 enableIO; + enableIO = 1; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &enableIO, + sizeof(enableIO) ) ); + } + /* -- if there is no output, we must explicitly disable output -- */ + if( !outStreamParams ) + { + UInt32 enableIO; + enableIO = 0; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + OUTPUT_ELEMENT, + &enableIO, + sizeof(enableIO) ) ); + } + /* -- set the devices -- */ + /* make sure input and output are the same device if we are doing input and + output. */ + if( inStreamParams && outStreamParams ) + assert( outStreamParams->device == inStreamParams->device ); + if( inStreamParams ) + { + *audioDevice = auhalHostApi->devIds[inStreamParams->device] ; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + INPUT_ELEMENT, + audioDevice, + sizeof(AudioDeviceID) ) ); + } + if( outStreamParams ) + { + *audioDevice = auhalHostApi->devIds[outStreamParams->device] ; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + audioDevice, + sizeof(AudioDeviceID) ) ); + } + + /* -- set format -- */ + bzero( &desiredFormat, sizeof(desiredFormat) ); + desiredFormat.mFormatID = kAudioFormatLinearPCM ; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof( float ) * 8; + + result = 0; + /* set device format first, but only touch the device if the user asked */ + if( inStreamParams ) { + /*The callback never calls back if we don't set the FPB */ + /*This seems wierd, because I would think setting anything on the device + would be disruptive.*/ + paResult = setBestFramesPerBuffer( *audioDevice, FALSE, + requestedFramesPerBuffer, + actualInputFramesPerBuffer ); + if( paResult ) goto error; + if( macInputStreamFlags & paMacCore_ChangeDeviceParameters ) { + bool requireExact; + requireExact=macInputStreamFlags&paMacCore_FailIfConversionRequired; + paResult = setBestSampleRateForDevice( *audioDevice, FALSE, + requireExact, sampleRate ); + if( paResult ) goto error; + } + if( actualInputFramesPerBuffer && actualOutputFramesPerBuffer ) + *actualOutputFramesPerBuffer = *actualInputFramesPerBuffer ; + } + if( outStreamParams && !inStreamParams ) { + /*The callback never calls back if we don't set the FPB */ + /*This seems wierd, because I would think setting anything on the device + would be disruptive.*/ + paResult = setBestFramesPerBuffer( *audioDevice, TRUE, + requestedFramesPerBuffer, + actualOutputFramesPerBuffer ); + if( paResult ) goto error; + if( macOutputStreamFlags & paMacCore_ChangeDeviceParameters ) { + bool requireExact; + requireExact=macOutputStreamFlags&paMacCore_FailIfConversionRequired; + paResult = setBestSampleRateForDevice( *audioDevice, TRUE, + requireExact, sampleRate ); + if( paResult ) goto error; + } + } + /* now set the format on the Audio Units. */ + if( outStreamParams ) + { + desiredFormat.mSampleRate =sampleRate; + desiredFormat.mBytesPerPacket=sizeof(float)*outStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*outStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = outStreamParams->channelCount; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription) ) ); + } + if( inStreamParams ) + { + AudioStreamBasicDescription sourceFormat; + UInt32 size = sizeof( AudioStreamBasicDescription ); + + /* keep the sample rate of the device, or we confuse AUHAL */ + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &sourceFormat, + &size ) ); + desiredFormat.mSampleRate = sourceFormat.mSampleRate; + desiredFormat.mBytesPerPacket=sizeof(float)*inStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription) ) ); + } + /* set the maximumFramesPerSlice */ + /* not doing this causes real problems + (eg. the callback might not be called). The idea of setting both this + and the frames per buffer on the device is that we'll be most likely + to actually get the frame size we requested in the callback with the + minimum latency. */ + if( outStreamParams ) { + UInt32 size = sizeof( *actualOutputFramesPerBuffer ); + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + sizeof(unsigned long) ) ); + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + &size ) ); + } + if( inStreamParams ) { + /*UInt32 size = sizeof( *actualInputFramesPerBuffer );*/ + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Output, + INPUT_ELEMENT, + actualInputFramesPerBuffer, + sizeof(unsigned long) ) ); +/* Don't know why this causes problems + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, //Output, + INPUT_ELEMENT, + actualInputFramesPerBuffer, + &size ) ); +*/ + } + + /* -- if we have input, we may need to setup an SR converter -- */ + /* even if we got the sample rate we asked for, we need to do + the conversion in case another program changes the underlying SR. */ + /* FIXME: I think we need to monitor stream and change the converter if the incoming format changes. */ + if( inStreamParams ) { + AudioStreamBasicDescription desiredFormat; + AudioStreamBasicDescription sourceFormat; + UInt32 sourceSize = sizeof( sourceFormat ); + bzero( &desiredFormat, sizeof(desiredFormat) ); + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mFormatID = kAudioFormatLinearPCM ; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof( float ) * 8; + desiredFormat.mBytesPerPacket=sizeof(float)*inStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + + /* get the source format */ + ERR_WRAP( AudioUnitGetProperty( + *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &sourceFormat, + &sourceSize ) ); + + if( desiredFormat.mSampleRate != sourceFormat.mSampleRate ) + { + UInt32 value = kAudioConverterQuality_Max; + VDBUG(( "Creating sample rate converter for input" + " to convert from %g to %g\n", + (float)sourceFormat.mSampleRate, + (float)desiredFormat.mSampleRate ) ); + /* create our converter */ + ERR_WRAP( AudioConverterNew( + &sourceFormat, + &desiredFormat, + srConverter ) ); + /* Use highest quality */ + ERR_WRAP( AudioConverterSetProperty( + *srConverter, + kAudioConverterSampleRateConverterQuality, + sizeof( value ), + &value ) ); + } + } + /* -- set IOProc (callback) -- */ + callbackKey = outStreamParams ? kAudioUnitProperty_SetRenderCallback + : kAudioOutputUnitProperty_SetInputCallback ; + rcbs.inputProc = AudioIOProc; + rcbs.inputProcRefCon = refCon; + ERR_WRAP( AudioUnitSetProperty( + *audioUnit, + callbackKey, + kAudioUnitScope_Output, + outStreamParams ? OUTPUT_ELEMENT : INPUT_ELEMENT, + &rcbs, + sizeof(rcbs)) ); + + if( inStreamParams && outStreamParams && *srConverter ) + ERR_WRAP( AudioUnitSetProperty( + *audioUnit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &rcbs, + sizeof(rcbs)) ); + + /*IMPLEMENTME: may need to worry about channel mapping.*/ + + /* initialize the audio unit */ + ERR_WRAP( AudioUnitInitialize(*audioUnit) ); + + if( inStreamParams && outStreamParams ) + VDBUG( ("Opened device %ld for input and output.\n", *audioDevice ) ); + else if( inStreamParams ) + VDBUG( ("Opened device %ld for input.\n", *audioDevice ) ); + else if( outStreamParams ) + VDBUG( ("Opened device %ld for output.\n", *audioDevice ) ); + return paNoError; +#undef ERR_WRAP + + error: + CloseComponent( *audioUnit ); + *audioUnit = NULL; + if( result ) + return PaMacCore_SetError( result, line, 1 ); + return paResult; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; + PaMacCoreStream *stream = 0; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + VDBUG( ("Opening Stream.\n") ); + + /*These first few bits of code are from paSkeleton with few modifications.*/ + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostInputSampleFormat = paFloat32; + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostOutputSampleFormat = paFloat32; + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; /* Surpress 'uninitialized var' warnings. */ + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaMacCoreStream*)PaUtil_AllocateMemory( sizeof(PaMacCoreStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + /* If we fail after this point, we my be left in a bad state, with + some data structures setup and others not. So, first thing we + do is initialize everything so that if we fail, we know what hasn't + been touched. + */ + + stream->inputAudioBufferList.mBuffers[0].mData = NULL; + stream->inputRingBuffer.buffer = NULL; + stream->inputSRConverter = NULL; + stream->inputUnit = NULL; + stream->outputUnit = NULL; + stream->inputFramesPerBuffer = 0; + stream->outputFramesPerBuffer = 0; + stream->bufferProcessorIsInitialized = FALSE; + + assert( streamCallback ) ; /* only callback mode is implemented */ + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &auhalHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &auhalHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + /* -- Now we actually open and setup streams. -- */ + if( inputParameters && outputParameters && outputParameters->device == inputParameters->device ) + { /* full duplex. One device. */ + result = OpenAndSetupOneAudioUnit( inputParameters, + outputParameters, + framesPerBuffer, + &(stream->inputFramesPerBuffer), + &(stream->outputFramesPerBuffer), + auhalHostApi, + &(stream->inputUnit), + &(stream->inputSRConverter), + &(stream->inputDevice), + sampleRate, + stream ); + stream->outputUnit = stream->inputUnit; + stream->outputDevice = stream->inputDevice; + if( result != paNoError ) + goto error; + } + else + { /* full duplex, different devices OR simplex */ + result = OpenAndSetupOneAudioUnit( NULL, + outputParameters, + framesPerBuffer, + NULL, + &(stream->outputFramesPerBuffer), + auhalHostApi, + &(stream->outputUnit), + NULL, + &(stream->outputDevice), + sampleRate, + stream ); + if( result != paNoError ) + goto error; + result = OpenAndSetupOneAudioUnit( inputParameters, + NULL, + framesPerBuffer, + &(stream->inputFramesPerBuffer), + NULL, + auhalHostApi, + &(stream->inputUnit), + &(stream->inputSRConverter), + &(stream->inputDevice), + sampleRate, + stream ); + if( result != paNoError ) + goto error; + } + + if( stream->inputUnit ) { + const size_t szfl = sizeof(float); + /* setup the AudioBufferList used for input */ + bzero( &stream->inputAudioBufferList, sizeof( AudioBufferList ) ); + stream->inputAudioBufferList.mNumberBuffers = 1; + stream->inputAudioBufferList.mBuffers[0].mNumberChannels + = inputChannelCount; + stream->inputAudioBufferList.mBuffers[0].mDataByteSize + = stream->inputFramesPerBuffer*inputChannelCount*szfl; + stream->inputAudioBufferList.mBuffers[0].mData + = (float *) calloc( + stream->inputFramesPerBuffer*inputChannelCount, + szfl ); + if( !stream->inputAudioBufferList.mBuffers[0].mData ) + { + result = paInsufficientMemory; + goto error; + } + + /* + * If input and output devs are different or we are doing SR conversion, + * we also need a + * ring buffer to store inpt data while waiting for output + * data. + */ + if( (stream->outputUnit && stream->inputUnit != stream->outputUnit) + || stream->inputSRConverter ) + { + /* May want the ringSize ot initial position in + ring buffer to depend somewhat on sample rate change */ + /* Calculate an appropriate ring buffer size. It must be at least + 3x framesPerBuffer and 2x suggested latency and it must be a + power of 2. FEEDBACK: too liberal/conservative/another way?*/ + double latency; + int index, i; + void *data; + long ringSize; + if( !outputParameters ) + latency = inputParameters->suggestedLatency; + else + latency = MAX( inputParameters->suggestedLatency, + outputParameters->suggestedLatency ); + ringSize = latency * sampleRate * 2 * inputChannelCount; + VDBUG( ( "suggested latency: %d\n", (int) (latency*sampleRate) ) ); + if( ringSize < stream->inputFramesPerBuffer * 3 ) + ringSize = stream->inputFramesPerBuffer * 3 * inputChannelCount; + if( outputParameters && ringSize < stream->outputFramesPerBuffer * 3 ) + ringSize = stream->outputFramesPerBuffer * 3 * inputChannelCount; + VDBUG(("inFramesPerBuffer:%d\n",(int)stream->inputFramesPerBuffer)); + if( outputParameters ) + VDBUG(("outFramesPerBuffer:%d\n", + (int)stream->outputFramesPerBuffer)); + VDBUG(("Ringbuffer size (1): %d\n", (int)ringSize )); + + /* round up to the next power of 2 */ + index = -1; + for( i=0; i> i & 0x01 ) + index = i; + assert( index > 0 ); + if( ringSize <= ( 0x01 << index ) ) + ringSize = 0x01 << index ; + else + ringSize = 0x01 << ( index + 1 ); + + /*ringSize <<= 4; *//*16x bigger, for testing */ + + VDBUG(( "Final Ringbuffer size (2): %d\n", (int)ringSize )); + + /*now, we need to allocate memory for the ring buffer*/ + data = calloc( ringSize, szfl ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + /* now we can initialize the ring buffer */ + assert( 0 == + RingBuffer_Init( &stream->inputRingBuffer, + ringSize*szfl, data ) ); + /* advance the read point a little, so we are reading from the + middle of the buffer */ + if( stream->outputUnit ) + RingBuffer_AdvanceWriteIndex( &stream->inputRingBuffer, ringSize*szfl / RING_BUFFER_ADVANCE_DENOMINATOR ); + } + } + + /* -- initialize Buffer Processor -- */ + { + unsigned long maxHostFrames = stream->inputFramesPerBuffer; + if( stream->outputFramesPerBuffer > maxHostFrames ) + maxHostFrames = stream->outputFramesPerBuffer; + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + framesPerBuffer, + /* If sample rate conversion takes place, the buffer size + will not be known. */ + maxHostFrames, + stream->inputSRConverter + ? paUtilUnknownHostBufferSize + : paUtilBoundedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + } + stream->bufferProcessorIsInitialized = TRUE; + + /* + IMPLEMENT ME: initialise the following fields with estimated or actual + values. + I think this is okay the way it is br 12/1/05 + maybe need to change input latency estimate if IO devs differ + */ + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->sampleRate = sampleRate; + stream->userInChan = inputChannelCount; + stream->userOutChan = outputChannelCount; + + stream->isTimeSet = FALSE; + stream->state = STOPPED; + stream->xrunFlags = 0; + + *s = (PaStream*)stream; + + return result; + +error: + CloseStream( stream ); + return result; +} + +PaTime GetStreamTime( PaStream *s ) +{ + /* FIXME: I am not at all sure this timing info stuff is right. + patest_sine_time reports negative latencies, which is wierd.*/ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + AudioTimeStamp timeStamp; + + if ( !stream->isTimeSet ) + return (PaTime)0; + + if ( stream->outputDevice ) + AudioDeviceGetCurrentTime( stream->outputDevice, &timeStamp); + else if ( stream->inputDevice ) + AudioDeviceGetCurrentTime( stream->inputDevice, &timeStamp); + else + return (PaTime)0; + + return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->sampleRate; +} + +static void setStreamStartTime( PaMacCoreStream *stream ) +{ + /* FIXME: I am not at all sure this timing info stuff is right. + patest_sine_time reports negative latencies, which is wierd.*/ + if( stream->inputDevice ) + AudioDeviceGetCurrentTime( stream->inputDevice, &stream->startTime); + else + AudioDeviceGetCurrentTime( stream->outputDevice, &stream->startTime); +} + + +static PaTime TimeStampToSecs(PaMacCoreStream *stream, const AudioTimeStamp* timeStamp) +{ + if (timeStamp->mFlags & kAudioTimeStampSampleTimeValid) + return (timeStamp->mSampleTime / stream->sampleRate); + else + return 0; +} + +#define RING_BUFFER_EMPTY (1000) + +static OSStatus ringBufferIOProc( AudioConverterRef inAudioConverter, + UInt32*ioDataSize, + void** outData, + void*inUserData ) +{ + void *dummyData; + long dummySize; + RingBuffer *rb = (RingBuffer *) inUserData; + + assert( sizeof( UInt32 ) == sizeof( long ) ); + if( RingBuffer_GetReadAvailable( rb ) == 0 ) { + *outData = NULL; + *ioDataSize = 0; + return RING_BUFFER_EMPTY; + } + RingBuffer_GetReadRegions( rb, *ioDataSize, + outData, (long *)ioDataSize, + &dummyData, &dummySize ); + + assert( *ioDataSize ); + RingBuffer_AdvanceReadIndex( rb, *ioDataSize ); + + return noErr; +} + +/* + * Called by the AudioUnit API to process audio from the sound card. + * This is where the magic happens. + */ +/* FEEDBACK: there is a lot of redundant code here because of how all the cases differ. This makes it hard to maintain, so if there are suggestinos for cleaning it up, I'm all ears. */ +static OSStatus AudioIOProc( void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData ) +{ + unsigned long framesProcessed = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + PaMacCoreStream *stream = (PaMacCoreStream*)inRefCon; + const bool isRender = inBusNumber == OUTPUT_ELEMENT; + int callbackResult = paContinue ; + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + if( !stream->isTimeSet ) + setStreamStartTime( stream ); + stream->isTimeSet = TRUE; + + + /* -----------------------------------------------------------------*\ + This output may be useful for debugging, + But printing durring the callback is a bad enough idea that + this is not enabled by enableing the usual debugging calls. + \* -----------------------------------------------------------------*/ + /* + static int renderCount = 0; + static int inputCount = 0; + printf( "------------------- starting reder/input\n" ); + if( isRender ) + printf("Render callback (%d):\t", ++renderCount); + else + printf("Input callback (%d):\t", ++inputCount); + printf( "Call totals: %d (input), %d (render)\n", inputCount, renderCount ); + + printf( "--- inBusNumber: %lu\n", inBusNumber ); + printf( "--- inNumberFrames: %lu\n", inNumberFrames ); + printf( "--- %x ioData\n", (unsigned) ioData ); + if( ioData ) + { + int i=0; + printf( "--- ioData.mNumBuffers %lu: \n", ioData->mNumberBuffers ); + for( i=0; imNumberBuffers; ++i ) + printf( "--- ioData buffer %d size: %lu.\n", i, ioData->mBuffers[i].mDataByteSize ); + } + ----------------------------------------------------------------- */ + + if( isRender ) { + AudioTimeStamp currentTime; + timeInfo.outputBufferDacTime = TimeStampToSecs(stream, inTimeStamp); + AudioDeviceGetCurrentTime(stream->outputDevice, ¤tTime); + timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); + } + if( isRender && stream->inputUnit == stream->outputUnit ) + timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); + if( !isRender ) { + AudioTimeStamp currentTime; + timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); + AudioDeviceGetCurrentTime(stream->inputDevice, ¤tTime); + timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); + } + + + if( isRender && stream->inputUnit == stream->outputUnit + && !stream->inputSRConverter ) + { + /* --------- Full Duplex, One Device, no SR Conversion ------- + * + * This is the lowest latency case, and also the simplest. + * Input data and output data are available at the same time. + * we do not use the input SR converter or the input ring buffer. + * + */ + OSErr err = 0; + unsigned long frames; + int callbackResult; + + /* -- start processing -- */ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + /* -- compute frames. do some checks -- */ + assert( ioData->mNumberBuffers == 1 ); + assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan ); + frames = ioData->mBuffers[0].mDataByteSize; + frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels; + /* -- copy and process input data -- */ + err= AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + inNumberFrames, + &stream->inputAudioBufferList ); + /* FEEDBACK: I'm not sure what to do when this call fails */ + assert( !err ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + stream->inputAudioBufferList.mBuffers[0].mData, + stream->inputAudioBufferList.mBuffers[0].mNumberChannels); + /* -- Copy and process output data -- */ + PaUtil_SetOutputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedOutputChannels( &(stream->bufferProcessor), + 0, + ioData->mBuffers[0].mData, + ioData->mBuffers[0].mNumberChannels); + /* -- complete processing -- */ + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + else if( isRender ) + { + /* -------- Output Side of Full Duplex (Separate Devices or SR Conversion) + * -- OR Simplex Output + * + * This case handles output data as in the full duplex case, + * and, if there is input data, reads it off the ring buffer + * and into the PA buffer processor. If sample rate conversion + * is required on input, that is done here as well. + */ + unsigned long frames; + + /* Sometimes, when stopping a duplex stream we get erroneous + xrun flags, so if this is our last run, clear the flags. */ + int xrunFlags = stream->xrunFlags; + if( xrunFlags & paInputUnderflow ) + printf( "input underflow.\n" ); + if( xrunFlags & paInputOverflow ) + printf( "input overflow.\n" ); + if( stream->state == STOPPING || stream->state == CALLBACK_STOPPED ) + xrunFlags = 0; + + /* -- start processing -- */ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + xrunFlags ); + stream->xrunFlags = 0; /* FEEDBACK: we only send flags to Buf Proc once */ + + /* -- Copy and process output data -- */ + assert( ioData->mNumberBuffers == 1 ); + frames = ioData->mBuffers[0].mDataByteSize; + frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels; + assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan ); + PaUtil_SetOutputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedOutputChannels( &(stream->bufferProcessor), + 0, + ioData->mBuffers[0].mData, + ioData->mBuffers[0].mNumberChannels); + + /* -- copy and process input data, and complete processing -- */ + if( stream->inputUnit ) { + const int flsz = sizeof( float ); + /* Here, we read the data out of the ring buffer, through the + audio converter. */ + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + if( stream->inputSRConverter ) + { + OSStatus err; + UInt32 size; + float data[ inChan * frames ]; + size = sizeof( data ); + err = AudioConverterFillBuffer( + stream->inputSRConverter, + ringBufferIOProc, + &stream->inputRingBuffer, + &size, + (void *)&data ); + if( err == RING_BUFFER_EMPTY ) + { /*the ring buffer callback underflowed */ + err = 0; + bzero( ((char *)data) + size, sizeof(data)-size ); + stream->xrunFlags |= paInputUnderflow; + } + ERR( err ); + assert( !err ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + else + { + /* Without the AudioConverter is actually a bit more complex + because we have to do a little buffer processing that the + AudioConverter would otherwise handle for us. */ + void *data1, *data2; + long size1, size2; + RingBuffer_GetReadRegions( &stream->inputRingBuffer, + inChan*frames*flsz, + &data1, &size1, + &data2, &size2 ); + if( size1 / ( flsz * inChan ) == frames ) { + /* simplest case: all in first buffer */ + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data1, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex(&stream->inputRingBuffer, size1 ); + } else if( ( size1 + size2 ) / ( flsz * inChan ) < frames ) { + /*we underflowed. take what data we can, zero the rest.*/ + float data[frames*inChan]; + if( size1 ) + memcpy( data, data1, size1 ); + if( size2 ) + memcpy( data+size1, data2, size2 ); + bzero( data+size1+size2, frames*flsz*inChan - size1 - size2 ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex( &stream->inputRingBuffer, + size1+size2 ); + /* flag underflow */ + stream->xrunFlags |= paInputUnderflow; + } else { + /*we got all the data, but split between buffers*/ + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), + size1 / ( flsz * inChan ) ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data1, + inChan ); + PaUtil_Set2ndInputFrameCount( &(stream->bufferProcessor), + size2 / ( flsz * inChan ) ); + PaUtil_Set2ndInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data2, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex(&stream->inputRingBuffer, size1+size2 ); + } + } + } else { + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + + } + else + { + /* ------------------ Input + * + * First, we read off the audio data and put it in the ring buffer. + * if this is an input-only stream, we need to process it more, + * otherwise, we let the output case deal with it. + */ + OSErr err = 0; + int chan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels ; + /* FIXME: looping here may not actually be necessary, but it was something I tried in testing. */ + do { + err= AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + inNumberFrames, + &stream->inputAudioBufferList ); + if( err == -10874 ) + inNumberFrames /= 2; + } while( err == -10874 && inNumberFrames > 1 ); + /* FEEDBACK: I'm not sure what to do when this call fails */ + ERR( err ); + assert( !err ); + if( stream->inputSRConverter || stream->outputUnit ) + { + /* If this is duplex or we use a converter, put the data + into the ring buffer. */ + long bytesIn, bytesOut; + bytesIn = sizeof( float ) * inNumberFrames * chan; + bytesOut = RingBuffer_Write( &stream->inputRingBuffer, + stream->inputAudioBufferList.mBuffers[0].mData, + bytesIn ); + if( bytesIn != bytesOut ) + stream->xrunFlags |= paInputOverflow ; + } + else + { + /* for simplex input w/o SR conversion, + just pop the data into the buffer processor.*/ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), inNumberFrames); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + stream->inputAudioBufferList.mBuffers[0].mData, + chan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + if( !stream->outputUnit && stream->inputSRConverter ) + { + /* ------------------ Simplex Input w/ SR Conversion + * + * if this is a simplex input stream, we need to read off the buffer, + * do our sample rate conversion and pass the results to the buffer + * processor. + * The logic here is complicated somewhat by the fact that we don't + * know how much data is available, so we loop on reasonably sized + * chunks, and let the BufferProcessor deal with the rest. + * + */ + /*This might be too big or small depending on SR conversion*/ + float data[ chan * inNumberFrames ]; + OSStatus err; + do + { /*Run the buffer processor until we are out of data*/ + UInt32 size; + long f; + + size = sizeof( data ); + err = AudioConverterFillBuffer( + stream->inputSRConverter, + ringBufferIOProc, + &stream->inputRingBuffer, + &size, + (void *)data ); + if( err != RING_BUFFER_EMPTY ) + ERR( err ); + assert( err == 0 || err == RING_BUFFER_EMPTY ); + + f = size / ( chan * sizeof(float) ); + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), f ); + if( f ) + { + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + chan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + } while( callbackResult == paContinue && !err ); + } + } + + switch( callbackResult ) + { + case paContinue: break; + case paComplete: + case paAbort: + stream->state = CALLBACK_STOPPED ; + if( stream->outputUnit ) + AudioOutputUnitStop(stream->outputUnit); + if( stream->inputUnit ) + AudioOutputUnitStop(stream->inputUnit); + break; + } + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + return noErr; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + /* This may be called from a failed OpenStream. + Therefore, each piece of info is treated seperately. */ + PaError result = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + VDBUG( ( "Closing stream.\n" ) ); + + if( stream ) { + if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { + AudioUnitUninitialize( stream->outputUnit ); + CloseComponent( stream->outputUnit ); + } + stream->outputUnit = NULL; + if( stream->inputUnit ) + { + AudioUnitUninitialize( stream->inputUnit ); + CloseComponent( stream->inputUnit ); + stream->inputUnit = NULL; + } + if( stream->inputRingBuffer.buffer ) + free( stream->inputRingBuffer.buffer ); + stream->inputRingBuffer.buffer = NULL; + /*TODO: is there more that needs to be done on error + from AudioConverterDispose?*/ + if( stream->inputSRConverter ) + ERR( AudioConverterDispose( stream->inputSRConverter ) ); + stream->inputSRConverter = NULL; + if( stream->inputAudioBufferList.mBuffers[0].mData ) + free( stream->inputAudioBufferList.mBuffers[0].mData ); + stream->inputAudioBufferList.mBuffers[0].mData = NULL; + + if( stream->bufferProcessorIsInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + } + + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + OSErr result = noErr; + VDBUG( ( "Starting stream.\n" ) ); + +#define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0) + + /*FIXME: maybe want to do this on close/abort for faster start? */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + if( stream->inputSRConverter ) + ERR_WRAP( AudioConverterReset( stream->inputSRConverter ) ); + + /* -- start -- */ + stream->state = ACTIVE; + if( stream->inputUnit ) { + ERR_WRAP( AudioOutputUnitStart(stream->inputUnit) ); + } + if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { + ERR_WRAP( AudioOutputUnitStart(stream->outputUnit) ); + } + + return paNoError; +#undef ERR_WRAP +} + + +static PaError StopStream( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + OSErr result = noErr; + VDBUG( ( "Stopping stream.\n" ) ); + + stream->state = STOPPING; + +#define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0) + /* -- stop and reset -- */ + if( stream->inputUnit == stream->outputUnit && stream->inputUnit ) + { + ERR_WRAP( AudioOutputUnitStop(stream->inputUnit) ); + ERR_WRAP( AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1) ); + ERR_WRAP( AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 0) ); + } + else + { + if( stream->inputUnit ) + { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit) ); + ERR_WRAP(AudioUnitReset(stream->inputUnit,kAudioUnitScope_Global,1)); + } + if( stream->outputUnit ) + { + ERR_WRAP(AudioOutputUnitStop(stream->outputUnit)); + ERR_WRAP(AudioUnitReset(stream->outputUnit,kAudioUnitScope_Global,0)); + } + } + if( stream->inputRingBuffer.buffer ) { + RingBuffer_Flush( &stream->inputRingBuffer ); + bzero(stream->inputRingBuffer.buffer,stream->inputRingBuffer.bufferSize); + /* advance the write point a little, so we are reading from the + middle of the buffer. We'll need extra at the end because + testing has shown that this helps. */ + if( stream->outputUnit ) + RingBuffer_AdvanceWriteIndex( &stream->inputRingBuffer, + stream->inputRingBuffer.bufferSize + / RING_BUFFER_ADVANCE_DENOMINATOR ); + } + + stream->isTimeSet = FALSE; + stream->xrunFlags = 0; + stream->state = STOPPED; + + VDBUG( ( "Stream Stopped.\n" ) ); + return paNoError; +#undef ERR_WRAP +} + +static PaError AbortStream( PaStream *s ) +{ + VDBUG( ( "Aborting stream.\n" ) ); + /* We have nothing faster than StopStream. */ + return StopStream(s); +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->state == STOPPED ? 1 : 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + return ( stream->state == ACTIVE || stream->state == STOPPING ); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. IMPLEMENTME: no blocking interface yet! +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} diff --git a/portaudio-v19/pa_mac_core/pa_mac_core_old.c b/portaudio-v19/pa_mac_core/pa_mac_core_old.c new file mode 100644 index 000000000..d9a2df26c --- /dev/null +++ b/portaudio-v19/pa_mac_core/pa_mac_core_old.c @@ -0,0 +1,907 @@ +/* + * $Id$ + * pa_mac_core.c + * Implementation of PortAudio for Mac OS X CoreAudio + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +// ===== constants ===== + +// ===== structs ===== +#pragma mark structs + +// PaMacCoreHostApiRepresentation - host api datastructure specific to this implementation +typedef struct PaMacCore_HAR +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + AudioDeviceID *macCoreDeviceIds; +} +PaMacCoreHostApiRepresentation; + +typedef struct PaMacCore_DI +{ + PaDeviceInfo inheritedDeviceInfo; +} +PaMacCoreDeviceInfo; + +// PaMacCoreStream - a stream data structure specifically for this implementation +typedef struct PaMacCore_S +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + int primeStreamUsingCallback; + + AudioDeviceID inputDevice; + AudioDeviceID outputDevice; + + // Processing thread management -------------- +// HANDLE abortEvent; +// HANDLE processingThread; +// DWORD processingThreadId; + + char throttleProcessingThreadOnOverload; // 0 -> don't throtte, non-0 -> throttle + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + unsigned long throttledSleepMsecs; + + int isStopped; + volatile int isActive; + volatile int stopProcessing; // stop thread once existing buffers have been returned + volatile int abortProcessing; // stop thread immediately + +// DWORD allBuffersDurationMs; // used to calculate timeouts +} +PaMacCoreStream; + +// Data needed by the CoreAudio callback functions +typedef struct PaMacCore_CD +{ + PaMacCoreStream *stream; + PaStreamCallback *callback; + void *userData; + PaUtilConverter *inputConverter; + PaUtilConverter *outputConverter; + void *inputBuffer; + void *outputBuffer; + int inputChannelCount; + int outputChannelCount; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + PaUtilTriangularDitherGenerator *ditherGenerator; +} +PaMacClientData; + +// ===== CoreAudio-PortAudio bridge functions ===== +#pragma mark CoreAudio-PortAudio bridge functions + +// Maps CoreAudio OSStatus codes to PortAudio PaError codes +static PaError conv_err(OSStatus error) +{ + PaError result; + + switch (error) { + case kAudioHardwareNoError: + result = paNoError; break; + case kAudioHardwareNotRunningError: + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + result = paBadStreamPtr; break; + case kAudioHardwareUnsupportedOperationError: + result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + result = paDeviceUnavailable; break; + default: + result = paInternalError; + } + + return result; +} + +/* This function is unused +static AudioStreamBasicDescription *InitializeStreamDescription(const PaStreamParameters *parameters, double sampleRate) +{ + struct AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(sizeof(AudioStreamBasicDescription)); + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = kAudioFormatLinearPCM; + streamDescription->mFormatFlags = 0; + streamDescription->mFramesPerPacket = 1; + + if (parameters->sampleFormat & paNonInterleaved) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsNonInterleaved; + streamDescription->mChannelsPerFrame = 1; + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat); + streamDescription->mBytesPerPacket = Pa_GetSampleSize(parameters->sampleFormat); + } + else { + streamDescription->mChannelsPerFrame = parameters->channelCount; + } + + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat) * streamDescription->mChannelsPerFrame; + streamDescription->mBytesPerPacket = streamDescription->mBytesPerFrame * streamDescription->mFramesPerPacket; + + if (parameters->sampleFormat & paFloat32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsFloat; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt24) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 24; + } + else if (parameters->sampleFormat & paInt16) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 16; + } + else if (parameters->sampleFormat & paInt8) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 8; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mBitsPerChannel = 8; + } + + return streamDescription; +} +*/ + +static PaStreamCallbackTimeInfo *InitializeTimeInfo(const AudioTimeStamp* now, const AudioTimeStamp* inputTime, const AudioTimeStamp* outputTime) +{ + PaStreamCallbackTimeInfo *timeInfo = PaUtil_AllocateMemory(sizeof(PaStreamCallbackTimeInfo)); + + timeInfo->inputBufferAdcTime = inputTime->mSampleTime; + timeInfo->currentTime = now->mSampleTime; + timeInfo->outputBufferDacTime = outputTime->mSampleTime; + + return timeInfo; +} + +// ===== support functions ===== +#pragma mark support functions + +static void CleanUp(PaMacCoreHostApiRepresentation *macCoreHostApi) +{ + if( macCoreHostApi->allocations ) + { + PaUtil_FreeAllAllocations( macCoreHostApi->allocations ); + PaUtil_DestroyAllocationGroup( macCoreHostApi->allocations ); + } + + PaUtil_FreeMemory( macCoreHostApi ); +} + +static PaError GetChannelInfo(PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, int isInput) +{ + UInt32 propSize; + PaError err = paNoError; + UInt32 i; + int numChannels = 0; + AudioBufferList *buflist; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); + buflist = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); + if (!err) { + for (i = 0; i < buflist->mNumberBuffers; ++i) { + numChannels += buflist->mBuffers[i].mNumberChannels; + } + + if (isInput) + deviceInfo->maxInputChannels = numChannels; + else + deviceInfo->maxOutputChannels = numChannels; + + int frameLatency; + propSize = sizeof(UInt32); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); + if (!err) { + double secondLatency = frameLatency / deviceInfo->defaultSampleRate; + if (isInput) { + deviceInfo->defaultLowInputLatency = secondLatency; + deviceInfo->defaultHighInputLatency = secondLatency; + } + else { + deviceInfo->defaultLowOutputLatency = secondLatency; + deviceInfo->defaultHighOutputLatency = secondLatency; + } + } + } + PaUtil_FreeMemory(buflist); + + return err; +} + +static PaError InitializeDeviceInfo(PaMacCoreDeviceInfo *macCoreDeviceInfo, AudioDeviceID macCoreDeviceId, PaHostApiIndex hostApiIndex ) +{ + PaDeviceInfo *deviceInfo = &macCoreDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + PaError err = paNoError; + UInt32 propSize; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); + // FIXME: this allocation should be part of the allocations group + char *name = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); + if (!err) { + deviceInfo->name = name; + } + + Float64 sampleRate; + propSize = sizeof(Float64); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); + if (!err) { + deviceInfo->defaultSampleRate = sampleRate; + } + + + // Get channel info + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 1); + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 0); + + return err; +} + +static PaError InitializeDeviceInfos( PaMacCoreHostApiRepresentation *macCoreHostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaMacCoreDeviceInfo *deviceInfoArray; + + // initialise device counts and default devices under the assumption that there are no devices. These values are incremented below if and when devices are successfully initialized. + hostApi = &macCoreHostApi->inheritedHostApiRep; + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + UInt32 propsize; + AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propsize, NULL); + int numDevices = propsize / sizeof(AudioDeviceID); + hostApi->info.deviceCount = numDevices; + if (numDevices > 0) { + hostApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + if( !hostApi->deviceInfos ) + { + return paInsufficientMemory; + } + + // allocate all device info structs in a contiguous block + deviceInfoArray = (PaMacCoreDeviceInfo*)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaMacCoreDeviceInfo) * numDevices ); + if( !deviceInfoArray ) + { + return paInsufficientMemory; + } + + macCoreHostApi->macCoreDeviceIds = PaUtil_GroupAllocateMemory(macCoreHostApi->allocations, propsize); + AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propsize, macCoreHostApi->macCoreDeviceIds); + + AudioDeviceID defaultInputDevice, defaultOutputDevice; + propsize = sizeof(AudioDeviceID); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &defaultInputDevice); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propsize, &defaultOutputDevice); + + UInt32 i; + for (i = 0; i < numDevices; ++i) { + if (macCoreHostApi->macCoreDeviceIds[i] == defaultInputDevice) { + hostApi->info.defaultInputDevice = i; + } + if (macCoreHostApi->macCoreDeviceIds[i] == defaultOutputDevice) { + hostApi->info.defaultOutputDevice = i; + } + InitializeDeviceInfo(&deviceInfoArray[i], macCoreHostApi->macCoreDeviceIds[i], hostApiIndex); + hostApi->deviceInfos[i] = &(deviceInfoArray[i].inheritedDeviceInfo); + } + } + + return result; +} + +static OSStatus CheckFormat(AudioDeviceID macCoreDeviceId, const PaStreamParameters *parameters, double sampleRate, int isInput) +{ + UInt32 propSize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(propSize); + + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = 0; + streamDescription->mFormatFlags = 0; + streamDescription->mBytesPerPacket = 0; + streamDescription->mFramesPerPacket = 0; + streamDescription->mBytesPerFrame = 0; + streamDescription->mChannelsPerFrame = 0; + streamDescription->mBitsPerChannel = 0; + streamDescription->mReserved = 0; + + OSStatus result = AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamFormatSupported, &propSize, streamDescription); + PaUtil_FreeMemory(streamDescription); + return result; +} + +static OSStatus CopyInputData(PaMacClientData* destination, const AudioBufferList *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (destination->inputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = destination->inputChannelCount; + } + else { + frameSpacing = destination->inputChannelCount; + channelSpacing = 1; + } + + AudioBuffer const *inputBuffer = &source->mBuffers[0]; + void *coreAudioBuffer = inputBuffer->mData; + void *portAudioBuffer = destination->inputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < destination->inputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= inputBuffer->mNumberChannels) { + ++streamNumber; + inputBuffer = &source->mBuffers[streamNumber]; + coreAudioBuffer = inputBuffer->mData; + streamChannel = 0; + } + destination->inputConverter(portAudioBuffer, frameSpacing, coreAudioBuffer, inputBuffer->mNumberChannels, frameCount, destination->ditherGenerator); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(destination->inputSampleFormat) * channelSpacing; + } + return noErr; +} + +static OSStatus CopyOutputData(AudioBufferList* destination, PaMacClientData *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (source->outputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = source->outputChannelCount; + } + else { + frameSpacing = source->outputChannelCount; + channelSpacing = 1; + } + + AudioBuffer *outputBuffer = &destination->mBuffers[0]; + void *coreAudioBuffer = outputBuffer->mData; + void *portAudioBuffer = source->outputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < source->outputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= outputBuffer->mNumberChannels) { + ++streamNumber; + outputBuffer = &destination->mBuffers[streamNumber]; + coreAudioBuffer = outputBuffer->mData; + streamChannel = 0; + } + source->outputConverter(coreAudioBuffer, outputBuffer->mNumberChannels, portAudioBuffer, frameSpacing, frameCount, NULL); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(source->outputSampleFormat) * channelSpacing; + } + return noErr; +} + +static OSStatus AudioIOProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + if (clientData->inputBuffer) { + CopyInputData(clientData, inInputData, frameCount); + } + PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + if (clientData->outputBuffer) { + CopyOutputData(outOutputData, clientData, frameCount); + } + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + + if (result == paComplete || result == paAbort) { + Pa_StopStream(clientData->stream); + } + + PaUtil_FreeMemory( timeInfo ); + return noErr; +} + +// This is not for input-only streams, this is for streams where the input device is different from the output device +static OSStatus AudioInputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer const *inputBuffer = &inInputData->mBuffers[0]; + unsigned long frameCount = inputBuffer->mDataByteSize / (inputBuffer->mNumberChannels * sizeof(Float32)); + + CopyInputData(clientData, inInputData, frameCount); + PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + if( result == paComplete || result == paAbort ) + Pa_StopStream(clientData->stream); + PaUtil_FreeMemory( timeInfo ); + return noErr; +} + +// This is not for output-only streams, this is for streams where the input device is different from the output device +static OSStatus AudioOutputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + //PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + //clientData->callback(NULL, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + + CopyOutputData(outOutputData, clientData, frameCount); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + return noErr; +} + +static PaError SetSampleRate(AudioDeviceID device, double sampleRate, int isInput) +{ + PaError result = paNoError; + + double actualSampleRate; + UInt32 propSize = sizeof(double); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyNominalSampleRate, propSize, &sampleRate)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyNominalSampleRate, &propSize, &actualSampleRate)); + + if (result == paNoError && actualSampleRate != sampleRate) { + result = paInvalidSampleRate; + } + + return result; +} + +static PaError SetFramesPerBuffer(AudioDeviceID device, unsigned long framesPerBuffer, int isInput) +{ + PaError result = paNoError; + UInt32 preferredFramesPerBuffer = framesPerBuffer; + // while (preferredFramesPerBuffer > UINT32_MAX) { + // preferredFramesPerBuffer /= 2; + // } + + UInt32 actualFramesPerBuffer; + UInt32 propSize = sizeof(UInt32); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyBufferFrameSize, propSize, &preferredFramesPerBuffer)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &actualFramesPerBuffer)); + + if (result != paNoError) { + // do nothing + } + else if (actualFramesPerBuffer > framesPerBuffer) { + result = paBufferTooSmall; + } + else if (actualFramesPerBuffer < framesPerBuffer) { + result = paBufferTooBig; + } + + return result; +} + +static PaError SetUpUnidirectionalStream(AudioDeviceID device, double sampleRate, unsigned long framesPerBuffer, int isInput) +{ + PaError err = paNoError; + err = SetSampleRate(device, sampleRate, isInput); + if( err == paNoError ) + err = SetFramesPerBuffer(device, framesPerBuffer, isInput); + return err; +} + +// ===== PortAudio functions ===== +#pragma mark PortAudio functions + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif // __cplusplus + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + + CleanUp(macCoreHostApi); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + PaDeviceInfo *deviceInfo; + + PaError result = paNoError; + if (inputParameters) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + if (inputParameters->channelCount > deviceInfo->maxInputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[inputParameters->device], inputParameters, sampleRate, 1) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + if (outputParameters && result == paNoError) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; + if (outputParameters->channelCount > deviceInfo->maxOutputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[outputParameters->device], outputParameters, sampleRate, 0) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError err = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)hostApi; + PaMacCoreStream *stream = PaUtil_AllocateMemory(sizeof(PaMacCoreStream)); + stream->isActive = 0; + stream->isStopped = 1; + stream->inputDevice = kAudioDeviceUnknown; + stream->outputDevice = kAudioDeviceUnknown; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + ( (streamCallback) + ? &macCoreHostApi->callbackStreamInterface + : &macCoreHostApi->blockingStreamInterface ), + streamCallback, userData ); + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + *s = (PaStream*)stream; + PaMacClientData *clientData = PaUtil_AllocateMemory(sizeof(PaMacClientData)); + clientData->stream = stream; + clientData->callback = streamCallback; + clientData->userData = userData; + clientData->inputBuffer = 0; + clientData->outputBuffer = 0; + clientData->ditherGenerator = PaUtil_AllocateMemory(sizeof(PaUtilTriangularDitherGenerator)); + PaUtil_InitializeTriangularDitherState(clientData->ditherGenerator); + + if (inputParameters != NULL) { + stream->inputDevice = macCoreHostApi->macCoreDeviceIds[inputParameters->device]; + clientData->inputConverter = PaUtil_SelectConverter(paFloat32, inputParameters->sampleFormat, streamFlags); + clientData->inputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(inputParameters->sampleFormat) * framesPerBuffer * inputParameters->channelCount); + clientData->inputChannelCount = inputParameters->channelCount; + clientData->inputSampleFormat = inputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->inputDevice, sampleRate, framesPerBuffer, 1); + } + + if (err == paNoError && outputParameters != NULL) { + stream->outputDevice = macCoreHostApi->macCoreDeviceIds[outputParameters->device]; + clientData->outputConverter = PaUtil_SelectConverter(outputParameters->sampleFormat, paFloat32, streamFlags); + clientData->outputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(outputParameters->sampleFormat) * framesPerBuffer * outputParameters->channelCount); + clientData->outputChannelCount = outputParameters->channelCount; + clientData->outputSampleFormat = outputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->outputDevice, sampleRate, framesPerBuffer, 0); + } + + if (inputParameters == NULL || outputParameters == NULL || stream->inputDevice == stream->outputDevice) { + AudioDeviceID device = (inputParameters == NULL) ? stream->outputDevice : stream->inputDevice; + + AudioDeviceAddIOProc(device, AudioIOProc, clientData); + } + else { + // using different devices for input and output + AudioDeviceAddIOProc(stream->inputDevice, AudioInputProc, clientData); + AudioDeviceAddIOProc(stream->outputDevice, AudioOutputProc, clientData); + } + + return err; +} + +// Note: When CloseStream() is called, the multi-api layer ensures that the stream has already been stopped or aborted. +static PaError CloseStream( PaStream* s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioIOProc)); + } + + return err; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 1; + stream->isStopped = 0; + return err; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 0; + stream->isStopped = 1; + return err; +} + +static PaError StopStream( PaStream *s ) +{ + // TODO: this should be nicer than abort + return AbortStream(s); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isStopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + OSStatus err; + PaTime result; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + AudioTimeStamp *timeStamp = PaUtil_AllocateMemory(sizeof(AudioTimeStamp)); + if (stream->inputDevice != kAudioDeviceUnknown) { + err = AudioDeviceGetCurrentTime(stream->inputDevice, timeStamp); + } + else { + err = AudioDeviceGetCurrentTime(stream->outputDevice, timeStamp); + } + + result = err ? 0 : timeStamp->mSampleTime; + PaUtil_FreeMemory(timeStamp); + + return result; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +// As separate stream interfaces are used for blocking and callback streams, the following functions can be guaranteed to only be called for blocking streams. + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return paInternalError; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return paInternalError; +} + +// HostAPI-specific initialization function +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaMacCoreHostApiRepresentation) ); + if( !macCoreHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + macCoreHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !macCoreHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &macCoreHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "CoreAudio"; + + result = InitializeDeviceInfos(macCoreHostApi, hostApiIndex); + if (result != paNoError) { + goto error; + } + + // Set up the proper callbacks to this HostApi's functions + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &macCoreHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &macCoreHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( macCoreHostApi ) { + CleanUp(macCoreHostApi); + } + + return result; +} diff --git a/portaudio-v19/pa_mac_core/pa_mac_core_utilities.c b/portaudio-v19/pa_mac_core/pa_mac_core_utilities.c new file mode 100644 index 000000000..f2cbd29cb --- /dev/null +++ b/portaudio-v19/pa_mac_core/pa_mac_core_utilities.c @@ -0,0 +1,466 @@ +/* + * + * pa_mac_core_utilities.c + * + * utilitiy functions for pa_mac_core.c + * + * This functions are more like helper functions than part of the core, + * so I moved them to a separate file so the core code would be cleaner. + * + * by Bjorn Roche. + */ + +/* + * Translates MacOS generated errors into PaErrors + */ +static PaError PaMacCore_SetError(OSStatus error, int line, int isError) +{ + /*FIXME: still need to handle possible ComponentResult values.*/ + /* unfortunately, they don't seem to be documented anywhere.*/ + PaError result; + const char *errorType; + const char *errorText; + + switch (error) { + case kAudioHardwareNoError: + return paNoError; + case kAudioHardwareNotRunningError: + errorText = "Audio Hardware Not Running"; + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + errorText = "Unspecified Audio Hardware Error"; + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + errorText = "Audio Hardware: Unknown Property"; + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + errorText = "Audio Hardware: Bad Property Size"; + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + errorText = "Audio Hardware: Illegal Operation"; + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + errorText = "Audio Hardware: Bad Device"; + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + errorText = "Audio Hardware: BadStream"; + result = paBadStreamPtr; break; + case kAudioHardwareUnsupportedOperationError: + errorText = "Audio Hardware: Unsupported Operation"; + result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + errorText = "Audio Device: Unsupported Format"; + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + errorText = "Audio Device: Permissions Error"; + result = paDeviceUnavailable; break; + /* Audio Unit Errors: http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudio/audio_units/chapter_5_section_3.html */ + case kAudioUnitErr_InvalidProperty: + errorText = "Audio Unit: Invalid Property"; + result = paInternalError; break; + case kAudioUnitErr_InvalidParameter: + errorText = "Audio Unit: Invalid Parameter"; + result = paInternalError; break; + case kAudioUnitErr_NoConnection: + errorText = "Audio Unit: No Connection"; + result = paInternalError; break; + case kAudioUnitErr_FailedInitialization: + errorText = "Audio Unit: Initialization Failed"; + result = paInternalError; break; + case kAudioUnitErr_TooManyFramesToProcess: + errorText = "Audio Unit: Too Many Frames"; + result = paInternalError; break; + case kAudioUnitErr_IllegalInstrument: + errorText = "Audio Unit: Illegal Instrument"; + result = paInternalError; break; + case kAudioUnitErr_InstrumentTypeNotFound: + errorText = "Audio Unit: Instrument Type Not Found"; + result = paInternalError; break; + case kAudioUnitErr_InvalidFile: + errorText = "Audio Unit: Invalid File"; + result = paInternalError; break; + case kAudioUnitErr_UnknownFileType: + errorText = "Audio Unit: Unknown File Type"; + result = paInternalError; break; + case kAudioUnitErr_FileNotSpecified: + errorText = "Audio Unit: File Not Specified"; + result = paInternalError; break; + case kAudioUnitErr_FormatNotSupported: + errorText = "Audio Unit: Format Not Supported"; + result = paInternalError; break; + case kAudioUnitErr_Uninitialized: + errorText = "Audio Unit: Unitialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidScope: + errorText = "Audio Unit: Invalid Scope"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotWritable: + errorText = "Audio Unit: PropertyNotWritable"; + result = paInternalError; break; + case kAudioUnitErr_InvalidPropertyValue: + errorText = "Audio Unit: Invalid Property Value"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotInUse: + errorText = "Audio Unit: Property Not In Use"; + result = paInternalError; break; + case kAudioUnitErr_Initialized: + errorText = "Audio Unit: Initialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidOfflineRender: + errorText = "Audio Unit: Invalid Offline Render"; + result = paInternalError; break; + case kAudioUnitErr_Unauthorized: + errorText = "Audio Unit: Unauthorized"; + result = paInternalError; break; + case kAudioUnitErr_CannotDoInCurrentContext: + errorText = "Audio Unit: cannot do in current context"; + result = paInternalError; break; + default: + errorText = "Unknown Error"; + result = paInternalError; + } + + if (isError) + errorType = "Error"; + else + errorType = "Warning"; + + if ((int)error < -99999 || (int)error > 99999) + DBUG(("%s on line %d: err='%4s', msg='%s'\n", errorType, line, (const char *)&error, errorText)); + else + DBUG(("%s on line %d: err=%d, 0x%x, msg='%s'\n", errorType, line, (int)error, (unsigned)error, errorText)); + + PaUtil_SetLastHostErrorInfo( paCoreAudio, error, errorText ); + + return result; +} + + + + +/* + * Durring testing of core audio, I found that serious crashes could occur + * if properties such as sample rate were changed multiple times in rapid + * succession. The function below has some fancy logic to make sure that changes + * are acknowledged before another is requested. That seems to help a lot. + */ + +#include +typedef struct { + bool once; /* I didn't end up using this. bdr */ + pthread_mutex_t mutex; +} MutexAndBool ; + +static OSStatus propertyProc( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData ) +{ + MutexAndBool *mab = (MutexAndBool *) inClientData; + mab->once = TRUE; + pthread_mutex_unlock( &(mab->mutex) ); + return noErr; +} + +/* sets the value of the given property and waits for the change to + be acknowledged, and returns the final value, which is not guaranteed + by this function to be the same as the desired value. Obviously, this + function can only be used for data whose input and output are the + same size and format, and their size and format are known in advance.*/ +PaError AudioDeviceSetPropertyNowAndWaitForChange( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + UInt32 inPropertyDataSize, + const void *inPropertyData, + void *outPropertyData ) +{ + OSStatus macErr; + int unixErr; + MutexAndBool mab; + UInt32 outPropertyDataSize = inPropertyDataSize; + + /* First, see if it already has that value. If so, return. */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac2; + if( inPropertyDataSize!=outPropertyDataSize ) + return paInternalError; + if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) ) + return paNoError; + + /* setup and lock mutex */ + mab.once = FALSE; + unixErr = pthread_mutex_init( &mab.mutex, NULL ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* add property listener */ + macErr = AudioDeviceAddPropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc, + &mab ); + if( macErr ) + goto failMac; + /* set property */ + macErr = AudioDeviceSetProperty( inDevice, NULL, inChannel, + isInput, inPropertyID, + inPropertyDataSize, inPropertyData ); + if( macErr ) { + /* we couldn't set the property, so we'll just unlock the mutex + and move on. */ + pthread_mutex_unlock( &mab.mutex ); + } + + /* wait for property to change */ + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* now read the property back out */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac; + /* cleanup */ + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + unixErr = pthread_mutex_unlock( &mab.mutex ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_destroy( &mab.mutex ); + if( unixErr ) + goto failUnix2; + + return paNoError; + + failUnix: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + + failUnix2: + DBUG( ("Error #%d while setting a device property: %s\n", unixErr, strerror( unixErr ) ) ); + return paUnanticipatedHostError; + + failMac: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + failMac2: + return ERR( macErr ); +} + +/* + * Sets the sample rate the HAL device. + * if requireExact: set the sample rate or fail. + * + * otherwise : set the exact sample rate. + * If that fails, check for available sample rates, and choose one + * higher than the requested rate. If there isn't a higher one, + * just use the highest available. + */ +static PaError setBestSampleRateForDevice( const AudioDeviceID device, + const bool isOutput, + const bool requireExact, + const Float64 desiredSrate ) +{ + /*FIXME: changing the sample rate is disruptive to other programs using the + device, so it might be good to offer a custom flag to not change the + sample rate and just do conversion. (in my casual tests, there is + no disruption unless the sample rate really does need to change) */ + const bool isInput = isOutput ? 0 : 1; + Float64 srate; + UInt32 propsize = sizeof( Float64 ); + OSErr err; + AudioValueRange *ranges; + int i=0; + Float64 max = -1; /*the maximum rate available*/ + Float64 best = -1; /*the lowest sample rate still greater than desired rate*/ + VDBUG(("Setting sample rate for device %ld to %g.\n",device,(float)desiredSrate)); + + /* -- try setting the sample rate -- */ + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &desiredSrate, &srate ); + if( err ) + return err; + + /* -- if the rate agrees, and we got no errors, we are done -- */ + if( !err && srate == desiredSrate ) + return paNoError; + /* -- we've failed if the rates disagree and we are setting input -- */ + if( requireExact ) + return paInvalidSampleRate; + + /* -- generate a list of available sample rates -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested sample rate of %g was not available.\n", (float)desiredSrate)); + VDBUG(("%lu Available Sample Rates are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i max ) max = ranges[i].mMaximum; + if( ranges[i].mMinimum > desiredSrate ) { + if( best < 0 ) + best = ranges[i].mMinimum; + else if( ranges[i].mMinimum < best ) + best = ranges[i].mMinimum; + } + } + if( best < 0 ) + best = max; + VDBUG( ("Maximum Rate %g. best is %g.\n", max, best ) ); + free( ranges ); + + /* -- set the sample rate -- */ + propsize = sizeof( best ); + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &best, &srate ); + if( err ) + return err; + + if( err ) + return ERR( err ); + /* -- if the set rate matches, we are done -- */ + if( srate == best ) + return paNoError; + + /* -- otherwise, something wierd happened: we didn't set the rate, and we got no errors. Just bail. */ + return paInternalError; +} + + +/* + Attempts to set the requestedFramesPerBuffer. If it can't set the exact + value, it settles for something smaller if available. If nothing smaller + is available, it uses the smallest available size. + actualFramesPerBuffer will be set to the actual value on successful return. + OK to pass NULL to actualFramesPerBuffer. + The logic is very simmilar too setBestSampleRate only failure here is + not usually catastrophic. +*/ +static PaError setBestFramesPerBuffer( const AudioDeviceID device, + const bool isOutput, + unsigned long requestedFramesPerBuffer, + unsigned long *actualFramesPerBuffer ) +{ + UInt32 afpb; + const bool isInput = !isOutput; + UInt32 propsize = sizeof(UInt32); + OSErr err; + Float64 min = -1; /*the min blocksize*/ + Float64 best = -1; /*the best blocksize*/ + int i=0; + AudioValueRange *ranges; + + if( actualFramesPerBuffer == NULL ) + actualFramesPerBuffer = &afpb; + + + /* -- try and set exact FPB -- */ + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + propsize, &requestedFramesPerBuffer); + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + &propsize, actualFramesPerBuffer); + if( err ) + return ERR( err ); + if( *actualFramesPerBuffer == requestedFramesPerBuffer ) + return paNoError; /* we are done */ + + /* -- fetch available block sizes -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested block size of %lu was not available.\n", + requestedFramesPerBuffer )); + VDBUG(("%lu Available block sizes are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i best ) + best = ranges[i].mMaximum; + } + } + if( best == -1 ) + best = min; + VDBUG( ("Minimum FPB %g. best is %g.\n", min, best ) ); + free( ranges ); + + /* --- set the buffer size (ignore errors) -- */ + requestedFramesPerBuffer = (UInt32) best ; + propsize = sizeof( UInt32 ); + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferSize, + propsize, &requestedFramesPerBuffer ); + /* --- read the property to check that it was set -- */ + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSize, + &propsize, actualFramesPerBuffer ); + + if( err ) + return ERR( err ); + + return paNoError; +} diff --git a/portaudio-v19/pa_mac_sm/pa_mac_sm.c b/portaudio-v19/pa_mac_sm/pa_mac_sm.c new file mode 100644 index 000000000..7a8e03009 --- /dev/null +++ b/portaudio-v19/pa_mac_sm/pa_mac_sm.c @@ -0,0 +1,1656 @@ +/* + * $Id$ + * Portable Audio I/O Library for Macintosh + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Special thanks to Chris Rolfe for his many helpful suggestions, bug fixes, + * and code contributions. + * Thanks also to Tue Haste Andersen, Alberto Ricci, Nico Wald, + * Roelf Toxopeus and Tom Erbe for testing the code and making + * numerous suggestions. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +/* Modification History + PLB20010415 - ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID + PLB20010415 - Device Scan was crashing for anything other than siBadSoundInDevice, but some Macs may return other errors! + PLB20010420 - Fix TIMEOUT in record mode. + PLB20010420 - Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON + PLB20010907 - Pass unused event to WaitNextEvent to prevent Mac OSX crash. Thanks Dominic Mazzoni. + PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. + PLB20011009 - Use NewSndCallBackUPP() for CARBON + PLB20020417 - I used to call Pa_GetMinNumBuffers() which doesn't take into account the + variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will + give lower latency when virtual memory is turned off. + Thanks Kristoffer Jensen and Georgios Marentakis for spotting this bug. + PLB20020423 - Use new method to calculate CPU load similar to other ports. Based on num frames calculated. + Fixed Pa_StreamTime(). Now estimates how many frames have played based on MicroSecond timer. + Added PA_MAX_USAGE_ALLOWED to prevent Mac form hanging when CPU load approaches 100%. + PLB20020424 - Fixed return value in Pa_StreamTime +*/ + +/* +COMPATIBILITY +This Macintosh implementation is designed for use with Mac OS 7, 8 and +9 on PowerMacs, and OS X if compiled with CARBON + +OUTPUT +A circular array of CmpSoundHeaders is used as a queue. For low latency situations +there will only be two small buffers used. For higher latency, more and larger buffers +may be used. +To play the sound we use SndDoCommand() with bufferCmd. Each buffer is followed +by a callbackCmd which informs us when the buffer has been processsed. + +INPUT +The SndInput Manager SPBRecord call is used for sound input. If only +input is used, then the PA user callback is called from the Input completion proc. +For full-duplex, or output only operation, the PA callback is called from the +HostBuffer output completion proc. In that case, input sound is passed to the +callback by a simple FIFO. + +TODO: +O- Add support for native sample data formats other than int16. +O- Review buffer sizing. Should it be based on result of siDeviceBufferInfo query? +O- Determine default devices somehow. +*/ +#include +#include +#include +#include +#include + +/* Mac specific includes */ +#include "OSUtils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +/* #define TARGET_API_MAC_CARBON (1) */ + +/* + * Define maximum CPU load that will be allowed. User callback will + * be skipped if load exceeds this limit. This is to prevent the Mac + * from hanging when the CPU is hogged by the sound thread. + * On my PowerBook G3, the mac hung when I used 94% of CPU ( usage = 0.94 ). + */ +#define PA_MAX_USAGE_ALLOWED (0.92) + +/* Debugging output macros. */ +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) /**/ +#define DBUGX(x) /* PRINT(x) /**/ + +#define MAC_PHYSICAL_FRAMES_PER_BUFFER (512) /* Minimum number of stereo frames per SoundManager double buffer. */ +#define MAC_VIRTUAL_FRAMES_PER_BUFFER (4096) /* Need this many when using Virtual Memory for recording. */ +#define PA_MIN_NUM_HOST_BUFFERS (2) +#define PA_MAX_NUM_HOST_BUFFERS (16) /* Do not exceed!! */ +#define PA_MAX_DEVICE_INFO (32) + +/* Conversions for 16.16 fixed point code. */ +#define DoubleToUnsignedFixed(x) ((UnsignedFixed) ((x) * 65536.0)) +#define UnsignedFixedToDouble(fx) (((double)(fx)) * (1.0/(1<<16))) + +/************************************************************************************/ +/****************** Structures ******************************************************/ +/************************************************************************************/ +/* Use for passing buffers from input callback to output callback for processing. */ +typedef struct MultiBuffer +{ + char *buffers[PA_MAX_NUM_HOST_BUFFERS]; + int numBuffers; + int nextWrite; + int nextRead; +} +MultiBuffer; + +/* Define structure to contain all Macintosh specific data. */ +typedef struct PaHostSoundControl +{ + UInt64 pahsc_EntryCount; + double pahsc_InverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */ + + /* Use char instead of Boolean for atomic operation. */ + volatile char pahsc_IsRecording; /* Recording in progress. Set by foreground. Cleared by background. */ + volatile char pahsc_StopRecording; /* Signal sent to background. */ + volatile char pahsc_IfInsideCallback; + /* Input */ + SPB pahsc_InputParams; + SICompletionUPP pahsc_InputCompletionProc; + MultiBuffer pahsc_InputMultiBuffer; + int32 pahsc_BytesPerInputHostBuffer; + int32 pahsc_InputRefNum; + /* Output */ + CmpSoundHeader pahsc_SoundHeaders[PA_MAX_NUM_HOST_BUFFERS]; + int32 pahsc_BytesPerOutputHostBuffer; + SndChannelPtr pahsc_Channel; + SndCallBackUPP pahsc_OutputCompletionProc; + int32 pahsc_NumOutsQueued; + int32 pahsc_NumOutsPlayed; + PaTimestamp pahsc_NumFramesDone; + UInt64 pahsc_WhenFramesDoneIncremented; + /* Init Time -------------- */ + int32 pahsc_NumHostBuffers; + int32 pahsc_FramesPerHostBuffer; + int32 pahsc_UserBuffersPerHostBuffer; + int32 pahsc_MinFramesPerHostBuffer; /* Can vary depending on virtual memory usage. */ +} +PaHostSoundControl; + +/* Mac specific device information. */ +typedef struct internalPortAudioDevice +{ + long pad_DeviceRefNum; + long pad_DeviceBufferSize; + Component pad_Identifier; + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/************************************************************************************/ +/****************** Data ************************************************************/ +/************************************************************************************/ +static int sNumDevices = 0; +static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 }; +static int32 sPaHostError = 0; +static int sDefaultOutputDeviceID; +static int sDefaultInputDeviceID; + +/************************************************************************************/ +/****************** Prototypes ******************************************************/ +/************************************************************************************/ +static PaError PaMac_TimeSlice( internalPortAudioStream *past, int16 *macOutputBufPtr ); +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ); +static PaError PaMac_RecordNext( internalPortAudioStream *past ); +static void PaMac_StartLoadCalculation( internalPortAudioStream *past ); +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerBuffer, double sampleRate ); +static double *PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ); +static PaError PaMac_ScanInputDevices( void ); +static PaError PaMac_ScanOutputDevices( void ); +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ); +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ); +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ); +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ); +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ); +static long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ); +static pascal void PaMac_InputCompletionProc(SPBPtr recParams); +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCmd); +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ); +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static int Mac_IsVirtualMemoryOn( void ); +static void PToCString(unsigned char* inString, char* outString); +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ); +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ); +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ); +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ); +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ); +int MultiBuffer_IsReadable( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ); + +/************************************************************************* +** Simple FIFO index control for multiple buffers. +** Read and Write indices range from 0 to 2N-1. +** This allows us to distinguish between full and empty. +*/ +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextWrite % mbuf->numBuffers]; +} +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextRead % mbuf->numBuffers]; +} +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextRead % mbuf->numBuffers; +} +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextWrite % mbuf->numBuffers; +} + +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull < mbuf->numBuffers); +} +int MultiBuffer_IsReadable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull > 0); +} +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextRead + 1; + mbuf->nextRead = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextWrite + 1; + mbuf->nextWrite = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} + +/************************************************************************* +** String Utility by Chris Rolfe +*/ +static void PToCString(unsigned char* inString, char* outString) +{ + long i; + for(i=0; isampleRates; + if( (rates != NULL) ) free( rates ); /* MEM_011 */ + dev->sampleRates = NULL; + if( dev->name != NULL ) free( (void *) dev->name ); /* MEM_010 */ + dev->name = NULL; + } + sNumDevices = 0; + return paNoError; +} + +/************************************************************************* + PaHost_Init() is the library initialization function - call this before + using the library. +*/ +PaError PaHost_Init( void ) +{ + PaError err; + NumVersionVariant version; + + version.parts = SndSoundManagerVersion(); + DBUG(("SndSoundManagerVersion = 0x%x\n", version.whole)); + + /* Have we already initialized the device info? */ + err = (PaError) Pa_CountDevices(); + if( err < 0 ) return err; + else return paNoError; +} + +/************************************************************************* + PaMac_ScanOutputDevices() queries the properties of all output devices. +*/ +static PaError PaMac_ScanOutputDevices( void ) +{ + PaError err; + Component identifier=0; + ComponentDescription criteria = { kSoundOutputDeviceType, 0, 0, 0, 0 }; + long numComponents, i; + + /* Search the system linked list for output components */ + numComponents = CountComponents (&criteria); + identifier = 0; + sDefaultOutputDeviceID = sNumDevices; /* FIXME - query somehow */ + for (i = 0; i < numComponents; i++) + { + /* passing nil returns first matching component. */ + identifier = FindNextComponent( identifier, &criteria); + sDevices[sNumDevices].pad_Identifier = identifier; + + /* Set up for default OUTPUT devices. */ + err = PaMac_QueryOutputDeviceInfo( identifier, &sDevices[sNumDevices] ); + if( err < 0 ) return err; + else sNumDevices++; + + } + + return paNoError; +} + +/************************************************************************* + PaMac_ScanInputDevices() queries the properties of all input devices. +*/ +static PaError PaMac_ScanInputDevices( void ) +{ + Str255 deviceName; + int count; + Handle iconHandle; + PaError err; + OSErr oserr; + count = 1; + sDefaultInputDeviceID = sNumDevices; /* FIXME - query somehow */ /* PLB20010415 - was setting sDefaultOutputDeviceID */ + while(true) + { + /* Thanks Chris Rolfe and Alberto Ricci for this trick. */ + oserr = SPBGetIndexedDevice(count++, deviceName, &iconHandle); + DBUG(("PaMac_ScanInputDevices: SPBGetIndexedDevice returned %d\n", oserr )); +#if 1 + /* PLB20010415 - was giving error for anything other than siBadSoundInDevice, but some Macs may return other errors! */ + if(oserr != noErr) break; /* Some type of error is expected when count > devices */ +#else + if(oserr == siBadSoundInDevice) + { /* it's expected when count > devices */ + oserr = noErr; + break; + } + if(oserr != noErr) + { + ERR_RPT(("ERROR: SPBGetIndexedDevice(%d,,) returned %d\n", count-1, oserr )); + sPaHostError = oserr; + return paHostError; + } +#endif + DisposeHandle(iconHandle); /* Don't need the icon */ + + err = PaMac_QueryInputDeviceInfo( deviceName, &sDevices[sNumDevices] ); + DBUG(("PaMac_ScanInputDevices: PaMac_QueryInputDeviceInfo returned %d\n", err )); + if( err < 0 ) return err; + else if( err == 1 ) sNumDevices++; + } + + return paNoError; +} + +/* Sample rate info returned by using siSampleRateAvailable selector in SPBGetDeviceInfo() */ +/* Thanks to Chris Rolfe for help with this query. */ +#pragma options align=mac68k +typedef struct +{ + int16 numRates; + UnsignedFixed (**rates)[]; /* Handle created by SPBGetDeviceInfo */ +} +SRateInfo; +#pragma options align=reset + +/************************************************************************* +** PaMac_QueryOutputDeviceInfo() +** Query information about a named output device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ) +{ + int len; + OSErr err; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + ComponentDescription tempD; + Handle nameH=nil, infoH=nil, iconH=nil; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + + dev->structVersion = 1; + dev->maxInputChannels = 0; + dev->maxOutputChannels = 2; + dev->nativeSampleFormats = paInt16; /* FIXME - query to see if 24 or 32 bit data can be handled. */ + + /* Get sample rates supported. */ + err = GetSoundOutputInfo(identifier, siSampleRateAvailable, (Ptr) &srinfo); + if(err != noErr) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetSoundOutputInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + numRates = srinfo.numRates; + DBUG(("PaMac_QueryOutputDeviceInfo: srinfo.numRates = 0x%x\n", srinfo.numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Device name */ + /* we pass an existing handle for the component name; + we don't care about the info (type, subtype, etc.) or icon, so set them to nil */ + infoH = nil; + iconH = nil; + nameH = NewHandle(0); + if(nameH == nil) return paInsufficientMemory; + err = GetComponentInfo(identifier, &tempD, nameH, infoH, iconH); + if (err) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetComponentInfo returned %d\n", err )); + goto error; + } + len = (*nameH)[0] + 1; + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + DisposeHandle(nameH); + return paInsufficientMemory; + } + else + { + PToCString((unsigned char *)(*nameH), (char *) dev->name); + DisposeHandle(nameH); + } + + DBUG(("PaMac_QueryOutputDeviceInfo: dev->name = %s\n", dev->name )); + return paNoError; + +error: + sPaHostError = err; + return paHostError; + +} + +/************************************************************************* +** PaMac_QueryInputDeviceInfo() +** Query information about a named input device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ) +{ + PaError result = paNoError; + int len; + OSErr err; + long mRefNum = 0; + long tempL; + int16 tempS; + Fixed tempF; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + dev->maxOutputChannels = 0; + + /* Open device based on name. If device is in use, it may not be able to open in write mode. */ + err = SPBOpenDevice( deviceName, siWritePermission, &mRefNum); + if (err) + { + /* If device is in use, it may not be able to open in write mode so try read mode. */ + err = SPBOpenDevice( deviceName, siReadPermission, &mRefNum); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBOpenDevice returned %d\n", err )); + sPaHostError = err; + return paHostError; + } + } + + /* Define macros for printing out device info. */ +#define PrintDeviceInfo(selector,var) \ + err = SPBGetDeviceInfo(mRefNum, selector, (Ptr) &var); \ + if (err) { \ + DBUG(("query %s failed\n", #selector )); \ + }\ + else { \ + DBUG(("query %s = 0x%x\n", #selector, var )); \ + } + + PrintDeviceInfo( siContinuous, tempS ); + PrintDeviceInfo( siAsync, tempS ); + PrintDeviceInfo( siNumberChannels, tempS ); + PrintDeviceInfo( siSampleSize, tempS ); + PrintDeviceInfo( siSampleRate, tempF ); + PrintDeviceInfo( siChannelAvailable, tempS ); + PrintDeviceInfo( siActiveChannels, tempL ); + PrintDeviceInfo( siDeviceBufferInfo, tempL ); + + err = SPBGetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err == 0) DBUG(("%s = 0x%x\n", "siActiveChannels", tempL )); + /* Can we use this device? */ + err = SPBGetDeviceInfo(mRefNum, siAsync, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siAsync returned %d\n", err )); + goto error; + } + if( tempS == 0 ) goto useless; /* Does not support async recording so forget about it. */ + + err = SPBGetDeviceInfo(mRefNum, siChannelAvailable, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siChannelAvailable returned %d\n", err )); + goto error; + } + dev->maxInputChannels = tempS; + + /* Get sample rates supported. */ + err = SPBGetDeviceInfo(mRefNum, siSampleRateAvailable, (Ptr) &srinfo); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + + numRates = srinfo.numRates; + DBUG(("numRates = 0x%x\n", numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Get size of device buffer. */ + err = SPBGetDeviceInfo(mRefNum, siDeviceBufferInfo, (Ptr) &tempL); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siDeviceBufferInfo returned %d\n", err )); + goto error; + } + ipad->pad_DeviceBufferSize = tempL; + DBUG(("siDeviceBufferInfo = %d\n", tempL )); + + /* Set format based on sample size. */ + err = SPBGetDeviceInfo(mRefNum, siSampleSize, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleSize returned %d\n", err )); + goto error; + } + switch( tempS ) + { + case 0x0020: + dev->nativeSampleFormats = paInt32; /* FIXME - warning, code probably won't support this! */ + break; + case 0x0010: + default: /* FIXME - What about other formats? */ + dev->nativeSampleFormats = paInt16; + break; + } + DBUG(("nativeSampleFormats = %d\n", dev->nativeSampleFormats )); + + /* Device name */ + len = deviceName[0] + 1; /* Get length of Pascal string */ + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + PToCString(deviceName, (char *) dev->name); + DBUG(("deviceName = %s\n", dev->name )); + result = (PaError) 1; + /* All done so close up device. */ +cleanup: + if( mRefNum ) SPBCloseDevice(mRefNum); + return result; + +error: + if( mRefNum ) SPBCloseDevice(mRefNum); + sPaHostError = err; + return paHostError; + +useless: + if( mRefNum ) SPBCloseDevice(mRefNum); + return (PaError) 0; +} + +/************************************************************************* +** Allocate a double array and fill it with listed sample rates. +*/ +static double * PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ) +{ + OSErr err = noErr; + SInt8 hState; + int i; + UnsignedFixed *fixedRates; + double *rates = (double *) malloc( numRates * sizeof(double) ); /* MEM_011 */ + if( rates == NULL ) return NULL; + /* Save and restore handle state as suggested by TechNote at: + http://developer.apple.com/technotes/tn/tn1122.html + */ + hState = HGetState (h); + if (!(err = MemError ())) + { + HLock (h); + if (!(err = MemError ( ))) + { + fixedRates = (UInt32 *) *h; + for( i=0; i= Pa_CountDevices()) ) return NULL; + return &sDevices[id].pad_Info; +} +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + return sDefaultInputDeviceID; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return sDefaultOutputDeviceID; +} + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void PaMac_StartLoadCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + UnsignedWide widePad; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + Microseconds( &widePad ); + pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad ); +} + +/****************************************************************************** +** Measure fractional CPU load based on real-time it took to calculate +** buffers worth of output. +*/ +/**************************************************************************/ +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ) +{ + UnsignedWide widePad; + UInt64 currentCount; + long usecsElapsed; + double newUsage; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + + /* Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.95) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + Microseconds( &widePad ); + currentCount = UnsignedWideToUInt64( widePad ); + + usecsElapsed = (long) U64Subtract(currentCount, pahsc->pahsc_EntryCount); + + /* Use inverse because it is faster than the divide. */ + newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerHostBuffer; + + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + +} + +/*********************************************************************** +** Called by Pa_StartStream() +*/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + pahsc->pahsc_InputMultiBuffer.nextWrite = 0; + pahsc->pahsc_InputMultiBuffer.nextRead = 0; + return PaMac_RecordNext( past ); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaError result = paNoError; + OSErr err = 0; + long mRefNum; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + (void) abort; + + mRefNum = pahsc->pahsc_InputRefNum; + + DBUG(("PaHost_StopInput: mRefNum = %d\n", mRefNum )); + if( mRefNum ) + { + DBUG(("PaHost_StopInput: pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + if( pahsc->pahsc_IsRecording ) + { + /* PLB20010420 - Fix TIMEOUT in record mode. */ + pahsc->pahsc_StopRecording = 1; /* Request that we stop recording. */ + err = SPBStopRecording(mRefNum); + DBUG(("PaHost_StopInput: then pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + + /* Calculate timeOut longer than longest time it could take to play one buffer. */ + timeOutMsec = (int32) ((1500.0 * pahsc->pahsc_FramesPerHostBuffer) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( !err && pahsc->pahsc_IsRecording && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopInput: timed out!\n")); + return paTimedOut; + } + } + } + if( err ) + { + sPaHostError = err; + result = paHostError; + } + + DBUG(("PaHost_StopInput: finished.\n", mRefNum )); + return result; +} + +/***********************************************************************/ +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ) +{ + sndHeader->numChannels = past->past_NumOutputChannels; + sndHeader->sampleRate = DoubleToUnsignedFixed(past->past_SampleRate); + sndHeader->loopStart = 0; + sndHeader->loopEnd = 0; + sndHeader->encode = cmpSH; + sndHeader->baseFrequency = kMiddleC; + sndHeader->markerChunk = nil; + sndHeader->futureUse2 = nil; + sndHeader->stateVars = nil; + sndHeader->leftOverSamples = nil; + sndHeader->compressionID = 0; + sndHeader->packetSize = 0; + sndHeader->snthID = 0; + sndHeader->sampleSize = 8 * sizeof(int16); // FIXME - might be 24 or 32 bits some day; + sndHeader->sampleArea[0] = 0; + sndHeader->format = kSoundNotCompressed; +} + +static void SetFramesDone( PaHostSoundControl *pahsc, PaTimestamp framesDone ) +{ + UnsignedWide now; + Microseconds( &now ); + pahsc->pahsc_NumFramesDone = framesDone; + pahsc->pahsc_WhenFramesDoneIncremented = UnsignedWideToUInt64( now ); +} + +/***********************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + SndCommand pauseCommand; + SndCommand resumeCommand; + int i; + OSErr error; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + if( pahsc->pahsc_Channel == NULL ) return paInternalError; + + past->past_StopSoon = 0; + past->past_IsActive = 1; + pahsc->pahsc_NumOutsQueued = 0; + pahsc->pahsc_NumOutsPlayed = 0; + + SetFramesDone( pahsc, 0.0 ); + + /* Pause channel so it does not do back ground processing while we are still filling the queue. */ + pauseCommand.cmd = pauseCmd; + pauseCommand.param1 = pauseCommand.param2 = 0; + error = SndDoCommand (pahsc->pahsc_Channel, &pauseCommand, true); + if (noErr != error) goto exit; + + /* Queue all of the buffers so we start off full. */ + for (i = 0; ipahsc_NumHostBuffers; i++) + { + PaMac_PlayNext( past, i ); + } + + /* Resume channel now that the queue is full. */ + resumeCommand.cmd = resumeCmd; + resumeCommand.param1 = resumeCommand.param2 = 0; + error = SndDoImmediate( pahsc->pahsc_Channel, &resumeCommand ); + if (noErr != error) goto exit; + + return paNoError; +exit: + past->past_IsActive = 0; + sPaHostError = error; + ERR_RPT(("Error in PaHost_StartOutput: SndDoCommand returned %d\n", error )); + return paHostError; +} + +/*******************************************************************/ +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (long) (pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + if( pahsc->pahsc_Channel == NULL ) return paNoError; + + DBUG(("PaHost_StopOutput()\n")); + if( past->past_IsActive == 0 ) return paNoError; + + /* Set flags for callback function to see. */ + if( abort ) past->past_StopNow = 1; + past->past_StopSoon = 1; + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + timeOutMsec = (int32) ((1500.0 * PaHost_GetTotalBufferFrames( past )) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( past->past_IsActive && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopOutput: timed out!\n")); + return paTimedOut; + } + else return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + (void) past; /* Prevent unused variable warnings. */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + (void) past; /* Prevent unused variable warnings. */ + (void) abort; /* Prevent unused variable warnings. */ + return paNoError; +} +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (PaError) ( past->past_IsActive + pahsc->pahsc_IsRecording ); +} +int Mac_IsVirtualMemoryOn( void ) +{ + long attr; + OSErr result = Gestalt( gestaltVMAttr, &attr ); + DBUG(("gestaltVMAttr : 0x%x\n", attr )); + return ((attr >> gestaltVMHasPagingControl ) & 1); +} + +/******************************************************************* +* Determine number of host Buffers +* and how many User Buffers we can put into each host buffer. +*/ +static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int32 minNumBuffers; + int32 minFramesPerHostBuffer; + int32 minTotalFrames; + int32 userBuffersPerHostBuffer; + int32 framesPerHostBuffer; + int32 numHostBuffers; + + minFramesPerHostBuffer = pahsc->pahsc_MinFramesPerHostBuffer; + minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); + + /* Determine number of user buffers based on minimum latency. */ + /* PLB20020417 I used to call Pa_GetMinNumBuffers() which doesn't take into account the + ** variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will + ** gove lower latency when virtual memory is turned off. */ + /* minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); WRONG */ + minNumBuffers = PaMac_GetMinNumBuffers( minFramesPerHostBuffer, past->past_FramesPerUserBuffer, past->past_SampleRate ); + + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + + /* We cannot make the buffers too small because they may not get serviced quickly enough. */ + if( (int32) past->past_FramesPerUserBuffer < minFramesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) / + past->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + + /* Calculate number of host buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* Make sure we have enough host buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else + { + /* If we have too many host buffers, try to put more user buffers in a host buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + } + } + + pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer; + pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer; + pahsc->pahsc_NumHostBuffers = numHostBuffers; + DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + OSErr err; + PaError result = paHostError; + PaHostSoundControl *pahsc; + int i; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + return paInsufficientMemory; + } + past->past_DeviceData = (void *) pahsc; + + /* If recording, and virtual memory is turned on, then use bigger buffers to prevent glitches. */ + if( (past->past_NumInputChannels > 0) && Mac_IsVirtualMemoryOn() ) + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_VIRTUAL_FRAMES_PER_BUFFER; + } + else + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_PHYSICAL_FRAMES_PER_BUFFER; + } + + PaHost_CalcNumHostBuffers( past ); + + /* Setup constants for CPU load measurement. */ + pahsc->pahsc_InverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * pahsc->pahsc_FramesPerHostBuffer); + + /* ------------------ OUTPUT */ + if( past->past_NumOutputChannels > 0 ) + { + /* Create sound channel to which we can send commands. */ + pahsc->pahsc_Channel = 0L; + err = SndNewChannel(&pahsc->pahsc_Channel, sampledSynth, 0, nil); /* FIXME - use kUseOptionalOutputDevice if not default. */ + if(err != 0) + { + ERR_RPT(("Error in PaHost_OpenStream: SndNewChannel returned 0x%x\n", err )); + goto error; + } + + /* Install our callback function pointer straight into the sound channel structure */ + /* Use new CARBON name for callback procedure. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_OutputCompletionProc = NewSndCallBackUPP(PaMac_OutputCompletionProc); +#else + pahsc->pahsc_OutputCompletionProc = NewSndCallBackProc(PaMac_OutputCompletionProc); +#endif + + pahsc->pahsc_Channel->callBack = pahsc->pahsc_OutputCompletionProc; + + pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumOutputChannels * sizeof(int16); + for (i = 0; ipahsc_NumHostBuffers; i++) + { + char *buf = (char *)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer); + if (buf == NULL) + { + ERR_RPT(("Error in PaHost_OpenStream: could not allocate output buffer. Size = \n", pahsc->pahsc_BytesPerOutputHostBuffer )); + goto memerror; + } + + PaMac_InitSoundHeader( past, &pahsc->pahsc_SoundHeaders[i] ); + pahsc->pahsc_SoundHeaders[i].samplePtr = buf; + pahsc->pahsc_SoundHeaders[i].numFrames = (unsigned long) pahsc->pahsc_FramesPerHostBuffer; + + } + } +#ifdef SUPPORT_AUDIO_CAPTURE + /* ------------------ INPUT */ + /* Use double buffer scheme that matches output. */ + if( past->past_NumInputChannels > 0 ) + { + int16 tempS; + long tempL; + Fixed tempF; + long mRefNum; + unsigned char noname = 0; /* FIXME - use real device names. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_InputCompletionProc = NewSICompletionUPP((SICompletionProcPtr)PaMac_InputCompletionProc); +#else + pahsc->pahsc_InputCompletionProc = NewSICompletionProc((ProcPtr)PaMac_InputCompletionProc); +#endif + pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumInputChannels * sizeof(int16); + for (i = 0; ipahsc_NumHostBuffers; i++) + { + char *buf = (char *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer); + if ( buf == NULL ) + { + ERR_RPT(("PaHost_OpenStream: could not allocate input buffer. Size = \n", pahsc->pahsc_BytesPerInputHostBuffer )); + goto memerror; + } + pahsc->pahsc_InputMultiBuffer.buffers[i] = buf; + } + pahsc->pahsc_InputMultiBuffer.numBuffers = pahsc->pahsc_NumHostBuffers; + + err = SPBOpenDevice( (const unsigned char *) &noname, siWritePermission, &mRefNum); /* FIXME - use name so we get selected device */ + // FIXME err = SPBOpenDevice( (const unsigned char *) sDevices[past->past_InputDeviceID].pad_Info.name, siWritePermission, &mRefNum); + if (err) goto error; + pahsc->pahsc_InputRefNum = mRefNum; + DBUG(("PaHost_OpenStream: mRefNum = %d\n", mRefNum )); + + /* Set input device characteristics. */ + tempS = 1; + err = SPBSetDeviceInfo(mRefNum, siContinuous, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siContinuous returned %d\n", err )); + goto error; + } + + tempL = 0x03; + err = SPBSetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err) + { + DBUG(("PaHost_OpenStream: setting siActiveChannels returned 0x%x. Error ignored.\n", err )); + } + + /* PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. */ + tempS = past->past_NumInputChannels; + err = SPBSetDeviceInfo(mRefNum, siNumberChannels, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siNumberChannels returned %d\n", err )); + goto error; + } + + tempF = ((unsigned long)past->past_SampleRate) << 16; + err = SPBSetDeviceInfo(mRefNum, siSampleRate, (Ptr) &tempF); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siSampleRate returned %d\n", err )); + goto error; + } + + /* Setup record-parameter block */ + pahsc->pahsc_InputParams.inRefNum = mRefNum; + pahsc->pahsc_InputParams.milliseconds = 0; // not used + pahsc->pahsc_InputParams.completionRoutine = pahsc->pahsc_InputCompletionProc; + pahsc->pahsc_InputParams.interruptRoutine = 0; + pahsc->pahsc_InputParams.userLong = (long) past; + pahsc->pahsc_InputParams.unused1 = 0; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + DBUG(("PaHost_OpenStream: complete.\n")); + return paNoError; + +error: + PaHost_CloseStream( past ); + ERR_RPT(("PaHost_OpenStream: sPaHostError = 0x%x.\n", err )); + sPaHostError = err; + return paHostError; + +memerror: + PaHost_CloseStream( past ); + return paInsufficientMemory; +} + +/*********************************************************************** +** Called by Pa_CloseStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err = 0; + int i; + PaHostSoundControl *pahsc; + + DBUG(("PaHost_CloseStream( 0x%x )\n", past )); + + if( past == NULL ) return paBadStreamPtr; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + if( past->past_NumOutputChannels > 0 ) + { + /* TRUE means flush now instead of waiting for quietCmd to be processed. */ + if( pahsc->pahsc_Channel != NULL ) SndDisposeChannel(pahsc->pahsc_Channel, TRUE); + { + for (i = 0; ipahsc_NumHostBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_SoundHeaders[i].samplePtr; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerOutputHostBuffer ); + } + } + } + + if( past->past_NumInputChannels > 0 ) + { + if( pahsc->pahsc_InputRefNum ) + { + err = SPBCloseDevice(pahsc->pahsc_InputRefNum); + pahsc->pahsc_InputRefNum = 0; + if( err ) + { + sPaHostError = err; + result = paHostError; + } + } + { + for (i = 0; ipahsc_InputMultiBuffer.numBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_InputMultiBuffer.buffers[i]; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerInputHostBuffer ); + } + } + } + + past->past_DeviceData = NULL; + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); + + DBUG(("PaHost_CloseStream: complete.\n", past )); + return result; +} +/*************************************************************************/ +int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate ) +{ +/* We use the MAC_VIRTUAL_FRAMES_PER_BUFFER because we might be recording. +** This routine doesn't have enough information to determine the best value +** and is being depracated. */ + return PaMac_GetMinNumBuffers( MAC_VIRTUAL_FRAMES_PER_BUFFER, framesPerUserBuffer, sampleRate ); +} +/*************************************************************************/ +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerUserBuffer, double sampleRate ) +{ + int minUserPerHost = ( minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer; + int numBufs = PA_MIN_NUM_HOST_BUFFERS * minUserPerHost; + if( numBufs < PA_MIN_NUM_HOST_BUFFERS ) numBufs = PA_MIN_NUM_HOST_BUFFERS; + (void) sampleRate; + return numBufs; +} + +/*************************************************************************/ +void Pa_Sleep( int32 msec ) +{ + EventRecord event; + int32 sleepTime, endTime; + /* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */ + sleepTime = ((msec * 60) + 999) / 1000; + if( sleepTime < 1 ) sleepTime = 1; + endTime = TickCount() + sleepTime; + do + { + DBUGX(("Sleep for %d ticks.\n", sleepTime )); + /* Use WaitNextEvent() to sleep without getting events. */ + /* PLB20010907 - Pass unused event to WaitNextEvent instead of NULL to prevent + * Mac OSX crash. Thanks Dominic Mazzoni. */ + WaitNextEvent( 0, &event, sleepTime, NULL ); + sleepTime = endTime - TickCount(); + } + while( sleepTime > 0 ); +} +/*************************************************************************/ +int32 Pa_GetHostError( void ) +{ + int32 err = sPaHostError; + sPaHostError = 0; + return err; +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = NewPtrClear( numBytes ); + if( (addr == NULL) || (MemError () != 0) ) return NULL; + +#if (TARGET_API_MAC_CARBON == 0) + if( HoldMemory( addr, numBytes ) != noErr ) + { + DisposePtr( (Ptr) addr ); + return NULL; + } +#endif + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr == NULL ) return; +#if TARGET_API_MAC_CARBON + (void) numBytes; +#else + UnholdMemory( addr, numBytes ); +#endif + DisposePtr( (Ptr) addr ); +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaTimestamp framesDone1; + PaTimestamp framesDone2; + UInt64 whenIncremented; + UnsignedWide now; + UInt64 now64; + long microsElapsed; + long framesElapsed; + + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + +/* Capture information from audio thread. + * We have to be careful that we don't get interrupted in the middle. + * So we grab the pahsc_NumFramesDone twice and make sure it didn't change. + */ + do + { + framesDone1 = pahsc->pahsc_NumFramesDone; + whenIncremented = pahsc->pahsc_WhenFramesDoneIncremented; + framesDone2 = pahsc->pahsc_NumFramesDone; + } while( framesDone1 != framesDone2 ); + + /* Calculate how many microseconds have elapsed and convert to frames. */ + Microseconds( &now ); + now64 = UnsignedWideToUInt64( now ); + microsElapsed = U64Subtract( now64, whenIncremented ); + framesElapsed = microsElapsed * past->past_SampleRate * 0.000001; + + return framesDone1 + framesElapsed; +} + +/************************************************************************** +** Callback for Input, SPBRecord() +*/ +int gRecordCounter = 0; +int gPlayCounter = 0; +pascal void PaMac_InputCompletionProc(SPBPtr recParams) +{ + PaError result = paNoError; + int finished = 1; + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + + gRecordCounter += 1; /* debug hack to see if engine running */ + + /* Get our PA data from Mac structure. */ + past = (internalPortAudioStream *) recParams->userLong; + if( past == NULL ) return; + + if( past->past_Magic != PA_MAGIC ) + { + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, past", (long) past ); + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, magic", (long) past->past_Magic ); + goto error; + } + pahsc = (PaHostSoundControl *) past->past_DeviceData; + past->past_NumCallbacks += 1; + + /* Have we been asked to stop recording? */ + if( (recParams->error == abortErr) || pahsc->pahsc_StopRecording ) goto error; + + /* If there are no output channels, then we need to call the user callback function from here. + * Otherwise we will call the user code during the output completion routine. + */ + if(past->past_NumOutputChannels == 0) + { + SetFramesDone( pahsc, + pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer ); + result = PaMac_CallUserLoop( past, NULL ); + } + + /* Did user code ask us to stop? If not, issue another recording request. */ + if( (result == paNoError) && (pahsc->pahsc_StopRecording == 0) ) + { + result = PaMac_RecordNext( past ); + if( result != paNoError ) pahsc->pahsc_IsRecording = 0; + } + else goto error; + + return; + +error: + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + return; +} + +/*********************************************************************** +** Called by either input or output completion proc. +** Grabs input data if any present, and calls PA conversion code, +** that in turn calls user code. +*/ +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int16 *inPtr = NULL; + int i; + + + /* Advance read index for sound input FIFO here, independantly of record/write process. */ + if(past->past_NumInputChannels > 0) + { + if( MultiBuffer_IsReadable( &pahsc->pahsc_InputMultiBuffer ) ) + { + inPtr = (int16 *) MultiBuffer_GetNextReadBuffer( &pahsc->pahsc_InputMultiBuffer ); + MultiBuffer_AdvanceReadIndex( &pahsc->pahsc_InputMultiBuffer ); + } + } + + /* Call user code enough times to fill buffer. */ + if( (inPtr != NULL) || (outPtr != NULL) ) + { + PaMac_StartLoadCalculation( past ); /* CPU usage */ + +#ifdef PA_MAX_USAGE_ALLOWED + /* If CPU usage exceeds limit, skip user callback to prevent hanging CPU. */ + if( past->past_Usage > PA_MAX_USAGE_ALLOWED ) + { + past->past_FrameCount += (PaTimestamp) pahsc->pahsc_FramesPerHostBuffer; + } + else +#endif + { + + for( i=0; ipahsc_UserBuffersPerHostBuffer; i++ ) + { + result = (PaError) Pa_CallConvertInt16( past, inPtr, outPtr ); + if( result != 0) + { + /* Recording might be in another process, so tell it to stop with a flag. */ + pahsc->pahsc_StopRecording = pahsc->pahsc_IsRecording; + break; + } + /* Advance sample pointers. */ + if(inPtr != NULL) inPtr += past->past_FramesPerUserBuffer * past->past_NumInputChannels; + if(outPtr != NULL) outPtr += past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + } + } + + PaMac_EndLoadCalculation( past ); + } + return result; +} + +/*********************************************************************** +** Setup next recording buffer in FIFO and issue recording request to Snd Input Manager. +*/ +static PaError PaMac_RecordNext( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Get pointer to next buffer to record into. */ + pahsc->pahsc_InputParams.bufferPtr = MultiBuffer_GetNextWriteBuffer( &pahsc->pahsc_InputMultiBuffer ); + + /* Advance write index if there is room. Otherwise keep writing same buffer. */ + if( MultiBuffer_IsWriteable( &pahsc->pahsc_InputMultiBuffer ) ) + { + MultiBuffer_AdvanceWriteIndex( &pahsc->pahsc_InputMultiBuffer ); + } + + AddTraceMessage("PaMac_RecordNext: bufferPtr", (long) pahsc->pahsc_InputParams.bufferPtr ); + AddTraceMessage("PaMac_RecordNext: nextWrite", pahsc->pahsc_InputMultiBuffer.nextWrite ); + + /* Setup parameters and issue an asynchronous recording request. */ + pahsc->pahsc_InputParams.bufferLength = pahsc->pahsc_BytesPerInputHostBuffer; + pahsc->pahsc_InputParams.count = pahsc->pahsc_BytesPerInputHostBuffer; + err = SPBRecord(&pahsc->pahsc_InputParams, true); + if( err ) + { + AddTraceMessage("PaMac_RecordNext: SPBRecord error ", err ); + sPaHostError = err; + result = paHostError; + } + else + { + pahsc->pahsc_IsRecording = 1; + } + return result; +} + +/************************************************************************** +** Callback for Output Playback() +** Return negative error, 0 to continue, 1 to stop. +*/ +long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ) +{ + PaHostSoundControl *pahsc; + long result = 0; + int finished = 1; + char *outPtr; + + gPlayCounter += 1; /* debug hack */ + + past->past_NumCallbacks += 1; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return -1; + /* Are we nested?! */ + if( pahsc->pahsc_IfInsideCallback ) return 0; + pahsc->pahsc_IfInsideCallback = 1; + /* Get pointer to buffer to fill. */ + outPtr = pahsc->pahsc_SoundHeaders[index].samplePtr; + /* Combine with any sound input, and call user callback. */ + result = PaMac_CallUserLoop( past, (int16 *) outPtr ); + + pahsc->pahsc_IfInsideCallback = 0; + return result; +} + +/************************************************************************************* +** Called by SoundManager when ready for another buffer. +*/ +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCallBackCmd) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + (void) theChannel; + (void) theCallBackCmd; + + /* Get our data from Mac structure. */ + past = (internalPortAudioStream *) theCallBackCmd->param2; + if( past == NULL ) return; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_NumOutsPlayed += 1; + + SetFramesDone( pahsc, + pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer ); + + PaMac_BackgroundManager( past, theCallBackCmd->param1 ); +} + +/*******************************************************************/ +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Has someone asked us to abort by calling Pa_AbortStream()? */ + if( past->past_StopNow ) + { + SndCommand command; + /* Clear the queue of any pending commands. */ + command.cmd = flushCmd; + command.param1 = command.param2 = 0; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + /* Then stop currently playing buffer, if any. */ + command.cmd = quietCmd; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + past->past_IsActive = 0; + } + /* Has someone asked us to stop by calling Pa_StopStream() + * OR has a user callback returned '1' to indicate finished. + */ + else if( past->past_StopSoon ) + { + if( (pahsc->pahsc_NumOutsQueued - pahsc->pahsc_NumOutsPlayed) <= 0 ) + { + past->past_IsActive = 0; /* We're finally done. */ + } + } + else + { + PaMac_PlayNext( past, index ); + } + return result; +} + +/************************************************************************************* +** Fill next buffer with sound and queue it for playback. +*/ +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ) +{ + OSErr error; + long result; + SndCommand playCmd; + SndCommand callbackCmd; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* If this was the last buffer, or abort requested, then just be done. */ + if ( past->past_StopSoon ) goto done; + + /* Load buffer with sound. */ + result = PaMac_FillNextOutputBuffer ( past, index ); + if( result > 0 ) past->past_StopSoon = 1; /* Stop generating audio but wait until buffers play. */ + else if( result < 0 ) goto done; + + /* Play the next buffer. */ + playCmd.cmd = bufferCmd; + playCmd.param1 = 0; + playCmd.param2 = (long) &pahsc->pahsc_SoundHeaders[ index ]; + error = SndDoCommand (pahsc->pahsc_Channel, &playCmd, true ); + if( error != noErr ) goto gotError; + + /* Ask for a callback when it is done. */ + callbackCmd.cmd = callBackCmd; + callbackCmd.param1 = index; + callbackCmd.param2 = (long)past; + error = SndDoCommand (pahsc->pahsc_Channel, &callbackCmd, true ); + if( error != noErr ) goto gotError; + pahsc->pahsc_NumOutsQueued += 1; + + return; + +gotError: + sPaHostError = error; +done: + return; +} diff --git a/portaudio-v19/pa_sgi/pa_sgi.c b/portaudio-v19/pa_sgi/pa_sgi.c new file mode 100644 index 000000000..8b742d1cc --- /dev/null +++ b/portaudio-v19/pa_sgi/pa_sgi.c @@ -0,0 +1,1417 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library. + * Latest Version at: http://www.portaudio.com. + * Silicon Graphics (SGI) IRIX implementation by Pieter Suurmond. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +/** @file + @brief SGI IRIX AL implementation (according to V19 API version 2.0). + + @note This file started as a copy of pa_skeleton.c (v 1.1.2.35 2003/09/20), it + has nothing to do with the old V18 pa_sgi version: this implementation uses the + newer IRIX AL calls and uses pthreads instead of sproc. + + On IRIX, one may type './configure' followed by 'gmake' from the portaudio root + directory to build the static and shared libraries, as well as all the tests. + + On IRIX 6.5, using 'make' instead of 'gmake' may cause Makefile to fail. (This + happens on my machine: make does not understand syntax with 2 colons on a line, + like this: + $(TESTS): bin/%: [snip] + + Maybe this is due to an old make version(?), my only solution is: use gmake. + Anyway, all the tests compile well now, with GCC 3.3, as well as with MIPSpro 7.2.1. + Tested: + - paqa_devs ok, but at a certain point digital i/o fails: + TestAdvance: INPUT, device = 2, rate = 32000, numChannels = 1, format = 1 + Possibly, this is an illegal sr or number of channels for digital i/o. + - paqa_errs 13 of the tests run ok, but 5 of them give weird results. + + patest1 ok. + + patest_buffer ok. + + patest_callbackstop ok. + - patest_clip ok, but hear no difference between dithering turned OFF and ON. + + patest_hang ok. + + patest_latency ok. + + patest_leftright ok. + + patest_maxsines ok. + + patest_many ok. + + patest_multi_sine ok. + + patest_pink ok. + + patest_prime ok. + - patest_read_record ok, but playback stops a little earlier than 5 seconds it seems(?). + + patest_record ok. + + patest_ringmix ok. + + patest_saw ok. + + patest_sine ok. + + patest_sine8 ok. + - patest_sine_formats ok, FLOAT32 + INT16 + INT18 are OK, but UINT8 IS NOT OK! + + patest_sine_time ok. + + patest_start_stop ok, but under/overflow errors of course in the AL queue monitor. + + patest_stop ok. + - patest_sync ok? + + patest_toomanysines ok. + - patest_underflow ok? (stopping after SleepTime = 91: err=Stream is stopped) + - patest_wire ok. + + patest_write_sine ok. + + pa_devs ok. + Ok on an Indy, in both stereo and quadrophonic mode. + + pa_fuzz ok. + + pa_minlat ok. + + Worked on (or checked) proposals: + + 003: Improve Latency Specification OK, but not 100% sure: plus or minus 1 host buffer? + 004 OK: Allow Callbacks to Accept Variable Number of Frames per Buffer. + Simply using a fixed host buffer size. Very roughly implemented now, the adaption + to limited-requested latencies and samplerate may be improved. At least this + implementation chooses its own internal host buffer size (no coredumps any longer). + 005 OK: Blocking Read/Write Interface. + 006: Non-interleaved buffers seems OK? Covered by the buffer-processor and such?.... + 009 OK: Host error reporting should now be. + 010 OK: State Machine and State Querying Functions. + 011 OK: Renaming done. + 014 Implementation Style Guidelines (sorry, my braces are not ANSI style). + 015 OK: Callback Timestamps (During priming, though, these are still null!). + 016 OK: Use Structs for Pa_OpenStream() Parameters. + 019: Notify Client When All Buffers Have Played (Ok, covered by the buffer processor?) + 020 OK: Allow Callback to prime output stream (StartStream() should do the priming) + Should be tested more thoroughly for full duplex streams. (patest_prime seems ok). + + + @todo Underrun or overflow flags at some more places. + + @todo Callback Timestamps during priming. + + @todo Improve adaption to number of channels, samplerate and such when inventing + some frames per host buffer (when client requests 0). + + @todo Make a complete new version to support 'sproc'-applications. + Or could we manage that with some clever if-defs? + It must be clear which version we use (especially when using pa as lib!): + an irix-sproc() version or pthread version. + + @todo In Makefile.in: 'make clean' does not remove lib/libportaudio.so.0.0.19. + + Note: Even when mono-output is requested, with ALv7, the audio library opens + a outputs stereo. One can observe this in SGI's 'Audio Queue Monitor'. +*/ + +#include /* For strlen() but also for strerror()! */ +#include /* printf() */ +#include /* fabs() */ + +#include /* IRIX AL (audio library). Link with -laudio. */ +#include /* IRIX DL (digital media library), solely for */ + /* function dmGetUST(). Link with -ldmedia. */ +#include /* To catch 'oserror' after AL-calls. */ +#include /* POSIX threads. */ +#include + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + + /* Uncomment for diagnostics: */ +#define DBUG(x) /*{ printf x; fflush(stdout); }*/ + + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *ipp, + const PaStreamParameters *opp, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* + Apparently, we must use macros for reporting unanticipated host errors. + Only in case we return paUnanticipatedHostError from an Portaudio call, + we have to call one of the following three macro's. + (Constant paAL is defined in pa_common/portaudio.h. See also proposal 009.) + + After an AL error, use this to translate the AL error code to human text: +*/ +#define PA_SGI_SET_LAST_AL_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, alGetErrorString(ee));\ + } +/* + But after a generic IRIX error, let strerror() translate the error code from + the operating system and use this (strerror() gives the same as perror()): +*/ +#define PA_SGI_SET_LAST_IRIX_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, strerror(ee));\ + } + +/* GOT RID OF calling PaUtil_SetLastHostErrorInfo() with 0 as error number. +- Weird samplerate difference became: paInvalidSampleRate. +- Failing to set AL queue size became: paInternalError + (Because I cannot decide between paBufferTooBig and paBufferTooSmall + because it may even the 'default AL queue size that failed... Or + should we introduce another error-code like 'paInvalidQueueSize'?... NO) +*/ + +/* PaSGIHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + PaUtilAllocationGroup* allocations; + /* implementation specific data goes here. */ + ALvalue* sgiDeviceIDs; /* Array of AL resource device numbers. */ + /* PaHostApiIndex hostApiIndex; Hu? As in the linux and oss files? */ +} +PaSGIHostApiRepresentation; + +/* + Initialises sgiDeviceIDs array. +*/ +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int e, i, deviceCount, def_in, def_out; + PaSGIHostApiRepresentation* SGIHostApi; + PaDeviceInfo* deviceInfoArray; + static const short numParams = 4; /* Array with name, samplerate, channels */ + ALpv y[numParams]; /* and type. */ + static const short maxDevNameChars = 32; /* Including the terminating null char. */ + char devName[maxDevNameChars]; /* Too lazy for dynamic alloc. */ + + /* DBUG(("PaSGI_Initialize() started.\n")); */ + SGIHostApi = (PaSGIHostApiRepresentation*)PaUtil_AllocateMemory(sizeof(PaSGIHostApiRepresentation)); + if( !SGIHostApi ) + { result = paInsufficientMemory; goto cleanup; } + SGIHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !SGIHostApi->allocations ) + { result = paInsufficientMemory; goto cleanup; } + *hostApi = &SGIHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paAL; /* IRIX AL type id, was paInDevelopment. */ + (*hostApi)->info.name = "SGI IRIX AL"; + (*hostApi)->info.defaultInputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.defaultOutputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.deviceCount = 0; /* We 'll increment in the loop below. */ + + /* Determine the total number of input and output devices (thanks to Gary Scavone). */ + deviceCount = alQueryValues(AL_SYSTEM, AL_DEVICES, 0, 0, 0, 0); + if (deviceCount < 0) /* Returns -1 in case of failure. */ + { + DBUG(("Failed to count devices: alQueryValues()=%d; %s.\n", + deviceCount, alGetErrorString(oserror()))); + result = paDeviceUnavailable; /* Is this an appropriate error return code? */ + goto cleanup; + } + if (deviceCount > 0) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount); + if (!(*hostApi)->deviceInfos) + { result = paInsufficientMemory; goto cleanup; } + + /* Allocate all device info structs in a contiguous block. */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount); + if (!deviceInfoArray) + { result = paInsufficientMemory; goto cleanup; } + /* Store all AL device IDs in an array. */ + SGIHostApi->sgiDeviceIDs = (ALvalue*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, + deviceCount * sizeof(ALvalue)); + if (!SGIHostApi->sgiDeviceIDs) + { result = paInsufficientMemory; goto cleanup; } + /* Same query again, but now store all IDs in array sgiDeviceIDs (still using no qualifiers).*/ + e = alQueryValues(AL_SYSTEM, AL_DEVICES, SGIHostApi->sgiDeviceIDs, deviceCount, 0, 0); + if (e != deviceCount) + { + if (e < 0) /* Sure an AL error really occurred. */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else /* Seems we lost some devices. */ + { DBUG(("Number of devices suddenly changed!\n")); result = paDeviceUnavailable; } + goto cleanup; + } + y[0].param = AL_DEFAULT_INPUT; + y[1].param = AL_DEFAULT_OUTPUT; + e = alGetParams(AL_SYSTEM, y, 2); /* Get params global to the AL system. */ + if (e != 2) + { + if (e < 0) + { + PA_SGI_SET_LAST_AL_ERROR() /* Calls oserror() and alGetErrorString(). */ + result = paUnanticipatedHostError; /* Sure an AL error really occurred. */ + } + else + { + DBUG(("Default input and/or output could not be found!\n")); + result = paDeviceUnavailable; /* FIX: What if only in or out are available? */ + } + goto cleanup; + } + def_in = y[0].value.i; /* Remember both AL devices for a while. */ + def_out = y[1].value.i; + y[0].param = AL_NAME; + y[0].value.ptr = devName; + y[0].sizeIn = maxDevNameChars; /* Including terminating null char. */ + y[1].param = AL_RATE; + y[2].param = AL_CHANNELS; + y[3].param = AL_TYPE; /* Subtype of AL_INPUT_DEVICE_TYPE or AL_OUTPUT_DEVICE_TYPE? */ + for (i=0; i < deviceCount; ++i) /* Fill allocated deviceInfo structs. */ + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; /* Retrieve name, samplerate, channels and type. */ + e = alGetParams(SGIHostApi->sgiDeviceIDs[i].i, y, numParams); + if (e != numParams) + { + if (e < 0) /* Calls oserror() and alGetErrorString(). */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else + { DBUG(("alGetParams() could not get all params!\n")); result = paInternalError; } + goto cleanup; + } + deviceInfo->name = (char*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, strlen(devName) + 1); + if (!deviceInfo->name) + { result = paInsufficientMemory; goto cleanup; } + strcpy((char*)deviceInfo->name, devName); + + /* Determine whether the received number of channels belongs to input or output device. */ + if (alIsSubtype(AL_INPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = y[2].value.i; + deviceInfo->maxOutputChannels = 0; + } + else if (alIsSubtype(AL_OUTPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = y[2].value.i; + } + else /* Should never occur. */ + { + DBUG(("AL device is neither input nor output!\n")); + result = paInternalError; + goto cleanup; + } + + /* Determine if this device is the default (in or out). If so, assign. */ + if (def_in == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultInputDevice != paNoDevice) + { + DBUG(("Default input already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultInputDevice = i; + /* DBUG(("Default input assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + else if (def_out == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultOutputDevice != paNoDevice) + { + DBUG(("Default output already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultOutputDevice = i; + /* DBUG(("Default output assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + /*---------------------------------------------- Default latencies set to 'reasonable' values. */ + deviceInfo->defaultLowInputLatency = 0.050; /* 50 milliseconds seems ok. */ + deviceInfo->defaultLowOutputLatency = 0.050; /* These are ALSO ABSOLUTE MINIMA in OpenStream(). */ + deviceInfo->defaultHighInputLatency = 0.500; /* 500 milliseconds a reasonable value? */ + deviceInfo->defaultHighOutputLatency = 0.500; /* Ten times these are ABSOLUTE MAX in OpenStream()). */ + + deviceInfo->defaultSampleRate = alFixedToDouble(y[1].value.ll); /* Read current sr. */ + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + /* What if (deviceCount==0)? */ + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface(&SGIHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface(&SGIHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); +cleanup: + if (result != paNoError) + { + if (SGIHostApi) + { + if (SGIHostApi->allocations) + { + PaUtil_FreeAllAllocations(SGIHostApi->allocations); + PaUtil_DestroyAllocationGroup(SGIHostApi->allocations); + } + PaUtil_FreeMemory(SGIHostApi); + } + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSGIHostApiRepresentation *SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + + /* Clean up any resources not handled by the allocation group. */ + if( SGIHostApi->allocations ) + { + PaUtil_FreeAllAllocations( SGIHostApi->allocations ); + PaUtil_DestroyAllocationGroup( SGIHostApi->allocations ); + } + PaUtil_FreeMemory( SGIHostApi ); +} + +/* + Check if samplerate is supported for this output device. Called once + or twice by function IsFormatSupported() and one time by OpenStream(). + When paUnanticipatedHostError is returned, the caller does NOT have + to call PA_SGI_SET_LAST_AL_ERROR() or such. +*/ +static PaError sr_supported(int al_device, double sr) +{ + int e; + PaError result; + ALparamInfo pinfo; + long long lsr; /* 64 bit fixed point internal AL samplerate. */ + + if (alGetParamInfo(al_device, AL_RATE, &pinfo)) + { + e = oserror(); + DBUG(("alGetParamInfo(AL_RATE) failed: %s.\n", alGetErrorString(e))); + if (e == AL_BAD_RESOURCE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() /* Sure an AL error occured. */ + result = paUnanticipatedHostError; + } + } + else + { + lsr = alDoubleToFixed(sr); /* Within the range? */ + if ((pinfo.min.ll <= lsr) && (lsr <= pinfo.max.ll)) + result = paFormatIsSupported; + else + result = paInvalidSampleRate; + } + /* DBUG(("sr_supported()=%d.\n", result)); */ + return result; +} + + +/* + See common/portaudio.h (suggestedLatency field is ignored). +*/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + int inputChannelCount, outputChannelCount, result; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + /* Unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification. */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[inputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + } + else + { + inputChannelCount = 0; + } + if( outputParameters ) /* As with input above. */ + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[outputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + } + else + { + outputChannelCount = 0; + } + /* IMPLEMENT ME: + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + /* suppress unused variable warnings */ + (void) inputSampleFormat; + (void) outputSampleFormat; + return paFormatIsSupported; +} + +/** Auxilary struct, embedded twice in the struct below, for inputs and outputs. */ +typedef struct PaSGIhostPortBuffer +{ + /** NULL means IRIX AL port closed. */ + ALport port; + /** NULL means memory not allocated. */ + void* buffer; +} + PaSGIhostPortBuffer; + +/** Stream data structure specifically for this IRIX AL implementation. */ +typedef struct PaSGIStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + unsigned long framesPerHostCallback; + /** Allocated host buffers and AL ports. */ + PaSGIhostPortBuffer hostPortBuffIn, + hostPortBuffOut; + /** Copy of stream flags given to OpenStream(). */ + PaStreamFlags streamFlags; + /** Stream state may be 0 or 1 or 2, but never 3. */ + unsigned char state; + /** Requests to stop or abort may come from the parent, + or from the child itself (user callback result). */ + unsigned char stopAbort; + pthread_t thread; +} + PaSGIStream; + +/** Stream can be in only one of the following three states: stopped (1), active (2), or + callback finshed (0). To prevent 'state 3' from occurring, Setting and testing of the + state bits is done atomically. +*/ +#define PA_SGI_STREAM_FLAG_FINISHED_ (0) /* After callback finished or cancelled queued buffers. */ +#define PA_SGI_STREAM_FLAG_STOPPED_ (1) /* Set by OpenStream(), StopStream() and AbortStream(). */ +#define PA_SGI_STREAM_FLAG_ACTIVE_ (2) /* Set by StartStream. Reset by OpenStream(), */ + /* StopStream() and AbortStream(). */ + +/** Stop requests, via the 'stopAbort' field can be either 1, meaning 'stop' or 2, meaning 'abort'. + When both occur at the same time, 'abort' takes precedence, even after a first 'stop'. +*/ +#define PA_SGI_REQ_CONT_ (0) /* Reset by OpenStream(), StopStream and AbortStream. */ +#define PA_SGI_REQ_STOP_ (1) /* Set by StopStream(). */ +#define PA_SGI_REQ_ABORT_ (2) /* Set by AbortStream(). */ + + +/** Called by OpenStream() once or twice. First, the number of channels, sampleformat, and + queue size are configured. The configuration is then bound to the specified AL device. + Then an AL port is opened. Finally, the samplerate of the device is altered (or at least + set again). + + After successful return, actual latency is written in *latency, and actual samplerate + in *samplerate. + + @param pa_params may be NULL and pa_params->channelCount may also be null, in both + cases the function immediately returns. + @return paNoError if configuration was skipped or if it succeeded. +*/ +static PaError set_sgi_device(ALvalue* sgiDeviceIDs, /* Array built by PaSGI_Initialize(). */ + const PaStreamParameters* pa_params, /* read device and channels. */ + double* latency, /* Read and write in seconds. */ + + PaSampleFormat pasfmt, /* Don't read from pa_params!. */ + char* direction, /* "r" or "w". */ + char* name, + long framesPerHostBuffer, + double* samplerate, /* Also writes back here. */ + PaSGIhostPortBuffer* hostPortBuff) /* Receive pointers here. */ +{ + int bytesPerFrame, sgiDevice, alErr, d, dd, iq_size, default_iq_size; + ALpv pvs[2]; + ALconfig alc = NULL; + PaError result = paNoError; + + if (!pa_params) + goto cleanup; /* Not errors, just not full duplex, skip all. */ + if (!pa_params->channelCount) + goto cleanup; + alc = alNewConfig(); /* Create default config. This defaults to stereo, 16-bit integer data. */ + if (!alc) /* Call alFreeConfig() later, when done with it. */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- CONFIGURE NUMBER OF CHANNELS: ---------------------------*/ + if (alSetChannels(alc, pa_params->channelCount)) /* Returns 0 on success. */ + { + if (oserror() == AL_BAD_CHANNELS) + result = paInvalidChannelCount; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame = pa_params->channelCount; /* Is multiplied by width below. */ + /*----------------------- CONFIGURE SAMPLE FORMAT: --------------------------------*/ + if (pasfmt == paFloat32) + { + if (alSetSampFmt(alc, AL_SAMPFMT_FLOAT)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 4; /* No need to set width for floats. */ + } + else + { + if (alSetSampFmt(alc, AL_SAMPFMT_TWOSCOMP)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + if (pasfmt == paInt8) + { + if (alSetWidth(alc, AL_SAMPLE_8)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /* bytesPerFrame *= 1; */ + } + else if (pasfmt == paInt16) + { + if (alSetWidth(alc, AL_SAMPLE_16)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 2; + } + else if (pasfmt == paInt24) + { + if (alSetWidth(alc, AL_SAMPLE_24)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 3; /* OR 4 ???????! */ + } + else return paSampleFormatNotSupported; + } + /*----------------------- SET INTERNAL AL QUEUE SIZE: -------------------------------*/ + /* The AL API doesn't provide a means for querying minimum and maximum buffer sizes. + So, if the requested size fails, try again with a value that is closer to the AL's + default queue size. In this implementation, 'Portaudio latency' corresponds to + the AL queue size minus one buffersize: + AL queue size - framesPerHostBuffer + PA latency = ----------------------------------- + sample rate */ + default_iq_size = alGetQueueSize(alc); + if (default_iq_size < 0) /* So let's first get that 'default size'. */ + { /* Default internal queue size could not be determined. */ + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + /* AL buffer becomes somewhat bigger than the suggested latency, notice this is */ + /* based on requsted samplerate, not in the actual rate, which is measured later. */ + /* Do NOT read pa_params->suggestedLatency, but use the limited *latency param! */ + + iq_size = (int)(0.5 + ((*latency) * (*samplerate))) + (int)framesPerHostBuffer; + /* The AL buffer becomes somewhat */ + /* bigger than the suggested latency. */ + if (iq_size < (framesPerHostBuffer << 1)) /* Make sure the minimum is twice */ + { /* framesPerHostBuffer. */ + DBUG(("Setting minimum queue size.\n")); + iq_size = (framesPerHostBuffer << 1); + } + d = iq_size - default_iq_size; /* Determine whether we'll decrease */ + while (alSetQueueSize(alc, iq_size)) /* or increase after failing. */ + { /* Size in sample frames. */ + if (oserror() != AL_BAD_QSIZE) /* Stop at AL_BAD_CONFIG. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + dd = iq_size - default_iq_size; /* Stop when even the default size failed */ + if (((d >= 0) && (dd <= 0)) || /* (dd=0) or when difference flipped sign. */ + ((d <= 0) && (dd >= 0)) || + (iq_size <= framesPerHostBuffer)) /* Also guarentee that framesPerHostBuffer */ + { /* can be subtracted (res>0) after return. */ + DBUG(("Could not set AL queue size to %d sample frames!\n", iq_size)); + result = paInternalError; /* FIX: PROBABLY AN INAPROPRIATE ERROR CODE HERE. */ + goto cleanup; /* As inapropriate as paUnanticipatedHostError was? */ + } + DBUG(("Failed to set internal queue size to %d frames, ", iq_size)); + if (d > 0) + iq_size -= framesPerHostBuffer; /* Try lesser multiple. */ + else + iq_size += framesPerHostBuffer; /* Try larger multiple. */ + DBUG(("trying %d frames now...\n", iq_size)); + } + /* Note: Actual latency is written back to *latency after meausuring actual (not + the requested) samplerate. See below. + */ + /*----------------------- ALLOCATE HOST BUFFER: --------------------------------------*/ + hostPortBuff->buffer = PaUtil_AllocateMemory((long)bytesPerFrame * framesPerHostBuffer); + if (!hostPortBuff->buffer) /* Caller is responsible for cleanup+close after failures! */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- BIND CONFIGURATION TO DEVICE: ------------------------------*/ + sgiDevice = sgiDeviceIDs[pa_params->device].i; + if (alSetDevice(alc, sgiDevice)) /* Try to switch the hardware. */ + { + if (oserror() == AL_BAD_DEVICE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /*----------------------- OPEN PORT: ----------------------------------------------*/ + hostPortBuff->port = alOpenPort(name, direction, alc); /* Caller is responsible */ + if (!hostPortBuff->port) /* for closing after fail. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } /* Maybe set SR earlier? */ + /*----------------------- SET SAMPLERATE: -----------------------------------------*/ + pvs[0].param = AL_MASTER_CLOCK; /* Attempt to set a crystal-based sample- */ + pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; /* rate on input or output device. */ + pvs[1].param = AL_RATE; + pvs[1].value.ll = alDoubleToFixed(*samplerate); + if (2 != alSetParams(sgiDevice, pvs, 2)) + { + DBUG(("alSetParams() failed to set samplerate to %.4f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- GET ACTUAL SAMPLERATE: ---------------------------*/ + alErr = alGetParams(sgiDevice, &pvs[1], 1); /* SEE WHAT WE REALY SET IT TO. */ + if (alErr != 1) /* And return that to caller. */ + { + DBUG(("alGetParams() failed to read samplerate!\n")); + result = paInvalidSampleRate; + goto cleanup; + } + *samplerate = alFixedToDouble(pvs[1].value.ll); /* Between 1 Hz and 1 MHz. */ + if ((*samplerate < 1.0) || (*samplerate > 1000000.0)) + { + DBUG(("alFixedToDouble() resulted a weird samplerate: %.6f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- CALC ACTUAL LATENCY (based on actual SR): -----------------------*/ + *latency = (iq_size - framesPerHostBuffer) / (*samplerate); /* FIX: SURE > 0!???? */ +cleanup: + if (alc) + alFreeConfig(alc); /* We no longer need configuration. */ + return result; +} + +/** + Called by OpenStream() if it fails and by CloseStream. Only used here, in this file. + Fields MUST be set to NULL or to a valid value, prior to call. +*/ +static void streamCleanupAndClose(PaSGIStream* stream) +{ + if (stream->hostPortBuffIn.port) alClosePort(stream->hostPortBuffIn.port); /* Close AL ports. */ + if (stream->hostPortBuffIn.buffer) PaUtil_FreeMemory(stream->hostPortBuffIn.buffer); /* Release buffers. */ + if (stream->hostPortBuffOut.port) alClosePort(stream->hostPortBuffOut.port); + if (stream->hostPortBuffOut.buffer) PaUtil_FreeMemory(stream->hostPortBuffOut.buffer); +} + + +/* See pa_hostapi.h for a list of validity guarantees made about OpenStream parameters. */ +static PaError OpenStream(struct PaUtilHostApiRepresentation* hostApi, + PaStream** s, + const PaStreamParameters* ipp, + const PaStreamParameters* opp, + double sampleRate, /* Common to both i and o. */ + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback* streamCallback, + void* userData) +{ + PaError result = paNoError; + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + PaSGIStream* stream = 0; + unsigned long framesPerHostBuffer; /* Not necessarily the same as framesPerBuffer. */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat, + hostInputSampleFormat, hostOutputSampleFormat; + double sr_in, sr_out, + latency_in, latency_out; + static const PaSampleFormat irixFormats = (paInt8 | paInt16 | paInt24 | paFloat32); + /* Constant used by PaUtil_SelectClosestAvailableFormat(). Because IRIX AL does not + provide a way to query for possible formats for a given device, interface or port, + just add together the formats we know that are supported in general by IRIX AL + (at the end of the year 2003): AL_SAMPFMT_TWOSCOMP with AL_SAMPLE_8(=paInt8), + AL_SAMPLE_16(=paInt16) or AL_SAMPLE_24(=paInt24); AL_SAMPFMT_FLOAT(=paFloat32); + AL_SAMPFMT_DOUBLE(=paFloat64); IRIX misses unsigned 8 and 32 bit signed ints. + */ + DBUG(("OpenStream() started.\n")); + if (ipp) + { + inputChannelCount = ipp->channelCount; + inputSampleFormat = ipp->sampleFormat; + /* Unless alternate device specification is supported, reject the use of paUseHostApiSpecificDeviceSpecification. */ + if (ipp->device == paUseHostApiSpecificDeviceSpecification) /* DEVICE CHOOSEN BY CLIENT. */ + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if (inputChannelCount > hostApi->deviceInfos[ipp->device]->maxInputChannels) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if (ipp->hostApiSpecificStreamInfo) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, inputSampleFormat); + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[ipp->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + /* Validate input latency. Use defaults if necessary. */ + if (ipp->suggestedLatency < hostApi->deviceInfos[ipp->device]->defaultLowInputLatency) + latency_in = hostApi->deviceInfos[ipp->device]->defaultLowInputLatency; /* Low = minimum. */ + else if (ipp->suggestedLatency > 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency) + latency_in = 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency; /* 10*High = max. */ + else + latency_in = ipp->suggestedLatency; + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + latency_in = 0.0; /* Necessary? */ + } + if (opp) + { + outputChannelCount = opp->channelCount; + outputSampleFormat = opp->sampleFormat; + if (opp->device == paUseHostApiSpecificDeviceSpecification) /* Like input (above). */ + return paInvalidDevice; + if (outputChannelCount > hostApi->deviceInfos[opp->device]->maxOutputChannels) + return paInvalidChannelCount; + if (opp->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, outputSampleFormat); + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[opp->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + /* Validate output latency. Use defaults if necessary. */ + if (opp->suggestedLatency < hostApi->deviceInfos[opp->device]->defaultLowOutputLatency) + latency_out = hostApi->deviceInfos[opp->device]->defaultLowOutputLatency; /* Low = minimum. */ + else if (opp->suggestedLatency > 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency) + latency_out = 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency; /* 10*High = max. */ + else + latency_out = opp->suggestedLatency; + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialised var' warning. */ + latency_out = 0.0; + } + /* Sure that ipp and opp will never be both NULL. */ + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) /* Validate platform specific flags. */ + return paInvalidFlag; /* Unexpected platform specific flag. */ + + stream = (PaSGIStream*)PaUtil_AllocateMemory( sizeof(PaSGIStream) ); + if (!stream) + { result = paInsufficientMemory; goto cleanup; } + + stream->hostPortBuffIn.port = (ALport)NULL; /* Ports closed. */ + stream->hostPortBuffIn.buffer = NULL; /* No buffers yet. */ + stream->hostPortBuffOut.port = (ALport)NULL; + stream->hostPortBuffOut.buffer = NULL; + + if (streamCallback) + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->callbackStreamInterface, streamCallback, userData); + else + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->blockingStreamInterface, streamCallback, userData); + /* (NULL.) */ + if (framesPerBuffer == paFramesPerBufferUnspecified) /* Proposal 004. */ + { /* Keep framesPerBuffer zero but come up with some fixed host buffer size. */ + double lowest_lat = 0.0; /* 0.0 to surpress uninit warning, we're sure it will end up higher. */ + if (ipp) + lowest_lat = latency_in; /* Sure > 0.0! */ + if (opp && (latency_out < lowest_lat)) + lowest_lat = latency_out; + /* So that queue size becomes approximately 5 times framesPerHostBuffer: */ + framesPerHostBuffer = (unsigned long)((lowest_lat * sampleRate) / 4.0); + /* But always limit: */ + if (framesPerHostBuffer < 64L) + framesPerHostBuffer = 64L; + else if (framesPerHostBuffer > 32768L) + framesPerHostBuffer = 32768L; + DBUG(("Decided to use a fixed host buffer size of %ld frames.\n", framesPerHostBuffer)); + } + else + framesPerHostBuffer = framesPerBuffer; /* Then just take the requested amount. No buffer-adaption yet? */ + + sr_in = sr_out = sampleRate; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, /* Needed by alSetDevice and other functs. */ + ipp, /* Reads channelCount, device but NOT latency. */ + &latency_in, /* Read limited requested latency but also WRITE actual. */ + hostInputSampleFormat, /* For alSetSampFmt and alSetWidth. */ + "r", /* "r" for reading from input port. */ + "portaudio in", /* Name string. */ + framesPerHostBuffer, /* As calculated or as requested by the client. */ + &sr_in, /* Receive actual s.rate after setting it. */ + &stream->hostPortBuffIn); /* Receives ALport and input host buffer. */ + if (result != paNoError) goto cleanup; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, + opp, + &latency_out, + hostOutputSampleFormat, + "w", /* "w" for writing. */ + "portaudio out", + framesPerHostBuffer, + &sr_out, + &stream->hostPortBuffOut); + if (result != paNoError) goto cleanup; + /*------------------------------------------------------------------------------------------*/ + if (fabs(sr_in - sr_out) > 0.001) /* Make sure both are the 'same'. */ + { + DBUG(("Weird samplerate difference between input and output!\n")); + result = paInvalidSampleRate; /* Could not come up with a better error code. */ + goto cleanup; + } /* sr_in '==' sr_out. */ + sampleRate = sr_in; /* Following fields set to estimated or actual values: */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + stream->streamRepresentation.streamInfo.inputLatency = latency_in; /* 0.0 if output only. */ + stream->streamRepresentation.streamInfo.outputLatency = latency_out; /* 0.0 if input only. */ + + PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); + result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, + framesPerBuffer, /* As requested by OpenStream(), may be zero! */ + framesPerHostBuffer, /* Use fixed number of frames per host buffer */ + paUtilFixedHostBufferSize, /* to keep things simple. See pa_common/pa_ */ + streamCallback, userData); /* process.h for more hostbuffersize options. */ + if (result != paNoError) + goto cleanup; + + stream->framesPerHostCallback = framesPerHostBuffer; + stream->streamFlags = streamFlags; /* Remember priming request. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; /* After opening, the stream */ + stream->stopAbort = PA_SGI_REQ_CONT_; /* is in the stopped state. */ + *s = (PaStream*)stream; /* Pass object to caller. */ +cleanup: + if (result != paNoError) /* Always set result when jumping to cleanup after failure. */ + { + if (stream) + { + streamCleanupAndClose(stream); /* Frees i/o buffers and closes AL ports. */ + PaUtil_FreeMemory(stream); + } + } + return result; +} + +/** POSIX thread that performs the actual i/o and calls the client's callback, + spawned by StartStream(). +*/ +static void* PaSGIpthread(void *userData) +{ + PaSGIStream* stream = (PaSGIStream*)userData; + int callbackResult = paContinue; + double nanosec_per_frame; + PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 }; + + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Parent thread also sets active flag, but we + make no assumption about who does this first. */ + nanosec_per_frame = 1000000000.0 / stream->streamRepresentation.streamInfo.sampleRate; + /*----------------------------------------------- OUTPUT PRIMING: -----------------------------*/ + if (stream->hostPortBuffOut.port) /* Somewhat less than AL queue size so the next */ + { /* output buffer will (probably) not block. */ + unsigned long frames_to_prime = (long)(0.5 + + (stream->streamRepresentation.streamInfo.outputLatency + * stream->streamRepresentation.streamInfo.sampleRate)); + if (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) + { + PaStreamCallbackFlags cbflags = paPrimingOutput; + if (stream->hostPortBuffIn.port) /* Only set this flag in case of full duplex. */ + cbflags |= paInputUnderflow; + DBUG(("Prime with client's callback: < %ld frames.\n", frames_to_prime)); + while (frames_to_prime >= stream->framesPerHostCallback) /* We will not do less (yet). */ + { /* TODO: Timestamps and CPU load */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, /* measurement during priming. */ + &timeInfo, + cbflags); /* Pass underflow + priming flags. */ + if (stream->hostPortBuffIn.port) /* Does that provide client's call- */ + PaUtil_SetNoInput(&stream->bufferProcessor); /* back with silent inputbuffers? */ + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0=take host buffer size. */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + stream->hostPortBuffOut.buffer, 0); + callbackResult = paContinue; /* Call the client's callback. */ + frames_to_prime -= PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + if (callbackResult == paAbort) + { /* What should we do in other cases */ + stream->stopAbort = PA_SGI_REQ_ABORT_; /* where (callbackResult!=paContinue). */ + break; /* Don't even output the samples just returned (also skip following while). */ + } + else /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, + stream->framesPerHostCallback); + } + } + else /* Prime with silence. */ + { + DBUG(("Prime with silence: %ld frames.\n", frames_to_prime)); + alZeroFrames(stream->hostPortBuffOut.port, frames_to_prime); + } + } + /*------------------------------------------------------ I/O: ---------------------------------*/ + while (!stream->stopAbort) /* Exit loop immediately when 'stop' or 'abort' are raised. */ + { + unsigned long framesProcessed; + stamp_t fn, t, fnd, td; /* Unsigned 64 bit. */ + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + /* IMPLEMENT ME: - handle buffer slips. */ + if (stream->hostPortBuffIn.port) + { + /* Get device sample frame number associated with next audio sample frame + we're going to read from this port. */ + alGetFrameNumber(stream->hostPortBuffIn.port, &fn); + /* Get some recent pair of (frame number, time) from the audio device to + which our port is connected. time is 'UST' which is given in nanoseconds + and shared with the other audio devices and with other media. */ + alGetFrameTime(stream->hostPortBuffIn.port, &fnd, &td); + /* Calculate UST associated with fn, the next sample frame we're going to read or + write. Because this is signed arithmetic, code works for both inputs and outputs. */ + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + /* If port is not in underflow or overflow state, we can alReadFrames() or + alWriteFrames() here and know that t is the time associated with the first + sample frame of the buffer we read or write. */ + timeInfo.inputBufferAdcTime = ((PaTime)t) / 1000000000.0; + /* Read interleaved samples from AL port (I think it will block only the first time). */ + alReadFrames(stream->hostPortBuffIn.port, stream->hostPortBuffIn.buffer, + stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) + { + alGetFrameNumber(stream->hostPortBuffOut.port, &fn); + alGetFrameTime(stream->hostPortBuffOut.port, &fnd, &td); + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + timeInfo.outputBufferDacTime = ((PaTime)t) / 1000000000.0; + } + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + timeInfo.currentTime = ((PaTime)t) / 1000000000.0; + + /* If you need to byte swap or shift inputBuffer to convert it into a pa format, do it here. */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, + 0 /* IMPLEMENT ME: pass underflow/overflow flags when necessary */); + + if (stream->hostPortBuffIn.port) /* Equivalent to (inputChannelCount > 0) */ + { /* We are sure about the amount to transfer (PaUtil_Set before alRead). */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ + } + if (stream->hostPortBuffOut.port) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of outputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + } + /* + You must pass a valid value of callback result to PaUtil_EndBufferProcessing() + in general you would pass paContinue for normal operation, and + paComplete to drain the buffer processor's internal output buffer. + You can check whether the buffer processor's output buffer is empty + using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) + */ + callbackResult = paContinue; /* Whoops, lost this somewhere, back again in v 1.2.2.16! */ + framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + /* If you need to byte swap or shift outputBuffer to convert it to host format, do it here. */ + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if (callbackResult != paContinue) + { /* Once finished, call the finished callback. */ + DBUG(("SGI callbackResult = %d.\n", callbackResult)); + if (stream->streamRepresentation.streamFinishedCallback) + stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData); + if (callbackResult == paAbort) + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + break; /* Don't play the last buffer returned. */ + } + else /* paComplete or some other non-zero value. */ + stream->stopAbort = PA_SGI_REQ_STOP_; + } + if (stream->hostPortBuffOut.port) /* Write interleaved samples to SGI device */ + alWriteFrames(stream->hostPortBuffOut.port, /* (like unix_oss, AFTER checking callback result). */ + stream->hostPortBuffOut.buffer, stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) /* Drain output buffer(s), as long as we don't see an 'abort' request. */ + { + while ((!(stream->stopAbort & PA_SGI_REQ_ABORT_)) && /* Assume _STOP_ is set (or meant). */ + (alGetFilled(stream->hostPortBuffOut.port) > 1)) /* In case of ABORT we quickly leave (again). */ + ; /* Don't provide any new (not even silent) samples, but let an underrun [almost] occur. */ + } + if (callbackResult != paContinue) + stream->state = PA_SGI_STREAM_FLAG_FINISHED_; + return NULL; +} + + +/* + When CloseStream() is called, the multi-api layer ensures + that the stream has already been stopped or aborted. +*/ +static PaError CloseStream(PaStream* s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("SGI CloseStream() started.\n")); + streamCleanupAndClose(stream); /* Releases i/o buffers and closes AL ports. */ + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); + PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); + return result; +} + + +static PaError StartStream(PaStream *s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("StartStream() started.\n")); + PaUtil_ResetBufferProcessor(&stream->bufferProcessor); /* See pa_common/pa_process.h. */ + if (stream->bufferProcessor.streamCallback) + { /* only when callback is used */ + if (pthread_create(&stream->thread, + NULL, /* pthread_attr_t * attr */ + PaSGIpthread, /* Function to spawn. */ + (void*)stream)) /* Pass stream as arg. */ + { + PA_SGI_SET_LAST_IRIX_ERROR() /* Let's hope oserror() tells something useful. */ + result = paUnanticipatedHostError; + } + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; + } /* Set active before returning from this function. */ + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Apparently, setting active for blocking i/o is */ + return result; /* necessary (for patest_write_sine for example). */ +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_STOP_; /* Signal and wait for the thread to drain outputs. */ + if (pthread_join(stream->thread, NULL)) /* When succesful, stream->state */ + { /* is still ACTIVE, or FINISHED. */ + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream *stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + if (pthread_join(stream->thread, NULL)) + { + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) /* Not just the opposite of IsStreamActive(): */ +{ /* in the 'callback finished' state, it */ + /* returns zero instead of nonzero. */ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_STOPPED_) + return 1; + return 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_ACTIVE_) + return 1; + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + stamp_t t; + + (void) s; /* Suppress unused argument warning. */ + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + return (PaTime)t / 1000000000.0; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSGIStream *stream = (PaSGIStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + int n; + +printf("stream->framesPerHostCallback=%ld.\n", stream->framesPerHostCallback); +fflush(stdout); + + while (frames) + { + if (frames > stream->framesPerHostCallback) n = stream->framesPerHostCallback; + else n = frames; + /* Read interleaved samples from SGI device. */ + alReadFrames(stream->hostPortBuffIn.port, /* Port already opened by OpenStream(). */ + stream->hostPortBuffIn.buffer, n); /* Already allocated by OpenStream(). */ + /* alReadFrames() always returns 0. */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from host input channels set up by the PaUtil_SetInterleavedInputChannels + to a user supplied buffer. */ +printf("frames=%ld, buffer=%ld\n", frames, (long)buffer); +fflush(stdout); + PaUtil_CopyInput(&stream->bufferProcessor, &buffer, n); + frames -= n; + } +printf("DONE: frames=%ld, buffer=%ld\n", frames, (long)buffer); + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + unsigned long n; + while (frames) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from user supplied buffer to host input channels set up by + PaUtil_SetInterleavedOutputChannels. Copies the minimum of the number of user frames + (specified by the frameCount parameter) and the number of host frames (specified in + a previous call to SetOutputFrameCount()). */ + n = PaUtil_CopyOutput(&stream->bufferProcessor, &buffer, frames); + /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, n); + frames -= n; /* alWriteFrames always returns 0. */ + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return (signed long)alGetFilled(((PaSGIStream*)s)->hostPortBuffIn.port); +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return (signed long)alGetFillable(((PaSGIStream*)s)->hostPortBuffOut.port); +} + + +/* CVS reminder: + To download the 'v19-devel' branch from portaudio's CVS server for the first time, type: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs checkout -r v19-devel portaudio + Then 'cd' to the 'portaudio' directory that should have been created. + To commit changes: + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs login + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs commit -m 'blabla.' -r v19-devel pa_sgi/pa_sgi.c + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs logout + To see if someone else worked on something: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -r v19-devel + To get an older revision of a certain file (without sticky business): + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -p -r 1.1.1.1.2.4 pa_tests/patest1.c >pa_tests/patest1.c-OLD + To see logs: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs log pa_common/pa_skeleton.c +*/ diff --git a/portaudio-v19/pa_tests/README.txt b/portaudio-v19/pa_tests/README.txt new file mode 100644 index 000000000..f3a0b8226 --- /dev/null +++ b/portaudio-v19/pa_tests/README.txt @@ -0,0 +1,60 @@ +This directory contains various programs to test PortAudio. The files +named patest_* are tests, the files named debug_* are just scratch +files that may or may not work. + +All following tests are up to date with the V19 API. They should all compile +(without any warnings on GCC 3.3). Note that this does not necissarily mean that +the tests pass, just that they compile. + + x- paqa_devs.c + x- paqa_errs.c (needs reviewing) + x- patest1.c + x- patest_buffer.c + x- patest_callbackstop.c + x- patest_clip.c (last test fails, dither doesn't currently force clip in V19) + x- patest_dither.c + x- patest_hang.c + x- patest_latency.c + x- patest_leftright.c + x- patest_longsine.c + x- patest_many.c + x- patest_maxsines.c + o- patest_mono.c + x- patest_multi_sine.c + x- patest_pink.c + x- patest_prime.c + x- patest_read_record.c + x- patest_record.c + x- patest_ringmix.c + x- patest_saw.c + x- patest_sine.c + x- patest_sine8.c + x- patest_sine_formats.c + x- patest_sine_time.c + x- patest_start_stop.c + x- patest_stop.c + x- patest_sync.c + x- patest_toomanysines.c + o- patest_two_rates.c + x- patest_underflow.c + x- patest_wire.c + x- patest_write_sine.c + x- pa_devs.c + x- pa_fuzz.c + x- pa_minlat.c + +The debug_ files are still in V18 format and may need some V19 adaption. +Feel free to fix them, most simply require adjusting to the new API. + +o- pa_tests/debug_convert.c +o- pa_tests/debug_dither_calc.c +o- pa_tests/debug_dual.c +o- pa_tests/debug_multi_in.c +o- pa_tests/debug_multi_out.c +o- pa_tests/debug_record.c +o- pa_tests/debug_record_reuse.c +o- pa_tests/debug_sine.c +o- pa_tests/debug_sine_amp.c +o- pa_tests/debug_sine_formats.c +o- pa_tests/debug_srate.c +o- pa_tests/debug_test1.c diff --git a/portaudio-v19/pa_tests/debug_convert.c b/portaudio-v19/pa_tests/debug_convert.c new file mode 100644 index 000000000..a17b556e4 --- /dev/null +++ b/portaudio-v19/pa_tests/debug_convert.c @@ -0,0 +1,131 @@ +/* + * $Id$ + * Convert tagged values. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (11) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) + +#define NUM_BUFFERS (0) + +typedef struct +{ + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) finished = 1; + + for( i=0; i +#include +#include "portaudio.h" +#include "pa_host.h" + +/*******************************************************************/ +int main(void); +int main(void) +{ + long max,min; + int i; + + for( i=0; i<10000; i++ ) + { + long dither = PaConvert_TriangularDither(); + // printf("dither = 0x%08X\n", dither ); + if( dither < min ) min = dither; + else if( dither > max ) max = dither; + } + printf("min = 0x%08X = %d, max = 0x%08X = %d\n", min, min, max, max ); +} diff --git a/portaudio-v19/pa_tests/debug_dual.c b/portaudio-v19/pa_tests/debug_dual.c new file mode 100644 index 000000000..35ba032d0 --- /dev/null +++ b/portaudio-v19/pa_tests/debug_dual.c @@ -0,0 +1,183 @@ +/* + * $Id$ + * debug_dual.c + * Try to open TWO streams on separate cards. + * Play a sine sweep using the Portable Audio api for several seconds. + * Hacked test for debugging PA. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#define DEV_ID_1 (13) +#define DEV_ID_2 (15) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 0 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + return 0; +} + +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, + paTestData *data ); +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream1, *stream2; + PaError err; + paTestData DATA1, DATA2; + printf("PortAudio Test: DUAL sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = TestStart( &stream1, DEV_ID_1, &DATA1 ); + if( err != paNoError ) goto error; + err = TestStart( &stream2, DEV_ID_2, &DATA2 ); + if( err != paNoError ) goto error; + printf("Hit ENTER\n"); + getchar(); + err = Pa_StopStream( stream1 ); + if( err != paNoError ) goto error; + err = Pa_StopStream( stream2 ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, paTestData *data ) +{ + PortAudioStream *stream; + PaError err; + int i; + /* initialise sinusoidal wavetable */ + for( i=0; isine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data->sine[TABLE_SIZE] = data->sine[0]; // set guard point + data->left_phase = data->right_phase = 0.0; + data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + printf("PortAudio Test: output device = %d\n", devID ); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + devID, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + *streamPtr = stream; + return 0; +error: + return err; +} diff --git a/portaudio-v19/pa_tests/debug_multi_in.c b/portaudio-v19/pa_tests/debug_multi_in.c new file mode 100644 index 000000000..def729c1b --- /dev/null +++ b/portaudio-v19/pa_tests/debug_multi_in.c @@ -0,0 +1,179 @@ +/* + * $Id$ + * debug_multi_in.c + * Pass output from each of multiple channels + * to a stereo output using the Portable Audio api. + * Hacked test for debugging PA. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +//#define INPUT_DEVICE_NAME ("EWS88 MT Interleaved Rec") +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (18) +#define SAMPLE_RATE (22050) +#define FRAMES_PER_BUFFER (256) +#define MIN_LATENCY_MSEC (400) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + int liveChannel; + int numChannels; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float *in = (float*)inputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( in == NULL ) return 0; + for( i=0; i<(int)framesPerBuffer; i++ ) + { + /* Copy one channel of input to output. */ + *out++ = in[data->liveChannel]; + *out++ = in[data->liveChannel]; + in += data->numChannels; + } + return 0; +} +/*******************************************************************/ +int PaFindDeviceByName( const char *name ) +{ + int i; + int numDevices; + const PaDeviceInfo *pdi; + int len = strlen( name ); + PaDeviceID result = paNoDevice; + numDevices = Pa_CountDevices(); + for( i=0; iname, len ) == 0 ) + { + result = i; + break; + } + } + return result; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + PaDeviceID inputDevice; + const PaDeviceInfo *pdi; + printf("PortAudio Test: input signal from each channel. %d buffers\n", NUM_BUFFERS ); + data.liveChannel = 0; + err = Pa_Initialize(); + if( err != paNoError ) goto error; +#ifdef INPUT_DEVICE_NAME + printf("Try to use device: %s\n", INPUT_DEVICE_NAME ); + inputDevice = PaFindDeviceByName(INPUT_DEVICE_NAME); + if( inputDevice == paNoDevice ) + { + printf("Could not find %s. Using default instead.\n", INPUT_DEVICE_NAME ); + inputDevice = Pa_GetDefaultInputDeviceID(); + } +#else + printf("Using default input device.\n"); + inputDevice = Pa_GetDefaultInputDeviceID(); +#endif + pdi = Pa_GetDeviceInfo( inputDevice ); + if( pdi == NULL ) + { + printf("Could not get device info!\n"); + goto error; + } + data.numChannels = pdi->maxInputChannels; + printf("Input Device name is %s\n", pdi->name ); + printf("Input Device has %d channels.\n", pdi->maxInputChannels); + err = Pa_OpenStream( + &stream, + inputDevice, + pdi->maxInputChannels, + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + data.liveChannel = 0; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + for( i=0; i +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + int numChannels; + double phases[MAX_CHANNELS]; +} +paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndexnumChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + const PaDeviceInfo *pdi; + paTestData data = {0}; + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE ); + data.numChannels = pdi->maxOutputChannels; + if( data.numChannels > MAX_CHANNELS ) data.numChannels = MAX_CHANNELS; + printf("Number of Channels = %d\n", data.numChannels ); + + err = Pa_OpenStream( + &stream, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + data.numChannels, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop sound.\n"); + fflush(stdout); + getchar(); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream ); + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/debug_record.c b/portaudio-v19/pa_tests/debug_record.c new file mode 100644 index 000000000..57b11f581 --- /dev/null +++ b/portaudio-v19/pa_tests/debug_record.c @@ -0,0 +1,339 @@ +/* + * $Id$ + * debug_record.c + * Record input into an array. + * Save array to a file. + * Based on patest_record.c but with various ugly debug hacks thrown in. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (10) +#define SLEEP_DUR_MSEC (200) +#define FRAMES_PER_BUFFER (1<<10) +#define NUM_REC_BUFS (0) + +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +typedef struct +{ + long frameIndex; /* Index into sample array. */ + long maxFrameIndex; + long samplesPerFrame; + long numSamples; + SAMPLE *recordedSamples; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + unsigned long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; iframeIndex += framesToCalc; + return finished; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned long i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + if( outputBuffer == NULL ) return 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; iframeIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; iframeIndex += framesPerBuffer; + finished = 0; + } + return finished; +} + +/****************************************************************/ +PaError TestRecording( paTestData *dataPtr ) +{ + PortAudioStream *stream; + PaError err; + int i; + + /* Record some audio. */ + err = Pa_OpenStream( + &stream, + Pa_GetDefaultInputDeviceID(), + dataPtr->samplesPerFrame, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + dataPtr ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Now recording!\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + if( Pa_StreamActive( stream ) <= 0) + { + printf("Stream inactive!\n"); + break; + } + if( dataPtr->maxFrameIndex <= dataPtr->frameIndex ) + { + printf("Buffer recording complete.\n"); + break; + } + Pa_Sleep(100); + printf("index = %d\n", dataPtr->frameIndex ); fflush(stdout); + } + + printf("Finished loop. Close stream.\n"); fflush(stdout); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + printf("Done.\n"); fflush(stdout); + { + SAMPLE max = 0; + SAMPLE posVal; + int i; + for( i=0; inumSamples; i++ ) + { + posVal = dataPtr->recordedSamples[i]; + if( posVal < 0 ) posVal = -posVal; + if( posVal > max ) max = posVal; + } + printf("Largest recorded sample = %d\n", max ); + } + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( dataPtr->recordedSamples, dataPtr->samplesPerFrame * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + +error: + return err; +} + +/****************************************************************/ +PaError TestPlayback( paTestData *dataPtr ) +{ + PortAudioStream *stream; + PaError err; + int i; + + /* Playback recorded data. */ + dataPtr->frameIndex = 0; + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + Pa_GetDefaultOutputDeviceID(), + dataPtr->samplesPerFrame, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + dataPtr ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for playback to finish.\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + Pa_Sleep(100); + printf("index = %d\n", dataPtr->frameIndex ); + } + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + +error: + return err; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data; + long totalFrames; + long numBytes; + long i; + printf("patest_record.c\n"); fflush(stdout); + + data.frameIndex = 0; + data.samplesPerFrame = 2; + data.maxFrameIndex = totalFrames = NUM_SECONDS*SAMPLE_RATE; + + printf("totalFrames = %d\n", totalFrames ); fflush(stdout); + data.numSamples = totalFrames * data.samplesPerFrame; + + numBytes = data.numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i +#include +#include +#include "portaudio.h" +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (4) +#define SLEEP_DUR_MSEC (200) +#define FRAMES_PER_BUFFER (256) +#define NUM_REC_BUFS (0) + +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +typedef struct +{ + long frameIndex; /* Index into sample array. */ + long maxFrameIndex; + long samplesPerFrame; + long numSamples; + PortAudioStream *outputStream; + PortAudioStream *inputStream; + SAMPLE *recordedSamples; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + unsigned long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; iframeIndex += framesToCalc; + return finished; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned long i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + if( outputBuffer == NULL ) return 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; iframeIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; iframeIndex += framesPerBuffer; + finished = 0; + } + return finished; +} + +/****************************************************************/ +PaError TestRecording( paTestData *dataPtr ) +{ + PaError err; + int i; + int lastIndex = 0; + +/* Open input stream if not already open. */ + if( dataPtr->inputStream == NULL ) + { + /* Record some audio. */ + err = Pa_OpenStream( + &dataPtr->inputStream, + Pa_GetDefaultInputDeviceID(), + dataPtr->samplesPerFrame, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + dataPtr ); + if( err != paNoError ) goto error; + } + + dataPtr->frameIndex = 0; + + err = Pa_StartStream( dataPtr->inputStream ); + if( err != paNoError ) goto error; + + printf("Now recording!\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int frameIndex, delta; + Pa_Sleep(SLEEP_DUR_MSEC); + + frameIndex = dataPtr->frameIndex; + if( Pa_StreamActive( dataPtr->inputStream ) <= 0) + { + printf("Stream inactive!\n"); + break; + } + if( dataPtr->maxFrameIndex <= frameIndex ) + { + printf("Buffer recording complete.\n"); + break; + } + + delta = frameIndex - lastIndex; + lastIndex = frameIndex; + printf("index = %d, delta = %d\n", frameIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( dataPtr->inputStream ); + if( err != paNoError ) goto error; + + printf("Done.\n"); fflush(stdout); + +error: + return err; +} + +/****************************************************************/ +PaError TestPlayback( paTestData *dataPtr ) +{ + PaError err; + int i; + int lastIndex = 0; + + /* Playback recorded data. */ + dataPtr->frameIndex = 0; + printf("Begin playback.\n"); fflush(stdout); + +/* Open output stream if not already open. */ + if( dataPtr->outputStream == NULL ) + { + err = Pa_OpenStream( + &dataPtr->outputStream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + Pa_GetDefaultOutputDeviceID(), + dataPtr->samplesPerFrame, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + dataPtr ); + if( err != paNoError ) goto error; + } + + err = Pa_StartStream( dataPtr->outputStream ); + if( err != paNoError ) goto error; + + printf("Waiting for playback to finish.\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int frameIndex, delta; + Pa_Sleep(SLEEP_DUR_MSEC); + frameIndex = dataPtr->frameIndex; + delta = frameIndex - lastIndex; + lastIndex = frameIndex; + printf("index = %d, delta = %d\n", frameIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( dataPtr->outputStream ); + if( err != paNoError ) goto error; + +error: + return err; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data = { 0 }; + long totalFrames; + long numBytes; + long i; + printf("patest_record.c\n"); fflush(stdout); + +/* Set up test data structure and sample array. */ + data.frameIndex = 0; + data.samplesPerFrame = 2; + data.maxFrameIndex = totalFrames = NUM_SECONDS*SAMPLE_RATE; + + printf("totalFrames = %d\n", totalFrames ); fflush(stdout); + data.numSamples = totalFrames * data.samplesPerFrame; + + numBytes = data.numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (11) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 0 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; + unsigned int framesToGo; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int framesToCalc; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + int numChannels; + double phases[MAX_CHANNELS]; + float amplitude; +} +paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndexnumChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) ( data->amplitude * sin(data->phases[channelIndex]) ); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + char pad[256]; + PortAudioStream *stream; + PaError err; + const PaDeviceInfo *pdi; + paTestData data = {0}; + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE ); + data.numChannels = pdi->maxOutputChannels; + if( data.numChannels > MAX_CHANNELS ) data.numChannels = MAX_CHANNELS; + printf("Number of Channels = %d\n", data.numChannels ); + data.amplitude = 1.0; + + err = Pa_OpenStream( + &stream, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + data.numChannels, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + do + { + printf("Current amplitude = %f\n", data.amplitude ); + printf("Enter new amplitude or 'q' to quit.\n"); + fflush(stdout); + gets( pad ); + if( pad[0] != 'q' ) + { + // I tried to use atof but it seems to be broken on Mac OS X 10.1 + float amp; + sscanf( pad, "%f", & ); + data.amplitude = amp; + } + } while( pad[0] != 'q' ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream ); + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/debug_sine_formats.c b/portaudio-v19/pa_tests/debug_sine_formats.c new file mode 100644 index 000000000..a1fce567c --- /dev/null +++ b/portaudio-v19/pa_tests/debug_sine_formats.c @@ -0,0 +1,202 @@ +/* + * $Id$ + * patest_sine_formats.c + * Play a sine wave using the Portable Audio api for several seconds. + * Test various data formats. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) + +#define LEFT_FREQ (SAMPLE_RATE/512.0) /* So we hit 1.0 */ +#define RIGHT_FREQ (500.0) + +#define AMPLITUDE (1.0) + +/* Select ONE format for testing. */ +#define TEST_UINT8 (1) +#define TEST_INT8 (0) +#define TEST_INT16 (0) +#define TEST_FLOAT32 (0) + +#if TEST_UINT8 +#define TEST_FORMAT paUInt8 +typedef unsigned char SAMPLE_t; +#define SAMPLE_ZERO (0x80) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Unsigned 8 Bit" + +#elif TEST_INT8 +#define TEST_FORMAT paInt8 +typedef char SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Signed 8 Bit" + +#elif TEST_INT16 +#define TEST_FORMAT paInt16 +typedef short SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(32767 * (x))) +#define FORMAT_NAME "Signed 16 Bit" + +#elif TEST_FLOAT32 +#define TEST_FORMAT paFloat32 +typedef float SAMPLE_t; +#define SAMPLE_ZERO (0.0) +#define DOUBLE_TO_SAMPLE(x) ((SAMPLE_t)(x)) +#define FORMAT_NAME "Float 32 Bit" +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + + +typedef struct +{ + double left_phase; + double right_phase; + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE_t *out = (SAMPLE_t *)outputBuffer; + SAMPLE_t sample; + int i; + int framesToCalc; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; ileft_phase += (LEFT_FREQ / SAMPLE_RATE); + if( data->left_phase > 1.0) data->left_phase -= 1.0; + sample = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->left_phase * M_PI * 2. ))); /**/ + *out++ = sample; +/* *out++ = sample; /**/ +/* *out++ = 0; /**/ + + data->right_phase += (RIGHT_FREQ / SAMPLE_RATE); + if( data->right_phase > 1.0) data->right_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->right_phase * M_PI * 2. ))); /**/ +/* *out++ = 0; /* */ + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = SAMPLE_ZERO; /* left */ + *out++ = SAMPLE_ZERO; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int totalSamps; + + printf("PortAudio Test: output " FORMAT_NAME "\n"); + + + data.left_phase = data.right_phase = 0.0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + TEST_FORMAT, + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + TEST_FORMAT, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Waiting %d seconds for sound to finish.\n", NUM_SECONDS ); + while( Pa_StreamActive( stream ) ) Pa_Sleep(10); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("PortAudio Test Finished: " FORMAT_NAME "\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/debug_srate.c b/portaudio-v19/pa_tests/debug_srate.c new file mode 100644 index 000000000..07371f28e --- /dev/null +++ b/portaudio-v19/pa_tests/debug_srate.c @@ -0,0 +1,265 @@ +/* + * $Id$ + * debug_record_reuse.c + * Record input into an array. + * Save array to a file. + * Based on patest_record.c but with various ugly debug hacks thrown in. + * Loop twice and reuse same streams. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" + +#define EWS88MT_12_REC (1) +#define EWS88MT_12_PLAY (10) +#define SBLIVE_REC (2) +#define SBLIVE_PLAY (11) + +#if 0 +#define INPUT_DEVICE_ID Pa_GetDefaultInputDeviceID() +#define OUTPUT_DEVICE_ID Pa_GetDefaultOutputDeviceID() +#else +#define INPUT_DEVICE_ID (EWS88MT_12_REC) +#define OUTPUT_DEVICE_ID (SBLIVE_PLAY) +#endif + +#define INPUT_SAMPLE_RATE (22050.0) +#define OUTPUT_SAMPLE_RATE (22050.0) +#define NUM_SECONDS (4) +#define SLEEP_DUR_MSEC (1000) +#define FRAMES_PER_BUFFER (64) +#define NUM_REC_BUFS (0) +#define SAMPLES_PER_FRAME (2) + +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; + +typedef struct +{ + long frameIndex; /* Index into sample array. */ +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData *) userData; + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( inputBuffer != NULL ) + { + data->frameIndex += framesPerBuffer; + } + return 0; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData *) userData; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( outputBuffer != NULL ) + { + data->frameIndex += framesPerBuffer; + } + return 0; +} + +/****************************************************************/ +PaError MeasureStreamRate( PortAudioStream *stream, paTestData *dataPtr, double *ratePtr ) +{ + PaError err; + int i; + int totalFrames = 0; + int totalMSec = 0; + + dataPtr->frameIndex = 0; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int delta, endIndex; + + int startIndex = dataPtr->frameIndex; + Pa_Sleep(SLEEP_DUR_MSEC); + endIndex = dataPtr->frameIndex; + + delta = endIndex - startIndex; + totalFrames += delta; + totalMSec += SLEEP_DUR_MSEC; + + printf("index = %d, delta = %d\n", endIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + *ratePtr = (totalFrames * 1000.0) / totalMSec; + +error: + return err; +} + +void ReportRate( double measuredRate, double expectedRate ) +{ + double error; + + error = (measuredRate - expectedRate) / expectedRate; + error = (error < 0 ) ? -error : error; + + printf("Measured rate = %6.1f, expected rate = %6.1f\n", + measuredRate, expectedRate ); + if( error > 0.1 ) + { + printf("ERROR: unexpected rate! --------------------- ERROR!\n"); + } + else + { + printf("SUCCESS: rate within tolerance!\n"); + } +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data = { 0 }; + long i; + double rate; + const PaDeviceInfo *pdi; + + PortAudioStream *outputStream; + PortAudioStream *inputStream; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + + pdi = Pa_GetDeviceInfo( INPUT_DEVICE_ID ); + printf("Input device = %s\n", pdi->name ); + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE_ID ); + printf("Output device = %s\n", pdi->name ); + +/* Open input stream. */ + err = Pa_OpenStream( + &inputStream, + INPUT_DEVICE_ID, + SAMPLES_PER_FRAME, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + INPUT_SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &outputStream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + OUTPUT_DEVICE_ID, + SAMPLES_PER_FRAME, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + OUTPUT_SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto error; + +/* Record and playback multiple times. */ + for( i=0; i<2; i++ ) + { + printf("Measuring INPUT ------------------------- \n"); + err = MeasureStreamRate( inputStream, &data, &rate ); + if( err != paNoError ) goto error; + ReportRate( rate, INPUT_SAMPLE_RATE ); + + printf("Measuring OUTPUT ------------------------- \n"); + err = MeasureStreamRate( outputStream, &data, &rate ); + if( err != paNoError ) goto error; + ReportRate( rate, OUTPUT_SAMPLE_RATE ); + } + +/* Clean up. */ + err = Pa_CloseStream( inputStream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( outputStream ); + if( err != paNoError ) goto error; + + if( err != paNoError ) goto error; + + Pa_Terminate(); + + printf("Test complete.\n"); fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + if( err == paHostError ) + { + fprintf( stderr, "Host Error number: %d\n", Pa_GetHostError() ); + } + return -1; +} diff --git a/portaudio-v19/pa_tests/debug_test1.c b/portaudio-v19/pa_tests/debug_test1.c new file mode 100644 index 000000000..1321f62c8 --- /dev/null +++ b/portaudio-v19/pa_tests/debug_test1.c @@ -0,0 +1,114 @@ +/* + * $Id$ + patest1.c + Ring modulate the audio input with a 441hz sine wave for 20 seconds + using the Portable Audio api + Author: Ross Bencina + Modifications: + April 5th, 2001 - PLB - Check for NULL inputBuffer. +*/ +#include +#include +#include "portaudio.h" +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long bufferFrames, + PaTimestamp outTime, void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = bufferFrames; + unsigned long i; + int finished = 0; + if(inputBuffer == NULL) return 0; + if( data->sampsToGo < bufferFrames ) + { + finished = 1; + } + for( i=0; iphase >= 100 ) + data->phase = 0; + } + data->sampsToGo -= bufferFrames; + /* zero remainder of final buffer if not already done */ + for( ; i +#include +#include "portaudio.h" + +#ifdef WIN32 +#ifndef PA_NO_ASIO +#include "pa_asio.h" +#endif +#endif + +/*******************************************************************/ +static void PrintSupportedStandardSampleRates( + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters ) +{ + static double standardSampleRates[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, + 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ + }; + int i, printCount; + PaError err; + + printCount = 0; + for( i=0; standardSampleRates[i] > 0; i++ ) + { + err = Pa_IsFormatSupported( inputParameters, outputParameters, standardSampleRates[i] ); + if( err == paFormatIsSupported ) + { + if( printCount == 0 ) + { + printf( "\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else if( printCount == 4 ) + { + printf( ",\n\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else + { + printf( ", %8.2f", standardSampleRates[i] ); + ++printCount; + } + } + } + if( !printCount ) + printf( "None\n" ); + else + printf( "\n" ); +} +/*******************************************************************/ +int main(void); +int main(void) +{ + int i, numDevices, defaultDisplayed; + const PaDeviceInfo *deviceInfo; + PaStreamParameters inputParameters, outputParameters; + PaError err; + + + Pa_Initialize(); + + printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n", + Pa_GetVersion(), Pa_GetVersionText() ); + + + numDevices = Pa_GetDeviceCount(); + if( numDevices < 0 ) + { + printf( "ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); + err = numDevices; + goto error; + } + + printf( "Number of devices = %d\n", numDevices ); + for( i=0; ihostApi )->defaultInputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( "[ Default %s Input", hostInfo->name ); + defaultDisplayed = 1; + } + + if( i == Pa_GetDefaultOutputDevice() ) + { + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default Output" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default %s Output", hostInfo->name ); + defaultDisplayed = 1; + } + + if( defaultDisplayed ) + printf( " ]\n" ); + + /* print device info fields */ + printf( "Name = %s\n", deviceInfo->name ); + printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name ); + printf( "Max inputs = %d", deviceInfo->maxInputChannels ); + printf( ", Max outputs = %d\n", deviceInfo->maxOutputChannels ); + + printf( "Default low input latency = %8.3f\n", deviceInfo->defaultLowInputLatency ); + printf( "Default low output latency = %8.3f\n", deviceInfo->defaultLowOutputLatency ); + printf( "Default high input latency = %8.3f\n", deviceInfo->defaultHighInputLatency ); + printf( "Default high output latency = %8.3f\n", deviceInfo->defaultHighOutputLatency ); + +#ifdef WIN32 +#ifndef PA_NO_ASIO +/* ASIO specific latency information */ + if( Pa_GetHostApiInfo( deviceInfo->hostApi )->type == paASIO ){ + long minLatency, maxLatency, preferredLatency, granularity; + + err = PaAsio_GetAvailableLatencyValues( i, + &minLatency, &maxLatency, &preferredLatency, &granularity ); + + printf( "ASIO minimum buffer size = %ld\n", minLatency ); + printf( "ASIO maximum buffer size = %ld\n", maxLatency ); + printf( "ASIO preferred buffer size = %ld\n", preferredLatency ); + + if( granularity == -1 ) + printf( "ASIO buffer granularity = power of 2\n" ); + else + printf( "ASIO buffer granularity = %ld\n", granularity ); + } +#endif /* !PA_NO_ASIO */ +#endif /* WIN32 */ + + printf( "Default sample rate = %8.2f\n", deviceInfo->defaultSampleRate ); + + /* poll for standard sample rates */ + inputParameters.device = i; + inputParameters.channelCount = deviceInfo->maxInputChannels; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = i; + outputParameters.channelCount = deviceInfo->maxOutputChannels; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + outputParameters.hostApiSpecificStreamInfo = NULL; + + if( inputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for half-duplex 16 bit %d channel input = \n", + inputParameters.channelCount ); + PrintSupportedStandardSampleRates( &inputParameters, NULL ); + } + + if( outputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for half-duplex 16 bit %d channel output = \n", + outputParameters.channelCount ); + PrintSupportedStandardSampleRates( NULL, &outputParameters ); + } + + if( inputParameters.channelCount > 0 && outputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for full-duplex 16 bit %d channel input, %d channel output = \n", + inputParameters.channelCount, outputParameters.channelCount ); + PrintSupportedStandardSampleRates( &inputParameters, &outputParameters ); + } + } + + Pa_Terminate(); + + printf("----------------------------------------------\n"); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/pa_fuzz.c b/portaudio-v19/pa_tests/pa_fuzz.c new file mode 100644 index 000000000..127c2e3e6 --- /dev/null +++ b/portaudio-v19/pa_tests/pa_fuzz.c @@ -0,0 +1,168 @@ +/** @file pa_fuzz.c + @brief Distort input like a fuzz boz. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (44100) +#define PA_SAMPLE_TYPE paFloat32 +#define FRAMES_PER_BUFFER (64) + +typedef float SAMPLE; + +float CubicAmplifier( float input ); +static int fuzzCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* Non-linear amplifier with soft distortion curve. */ +float CubicAmplifier( float input ) +{ + float output, temp; + if( input < 0.0 ) + { + temp = input + 1.0f; + output = (temp * temp * temp) - 1.0f; + } + else + { + temp = input - 1.0f; + output = (temp * temp * temp) + 1.0f; + } + + return output; +} +#define FUZZ(x) CubicAmplifier(CubicAmplifier(CubicAmplifier(CubicAmplifier(x)))) + +static int gNumNoInputs = 0; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 fuzzCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + SAMPLE *out = (SAMPLE*)outputBuffer; + const SAMPLE *in = (const SAMPLE*)inputBuffer; + unsigned int i; + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) userData; + + if( inputBuffer == NULL ) + { + for( i=0; idefaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, /* paClipOff, */ /* we won't output out of range samples so don't bother clipping them */ + fuzzCallback, + NULL ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop program.\n"); + getchar(); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + printf("Finished. gNumNoInputs = %d\n", gNumNoInputs ); + Pa_Terminate(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/portaudio-v19/pa_tests/pa_minlat.c b/portaudio-v19/pa_tests/pa_minlat.c new file mode 100644 index 000000000..8400dfd27 --- /dev/null +++ b/portaudio-v19/pa_tests/pa_minlat.c @@ -0,0 +1,187 @@ +/** @file pa_minlat.c + @brief Experiment with different numbers of buffers to determine the + minimum latency for a computer. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include "portaudio.h" + +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define DEFAULT_BUFFER_SIZE (32) + +typedef struct +{ + double left_phase; + double right_phase; +} +paTestData; + +/* Very simple synthesis routine to generate two sine waves. */ +static int paminlatCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + double left_phaseInc = 0.02; + double right_phaseInc = 0.06; + + double left_phase = data->left_phase; + double right_phase = data->right_phase; + + for( i=0; i TWOPI ) left_phase -= TWOPI; + *out++ = (float) sin( left_phase ); + + right_phase += right_phaseInc; + if( right_phase > TWOPI ) right_phase -= TWOPI; + *out++ = (float) sin( right_phase ); + } + + data->left_phase = left_phase; + data->right_phase = right_phase; + return 0; +} + +int main( int argc, char **argv ); +int main( int argc, char **argv ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int go; + int outLatency = 0; + int minLatency = DEFAULT_BUFFER_SIZE * 2; + int framesPerBuffer; + double sampleRate = 44100.0; + char str[256]; + + printf("pa_minlat - Determine minimum latency for your computer.\n"); + printf(" usage: pa_minlat {userBufferSize}\n"); + printf(" for example: pa_minlat 64\n"); + printf("Adjust your stereo until you hear a smooth tone in each speaker.\n"); + printf("Then try to find the smallest number of frames that still sounds smooth.\n"); + printf("Note that the sound will stop momentarily when you change the number of buffers.\n"); + + /* Get bufferSize from command line. */ + framesPerBuffer = ( argc > 1 ) ? atol( argv[1] ) : DEFAULT_BUFFER_SIZE; + printf("Frames per buffer = %d\n", framesPerBuffer ); + + data.left_phase = data.right_phase = 0.0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outLatency = sampleRate * 200.0 / 1000.0; /* 200 msec. */ + + /* Try different numBuffers in a loop. */ + go = 1; + while( go ) + { + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + outputParameters.suggestedLatency = (double)outLatency / sampleRate; /* In seconds. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Latency = %d frames = %6.1f msec.\n", outLatency, outputParameters.suggestedLatency * 1000.0 ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + sampleRate, + framesPerBuffer, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + paminlatCallback, + &data ); + if( err != paNoError ) goto error; + if( stream == NULL ) goto error; + + /* Start audio. */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Ask user for a new nlatency. */ + printf("\nMove windows around to see if the sound glitches.\n"); + printf("Latency now %d, enter new number of frames, or 'q' to quit: ", outLatency ); + fgets( str, 256, stdin ); + { + /* Get rid of newline */ + size_t l = strlen( str ) - 1; + if( str[ l ] == '\n') + str[ l ] = '\0'; + } + if( str[0] == 'q' ) go = 0; + else + { + outLatency = atol( str ); + if( outLatency < minLatency ) + { + printf( "Latency below minimum of %d! Set to minimum!!!\n", minLatency ); + outLatency = minLatency; + } + } + /* Stop sound until ENTER hit. */ + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + } + printf("A good setting for latency would be somewhat higher than\n"); + printf("the minimum latency that worked.\n"); + printf("PortAudio: Test finished.\n"); + Pa_Terminate(); + return 0; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return 1; +} diff --git a/portaudio-v19/pa_tests/paqa_devs.c b/portaudio-v19/pa_tests/paqa_devs.c new file mode 100644 index 000000000..f52143c19 --- /dev/null +++ b/portaudio-v19/pa_tests/paqa_devs.c @@ -0,0 +1,347 @@ +/** @file paqa_devs.c + @brief Self Testing Quality Assurance app for PortAudio + Try to open each device and run through all the + possible configurations. + + @author Phil Burk http://www.softsynth.com + + Pieter adapted to V19 API. Test now relies heavily on + Pa_IsFormatSupported(). Uses same 'standard' sample rates + as in test pa_devs.c. +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#include "pa_trace.h" + +/****************************************** Definitions ***********/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) + +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; + short sawPhase; + PaSampleFormat format; +} +PaQaData; + +/****************************************** Prototypes ***********/ +static void TestDevices( int mode ); +static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels ); +static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels, PaSampleFormat format ); +static int QaCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/****************************************** Globals ***********/ +static int gNumPassed = 0; +static int gNumFailed = 0; + +/****************************************** Macros ***********/ +/* Print ERROR if it fails. Tally success or failure. */ +/* Odd do-while wrapper seems to be needed for some compilers. */ +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + /* printf("SUCCESS for %s\n", #_exp ); */ \ + gNumPassed++; \ + } \ + else { \ + printf("ERROR - 0x%x - %s for %s\n", result, \ + ((result == 0) ? "-" : Pa_GetErrorText(result)), \ + #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) + +/*******************************************************************/ +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 QaCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + unsigned long i; + short phase; + PaQaData *data = (PaQaData *) userData; + (void) inputBuffer; + + /* Play simle sawtooth wave. */ + if( data->mode == MODE_OUTPUT ) + { + phase = data->sawPhase; + switch( data->format ) + { + case paFloat32: + { + float *out = (float *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = (float) (phase * (1.0 / 32768.0)); + } + } + } + break; + + case paInt32: + { + int *out = (int *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = ((int) phase ) << 16; + } + } + } + break; + case paInt16: + { + short *out = (short *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = phase; + } + } + } + break; + + default: + { + unsigned char *out = (unsigned char *) outputBuffer; + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; isawPhase = phase; + } + /* Are we through yet? */ + if( data->framesLeft > framesPerBuffer ) + { + PaUtil_AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft ); + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + PaUtil_AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft ); + data->framesLeft = 0; + return 1; + } +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError result; + EXPECT( ((result=Pa_Initialize()) == 0) ); + printf("Test OUTPUT ---------------\n"); + TestDevices( MODE_OUTPUT ); + printf("Test INPUT ---------------\n"); + TestDevices( MODE_INPUT ); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed ); + return 0; +} + +/******************************************************************* +* Try each output device, through its full range of capabilities. */ +static void TestDevices( int mode ) +{ + int id, jc, i; + int maxChannels; + const PaDeviceInfo *pdi; + static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0, + 16000.0, 22050.0, 24000.0, + 32000.0, 44100.0, 48000.0, + 88200.0, 96000.0, + -1.0 }; /* Negative terminated list. */ + int numDevices = Pa_GetDeviceCount(); + for( id=0; idmaxInputChannels : pdi->maxOutputChannels); + + for( jc=1; jc<=maxChannels; jc++ ) + { + printf("Name = %s\n", pdi->name ); + /* Try each standard sample rate. */ + for( i=0; standardSampleRates[i] > 0; i++ ) + { + TestFormats( mode, (PaDeviceIndex)id, standardSampleRates[i], jc ); + } + } + } +} + +/*******************************************************************/ +static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels ) +{ + TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 ); + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 ); + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 ); + /* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */ +} + +/*******************************************************************/ +static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels, PaSampleFormat format ) +{ + PaStreamParameters inputParameters, outputParameters, *ipp, *opp; + PaStream *stream = NULL; + PaError result = paNoError; + PaQaData myData; + #define FRAMES_PER_BUFFER (64) + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (sampleRate * 100); /* 100 seconds */ + myData.numChannels = numChannels; + myData.mode = mode; + myData.format = format; + switch( format ) + { + case paFloat32: + case paInt32: + case paInt24: + myData.bytesPerSample = 4; + break; +/* case paPackedInt24: + myData.bytesPerSample = 3; + break; */ + default: + myData.bytesPerSample = 2; + break; + } + + if( mode == MODE_INPUT ) + { + inputParameters.device = deviceID; + inputParameters.channelCount = numChannels; + inputParameters.sampleFormat = format; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + ipp = &inputParameters; + } + else + ipp = NULL; + if( mode == MODE_OUTPUT ) /* Pa_GetDeviceInfo(paNoDevice) COREDUMPS!!! */ + { + outputParameters.device = deviceID; + outputParameters.channelCount = numChannels; + outputParameters.sampleFormat = format; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + opp = &outputParameters; + } + else + opp = NULL; + + if(paFormatIsSupported == Pa_IsFormatSupported( ipp, opp, sampleRate )) + { + printf("------ TestAdvance: %s, device = %d, rate = %g, numChannels = %d, format = %lu -------\n", + ( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT", + deviceID, sampleRate, numChannels, (unsigned long)format); + EXPECT( ((result = Pa_OpenStream( &stream, + ipp, + opp, + sampleRate, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + QaCallback, + &myData ) ) == 0) ); + if( stream ) + { + PaTime oldStamp, newStamp; + unsigned long oldFrames; + int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400; + /* Was: + int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate); + */ + int msec = (int)( 3.0 * + (( mode == MODE_INPUT ) ? inputParameters.suggestedLatency : outputParameters.suggestedLatency )); + if( msec < minDelay ) msec = minDelay; + printf("msec = %d\n", msec); /**/ + EXPECT( ((result=Pa_StartStream( stream )) == 0) ); + /* Check to make sure PortAudio is advancing timeStamp. */ + oldStamp = Pa_GetStreamTime(stream); + Pa_Sleep(msec); + newStamp = Pa_GetStreamTime(stream); + printf("oldStamp = %g,newStamp = %g\n", oldStamp, newStamp ); /**/ + EXPECT( (oldStamp < newStamp) ); + /* Check to make sure callback is decrementing framesLeft. */ + oldFrames = myData.framesLeft; + Pa_Sleep(msec); + printf("oldFrames = %lu, myData.framesLeft = %lu\n", oldFrames, myData.framesLeft ); /**/ + EXPECT( (oldFrames > myData.framesLeft) ); + EXPECT( ((result=Pa_CloseStream( stream )) == 0) ); + stream = NULL; + } + } +error: + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} diff --git a/portaudio-v19/pa_tests/paqa_errs.c b/portaudio-v19/pa_tests/paqa_errs.c new file mode 100644 index 000000000..3f9077173 --- /dev/null +++ b/portaudio-v19/pa_tests/paqa_errs.c @@ -0,0 +1,380 @@ +/** @file paqa_errs.c + @brief Self Testing Quality Assurance app for PortAudio + Do lots of bad things to test error reporting. + @author Phil Burk http://www.softsynth.com + Pieter Suurmond adapted to V19 API. +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +#include "portaudio.h" + +/*--------- Definitions ---------*/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) +#define FRAMES_PER_BUFFER (64) +#define SAMPLE_RATE (44100.0) + +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; +} +PaQaData; + +static int gNumPassed = 0; /* Two globals */ +static int gNumFailed = 0; + +/*------------------- Macros ------------------------------*/ +/* Print ERROR if it fails. Tally success or failure. Odd */ +/* do-while wrapper seems to be needed for some compilers. */ + +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) + +#define HOPEFOR(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + } \ + } while(0) + +/*-------------------------------------------------------------------------*/ +/* This routine will be called by the PortAudio engine when audio is needed. + It may be 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 QaCallback( const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ) +{ + unsigned long i; + unsigned char* out = (unsigned char *) outputBuffer; + PaQaData* data = (PaQaData *) userData; + + (void)inputBuffer; /* Prevent "unused variable" warnings. */ + + /* Zero out buffer so we don't hear terrible noise. */ + if( data->mode == MODE_OUTPUT ) + { + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; iframesLeft > framesPerBuffer ) + { + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + data->framesLeft = 0; + return 1; + } +} + +static PaDeviceIndex FindInputOnlyDevice(void) +{ + PaDeviceIndex result = Pa_GetDefaultInputDevice(); + if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxOutputChannels == 0 ) + return result; + + for( result = 0; result < Pa_GetDeviceCount(); ++result ) + { + if( Pa_GetDeviceInfo(result)->maxOutputChannels == 0 ) + return result; + } + + return paNoDevice; +} + +static PaDeviceIndex FindOutputOnlyDevice(void) +{ + PaDeviceIndex result = Pa_GetDefaultOutputDevice(); + if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxInputChannels == 0 ) + return result; + + for( result = 0; result < Pa_GetDeviceCount(); ++result ) + { + if( Pa_GetDeviceInfo(result)->maxInputChannels == 0 ) + return result; + } + + return paNoDevice; +} + +/*-------------------------------------------------------------------------------------------------*/ +static int TestBadOpens( void ) +{ + PaStream* stream = NULL; + PaError result; + PaQaData myData; + PaStreamParameters ipp, opp; + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + + /*----------------------------- No devices specified: */ + ipp.device = opp.device = paNoDevice; + ipp.channelCount = opp.channelCount = 0; /* Also no channels. */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + /* Take the low latency of the default device for all subsequent tests. */ + ipp.suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice())->defaultLowInputLatency; + opp.suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice())->defaultLowOutputLatency; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- No devices specified #2: */ + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Out of range input device specified: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = Pa_GetDeviceCount(); /* And no output device, and no channels. */ + opp.channelCount = 0; opp.device = paNoDevice; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Out of range output device specified: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */ + opp.channelCount = 0; opp.device = Pa_GetDeviceCount(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Zero input channels: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = Pa_GetDefaultInputDevice(); + opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no output channels. */ + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + /*----------------------------- Zero output channels: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no input channels. */ + opp.channelCount = 0; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + /*----------------------------- Nonzero input and output channels but no output device: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = Pa_GetDefaultInputDevice(); /* Both stereo. */ + opp.channelCount = 2; opp.device = paNoDevice; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Nonzero input and output channels but no input device: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- NULL stream pointer: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* Output is more likely than input. */ + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); /* Only 2 output channels. */ + HOPEFOR(((result = Pa_OpenStream(NULL, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paBadStreamPtr)); + + /*----------------------------- Low sample rate: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + 1.0, FRAMES_PER_BUFFER, /* 1 cycle per second (1 Hz) is too low. */ + paClipOff, QaCallback, &myData )) == paInvalidSampleRate)); + + /*----------------------------- High sample rate: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + 10000000.0, FRAMES_PER_BUFFER, /* 10^6 cycles per second (10 MHz) is too high. */ + paClipOff, QaCallback, &myData )) == paInvalidSampleRate)); + + /*----------------------------- NULL callback: */ + /* NULL callback is valid in V19 -- it means use blocking read/write stream + + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, + NULL, + &myData )) == paNullCallback)); + */ + + /*----------------------------- Bad flag: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + 255, /* Is 8 maybe legal V19 API? */ + QaCallback, &myData )) == paInvalidFlag)); + + /*----------------------------- using input device as output device: */ + if( FindInputOnlyDevice() != paNoDevice ) + { + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */ + opp.channelCount = 2; opp.device = FindInputOnlyDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + } + + /*----------------------------- using output device as input device: */ + if( FindOutputOnlyDevice() != paNoDevice ) + { + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = FindOutputOnlyDevice(); + opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no channels. */ + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + } + + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} + +/*-----------------------------------------------------------------------------------------*/ +static int TestBadActions( void ) +{ + PaStream* stream = NULL; + PaError result; + PaQaData myData; + PaStreamParameters opp; + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long)(SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + + opp.device = Pa_GetDefaultOutputDevice(); /* Default output. */ + opp.channelCount = 2; /* Stereo output. */ + opp.hostApiSpecificStreamInfo = NULL; + opp.sampleFormat = paFloat32; + opp.suggestedLatency = Pa_GetDeviceInfo(opp.device)->defaultLowOutputLatency; + + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, /* Take NULL as input parame- */ + &opp, /* ters, meaning try only output. */ + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paNoError)); + + HOPEFOR(((result = Pa_StartStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_StopStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_IsStreamStopped(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_IsStreamActive(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_CloseStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_SetStreamFinishedCallback(NULL, NULL)) == paBadStreamPtr)); + HOPEFOR(((result = !Pa_GetStreamInfo(NULL)))); + HOPEFOR(((result = Pa_GetStreamTime(NULL)) == 0.0)); + HOPEFOR(((result = Pa_GetStreamCpuLoad(NULL)) == 0.0)); + HOPEFOR(((result = Pa_ReadStream(NULL, NULL, 0)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_WriteStream(NULL, NULL, 0)) == paBadStreamPtr)); + + /** @todo test Pa_GetStreamReadAvailable and Pa_GetStreamWriteAvailable */ + + if (stream != NULL) Pa_CloseStream(stream); + return result; +} + +/*---------------------------------------------------------------------*/ +int main(void); +int main(void) +{ + PaError result; + + EXPECT(((result = Pa_Initialize()) == paNoError)); + TestBadOpens(); + TestBadActions(); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed); + return 0; +} diff --git a/portaudio-v19/pa_tests/patest1.c b/portaudio-v19/pa_tests/patest1.c new file mode 100644 index 000000000..15fdfdd3d --- /dev/null +++ b/portaudio-v19/pa_tests/patest1.c @@ -0,0 +1,192 @@ +/** @file patest1.c + @brief Ring modulate the audio input with a sine wave for 20 seconds. + @author Ross Bencina +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define SAMPLE_RATE (44100) + +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; + +static int patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = framesPerBuffer; + unsigned long i = 0; + int finished; + + if( data->sampsToGo < framesPerBuffer ) + { + framesToCalc = data->sampsToGo; + finished = paComplete; + } + else + { + finished = paContinue; + } + + for( ; isine[data->phase]; /* left */ + *out++ = *in++ * data->sine[data->phase++]; /* right */ + if( data->phase >= 100 ) + data->phase = 0; + } + + data->sampsToGo -= framesToCalc; + + /* zero remainder of final buffer if not already done */ + for( ; idefaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + (double)SAMPLE_RATE, /* Samplerate in Hertz. */ + 512, /* Small buffers */ + paClipOff, /* We won't output out of range samples so don't bother clipping them. */ + patest1Callback, + &data ); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + + printf( "Press any key to end.\n" ); fflush(stdout); + + getc( stdin ); /* wait for input before exiting */ + + err = Pa_AbortStream( stream ); + if( err != paNoError ) goto done; + + printf( "Waiting for stream to complete...\n" ); + + /* sleep until playback has finished */ + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(1000); + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + +done: + Pa_Terminate(); + + if( err != paNoError ) + { + fprintf( stderr, "An error occured while using portaudio\n" ); + if( err == paUnanticipatedHostError ) + { + fprintf( stderr, " unanticipated host error.\n"); + herr = Pa_GetLastHostErrorInfo(); + if (herr) + { + fprintf( stderr, " Error number: %ld\n", herr->errorCode ); + if (herr->errorText) + fprintf( stderr, " Error text: %s\n", herr->errorText ); + } + else + fprintf( stderr, " Pa_GetLastHostErrorInfo() failed!\n" ); + } + else + { + fprintf( stderr, " Error number: %d\n", err ); + fprintf( stderr, " Error text: %s\n", Pa_GetErrorText( err ) ); + } + + err = 1; /* Always return 0 or 1, but no other return codes. */ + } + + printf( "bye\n" ); + + return err; +} diff --git a/portaudio-v19/pa_tests/patest_buffer.c b/portaudio-v19/pa_tests/patest_buffer.c new file mode 100644 index 000000000..39c5dd21d --- /dev/null +++ b/portaudio-v19/pa_tests/patest_buffer.c @@ -0,0 +1,180 @@ +/** @file patest_buffer.c + @brief Test opening streams with different buffer sizes. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +#define BUFFER_TABLE 9 +long buffer_table[] = {200,256,500,512,600, 723, 1000, 1024, 2345}; + +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( int buffersize ); + +/* 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 patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; isampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + printf("Test opening streams with different buffer sizes\n\n"); + + for (i = 0 ; i < BUFFER_TABLE; i++) + { + printf("Buffer size %ld\n", buffer_table[i]); + err = TestOnce(buffer_table[i]); + if( err < 0 ) return 0; + + } + return 0; +} + + +PaError TestOnce( int buffersize ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + buffersize, /* frames per buffer */ + (paClipOff | paDitherOff), + patest1Callback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for sound to finish.\n"); + Pa_Sleep(1000); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_callbackstop.c b/portaudio-v19/pa_tests/patest_callbackstop.c new file mode 100644 index 000000000..2da642e43 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_callbackstop.c @@ -0,0 +1,221 @@ +/** @file patest_callbackstop.c + @brief Test the paComplete callback result code. + @author Ross Bencina +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define NUM_LOOPS (4) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (67) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; + unsigned long generatedFramesCount; + volatile int callbackReturnedPaComplete; + volatile int callbackInvokedAfterReturningPaComplete; +} +TestData; + +/* + This routine will be called by the PortAudio stream when audio is needed. + It may be 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 TestCallback( const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + TestData *data = (TestData*)userData; + float *out = (float*)output; + unsigned long i; + float x; + + (void) input; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + + if( data->callbackReturnedPaComplete ) + data->callbackInvokedAfterReturningPaComplete = 1; + + for( i=0; isine[ data->phase++ ]; + if( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + *out++ = x; /* left */ + *out++ = x; /* right */ + } + + data->generatedFramesCount += frameCount; + if( data->generatedFramesCount >= (NUM_SECONDS * SAMPLE_RATE) ) + { + data->callbackReturnedPaComplete = 1; + return paComplete; + } + else + { + return paContinue; + } +} + +/*----------------------------------------------------------------------------*/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + TestData data; + int i, j; + + + printf( "PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", + SAMPLE_RATE, FRAMES_PER_BUFFER ); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* output will be in-range, so no need to clip */ + TestCallback, + &data ); + if( err != paNoError ) goto error; + + printf("Repeating test %d times.\n", NUM_LOOPS ); + + for( i=0; i < NUM_LOOPS; ++i ) + { + data.phase = 0; + data.generatedFramesCount = 0; + data.callbackReturnedPaComplete = 0; + data.callbackInvokedAfterReturningPaComplete = 0; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + + /* wait for the callback to complete generating NUM_SECONDS of tone */ + + do + { + Pa_Sleep( 500 ); + } + while( !data.callbackReturnedPaComplete ); + + printf( "Callback returned paComplete.\n" ); + printf( "Waiting for buffers to finish playing...\n" ); + + /* wait for stream to become inactive, + or for a timeout of approximately NUM_SECONDS + */ + + j = 0; + while( (err = Pa_IsStreamActive( stream )) == 1 && j < NUM_SECONDS * 2 ) + { + printf(".\n" ); + Pa_Sleep( 500 ); + ++j; + } + + if( err < 0 ) + { + goto error; + } + else if( err == 1 ) + { + printf( "TEST FAILED: Timed out waiting for buffers to finish playing.\n" ); + } + else + { + printf("Buffers finished.\n" ); + } + + if( data.callbackInvokedAfterReturningPaComplete ) + { + printf( "TEST FAILED: Callback was invoked after returning paComplete.\n" ); + } + + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_clip.c b/portaudio-v19/pa_tests/patest_clip.c new file mode 100644 index 000000000..1d17d11b2 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_clip.c @@ -0,0 +1,178 @@ +/** @file patest_clip.c + @brief Play a sine wave for several seconds at an amplitude + that would require clipping. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; + +PaError PlaySine( paTestData *data, unsigned long flags, float amplitude ); + +/* 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 sineCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) timeInfo; + (void) statusFlags; + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data; + int i; + + printf("PortAudio Test: output sine wave with and without clipping.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; ileft_phase = data->right_phase = 0; + data->amplitude = amplitude; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + 1024, + flags, + sineCallback, + data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetStreamCpuLoad( stream ) ); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + return paNoError; +error: + return err; +} diff --git a/portaudio-v19/pa_tests/patest_dither.c b/portaudio-v19/pa_tests/patest_dither.c new file mode 100644 index 000000000..10f03e42b --- /dev/null +++ b/portaudio-v19/pa_tests/patest_dither.c @@ -0,0 +1,178 @@ +/** @file patest_dither.c + @brief Attempt to hear difference between dithered and non-dithered signal. + + This only has an effect if the native format is 16 bit. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include + +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 sineCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) inputBuffer; + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} + +/*****************************************************************************/ +/* + V18 version did not call Pa_Terminate() if Pa_Initialize() failed. + This V19 version ALWAYS calls Pa_Terminate(). PS. +*/ +PaError PlaySine( paTestData *data, PaStreamFlags flags, float amplitude ); +PaError PlaySine( paTestData *data, PaStreamFlags flags, float amplitude ) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + + data->left_phase = data->right_phase = 0; + data->amplitude = amplitude; + + err = Pa_Initialize(); + if (err != paNoError) + goto done; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + /* When you change this, also */ + /* adapt the callback routine! */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device ) + ->defaultLowOutputLatency; /* Low latency. */ + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + 1024, /* frames per buffer */ + flags, + sineCallback, + (void*)data ); + if (err != paNoError) + goto done; + + err = Pa_StartStream( stream ); + if (err != paNoError) + goto done; + + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetStreamCpuLoad(stream)); + + err = Pa_CloseStream( stream ); +done: + Pa_Sleep( 250 ); /* Just a small silence. */ + Pa_Terminate(); + return err; +} + + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData DATA; + int i; + float amplitude = 4.0 / (1<<15); + + printf("PortAudio Test: output EXTREMELY QUIET sine wave with and without dithering.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; i + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sleepFor; + double phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + double phaseInc = 0.02; + double phase = data->phase; + + (void) inputBuffer; /* Prevent unused argument warning. */ + + for( i=0; i TWOPI ) phase -= TWOPI; + /* This is not a very efficient way to calc sines. */ + *out++ = (float) sin( phase ); /* mono */ + } + + if( data->sleepFor > 0 ) + { + Pa_Sleep( data->sleepFor ); + } + + data->phase = phase; + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + int i; + paTestData data = {0}; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", + SAMPLE_RATE, FRAMES_PER_BUFFER ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 1; /* Mono output. */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device) + ->defaultLowOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* No out of range samples. */ + patestCallback, + &data); + if (err != paNoError) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Gradually increase sleep time. */ + /* Was: for( i=0; i<10000; i+= 1000 ) */ + for(i=0; i <= 1000; i += 100) + { + printf("Sleep for %d milliseconds in audio callback.\n", i ); + data.sleepFor = i; + Pa_Sleep( ((i<1000) ? 1000 : i) ); + } + + printf("Suffer for 10 seconds.\n"); + Pa_Sleep( 10000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_in_overflow.c b/portaudio-v19/pa_tests/patest_in_overflow.c new file mode 100644 index 000000000..a0be2c6a1 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_in_overflow.c @@ -0,0 +1,224 @@ +/** @file patest_in_overflow.c + @brief Count input overflows (using paInputOverflow flag) under + overloaded and normal conditions. + This test uses the same method to overload the stream as does + patest_out_underflow.c -- it generates sine waves until the cpu load + exceeds a certain level. However this test is only concerned with + input and so doesn't ouput any sound. + + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2004 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sineCount; + double phases[MAX_SINES]; + int countOverflows; + int inputOverflowCount; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float out; /* variable to hold dummy output */ + unsigned long i; + int j; + int finished = paContinue; + (void) timeInfo; /* Prevent unused variable warning. */ + (void) inputBuffer; /* Prevent unused variable warning. */ + (void) outputBuffer; /* Prevent unused variable warning. */ + + if( data->countOverflows && (statusFlags & paInputOverflow) ) + data->inputOverflowCount++; + + for( i=0; isineCount; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + /* this is an input-only stream so we don't actually use the output */ + out = (float) (output / data->sineCount); + (void) out; /* suppress unused variable warning*/ + } + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters; + PaStream *stream; + PaError err; + int safeSineCount, stressedSineCount; + int safeOverflowCount, stressedOverflowCount; + paTestData data = {0}; + double load; + + + printf("PortAudio Test: input only, no sound output. Load callback by performing calculations, count input overflows. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, (float)MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = 1; /* mono output */ + inputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* no output */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Establishing load conditions...\n" ); + + /* Determine number of sines required to get to 50% */ + do + { + data.sineCount++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + while( load < 0.5 && data.sineCount < (MAX_SINES-1)); + + safeSineCount = data.sineCount; + + /* Calculate target stress value then ramp up to that level*/ + stressedSineCount = (int) (2.0 * data.sineCount * MAX_LOAD ); + if( stressedSineCount > MAX_SINES ) + stressedSineCount = MAX_SINES; + for( ; data.sineCount < stressedSineCount; data.sineCount++ ) + { + Pa_Sleep( 100 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + + printf("Counting overflows for 5 seconds.\n"); + data.countOverflows = 1; + Pa_Sleep( 5000 ); + + stressedOverflowCount = data.inputOverflowCount; + + data.countOverflows = 0; + data.sineCount = safeSineCount; + + printf("Resuming safe load...\n"); + Pa_Sleep( 1500 ); + data.inputOverflowCount = 0; + Pa_Sleep( 1500 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + + printf("Counting overflows for 5 seconds.\n"); + data.countOverflows = 1; + Pa_Sleep( 5000 ); + + safeOverflowCount = data.inputOverflowCount; + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + + if( stressedOverflowCount == 0 ) + printf("Test failed, no input overflows detected under stress.\n"); + else if( safeOverflowCount != 0 ) + printf("Test failed, %d unexpected overflows detected under safe load.\n", safeOverflowCount); + else + printf("Test passed, %d expected input overflows detected under stress, 0 unexpected overflows detected under safe load.\n", stressedOverflowCount ); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_latency.c b/portaudio-v19/pa_tests/patest_latency.c new file mode 100644 index 000000000..f4225112a --- /dev/null +++ b/portaudio-v19/pa_tests/patest_latency.c @@ -0,0 +1,182 @@ +/** @file + @brief Hear the latency caused by big buffers. + Play a sine wave and change frequency based on letter input. + @author Phil Burk , and Darren Gibbs +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#define MIN_FREQ (100.0f) +#define CalcPhaseIncrement(freq) ((freq)/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) + +typedef struct +{ + float sine[TABLE_SIZE + 1]; /* add one for guard point for interpolation */ + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; + +float LookupSine( paTestData *data, float phase ); +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int i; + + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int i; + int done = 0; + + printf("PortAudio Test: enter letter then hit ENTER.\n" ); + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Requested output latency = %.4f seconds.\n", outputParameters.suggestedLatency ); + printf("%d frames per buffer.\n.", FRAMES_PER_BUFFER ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Play ASCII keyboard. Hit 'q' to stop. (Use RETURN key on Mac)\n"); + fflush(stdout); + while ( !done ) + { + float freq; + int index; + char c; + do + { + c = getchar(); + } + while( c < ' '); /* Strip white space and control chars. */ + + if( c == 'q' ) done = 1; + index = c % 26; + freq = MIN_FREQ + (index * 40.0); + data.phase_increment = CalcPhaseIncrement(freq); + } + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_leftright.c b/portaudio-v19/pa_tests/patest_leftright.c new file mode 100644 index 000000000..158223649 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_leftright.c @@ -0,0 +1,173 @@ +/** @file patest_leftright.c + @brief Play different tone sine waves that + alternate between left and right channel. + + The low tone should be on the left channel. + + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int toggle; + int countDown; +} +paTestData; +/* 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 patestCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; itoggle ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = 0; /* right */ + } + else + { + *out++ = 0; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + } + + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + if( data->countDown < 0 ) + { + data->countDown = SAMPLE_RATE; + data->toggle = !data->toggle; + } + data->countDown -= framesPerBuffer; + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int i; + int timeout; + + printf("Play different tone sine waves that alternate between left and right channel.\n"); + printf("The low tone should be on the left channel.\n"); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for several seconds.\n"); + timeout = NUM_SECONDS * 4; + while( timeout > 0 ) + { + Pa_Sleep( 300 ); /*(Irix very much likes sleeps <= 1000 ms.)*/ + timeout -= 1; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_longsine.c b/portaudio-v19/pa_tests/patest_longsine.c new file mode 100644 index 000000000..3a6ec39ef --- /dev/null +++ b/portaudio-v19/pa_tests/patest_longsine.c @@ -0,0 +1,140 @@ +/** @file patest_longsine.c + @brief Play a sine wave until ENTER hit. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; /* Prevent unused argument warning. */ + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave.\n"); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + 256, /* Frames per buffer. */ + paClipOff, /* No out of range samples expected. */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop program.\n"); + getchar(); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("Test finished.\n"); + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_many.c b/portaudio-v19/pa_tests/patest_many.c new file mode 100644 index 000000000..6a678467b --- /dev/null +++ b/portaudio-v19/pa_tests/patest_many.c @@ -0,0 +1,198 @@ +/** @file patest_many.c + @brief Start and stop the PortAudio Driver multiple times. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( void ); +static int patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* 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 patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; isampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +#ifdef MACINTOSH +int main(void); +int main(void) +{ + int i; + PaError err; + int numLoops = 10; + printf("Loop %d times.\n", numLoops ); + for( i=0; i 1 ) + { + numLoops = atoi(argv[1]); + } + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + 1024, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patest1Callback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for sound to finish.\n"); + Pa_Sleep(1000); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_maxsines.c b/portaudio-v19/pa_tests/patest_maxsines.c new file mode 100644 index 000000000..56592ea47 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_maxsines.c @@ -0,0 +1,204 @@ +/** @file patest_maxsines.c + @brief How many sine waves can we calculate and play in less than 80% CPU Load. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_USAGE (0.8) +#define SAMPLE_RATE (44100) +#define FREQ_TO_PHASE_INC(freq) (freq/(float)SAMPLE_RATE) + +#define MIN_PHASE_INC FREQ_TO_PHASE_INC(200.0f) +#define MAX_PHASE_INC (MIN_PHASE_INC * (1 << 5)) + +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define TABLE_SIZE (512) + +typedef struct paTestData +{ + int numSines; + float sine[TABLE_SIZE + 1]; /* add one for guard point for interpolation */ + float phases[MAX_SINES]; +} +paTestData; + +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float outSample; + float scaler; + int numForScale; + unsigned long i; + int j; + int finished = 0; + (void) inputBuffer; /* Prevent unused argument warning. */ + + /* Determine amplitude scaling factor */ + numForScale = data->numSines; + if( numForScale < 8 ) numForScale = 8; /* prevent pops at beginning */ + scaler = 1.0f / numForScale; + + for( i=0; inumSines; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase >= 1.0 ) phase -= 1.0; + + output += LookupSine(data, phase); + data->phases[j] = phase; + + phaseInc *= 1.02f; + if( phaseInc > MAX_PHASE_INC ) phaseInc = MIN_PHASE_INC; + } + + outSample = (float) (output * scaler); + *out++ = outSample; /* Left */ + *out++ = outSample; /* Right */ + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data = {0}; + double load; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultHighOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* No out of range samples should occur. */ + patestCallback, + &data); + if( err != paNoError ) + goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + /* Play an increasing number of sine waves until we hit MAX_USAGE */ + do { + data.numSines++; + Pa_Sleep(200); + load = Pa_GetStreamCpuLoad(stream); + printf("numSines = %d, CPU load = %f\n", data.numSines, load ); + fflush(stdout); + } while((load < MAX_USAGE) && (data.numSines < MAX_SINES)); + + Pa_Sleep(2000); /* Stay for 2 seconds around 80% CPU. */ + + err = Pa_StopStream( stream ); + if( err != paNoError ) + goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) + goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_mono.c b/portaudio-v19/pa_tests/patest_mono.c new file mode 100644 index 000000000..e7b57362f --- /dev/null +++ b/portaudio-v19/pa_tests/patest_mono.c @@ -0,0 +1,145 @@ +/* + * $Id$ + * patest_sine.c + * Play a monophonic sine wave using the Portable Audio api for several seconds. + * + * Authors: + * Ross Bencina + * Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define AMPLITUDE (0.8) +#define FRAMES_PER_BUFFER (64) +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* avoid unused variable warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + for( i=0; isine[data->phase]; /* left */ + data->phase += 1; + if( data->phase >= TABLE_SIZE ) data->phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output MONO sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); fflush(stdout); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} \ No newline at end of file diff --git a/portaudio-v19/pa_tests/patest_mono_asio_channel_select.c b/portaudio-v19/pa_tests/patest_mono_asio_channel_select.c new file mode 100644 index 000000000..4c4ffccdf --- /dev/null +++ b/portaudio-v19/pa_tests/patest_mono_asio_channel_select.c @@ -0,0 +1,155 @@ +/* + * $Id$ + * patest_sine.c + * Play a monophonic sine wave using the Portable Audio api for several seconds. + * + * Authors: + * Ross Bencina + * Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#include "pa_asio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define AMPLITUDE (0.8) +#define FRAMES_PER_BUFFER (64) +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* avoid unused variable warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + for( i=0; isine[data->phase]; /* left */ + data->phase += 1; + if( data->phase >= TABLE_SIZE ) data->phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaAsioStreamInfo asioOutputInfo; + PaStream *stream; + PaError err; + paTestData data; + int outputChannelSelectors[1]; + int i; + printf("PortAudio Test: output MONO sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + + asioOutputInfo.size = sizeof(PaAsioStreamInfo); + asioOutputInfo.hostApiType = paASIO; + asioOutputInfo.version = 1; + asioOutputInfo.flags = paAsioUseChannelSelectors; + outputChannelSelectors[0] = 1; /* select the second (right) ASIO device channel */ + asioOutputInfo.channelSelectors = outputChannelSelectors; + outputParameters.hostApiSpecificStreamInfo = &asioOutputInfo; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); fflush(stdout); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_multi_sine.c b/portaudio-v19/pa_tests/patest_multi_sine.c new file mode 100644 index 000000000..161c88222 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_multi_sine.c @@ -0,0 +1,194 @@ +/** @file patest_multi_sine.c + @brief Play a different sine wave on each channel. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + short interleaved; /* Nonzero for interleaved / zero for non-interleaved. */ + int numChannels; /* Actually used. */ + double phases[MAX_CHANNELS]; /* Each channel gets its' own frequency. */ +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + int frameIndex, channelIndex; + float** outputs = (float**)outputBuffer; + paTestData* data = (paTestData*)userData; + + (void) inputBuffer; /* Prevent unused arg warning. */ + if (data->interleaved) + { + float *out = (float*)outputBuffer; /* interleaved version */ + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndexnumChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + } + else + { + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndexnumChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + outputs[channelIndex][frameIndex] = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + } + return 0; +} + +/*******************************************************************/ +int test(short interleaved) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + const PaDeviceInfo* pdi; + paTestData data; + short n; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device, max channels. */ + pdi = Pa_GetDeviceInfo(outputParameters.device); + outputParameters.channelCount = pdi->maxOutputChannels; + if (outputParameters.channelCount > MAX_CHANNELS) + outputParameters.channelCount = MAX_CHANNELS; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = pdi->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + data.interleaved = interleaved; + data.numChannels = outputParameters.channelCount; + for (n = 0; n < data.numChannels; n++) + data.phases[n] = 0.0; /* Phases wrap and maybe don't need initialisation. */ + printf("%d ", data.numChannels); + if (interleaved) + printf("interleaved "); + else + { + printf(" non-interleaved "); + outputParameters.sampleFormat |= paNonInterleaved; + } + printf("channels.\n"); + + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, /* Sample rate. */ + FRAMES_PER_BUFFER, /* Frames per buffer. */ + paClipOff, /* Samples never out of range, no clipping. */ + patestCallback, + &data); + if (err == paNoError) + { + err = Pa_StartStream(stream); + if (err == paNoError) + { + printf("Hit ENTER to stop this test.\n"); + getchar(); + err = Pa_StopStream(stream); + } + Pa_CloseStream( stream ); + } + return err; +} + + +/*******************************************************************/ +int main(void) +{ + PaError err; + + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if (err != paNoError) + goto done; + + err = test(1); /* 1 means interleaved. */ + if (err != paNoError) + goto done; + + err = test(0); /* 0 means not interleaved. */ + if (err != paNoError) + goto done; + + printf("Test finished.\n"); +done: + if (err) + { + fprintf(stderr, "An error occured while using the portaudio stream\n"); + fprintf(stderr, "Error number: %d\n", err ); + fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); + } + Pa_Terminate(); + return 0; +} diff --git a/portaudio-v19/pa_tests/patest_out_underflow.c b/portaudio-v19/pa_tests/patest_out_underflow.c new file mode 100644 index 000000000..8e6f27897 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_out_underflow.c @@ -0,0 +1,217 @@ +/** @file patest_out_underflow.c + @brief Count output underflows (using paOutputUnderflow flag) + under overloaded and normal conditions. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2004 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sineCount; + double phases[MAX_SINES]; + int countUnderflows; + int outputUnderflowCount; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int j; + int finished = paContinue; + (void) timeInfo; /* Prevent unused variable warning. */ + (void) inputBuffer; /* Prevent unused variable warning. */ + + + if( data->countUnderflows && (statusFlags & paOutputUnderflow) ) + data->outputUnderflowCount++; + + for( i=0; isineCount; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + *out++ = (float) (output / data->sineCount); + } + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + int safeSineCount, stressedSineCount; + int safeUnderflowCount, stressedUnderflowCount; + paTestData data = {0}; + double load; + + + printf("PortAudio Test: output sine waves, count underflows. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, (float)MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 1; /* mono output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Establishing load conditions...\n" ); + + /* Determine number of sines required to get to 50% */ + do + { + data.sineCount++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + while( load < 0.5 && data.sineCount < (MAX_SINES-1)); + + safeSineCount = data.sineCount; + + /* Calculate target stress value then ramp up to that level*/ + stressedSineCount = (int) (2.0 * data.sineCount * MAX_LOAD ); + if( stressedSineCount > MAX_SINES ) + stressedSineCount = MAX_SINES; + for( ; data.sineCount < stressedSineCount; data.sineCount++ ) + { + Pa_Sleep( 100 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + + printf("Counting underflows for 5 seconds.\n"); + data.countUnderflows = 1; + Pa_Sleep( 5000 ); + + stressedUnderflowCount = data.outputUnderflowCount; + + data.countUnderflows = 0; + data.sineCount = safeSineCount; + + printf("Resuming safe load...\n"); + Pa_Sleep( 1500 ); + data.outputUnderflowCount = 0; + Pa_Sleep( 1500 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + + printf("Counting underflows for 5 seconds.\n"); + data.countUnderflows = 1; + Pa_Sleep( 5000 ); + + safeUnderflowCount = data.outputUnderflowCount; + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + + if( stressedUnderflowCount == 0 ) + printf("Test failed, no output underflows detected under stress.\n"); + else if( safeUnderflowCount != 0 ) + printf("Test failed, %d unexpected underflows detected under safe load.\n", safeUnderflowCount); + else + printf("Test passed, %d expected output underflows detected under stress, 0 unexpected underflows detected under safe load.\n", stressedUnderflowCount ); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_pink.c b/portaudio-v19/pa_tests/patest_pink.c new file mode 100644 index 000000000..f2db85d9c --- /dev/null +++ b/portaudio-v19/pa_tests/patest_pink.c @@ -0,0 +1,269 @@ +/** @file patest_pink.c + @brief Generate Pink Noise using Gardner method. + + Optimization suggested by James McCartney uses a tree + to select which random value to replace. +
    +	x x x x x x x x x x x x x x x x 
    +	x   x   x   x   x   x   x   x   
    +	x       x       x       x       
    +	 x               x               
    +	   x   
    +
    + Tree is generated by counting trailing zeros in an increasing index. + When the index is zero, no random number is selected. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" + +#define PINK_MAX_RANDOM_ROWS (30) +#define PINK_RANDOM_BITS (24) +#define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS) + +typedef struct +{ + long pink_Rows[PINK_MAX_RANDOM_ROWS]; + long pink_RunningSum; /* Used to optimize summing of generators. */ + int pink_Index; /* Incremented each sample. */ + int pink_IndexMask; /* Index wrapped by ANDing with this mask. */ + float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */ +} +PinkNoise; + +/* Prototypes */ +static unsigned long GenerateRandomNumber( void ); +void InitializePinkNoise( PinkNoise *pink, int numRows ); +float GeneratePinkNoise( PinkNoise *pink ); + +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + /* Change this seed for different random sequences. */ + static unsigned long randSeed = 22222; + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} + +/************************************************************/ +/* Setup PinkNoise structure for N rows of generators. */ +void InitializePinkNoise( PinkNoise *pink, int numRows ) +{ + int i; + long pmax; + pink->pink_Index = 0; + pink->pink_IndexMask = (1<pink_Scalar = 1.0f / pmax; + /* Initialize rows. */ + for( i=0; ipink_Rows[i] = 0; + pink->pink_RunningSum = 0; +} + +#define PINK_MEASURE +#ifdef PINK_MEASURE +float pinkMax = -999.0; +float pinkMin = 999.0; +#endif + +/* Generate Pink noise values between -1.0 and +1.0 */ +float GeneratePinkNoise( PinkNoise *pink ) +{ + long newRandom; + long sum; + float output; + /* Increment and mask index. */ + pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask; + /* If index is zero, don't update any random values. */ + if( pink->pink_Index != 0 ) + { + /* Determine how many trailing zeros in PinkIndex. */ + /* This algorithm will hang if n==0 so test first. */ + int numZeros = 0; + int n = pink->pink_Index; + while( (n & 1) == 0 ) + { + n = n >> 1; + numZeros++; + } + /* Replace the indexed ROWS random value. + * Subtract and add back to RunningSum instead of adding all the random + * values together. Only one changes each time. + */ + pink->pink_RunningSum -= pink->pink_Rows[numZeros]; + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + pink->pink_RunningSum += newRandom; + pink->pink_Rows[numZeros] = newRandom; + } + + /* Add extra white noise value. */ + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + sum = pink->pink_RunningSum + newRandom; + /* Scale to range of -1.0 to 0.9999. */ + output = pink->pink_Scalar * sum; +#ifdef PINK_MEASURE + /* Check Min/Max */ + if( output > pinkMax ) pinkMax = output; + else if( output < pinkMin ) pinkMin = output; +#endif + return output; +} + +/*******************************************************************/ +#define PINK_TEST +#ifdef PINK_TEST + +/* Context for callback routine. */ +typedef struct +{ + PinkNoise leftPink; + PinkNoise rightPink; + unsigned int sampsToGo; +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + int finished; + int i; + int numFrames; + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + /* Are we almost at end. */ + if( data->sampsToGo < framesPerBuffer ) + { + numFrames = data->sampsToGo; + finished = 1; + } + else + { + numFrames = framesPerBuffer; + finished = 0; + } + for( i=0; ileftPink ); + *out++ = GeneratePinkNoise( &data->rightPink ); + } + data->sampsToGo -= numFrames; + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream* stream; + PaError err; + paTestData data; + PaStreamParameters outputParameters; + int totalSamps; + static const double SR = 44100.0; + static const int FPB = 2048; /* Frames per buffer: 46 ms buffers. */ + + /* Initialize two pink noise signals with different numbers of rows. */ + InitializePinkNoise( &data.leftPink, 12 ); + InitializePinkNoise( &data.rightPink, 16 ); + + /* Look at a few values. */ + { + int i; + float pink; + for( i=0; i<20; i++ ) + { + pink = GeneratePinkNoise( &data.leftPink ); + printf("Pink = %f\n", pink ); + } + } + + data.sampsToGo = totalSamps = (int)(60.0 * SR); /* Play a whole minute. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Open a stereo PortAudio stream so we can hear the result. */ + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Take the default output device. */ + outputParameters.channelCount = 2; /* Stereo output, most likely supported. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + outputParameters.suggestedLatency = + Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SR, /* Sample rate. */ + FPB, /* Frames per buffer. */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Stereo pink noise for one minute...\n"); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; +#ifdef PINK_MEASURE + printf("Pink min = %f, max = %f\n", pinkMin, pinkMax ); +#endif + Pa_Terminate(); + return 0; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return 0; +} +#endif /* PINK_TEST */ diff --git a/portaudio-v19/pa_tests/patest_prime.c b/portaudio-v19/pa_tests/patest_prime.c new file mode 100644 index 000000000..47d1f5f56 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_prime.c @@ -0,0 +1,223 @@ +/** @file patest_prime.c + @brief Test stream priming mode. + @author Ross Bencina http://www.audiomulch.com/~rossb +*/ + +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" +#include "pa_util.h" + +#define NUM_BEEPS (3) +#define SAMPLE_RATE (44100) +#define SAMPLE_PERIOD (1.0/44100.0) +#define FRAMES_PER_BUFFER (256) +#define BEEP_DURATION (400) +#define IDLE_DURATION (SAMPLE_RATE*2) /* 2 seconds */ +#define SLEEP_MSEC (50) + +#define STATE_BKG_IDLE (0) +#define STATE_BKG_BEEPING (1) + +typedef struct +{ + float leftPhase; + float rightPhase; + int state; + int beepCountdown; + int idleCountdown; +} +paTestData; + +static void InitializeTestData( paTestData *testData ) +{ + testData->leftPhase = 0; + testData->rightPhase = 0; + testData->state = STATE_BKG_BEEPING; + testData->beepCountdown = BEEP_DURATION; + testData->idleCountdown = IDLE_DURATION; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + int result = paContinue; + + /* supress unused parameter warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + + for( i=0; istate ) + { + case STATE_BKG_IDLE: + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + --data->idleCountdown; + + if( data->idleCountdown <= 0 ) result = paComplete; + break; + + case STATE_BKG_BEEPING: + if( data->beepCountdown <= 0 ) + { + data->state = STATE_BKG_IDLE; + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + } + else + { + /* Play sawtooth wave. */ + *out++ = data->leftPhase; /* left */ + *out++ = data->rightPhase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->leftPhase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->leftPhase >= 1.0f ) data->leftPhase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->rightPhase += 0.03f; + if( data->rightPhase >= 1.0f ) data->rightPhase -= 2.0f; + } + --data->beepCountdown; + break; + } + } + + return result; +} + +/*******************************************************************/ +static PaError DoTest( int flags ) +{ + PaStream *stream; + PaError err; + paTestData data; + PaStreamParameters outputParameters; + + InitializeTestData( &data ); + + outputParameters.device = Pa_GetDefaultOutputDevice(); + outputParameters.channelCount = 2; + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; + + /* Open an audio I/O stream. */ + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff | flags, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("hear \"BEEP\"\n" ); + fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(SLEEP_MSEC); + if( err < 0 ) goto error; + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + return err; +error: + return err; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err = paNoError; + int i; + + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + printf("PortAudio Test: Testing stream playback with no priming.\n"); + printf("PortAudio Test: you should see BEEP before you hear it.\n"); + printf("BEEP %d times.\n", NUM_BEEPS ); + + for( i=0; i< NUM_BEEPS; ++i ) + { + err = DoTest( 0 ); + if( err != paNoError ) + goto error; + } + + printf("PortAudio Test: Testing stream playback with priming.\n"); + printf("PortAudio Test: you should see BEEP around the same time you hear it.\n"); + for( i=0; i< NUM_BEEPS; ++i ) + { + err = DoTest( paPrimeOutputBuffersUsingStreamCallback ); + if( err != paNoError ) + goto error; + } + + printf("Test finished.\n"); + + Pa_Terminate(); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_read_record.c b/portaudio-v19/pa_tests/patest_read_record.c new file mode 100644 index 000000000..5e9ca0109 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_read_record.c @@ -0,0 +1,229 @@ +/** @file patest_read_record.c + @brief Record input into an array; Save array to a file; Playback recorded + data. Implemented using the blocking API (Pa_ReadStream(), Pa_WriteStream() ) + @author Phil Burk http://www.softsynth.com + @author Ross Bencina rossb@audiomulch.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" + +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#define NUM_SECONDS (5) +#define NUM_CHANNELS (2) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ + +/* Select sample format. */ +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#define SAMPLE_SILENCE (0.0f) +#define PRINTF_S_FORMAT "%.8f" +#elif 1 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#elif 0 +#define PA_SAMPLE_TYPE paInt8 +typedef char SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#define SAMPLE_SILENCE (128) +#define PRINTF_S_FORMAT "%d" +#endif + + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, outputParameters; + PaStream *stream; + PaError err; + SAMPLE *recordedSamples; + int i; + int totalFrames; + int numSamples; + int numBytes; + SAMPLE max, average, val; + + + printf("patest_read_record.c\n"); fflush(stdout); + + totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ + numSamples = totalFrames * NUM_CHANNELS; + + numBytes = numSamples * sizeof(SAMPLE); + recordedSamples = (SAMPLE *) malloc( numBytes ); + if( recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; idefaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + /* Record some audio. -------------------------------------------- */ + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* &outputParameters, */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Now recording!!\n"); fflush(stdout); + + err = Pa_ReadStream( stream, recordedSamples, totalFrames ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + /* Measure maximum peak amplitude. */ + max = 0; + average = 0; + for( i=0; i max ) + { + max = val; + } + average += val; + } + + average = average / numSamples; + + printf("Sample max amplitude = "PRINTF_S_FORMAT"\n", max ); + printf("Sample average = "PRINTF_S_FORMAT"\n", average ); +/* Was as below. Better choose at compile time because this + keeps generating compiler-warnings: + if( PA_SAMPLE_TYPE == paFloat32 ) + { + printf("sample max amplitude = %f\n", max ); + printf("sample average = %f\n", average ); + } + else + { + printf("sample max amplitude = %d\n", max ); + printf("sample average = %d\n", average ); + } +*/ + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + + /* Playback recorded data. -------------------------------------------- */ + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = NUM_CHANNELS; + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + if( stream ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for playback to finish.\n"); fflush(stdout); + + err = Pa_WriteStream( stream, recordedSamples, totalFrames ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + printf("Done.\n"); fflush(stdout); + } + free( recordedSamples ); + + Pa_Terminate(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} + diff --git a/portaudio-v19/pa_tests/patest_record.c b/portaudio-v19/pa_tests/patest_record.c new file mode 100644 index 000000000..7ba35825d --- /dev/null +++ b/portaudio-v19/pa_tests/patest_record.c @@ -0,0 +1,337 @@ +/** @file patest_record.c + @brief Record input into an array; Save array to a file; Playback recorded data. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" + +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#define NUM_SECONDS (5) +#define NUM_CHANNELS (2) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ + +/* Select sample format. */ +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#define SAMPLE_SILENCE (0.0f) +#define PRINTF_S_FORMAT "%.8f" +#elif 1 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#elif 0 +#define PA_SAMPLE_TYPE paInt8 +typedef char SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#define SAMPLE_SILENCE (128) +#define PRINTF_S_FORMAT "%d" +#endif + +typedef struct +{ + int frameIndex; /* Index into sample array. */ + int maxFrameIndex; + SAMPLE *recordedSamples; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + const SAMPLE *rptr = (const SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS]; + long framesToCalc; + long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = paComplete; + } + else + { + framesToCalc = framesPerBuffer; + finished = paContinue; + } + + if( inputBuffer == NULL ) + { + for( i=0; iframeIndex += framesToCalc; + return finished; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned int i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; iframeIndex += framesLeft; + finished = paComplete; + } + else + { + for( i=0; iframeIndex += framesPerBuffer; + finished = paContinue; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, + outputParameters; + PaStream* stream; + PaError err = paNoError; + paTestData data; + int i; + int totalFrames; + int numSamples; + int numBytes; + SAMPLE max, val; + double average; + + printf("patest_record.c\n"); fflush(stdout); + + data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ + data.frameIndex = 0; + numSamples = totalFrames * NUM_CHANNELS; + numBytes = numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); /* From now on, recordedSamples is initialised. */ + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + goto done; + } + for( i=0; idefaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + /* Record some audio. -------------------------------------------- */ + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* &outputParameters, */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + &data ); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + printf("Now recording!!\n"); fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + { + Pa_Sleep(1000); + printf("index = %d\n", data.frameIndex ); fflush(stdout); + } + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + /* Measure maximum peak amplitude. */ + max = 0; + average = 0.0; + for( i=0; i max ) + { + max = val; + } + average += val; + } + + average = average / (double)numSamples; + + printf("sample max amplitude = "PRINTF_S_FORMAT"\n", max ); + printf("sample average = %lf\n", average ); + + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( data.recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + + /* Playback recorded data. -------------------------------------------- */ + data.frameIndex = 0; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto done; + + if( stream ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + + printf("Waiting for playback to finish.\n"); fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + printf("Done.\n"); fflush(stdout); + } + +done: + Pa_Terminate(); + if( data.recordedSamples ) /* Sure it is NULL or valid. */ + free( data.recordedSamples ); + if( err != paNoError ) + { + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + err = 1; /* Always return 0 or 1, but no other return codes. */ + } + return err; +} + diff --git a/portaudio-v19/pa_tests/patest_ringmix.c b/portaudio-v19/pa_tests/patest_ringmix.c new file mode 100644 index 000000000..1df062388 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_ringmix.c @@ -0,0 +1,79 @@ +/** @file patest_ringmix.c + @brief Ring modulate inputs to left output, mix inputs to right output. +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#include "stdio.h" +#include "portaudio.h" +/* This will be called asynchronously by the PortAudio engine. */ +static int myCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + const float *in = (const float *) inputBuffer; + float *out = (float *) outputBuffer; + float leftInput, rightInput; + unsigned int i; + + /* Read input buffer, process data, and fill output buffer. */ + for( i=0; i +#include +#include "portaudio.h" +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) + +typedef struct +{ + float left_phase; + float right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; ileft_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + return 0; +} + +/*******************************************************************/ +static paTestData data; +int main(void); +int main(void) +{ + PaStream *stream; + PaError err; + + printf("PortAudio Test: output sawtooth wave.\n"); + /* Initialize our data for use by callback. */ + data.left_phase = data.right_phase = 0.0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Open an audio I/O stream. */ + err = Pa_OpenDefaultStream( &stream, + 0, /* no input channels */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + SAMPLE_RATE, + 256, /* frames per buffer */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Sleep for several seconds. */ + Pa_Sleep(NUM_SECONDS*1000); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_sine.c b/portaudio-v19/pa_tests/patest_sine.c new file mode 100644 index 000000000..c952b5805 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_sine.c @@ -0,0 +1,151 @@ +/** @file patest_sine.c + @brief Play a sine wave for several seconds. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_sine8.c b/portaudio-v19/pa_tests/patest_sine8.c new file mode 100644 index 000000000..5a9d1fe72 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_sine8.c @@ -0,0 +1,212 @@ +/** @file patest_sine8.c + @brief Test 8 bit data: play a sine wave for several seconds. + @author Ross Bencina +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define TABLE_SIZE (200) +#define TEST_UNSIGNED (0) + +#if TEST_UNSIGNED +#define TEST_FORMAT paUInt8 +#else +#define TEST_FORMAT paInt8 +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ +#if TEST_UNSIGNED + unsigned char sine[TABLE_SIZE]; +#else + char sine[TABLE_SIZE]; +#endif + int left_phase; + int right_phase; + unsigned int framesToGo; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + char *out = (char*)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { +#if TEST_UNSIGNED + *out++ = (unsigned char) 0x80; /* left */ + *out++ = (unsigned char) 0x80; /* right */ +#else + *out++ = 0; /* left */ + *out++ = 0; /* right */ +#endif + + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream* stream; + PaError err; + paTestData data; + PaTime streamOpened; + int i, totalSamps; + +#if TEST_UNSIGNED + printf("PortAudio Test: output UNsigned 8 bit sine wave.\n"); +#else + printf("PortAudio Test: output signed 8 bit sine wave.\n"); +#endif + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + 256, /* Frames per buffer. */ + paClipOff, /* We won't output out of range samples so don't bother clipping them. */ + patestCallback, + &data ); + if( err != paNoError ) + goto error; + + streamOpened = Pa_GetStreamTime( stream ); /* Time in seconds when stream was opened (approx). */ + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + /* Watch until sound is halfway finished. */ + /* (Was ( Pa_StreamTime( stream ) < (totalSamps/2) ) in V18. */ + while( (Pa_GetStreamTime( stream ) - streamOpened) < (PaTime)NUM_SECONDS / 2.0 ) + Pa_Sleep(10); + + /* Stop sound until ENTER hit. (Hu? don't see any keyboard-input here.) */ + err = Pa_StopStream( stream ); + if( err != paNoError ) + goto error; + + printf("Pause for 2 seconds.\n"); + Pa_Sleep( 2000 ); + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + printf("Waiting for sound to finish.\n"); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + Pa_Sleep(100); + if( err < 0 ) + goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) + goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_sine_formats.c b/portaudio-v19/pa_tests/patest_sine_formats.c new file mode 100644 index 000000000..448a4ebe9 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_sine_formats.c @@ -0,0 +1,196 @@ +/** @file patest_sine_formats.c + @brief Play a sine wave for several seconds. Test various data formats. + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#define LEFT_FREQ (SAMPLE_RATE/256.0) /* So we hit 1.0 */ +#define RIGHT_FREQ (500.0) +#define AMPLITUDE (1.0) + +/* Select ONE format for testing. */ +#define TEST_UINT8 (0) +#define TEST_INT8 (0) +#define TEST_INT16 (1) +#define TEST_FLOAT32 (0) + +#if TEST_UINT8 +#define TEST_FORMAT paUInt8 +typedef unsigned char SAMPLE_t; +#define SAMPLE_ZERO (0x80) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Unsigned 8 Bit" + +#elif TEST_INT8 +#define TEST_FORMAT paInt8 +typedef char SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Signed 8 Bit" + +#elif TEST_INT16 +#define TEST_FORMAT paInt16 +typedef short SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(32767 * (x))) +#define FORMAT_NAME "Signed 16 Bit" + +#elif TEST_FLOAT32 +#define TEST_FORMAT paFloat32 +typedef float SAMPLE_t; +#define SAMPLE_ZERO (0.0) +#define DOUBLE_TO_SAMPLE(x) ((SAMPLE_t)(x)) +#define FORMAT_NAME "Float 32 Bit" +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + + +typedef struct +{ + double left_phase; + double right_phase; + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE_t *out = (SAMPLE_t *)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; ileft_phase += (LEFT_FREQ / SAMPLE_RATE); + if( data->left_phase > 1.0) data->left_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->left_phase * M_PI * 2. ))); + + data->right_phase += (RIGHT_FREQ / SAMPLE_RATE); + if( data->right_phase > 1.0) data->right_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->right_phase * M_PI * 2. ))); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = SAMPLE_ZERO; /* left */ + *out++ = SAMPLE_ZERO; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int totalSamps; + + printf("PortAudio Test: output " FORMAT_NAME "\n"); + + data.left_phase = data.right_phase = 0.0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output */ + outputParameters.sampleFormat = TEST_FORMAT; /* Selected above. */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Waiting %d seconds for sound to finish.\n", NUM_SECONDS ); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("PortAudio Test Finished: " FORMAT_NAME "\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_sine_time.c b/portaudio-v19/pa_tests/patest_sine_time.c new file mode 100644 index 000000000..cf45275fe --- /dev/null +++ b/portaudio-v19/pa_tests/patest_sine_time.c @@ -0,0 +1,208 @@ +/** @file patest_sine_time.c + @brief Play a sine wave for several seconds, pausing in the middle. + Uses the Pa_GetStreamTime() call. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include + +#include "portaudio.h" +#include "pa_util.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define TABLE_SIZE (200) + +typedef struct +{ + double left_phase; + double right_phase; + volatile PaTime outTime; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + + double left_phaseInc = 0.02; + double right_phaseInc = 0.06; + + double left_phase = data->left_phase; + double right_phase = data->right_phase; + + (void) statusFlags; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + data->outTime = timeInfo->outputBufferDacTime; + + for( i=0; i TWOPI ) left_phase -= TWOPI; + *out++ = (float) sin( left_phase ); + + right_phase += right_phaseInc; + if( right_phase > TWOPI ) right_phase -= TWOPI; + *out++ = (float) sin( right_phase ); + } + + data->left_phase = left_phase; + data->right_phase = right_phase; + + return paContinue; +} + +/*******************************************************************/ +static void ReportStreamTime( PaStream *stream, paTestData *data ); +static void ReportStreamTime( PaStream *stream, paTestData *data ) +{ + PaTime streamTime, latency, outTime; + + streamTime = Pa_GetStreamTime( stream ); + outTime = data->outTime; + if( outTime < 0.0 ) + { + printf("Stream time = %8.1f\n", streamTime ); + } + else + { + latency = outTime - streamTime; + printf("Stream time = %8.4f, outTime = %8.4f, latency = %8.4f\n", + streamTime, outTime, latency ); + } + fflush(stdout); +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + PaTime startTime; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + data.left_phase = data.right_phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + /* Watch until sound is halfway finished. */ + printf("Play for %d seconds.\n", NUM_SECONDS/2 ); fflush(stdout); + + data.outTime = -1.0; /* mark time for callback as undefined */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + startTime = Pa_GetStreamTime( stream ); + + do + { + ReportStreamTime( stream, &data ); + Pa_Sleep(100); + } while( (Pa_GetStreamTime( stream ) - startTime) < (NUM_SECONDS/2) ); + + /* Stop sound for 2 seconds. */ + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + printf("Pause for 2 seconds.\n"); fflush(stdout); + Pa_Sleep( 2000 ); + + data.outTime = -1.0; /* mark time for callback as undefined */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + startTime = Pa_GetStreamTime( stream ); + + printf("Play until sound is finished.\n"); fflush(stdout); + do + { + ReportStreamTime( stream, &data ); + Pa_Sleep(100); + } while( (Pa_GetStreamTime( stream ) - startTime) < (NUM_SECONDS/2) ); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_start_stop.c b/portaudio-v19/pa_tests/patest_start_stop.c new file mode 100644 index 000000000..03ade1721 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_start_stop.c @@ -0,0 +1,162 @@ +/** @file patest_start_stop.c + @brief Play a sine wave for several seconds + - start and stop the stream multiple times. + + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() /* default output device */ + +#define NUM_SECONDS (3) +#define NUM_LOOPS (4) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (400) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + for( i=0; i +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) +#define SLEEP_DUR (200) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define LATENCY_SECONDS (3.f) +#define FRAMES_PER_NOTE (SAMPLE_RATE/2) +#define MAX_REPEATS (2) +#define FUNDAMENTAL (400.0f / SAMPLE_RATE) +#define NOTE_0 (FUNDAMENTAL * 1.0f / 1.0f) +#define NOTE_1 (FUNDAMENTAL * 5.0f / 4.0f) +#define NOTE_2 (FUNDAMENTAL * 4.0f / 3.0f) +#define NOTE_3 (FUNDAMENTAL * 3.0f / 2.0f) +#define NOTE_4 (FUNDAMENTAL * 2.0f / 1.0f) +#define MODE_FINISH (0) +#define MODE_STOP (1) +#define MODE_ABORT (2) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) + +typedef struct +{ + float waveform[TABLE_SIZE + 1]; /* Add one for guard point for interpolation. */ + float phase_increment; + float phase; + float *tune; + int notesPerTune; + int frameCounter; + int noteCounter; + int repeatCounter; + PaTime outTime; + int stopMode; + int done; +} +paTestData; + +/************* Prototypes *****************************/ +int TestStopMode( paTestData *data ); +float LookupWaveform( paTestData *data, float phase ); + +/****************************************************** + * Convert phase between 0.0 and 1.0 to waveform value + * using linear interpolation. + */ +float LookupWaveform( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->waveform[index]; + float hi = data->waveform[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float value; + unsigned int i = 0; + int finished = paContinue; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + + /* data->outTime = outTime; */ + + if( !data->done ) + { + for( i=0; iframeCounter >= FRAMES_PER_NOTE ) + { + data->noteCounter += 1; + data->frameCounter = 0; + /* Are we done with this tune? */ + if( data->noteCounter >= data->notesPerTune ) + { + data->noteCounter = 0; + data->repeatCounter += 1; + /* Are we totally done? */ + if( data->repeatCounter >= MAX_REPEATS ) + { + data->done = 1; + if( data->stopMode == MODE_FINISH ) + { + finished = paComplete; + break; + } + } + } + data->phase_increment = data->tune[data->noteCounter]; + } + value = LookupWaveform(data, data->phase); + *out++ = value; /* left */ + *out++ = value; /* right */ + data->phase += data->phase_increment; + if( data->phase >= 1.0f ) data->phase -= 1.0f; + + data->frameCounter += 1; + } + } + /* zero remainder of final buffer */ + for( ; idone = 0; + data->phase = 0.0; + data->frameCounter = 0; + data->noteCounter = 0; + data->repeatCounter = 0; + data->phase_increment = data->tune[data->noteCounter]; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = LATENCY_SECONDS; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + if( data->stopMode == MODE_FINISH ) + { + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); */ + Pa_Sleep( SLEEP_DUR ); + } + if( err < 0 ) goto error; + } + else + { + while( data->repeatCounter < MAX_REPEATS ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); */ + Pa_Sleep( SLEEP_DUR ); + } + } + + if( data->stopMode == MODE_ABORT ) + { + printf("Call Pa_AbortStream()\n"); + err = Pa_AbortStream( stream ); + } + else + { + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + } + if( err != paNoError ) goto error; + + printf("Call Pa_CloseStream()\n"); fflush(stdout); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_stop_playout.c b/portaudio-v19/pa_tests/patest_stop_playout.c new file mode 100644 index 000000000..41134d581 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_stop_playout.c @@ -0,0 +1,432 @@ +/** @file patest_stop_playout.c + @brief Test whether all queued samples are played when Pa_StopStream() + is used with a callback or read/write stream, or when the callback + returns paComplete. + @author Ross Bencina +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2004 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) + +#define TONE_SECONDS (1) /* long tone */ +#define TONE_FADE_SECONDS (.04) /* fades at start and end of long tone */ +#define GAP_SECONDS (.25) /* gap between long tone and blip */ +#define BLIP_SECONDS (.035) /* short blip */ + +#define NUM_REPEATS (3) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (2048) +typedef struct +{ + float sine[TABLE_SIZE+1]; + + int repeatCount; + + double phase; + double lowIncrement, highIncrement; + + int gap1Length, toneLength, toneFadesLength, gap2Length, blipLength; + int gap1Countdown, toneCountdown, gap2Countdown, blipCountdown; +} +TestData; + + +static void RetriggerTestSignalGenerator( TestData *data ) +{ + data->phase = 0.; + data->gap1Countdown = data->gap1Length; + data->toneCountdown = data->toneLength; + data->gap2Countdown = data->gap2Length; + data->blipCountdown = data->blipLength; +} + + +static void ResetTestSignalGenerator( TestData *data ) +{ + data->repeatCount = 0; + RetriggerTestSignalGenerator( data ); +} + + +static void InitTestSignalGenerator( TestData *data ) +{ + int signalLengthModBufferLength, i; + + /* initialise sinusoidal wavetable */ + for( i=0; isine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data->sine[TABLE_SIZE] = data->sine[0]; /* guard point for linear interpolation */ + + + + data->lowIncrement = (330. / SAMPLE_RATE) * TABLE_SIZE; + data->highIncrement = (1760. / SAMPLE_RATE) * TABLE_SIZE; + + data->gap1Length = GAP_SECONDS * SAMPLE_RATE; + data->toneLength = TONE_SECONDS * SAMPLE_RATE; + data->toneFadesLength = TONE_FADE_SECONDS * SAMPLE_RATE; + data->gap2Length = GAP_SECONDS * SAMPLE_RATE; + data->blipLength = BLIP_SECONDS * SAMPLE_RATE; + + /* adjust signal length to be a multiple of the buffer length */ + signalLengthModBufferLength = (data->gap1Length + data->toneLength + data->gap2Length + data->blipLength) % FRAMES_PER_BUFFER; + if( signalLengthModBufferLength > 0 ) + data->toneLength += signalLengthModBufferLength; + + ResetTestSignalGenerator( data ); +} + + +#define MIN( a, b ) (((a)<(b))?(a):(b)) + +static void GenerateTestSignal( TestData *data, float *stereo, int frameCount ) +{ + int framesGenerated = 0; + float output; + long index; + float fraction; + int count, i; + + while( framesGenerated < frameCount && data->repeatCount < NUM_REPEATS ) + { + if( framesGenerated < frameCount && data->gap1Countdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->gap1Countdown ); + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + + data->gap1Countdown -= count; + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->toneCountdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->toneCountdown ); + for( i=0; i < count; ++i ) + { + /* tone with data->lowIncrement phase increment */ + index = (long)data->phase; + fraction = data->phase - index; + output = data->sine[ index ] + (data->sine[ index + 1 ] - data->sine[ index ]) * fraction; + + data->phase += data->lowIncrement; + while( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + /* apply fade to ends */ + + if( data->toneCountdown < data->toneFadesLength ) + { + /* cosine-bell fade out at end */ + output *= (-cos(((float)data->toneCountdown / (float)data->toneFadesLength) * M_PI) + 1.) * .5; + } + else if( data->toneCountdown > data->toneLength - data->toneFadesLength ) + { + /* cosine-bell fade in at start */ + output *= (cos(((float)(data->toneCountdown - (data->toneLength - data->toneFadesLength)) / (float)data->toneFadesLength) * M_PI) + 1.) * .5; + } + + output *= .5; /* play tone half as loud as blip */ + + *stereo++ = output; + *stereo++ = output; + + data->toneCountdown--; + } + + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->gap2Countdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->gap2Countdown ); + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + + data->gap2Countdown -= count; + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->blipCountdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->blipCountdown ); + for( i=0; i < count; ++i ) + { + /* tone with data->highIncrement phase increment */ + index = (long)data->phase; + fraction = data->phase - index; + output = data->sine[ index ] + (data->sine[ index + 1 ] - data->sine[ index ]) * fraction; + + data->phase += data->highIncrement; + while( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + /* cosine-bell envelope over whole blip */ + output *= (-cos( ((float)data->blipCountdown / (float)data->blipLength) * 2. * M_PI) + 1.) * .5; + + *stereo++ = output; + *stereo++ = output; + + data->blipCountdown--; + } + + framesGenerated += count; + } + + + if( data->blipCountdown == 0 ) + { + RetriggerTestSignalGenerator( data ); + data->repeatCount++; + } + } + + if( framesGenerated < frameCount ) + { + count = frameCount - framesGenerated; + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + } +} + + +static int IsTestSignalFinished( TestData *data ) +{ + if( data->repeatCount >= NUM_REPEATS ) + return 1; + else + return 0; +} + + +static int TestCallback1( const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + GenerateTestSignal( (TestData*)userData, (float*)outputBuffer, frameCount ); + + if( IsTestSignalFinished( (TestData*)userData ) ) + return paComplete; + else + return paContinue; +} + + +volatile int testCallback2Finished = 0; + +static int TestCallback2( const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + GenerateTestSignal( (TestData*)userData, (float*)outputBuffer, frameCount ); + + if( IsTestSignalFinished( (TestData*)userData ) ) + testCallback2Finished = 1; + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + TestData data; + float writeBuffer[ FRAMES_PER_BUFFER * 2 ]; + + printf("PortAudio Test: check that stopping stream plays out all queued samples. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + InitTestSignalGenerator( &data ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + +/* test paComplete ---------------------------------------------------------- */ + + ResetTestSignalGenerator( &data ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + TestCallback1, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using callback, stops by returning paComplete from callback.\n", NUM_REPEATS ); + printf("If final blip is not intact, callback+paComplete implementation may be faulty.\n\n" ); + + while( (err = Pa_IsStreamActive( stream )) == 1 ) + Pa_Sleep( 5 ); + + if( err != 0 ) goto error; + + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( 500 ); + +/* test Pa_StopStream() with callback --------------------------------------- */ + + ResetTestSignalGenerator( &data ); + + testCallback2Finished = 0; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + TestCallback2, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using callback, stops by calling Pa_StopStream.\n", NUM_REPEATS ); + printf("If final blip is not intact, callback+Pa_StopStream implementation may be faulty.\n\n" ); + + /* note that polling a volatile flag is not a good way to synchronise with + the callback, but it's the best we can do portably. */ + while( !testCallback2Finished ) + Pa_Sleep( 2 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( 500 ); + +/* test Pa_StopStream() with Pa_WriteStream --------------------------------- */ + + ResetTestSignalGenerator( &data ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using Pa_WriteStream, stops by calling Pa_StopStream.\n", NUM_REPEATS ); + printf("If final blip is not intact, Pa_WriteStream+Pa_StopStream implementation may be faulty.\n\n" ); + + do{ + GenerateTestSignal( &data, writeBuffer, FRAMES_PER_BUFFER ); + err = Pa_WriteStream( stream, writeBuffer, FRAMES_PER_BUFFER ); + if( err != paNoError ) goto error; + + }while( !IsTestSignalFinished( &data ) ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + +/* -------------------------------------------------------------------------- */ + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_sync.c b/portaudio-v19/pa_tests/patest_sync.c new file mode 100644 index 000000000..01e471420 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_sync.c @@ -0,0 +1,259 @@ +/** @file patest_sync.c + @brief Test time stamping and synchronization of audio and video. + + A high latency is used so we can hear the difference in time. + Random durations are used so we know we are hearing the right beep + and not the one before or after. + + Sequence of events: + -# Foreground requests a beep. + -# Background randomly schedules a beep. + -# Foreground waits for the beep to be heard based on PaUtil_GetTime(). + -# Foreground outputs video (printf) in sync with audio. + -# Repeat. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" +#include "pa_util.h" +#define NUM_BEEPS (6) +#define SAMPLE_RATE (44100) +#define SAMPLE_PERIOD (1.0/44100.0) +#define FRAMES_PER_BUFFER (256) +#define BEEP_DURATION (400) +#define LATENCY_MSEC (2000) +#define SLEEP_MSEC (10) +#define TIMEOUT_MSEC (15000) + +#define STATE_BKG_IDLE (0) +#define STATE_BKG_PENDING (1) +#define STATE_BKG_BEEPING (2) +typedef struct +{ + float left_phase; + float right_phase; + int state; + int requestBeep; /* Set by foreground, cleared by background. */ + PaTime beepTime; + int beepCount; + double latency; /* For debugging. */ +} +paTestData; + +static unsigned long GenerateRandomNumber( void ); +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + static unsigned long randSeed = 99887766; /* Change this for different random sequences. */ + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; + + data->latency = timeInfo->outputBufferDacTime - timeInfo->currentTime; + + for( i=0; istate ) + { + case STATE_BKG_IDLE: + /* Schedule beep at some random time in the future. */ + if( data->requestBeep ) + { + int random = GenerateRandomNumber() >> 14; + data->beepTime = timeInfo->outputBufferDacTime + (( (double)(random + SAMPLE_RATE)) * SAMPLE_PERIOD ); + data->state = STATE_BKG_PENDING; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + + case STATE_BKG_PENDING: + if( (timeInfo->outputBufferDacTime + (i*SAMPLE_PERIOD)) >= data->beepTime ) + { + data->state = STATE_BKG_BEEPING; + data->beepCount = BEEP_DURATION; + data->left_phase = data->right_phase = 0.0; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + + case STATE_BKG_BEEPING: + if( data->beepCount <= 0 ) + { + data->state = STATE_BKG_IDLE; + data->requestBeep = 0; + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + } + else + { + /* Play sawtooth wave. */ + *out++ = data->left_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + data->beepCount -= 1; + break; + + default: + data->state = STATE_BKG_IDLE; + break; + } + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaError err; + paTestData DATA; + int i, timeout; + PaTime previousTime; + PaStreamParameters outputParameters; + printf("PortAudio Test: you should see BEEP at the same time you hear it.\n"); + printf("Wait for a few seconds random delay between BEEPs.\n"); + printf("BEEP %d times.\n", NUM_BEEPS ); + /* Initialize our DATA for use by callback. */ + DATA.left_phase = DATA.right_phase = 0.0; + DATA.state = STATE_BKG_IDLE; + DATA.requestBeep = 0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); + outputParameters.channelCount = 2; + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = (double)LATENCY_MSEC / 1000; + + /* Open an audio I/O stream. */ + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &DATA ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("started\n"); + fflush(stdout); + + previousTime = Pa_GetStreamTime( stream ); + for( i=0; i 0 ) ) Pa_Sleep(SLEEP_MSEC); + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for background to acknowledge request.\n" ); + goto error; + } + printf("calc beep for %9.3f, latency = %6.3f\n", DATA.beepTime, DATA.latency ); + fflush(stdout); + + /* Wait for scheduled beep time. */ + timeout = TIMEOUT_MSEC + (10000/SLEEP_MSEC); + while( (Pa_GetStreamTime( stream ) < DATA.beepTime) && (timeout-- > 0 ) ) + { + Pa_Sleep(SLEEP_MSEC); + } + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for time. Now = %9.3f, Beep for %9.3f.\n", + PaUtil_GetTime(), DATA.beepTime ); + goto error; + } + + /* Beep should be sounding now so print synchronized BEEP. */ + printf("hear \"BEEP\" at %9.3f, delta = %9.3f\n", + Pa_GetStreamTime( stream ), (DATA.beepTime - previousTime) ); + fflush(stdout); + + previousTime = DATA.beepTime; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_toomanysines.c b/portaudio-v19/pa_tests/patest_toomanysines.c new file mode 100644 index 000000000..b7b22667c --- /dev/null +++ b/portaudio-v19/pa_tests/patest_toomanysines.c @@ -0,0 +1,173 @@ +/** @file patest_toomanysines.c + @brief Play more sine waves than we can handle in real time as a stress test. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int numSines; + double phases[MAX_SINES]; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int j; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; inumSines; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + *out++ = (float) (output / data->numSines); + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + int numStress; + paTestData data = {0}; + double load; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 1; /* mono output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Determine number of sines required to get to 50% */ + do + { + data.numSines++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("numSines = %d, CPU load = %f\n", data.numSines, load ); + } + while( load < 0.5 ); + + /* Calculate target stress value then ramp up to that level*/ + numStress = (int) (2.0 * data.numSines * MAX_LOAD ); + for( ; data.numSines < numStress; data.numSines++ ) + { + Pa_Sleep( 200 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: numSines = %d, CPU load = %f\n", data.numSines, load ); + } + + printf("Suffer for 5 seconds.\n"); + Pa_Sleep( 5000 ); + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_two_rates.c b/portaudio-v19/pa_tests/patest_two_rates.c new file mode 100644 index 000000000..99532411f --- /dev/null +++ b/portaudio-v19/pa_tests/patest_two_rates.c @@ -0,0 +1,168 @@ +/* + * $Id$ + * patest_two_rates.c + * Play two streams at different rates to make sure they don't interfere. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE_1 (44100) +#define SAMPLE_RATE_2 (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (0.1) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + double phase; + double numFrames; +} paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + /* Generate sine wave. */ + float value = (float) 0.3 * sin(data->phase); + /* Stereo - two channels. */ + *out++ = value; + *out++ = value; + + data->phase += FREQ_INCR; + if( data->phase >= (2.0 * M_PI) ) data->phase -= (2.0 * M_PI); + } + + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + PortAudioStream *stream1; + PortAudioStream *stream2; + paTestData data1 = {0}; + paTestData data2 = {0}; + printf("PortAudio Test: two rates.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Start first stream. **********************/ + err = Pa_OpenStream( + &stream1, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* Stereo */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE_1, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data1 ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream1 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + /* Start second stream. **********************/ + err = Pa_OpenStream( + &stream2, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* Stereo */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE_2, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data2 ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream2 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + err = Pa_StopStream( stream2 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + err = Pa_StopStream( stream1 ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream2 ); + Pa_CloseStream( stream1 ); + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_underflow.c b/portaudio-v19/pa_tests/patest_underflow.c new file mode 100644 index 000000000..7ff3de6a4 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_underflow.c @@ -0,0 +1,151 @@ +/** @file patest_underflow.c + @brief Simulate an output buffer underflow condition. + Tests whether the stream can be stopped when underflowing buffers. + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (20) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (2048) +#define MSEC_PER_BUFFER ( (FRAMES_PER_BUFFER * 1000) / SAMPLE_RATE ) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int sleepTime; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + /* Cause underflow to occur. */ + if( data->sleepTime > 0 ) Pa_Sleep( data->sleepTime ); + data->sleepTime += 1; + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + while( data.sleepTime < (2 * MSEC_PER_BUFFER) ) + { + printf("SleepTime = %d\n", data.sleepTime ); + Pa_Sleep( data.sleepTime ); + } + + printf("Try to stop stream.\n"); + err = Pa_StopStream( stream ); /* */ + err = Pa_AbortStream( stream ); /* */ + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_tests/patest_wire.c b/portaudio-v19/pa_tests/patest_wire.c new file mode 100644 index 000000000..d83ce22d6 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_wire.c @@ -0,0 +1,284 @@ +/** @file patest_wire.c + @brief Pass input directly to output. + + Note that some HW devices, for example many ISA audio cards + on PCs, do NOT support full duplex! For a PC, you normally need + a PCI based audio card such as the SBLive. + + @author Phil Burk http://www.softsynth.com + + While adapting to V19-API, I excluded configs with framesPerCallback=0 + because of an assert in file pa_common/pa_process.c. Pieter, Oct 9, 2003. + +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define SAMPLE_RATE (44100) + +typedef struct WireConfig_s +{ + int isInputInterleaved; + int isOutputInterleaved; + int numInputChannels; + int numOutputChannels; + int framesPerCallback; +} WireConfig_t; + +#define USE_FLOAT_INPUT (1) +#define USE_FLOAT_OUTPUT (1) + +/* Latencies set to defaults. */ + +#if USE_FLOAT_INPUT + #define INPUT_FORMAT paFloat32 + typedef float INPUT_SAMPLE; +#else + #define INPUT_FORMAT paInt16 + typedef short INPUT_SAMPLE; +#endif + +#if USE_FLOAT_OUTPUT + #define OUTPUT_FORMAT paFloat32 + typedef float OUTPUT_SAMPLE; +#else + #define OUTPUT_FORMAT paInt16 + typedef short OUTPUT_SAMPLE; +#endif + +double gInOutScaler = 1.0; +#define CONVERT_IN_TO_OUT(in) ((OUTPUT_SAMPLE) ((in) * gInOutScaler)) + +#define INPUT_DEVICE (Pa_GetDefaultInputDevice()) +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) + +static PaError TestConfiguration( WireConfig_t *config ); + +static int wireCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 wireCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + INPUT_SAMPLE *in; + OUTPUT_SAMPLE *out; + int inStride; + int outStride; + int inDone = 0; + int outDone = 0; + WireConfig_t *config = (WireConfig_t *) userData; + unsigned int i; + int inChannel, outChannel; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer == NULL) return 0; + + inChannel=0, outChannel=0; + while( !(inDone && outDone) ) + { + if( config->isInputInterleaved ) + { + in = ((INPUT_SAMPLE*)inputBuffer) + inChannel; + inStride = config->numInputChannels; + } + else + { + in = ((INPUT_SAMPLE**)inputBuffer)[inChannel]; + inStride = 1; + } + + if( config->isOutputInterleaved ) + { + out = ((OUTPUT_SAMPLE*)outputBuffer) + outChannel; + outStride = config->numOutputChannels; + } + else + { + out = ((OUTPUT_SAMPLE**)outputBuffer)[outChannel]; + outStride = 1; + } + + for( i=0; inumInputChannels - 1)) inChannel++; + else inDone = 1; + if(outChannel < (config->numOutputChannels - 1)) outChannel++; + else outDone = 1; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + WireConfig_t CONFIG; + WireConfig_t *config = &CONFIG; + int configIndex = 0;; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + printf("Please connect audio signal to input and listen for it on output!\n"); + printf("input format = %lu\n", INPUT_FORMAT ); + printf("output format = %lu\n", OUTPUT_FORMAT ); + printf("input device ID = %d\n", INPUT_DEVICE ); + printf("output device ID = %d\n", OUTPUT_DEVICE ); + + if( INPUT_FORMAT == OUTPUT_FORMAT ) + { + gInOutScaler = 1.0; + } + else if( (INPUT_FORMAT == paInt16) && (OUTPUT_FORMAT == paFloat32) ) + { + gInOutScaler = 1.0/32768.0; + } + else if( (INPUT_FORMAT == paFloat32) && (OUTPUT_FORMAT == paInt16) ) + { + gInOutScaler = 32768.0; + } + + for( config->isInputInterleaved = 0; config->isInputInterleaved < 2; config->isInputInterleaved++ ) + { + for( config->isOutputInterleaved = 0; config->isOutputInterleaved < 2; config->isOutputInterleaved++ ) + { + for( config->numInputChannels = 1; config->numInputChannels < 3; config->numInputChannels++ ) + { + for( config->numOutputChannels = 1; config->numOutputChannels < 3; config->numOutputChannels++ ) + { + /* If framesPerCallback = 0, assertion fails in file pa_common/pa_process.c, line 1413: EX. */ + for( config->framesPerCallback = 64; config->framesPerCallback < 129; config->framesPerCallback += 64 ) + { + printf("-----------------------------------------------\n" ); + printf("Configuration #%d\n", configIndex++ ); + err = TestConfiguration( config ); + /* Give user a chance to bail out. */ + if( err == 1 ) + { + err = paNoError; + goto done; + } + else if( err != paNoError ) goto error; + } + } + } + } + } + +done: + Pa_Terminate(); + printf("Full duplex sound test complete.\n"); fflush(stdout); + printf("Hit ENTER to quit.\n"); fflush(stdout); + getchar(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + printf("Hit ENTER to quit.\n"); fflush(stdout); + getchar(); + return -1; +} + +static PaError TestConfiguration( WireConfig_t *config ) +{ + int c; + PaError err; + PaStream *stream; + PaStreamParameters inputParameters, outputParameters; + + printf("input %sinterleaved!\n", (config->isInputInterleaved ? " " : "NOT ") ); + printf("output %sinterleaved!\n", (config->isOutputInterleaved ? " " : "NOT ") ); + printf("input channels = %d\n", config->numInputChannels ); + printf("output channels = %d\n", config->numOutputChannels ); + printf("framesPerCallback = %d\n", config->framesPerCallback ); + + inputParameters.device = INPUT_DEVICE; /* default input device */ + inputParameters.channelCount = config->numInputChannels; + inputParameters.sampleFormat = INPUT_FORMAT | (config->isInputInterleaved ? 0 : paNonInterleaved); + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = OUTPUT_DEVICE; /* default output device */ + outputParameters.channelCount = config->numOutputChannels; + outputParameters.sampleFormat = OUTPUT_FORMAT | (config->isOutputInterleaved ? 0 : paNonInterleaved); + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + SAMPLE_RATE, + config->framesPerCallback, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + wireCallback, + config ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER for next configuration, or 'q' to quit.\n"); fflush(stdout); + c = getchar(); + + printf("Closing stream.\n"); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + if( c == 'q' ) return 1; + +error: + return err; +} diff --git a/portaudio-v19/pa_tests/patest_write_sine.c b/portaudio-v19/pa_tests/patest_write_sine.c new file mode 100644 index 000000000..f0fcba0b6 --- /dev/null +++ b/portaudio-v19/pa_tests/patest_write_sine.c @@ -0,0 +1,147 @@ +/** @file patest_write_sine.c + @brief Play a sine wave for several seconds using the blocking API (Pa_WriteStream()) + @author Ross Bencina + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) + + +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + float buffer[FRAMES_PER_BUFFER][2]; /* stereo output buffer */ + float sine[TABLE_SIZE]; /* sine wavetable */ + int left_phase = 0; + int right_phase = 0; + int left_inc = 1; + int right_inc = 3; /* higher pitch so we can distinguish left and right. */ + int i, j, k; + int bufferCount; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + + printf( "Play 3 times, higher each time.\n" ); + + for( k=0; k < 3; ++k ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + + bufferCount = ((NUM_SECONDS * SAMPLE_RATE) / FRAMES_PER_BUFFER); + + for( i=0; i < bufferCount; i++ ) + { + for( j=0; j < FRAMES_PER_BUFFER; j++ ) + { + buffer[j][0] = sine[left_phase]; /* left */ + buffer[j][1] = sine[right_phase]; /* right */ + left_phase += left_inc; + if( left_phase >= TABLE_SIZE ) left_phase -= TABLE_SIZE; + right_phase += right_inc; + if( right_phase >= TABLE_SIZE ) right_phase -= TABLE_SIZE; + } + + err = Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ); + if( err != paNoError ) goto error; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + ++left_inc; + ++right_inc; + + Pa_Sleep( 1000 ); + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/portaudio-v19/pa_unix/pa_unix_hostapis.c b/portaudio-v19/pa_unix/pa_unix_hostapis.c new file mode 100644 index 000000000..570f596e7 --- /dev/null +++ b/portaudio-v19/pa_unix/pa_unix_hostapis.c @@ -0,0 +1,64 @@ +/* + * $Id$ + * Portable Audio I/O Library UNIX initialization table + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include "pa_hostapi.h" + +PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +/* Added for IRIX, Pieter, oct 2, 2003: */ +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + + +PaUtilHostApiInitializer *paHostApiInitializers[] = + { +#ifdef PA_USE_OSS + PaOSS_Initialize, +#endif + +#ifdef PA_USE_ALSA + PaAlsa_Initialize, +#endif + +#ifdef PA_USE_JACK + PaJack_Initialize, +#endif + /* Added for IRIX, Pieter, oct 2, 2003: */ +#ifdef PA_USE_SGI + PaSGI_Initialize, +#endif + 0 /* NULL terminated array */ + }; + +int paDefaultHostApiIndex = 0; + + diff --git a/portaudio-v19/pa_unix/pa_unix_util.c b/portaudio-v19/pa_unix/pa_unix_util.c new file mode 100644 index 000000000..0aa2d0c65 --- /dev/null +++ b/portaudio-v19/pa_unix/pa_unix_util.c @@ -0,0 +1,192 @@ +/* + * $Id$ + * Portable Audio I/O Library + * UNIX platform-specific support functions + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include +#include +#include +#include +#include +#include +#include /* For memset */ + +#include "pa_util.h" +#include "pa_unix_util.h" + +/* + Track memory allocations to avoid leaks. + */ + +#if PA_TRACK_MEMORY +static int numAllocations_ = 0; +#endif + + +void *PaUtil_AllocateMemory( long size ) +{ + void *result = malloc( size ); + +#if PA_TRACK_MEMORY + if( result != NULL ) numAllocations_ += 1; +#endif + return result; +} + + +void PaUtil_FreeMemory( void *block ) +{ + if( block != NULL ) + { + free( block ); +#if PA_TRACK_MEMORY + numAllocations_ -= 1; +#endif + + } +} + + +int PaUtil_CountCurrentlyAllocatedBlocks( void ) +{ +#if PA_TRACK_MEMORY + return numAllocations_; +#else + return 0; +#endif +} + + +void Pa_Sleep( long msec ) +{ +#ifdef HAVE_NANOSLEEP + struct timespec req = {0}, rem = {0}; + PaTime time = msec / 1.e3; + req.tv_sec = (time_t)time; + assert(time - req.tv_sec < 1.0); + req.tv_nsec = (long)((time - req.tv_sec) * 1.e9); + nanosleep(&req, &rem); + /* XXX: Try sleeping the remaining time (contained in rem) if interrupted by a signal? */ +#else + while( msec > 999 ) /* For OpenBSD and IRIX, argument */ + { /* to usleep must be < 1000000. */ + usleep( 999000 ); + msec -= 999; + } + usleep( msec * 1000 ); +#endif +} + +/* *** NOT USED YET: *** +static int usePerformanceCounter_; +static double microsecondsPerTick_; +*/ + +void PaUtil_InitializeClock( void ) +{ + /* TODO */ +} + + +PaTime PaUtil_GetTime( void ) +{ +#ifdef HAVE_CLOCK_GETTIME + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + return (PaTime)(tp.tv_sec + tp.tv_nsec / 1.e9); +#else + struct timeval tv; + gettimeofday( &tv, NULL ); + return (PaTime) tv.tv_usec / 1000000. + tv.tv_sec; +#endif +} + +PaError PaUtil_InitializeThreading( PaUtilThreading *threading ) +{ + (void) paUtilErr_; + return paNoError; +} + +void PaUtil_TerminateThreading( PaUtilThreading *threading ) +{ +} + +PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data ) +{ + pthread_create( &threading->callbackThread, NULL, threadRoutine, data ); + return paNoError; +} + +PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + if( !wait ) + pthread_cancel( threading->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + pthread_join( threading->callbackThread, &pret ); + +#ifdef PTHREAD_CANCELED + if( pret && PTHREAD_CANCELED != pret ) +#else + /* !wait means the thread may have been canceled */ + if( pret && wait ) +#endif + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + + return result; +} + +/* +static void *CanaryFunc( void *userData ) +{ + const unsigned intervalMsec = 1000; + PaUtilThreading *th = (PaUtilThreading *) userData; + + while( 1 ) + { + th->canaryTime = PaUtil_GetTime(); + + pthread_testcancel(); + Pa_Sleep( intervalMsec ); + } + + pthread_exit( NULL ); +} +*/ diff --git a/portaudio-v19/pa_unix/pa_unix_util.h b/portaudio-v19/pa_unix/pa_unix_util.h new file mode 100644 index 000000000..01dda01e5 --- /dev/null +++ b/portaudio-v19/pa_unix/pa_unix_util.h @@ -0,0 +1,73 @@ +#ifndef PA_UNIX_UTIL_H +#define PA_UNIX_UTIL_H + +#include "pa_cpuload.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#define PA_MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define PA_MAX(x,y) ( (x) > (y) ? (x) : (y) ) + +/* Utilize GCC branch prediction for error tests */ +#if defined __GNUC__ && __GNUC__ >= 3 +#define UNLIKELY(expr) __builtin_expect( (expr), 0 ) +#else +#define UNLIKELY(expr) (expr) +#endif + +#define STRINGIZE_HELPER(expr) #expr +#define STRINGIZE(expr) STRINGIZE_HELPER(expr) + +#define PA_UNLESS(expr, code) \ + do { \ + if( UNLIKELY( (expr) == 0 ) ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while (0); + +static PaError paUtilErr_; /* Used with PA_ENSURE */ + +/* Check PaError */ +#define PA_ENSURE(expr) \ + do { \ + if( UNLIKELY( (paUtilErr_ = (expr)) < paNoError ) ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paUtilErr_; \ + goto error; \ + } \ + } while (0); + +typedef struct { + pthread_t callbackThread; +} PaUtilThreading; + +PaError PaUtil_InitializeThreading( PaUtilThreading *threading ); +void PaUtil_TerminateThreading( PaUtilThreading *threading ); +PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data ); +PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult ); + +/* State accessed by utility functions */ + +/* +void PaUnix_SetRealtimeScheduling( int rt ); + +void PaUtil_InitializeThreading( PaUtilThreading *th, PaUtilCpuLoadMeasurer *clm ); + +PaError PaUtil_CreateCallbackThread( PaUtilThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s ); + +PaError PaUtil_KillCallbackThread( PaUtilThreading *th, PaError *exitResult ); + +void PaUtil_CallbackUpdate( PaUtilThreading *th ); +*/ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/portaudio-v19/pa_unix_oss/low_latency_tip.txt b/portaudio-v19/pa_unix_oss/low_latency_tip.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d982b79baa6d943c1fae0c7122d190ebf46c6f4 GIT binary patch literal 3111 zcmb_e+in}l5zX^_MZtMka>JV_VPg}vVpflr&clN4ctL+YE&U7Sw$Nrvj#V9W!?cY$ zC`VjAku{{~Rhz%cu2sOcwF=P{YCodiN5gbJ8_|3=JEPZ&vp2N-oMqY46z(6RY;Pke z$K-9jli>L7sC9Nzb3>tG>YT;nPVE#zLoZ2p=y`+zuucjt4j znzm*?BwZ#ng2H1zTe6N`Z2Ruw#V&fWO%hDt8*A(&%LaudV*{1eZ3Uktl?S~8iB!hA z4iV=)QKv(NT|$s>@^F8*`uOqY?g59#vK(duiF%}_blL|h_!Ov5SQ!vkvB4hHI>iZ2 zZ@277mB=AIxn5q8_F3z=)O8I;2cDTWT-$Cq%(8oM2{3g{TfO6yivs4LYSN~S3BQd& zKGjIU5>{Id9SD$XKA8uE-U#Q32C}f&fTs)xe=|im9A&2K=wb4PVT7OkZGbtoAj*?b3 z)&=B4jfFNtg;*&23oUg{2ObQGF6fD#vlGlxpq}xv04oqiA>>-9I+1$hd6NAJRRvpk z09K`fJ+qKO;cSaH9tlIqH4n~)^h-u!y#lAcIIx8CX?cBxmn}P}sCj^`ACfMx5>^mA z&l|Ari2eB?2|Sb@FaMThtMVjLMJW7{eR^bPxF!)&tM;S8r^KAiAH}7*pz{}%)#9(Vj-Ggjm=Su)t=X{?UG_?nC7T#B#1!kJEZ|m7JR8qb zB&W=lX?eR`eLn-j=A5*XT1g-+ikZoEJmA5EC6 zRslDV-6O`az2ZDbyKe=hHX!}DzTlP(*gDAP6jnrvo;Q*zkZOD0q~9Kz3t5N|ZbF`Z zkH*;GM0gMlc9f;zgzr@d?P5CZx^7Zy6Q2@A1@9*4fJB1tlf`gB-}pgZ(=xT|Yj9oL zwt+e@Jmlw#kts96Y>^q0#>=R8k+T><*(G6x44v`pyYc)D&CeF+ujw~4JQ~rzO-h+D z{SIuxw)J{?>(KV#S0tO-OFwR;%D)z5GATU$E-g=g?!n_X9uukAhiZaH_+3#2I!Kh` zC$tz3`^S=DiQX(qNc3x)hkCI7@%rlihllI+PY>&x``f$a$ImxE-Ve#gJjcwFwi_x$ z*r_@KlS&1~fwQNx*=+V(8ery2YaM?`=!B@D7)F$Fsw-OiB`hipywcl>a8ro*2Yqdr z@|u-{As|nkv*=}X=+8c4On9hG@FIeiT))Lk{ZQHf-KH%aI6sh~q5O)B3ir8h8i1`NFBGL+-OowG}5Ic&Q9Z3qTP8RN+1(124i>TSCJz5w|)W&`}Qe zslm +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +# include +# include +# define DEVICE_NAME_BASE "/dev/dsp" +#elif defined __linux__ +#include +#include +# include +# define DEVICE_NAME_BASE "/dev/dsp" +#else +# include /* JH20010905 */ +# define DEVICE_NAME_BASE "/dev/audio" +#endif + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "../pa_unix/pa_unix_util.h" + +static int sysErr_; +static pthread_t mainThread_; + +/* Check return value of system call, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (sysErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \ + } \ + \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); + +#ifndef AFMT_S16_NE +#define AFMT_S16_NE Get_AFMT_S16_NE() +/********************************************************************* + * Some versions of OSS do not define AFMT_S16_NE. So check CPU. + * PowerPC is Big Endian. X86 is Little Endian. + */ +static int Get_AFMT_S16_NE( void ) +{ + long testData = 1; + char *ptr = (char *) &testData; + int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ + return isLittle ? AFMT_S16_LE : AFMT_S16_BE; +} +#endif + +/* PaOSSHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaOSSHostApiRepresentation; + +/** Per-direction structure for PaOssStream. + * + * Aspect StreamChannels: In case the user requests to open the same device for both capture and playback, + * but with different number of channels we will have to adapt between the number of user and host + * channels for at least one direction, since the configuration space is the same for both directions + * of an OSS device. + */ +typedef struct +{ + int fd; + const char *devName; + int userChannelCount, hostChannelCount; + int userInterleaved; + void *buffer; + PaSampleFormat userFormat, hostFormat; + double latency; + unsigned long hostFrames, numBufs; + void **userBuffers; /* For non-interleaved blocking */ +} PaOssStreamComponent; + +/** Implementation specific representation of a PaStream. + * + */ +typedef struct PaOssStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaUtilThreading threading; + + int sharedDevice; + unsigned long framesPerHostBuffer; + int triggered; /* Have the devices been triggered yet (first start) */ + + int isActive; + int isStopped; + + int lastPosPtr; + double lastStreamBytes; + + int framesProcessed; + + double sampleRate; + + int callbackMode; + int callbackStop, callbackAbort; + + PaOssStreamComponent *capture, *playback; + unsigned long pollTimeout; + sem_t semaphore; +} +PaOssStream; + +typedef enum { + StreamMode_In, + StreamMode_Out +} StreamMode; + +/* prototypes for functions declared in this file */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); +static PaError BuildDeviceList( PaOSSHostApiRepresentation *hostApi ); + + +/** Initialize the OSS API implementation. + * + * This function will initialize host API datastructures and query host devices for information. + * + * Aspect DeviceCapabilities: Enumeration of host API devices is initiated from here + * + * Aspect FreeResources: If an error is encountered under way we have to free each resource allocated in this function, + * this happens with the usual "error" label. + */ +PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = NULL; + + PA_UNLESS( ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) ), + paInsufficientMemory ); + PA_UNLESS( ossHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + ossHostApi->hostApiIndex = hostApiIndex; + + /* Initialize host API structure */ + *hostApi = &ossHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paOSS; + (*hostApi)->info.name = "OSS"; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PA_ENSURE( BuildDeviceList( ossHostApi ) ); + + PaUtil_InitializeStreamInterface( &ossHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &ossHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + mainThread_ = pthread_self(); + + return result; + +error: + if( ossHostApi ) + { + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); + } + return result; +} + +PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, PaHostApiIndex hostApiIndex, int maxInputChannels, + int maxOutputChannels, PaTime defaultLowInputLatency, PaTime defaultLowOutputLatency, PaTime defaultHighInputLatency, + PaTime defaultHighOutputLatency, double defaultSampleRate, PaUtilAllocationGroup *allocations ) +{ + PaError result = paNoError; + + deviceInfo->structVersion = 2; + if( allocations ) + { + size_t len = strlen( name ) + 1; + PA_UNLESS( deviceInfo->name = PaUtil_GroupAllocateMemory( allocations, len ), paInsufficientMemory ); + strncpy( (char *)deviceInfo->name, name, len ); + } + else + deviceInfo->name = name; + + deviceInfo->hostApi = hostApiIndex; + deviceInfo->maxInputChannels = maxInputChannels; + deviceInfo->maxOutputChannels = maxOutputChannels; + deviceInfo->defaultLowInputLatency = defaultLowInputLatency; + deviceInfo->defaultLowOutputLatency = defaultLowOutputLatency; + deviceInfo->defaultHighInputLatency = defaultHighInputLatency; + deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency; + deviceInfo->defaultSampleRate = defaultSampleRate; + +error: + return result; +} + +static PaError QueryDirection( const char *deviceName, StreamMode mode, double *defaultSampleRate, int *maxChannelCount, + double *defaultLowLatency, double *defaultHighLatency ) +{ + PaError result = paNoError; + int numChannels, maxNumChannels; + int busy = 0; + int devHandle = -1; + int sr; + *maxChannelCount = 0; /* Default value in case this fails */ + + if ( (devHandle = open( deviceName, (mode == StreamMode_In ? O_RDONLY : O_WRONLY) | O_NONBLOCK )) < 0 ) + { + if( errno == EBUSY || errno == EAGAIN ) + { + PA_DEBUG(( "%s: Device %s busy\n", __FUNCTION__, deviceName )); + } + else + { + PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) )); + } + + return paDeviceUnavailable; + } + + /* Negotiate for the maximum number of channels for this device. PLB20010927 + * Consider up to 16 as the upper number of channels. + * Variable maxNumChannels should contain the actual upper limit after the call. + * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. + */ + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + if( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ) < 0 ) + { + busy = EAGAIN == errno || EBUSY == errno; + /* ioctl() failed so bail out if we already have stereo */ + if( maxNumChannels >= 2 ) + break; + } + else + { + /* ioctl() worked but bail out if it does not support numChannels. + * We don't want to leave gaps in the numChannels supported. + */ + if( (numChannels > 2) && (temp != numChannels) ) + break; + if( temp > maxNumChannels ) + maxNumChannels = temp; /* Save maximum. */ + } + } + /* A: We're able to open a device for capture if it's busy playing back and vice versa, + * but we can't configure anything */ + if( 0 == maxNumChannels && busy ) + { + result = paDeviceUnavailable; + goto error; + } + + /* The above negotiation may fail for an old driver so try this older technique. */ + if( maxNumChannels < 1 ) + { + int stereo = 1; + if( ioctl( devHandle, SNDCTL_DSP_STEREO, &stereo ) < 0 ) + { + maxNumChannels = 1; + } + else + { + maxNumChannels = (stereo) ? 2 : 1; + } + PA_DEBUG(( "%s: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", __FUNCTION__, maxNumChannels )) + } + + /* During channel negotiation, the last ioctl() may have failed. This can + * also cause sample rate negotiation to fail. Hence the following, to return + * to a supported number of channels. SG20011005 */ + { + /* use most reasonable default value */ + int temp = PA_MIN( maxNumChannels, 2 ); + ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ), paUnanticipatedHostError ); + } + + /* Get supported sample rate closest to 44100 Hz */ + if( *defaultSampleRate < 0 ) + { + sr = 44100; + if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 ) + { + result = paUnanticipatedHostError; + goto error; + } + + *defaultSampleRate = sr; + } + + *maxChannelCount = maxNumChannels; + /* TODO */ + *defaultLowLatency = 512. / *defaultSampleRate; + *defaultHighLatency = 2048. / *defaultSampleRate; + +error: + if( devHandle >= 0 ) + close( devHandle ); + + return result; +} + +/** Query OSS device. + * + * This is where PaDeviceInfo objects are constructed and filled in with relevant information. + * + * Aspect DeviceCapabilities: The inferred device capabilities are recorded in a PaDeviceInfo object that is constructed + * in place. + */ +static PaError QueryDevice( char *deviceName, PaOSSHostApiRepresentation *ossApi, PaDeviceInfo **deviceInfo ) +{ + PaError result = paNoError; + double sampleRate = -1.; + int maxInputChannels, maxOutputChannels; + PaTime defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency; + PaError tmpRes = paNoError; + int busy = 0; + *deviceInfo = NULL; + + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settins. + e.g. a card might give you stereo at 22kHz but only mono at 44kHz. + the correct order for OSS is: format, channels, sample rate + */ + + /* Aspect StreamChannels: The number of channels supported for a device may depend on the mode it is + * opened in, it may have more channels available for capture than playback and vice versa. Therefore + * we will open the device in both read- and write-only mode to determine the supported number. + */ + if( (tmpRes = QueryDirection( deviceName, StreamMode_In, &sampleRate, &maxInputChannels, &defaultLowInputLatency, + &defaultHighInputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for capture failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + if( (tmpRes = QueryDirection( deviceName, StreamMode_Out, &sampleRate, &maxOutputChannels, &defaultLowOutputLatency, + &defaultHighOutputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for playback failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + assert( 0 <= busy && busy <= 2 ); + if( 2 == busy ) /* Both directions are unavailable to us */ + { + result = paDeviceUnavailable; + goto error; + } + + PA_UNLESS( *deviceInfo = PaUtil_GroupAllocateMemory( ossApi->allocations, sizeof (PaDeviceInfo) ), paInsufficientMemory ); + PA_ENSURE( PaUtil_InitializeDeviceInfo( *deviceInfo, deviceName, ossApi->hostApiIndex, maxInputChannels, maxOutputChannels, + defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency, sampleRate, + ossApi->allocations ) ); + +error: + return result; +} + +/** Query host devices. + * + * Loop over host devices and query their capabilitiesu + * + * Aspect DeviceCapabilities: This function calls QueryDevice on each device entry and receives a filled in PaDeviceInfo object + * per device, these are placed in the host api representation's deviceInfos array. + */ +static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *commonApi = &ossApi->inheritedHostApiRep; + int i; + int numDevices = 0, maxDeviceInfos = 1; + PaDeviceInfo **deviceInfos = NULL; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* Find devices by calling QueryDevice on each + * potential device names. When we find a valid one, + * add it to a linked list. + * A: Can there only be 10 devices? */ + + for( i = 0; i < 10; i++ ) + { + char deviceName[32]; + PaDeviceInfo *deviceInfo; + int testResult; + struct stat stbuf; + + if( i == 0 ) + snprintf(deviceName, sizeof (deviceName), "%s", DEVICE_NAME_BASE); + else + snprintf(deviceName, sizeof (deviceName), "%s%d", DEVICE_NAME_BASE, i); + + /* PA_DEBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); */ + if( stat( deviceName, &stbuf ) < 0 ) + { + if( ENOENT != errno ) + PA_DEBUG(( "%s: Error stat'ing %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) )); + continue; + } + if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError ) + { + if( testResult != paDeviceUnavailable ) + PA_ENSURE( testResult ); + + continue; + } + + ++numDevices; + if( !deviceInfos || numDevices > maxDeviceInfos ) + { + maxDeviceInfos *= 2; + PA_UNLESS( deviceInfos = (PaDeviceInfo **) realloc( deviceInfos, maxDeviceInfos * sizeof (PaDeviceInfo *) ), + paInsufficientMemory ); + } + deviceInfos[numDevices - 1] = deviceInfo; + + if( commonApi->info.defaultInputDevice == paNoDevice && deviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = i; + if( commonApi->info.defaultOutputDevice == paNoDevice && deviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = i; + } + + /* Make an array of PaDeviceInfo pointers out of the linked list */ + + PA_DEBUG(("PaOSS %s: Total number of devices found: %d\n", __FUNCTION__, numDevices)); + + commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + ossApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + memcpy( commonApi->deviceInfos, deviceInfos, numDevices * sizeof (PaDeviceInfo *) ); + + commonApi->info.deviceCount = numDevices; + +error: + free( deviceInfos ); + + return result; +} + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result = paNoError; + PaDeviceIndex device; + PaDeviceInfo *deviceInfo; + char *deviceName; + int inputChannelCount, outputChannelCount; + int tempDevHandle = -1; + int flags; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + if (inputChannelCount == 0 && outputChannelCount == 0) + return paInvalidChannelCount; + + /* if full duplex, make sure that they're the same device */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputParameters->device != outputParameters->device) + return paInvalidDevice; + + /* if full duplex, also make sure that they're the same number of channels */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputChannelCount != outputChannelCount) + return paInvalidChannelCount; + + /* open the device so we can do more tests */ + + if( inputChannelCount > 0 ) + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, inputParameters->device, hostApi); + if (result != paNoError) + return result; + } + else + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, outputParameters->device, hostApi); + if (result != paNoError) + return result; + } + + deviceInfo = hostApi->deviceInfos[device]; + deviceName = (char *)deviceInfo->name; + + flags = O_NONBLOCK; + if (inputChannelCount > 0 && outputChannelCount > 0) + flags |= O_RDWR; + else if (inputChannelCount > 0) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + ENSURE_( tempDevHandle = open( deviceInfo->name, flags ), paDeviceUnavailable ); + + /* PaOssStream_Configure will do the rest of the checking for us */ + /* PA_ENSURE( PaOssStream_Configure( tempDevHandle, deviceName, outputChannelCount, &sampleRate ) ); */ + + /* everything succeeded! */ + + error: + if( tempDevHandle >= 0 ) + close( tempDevHandle ); + + return result; +} + +/** Validate stream parameters. + * + * Aspect StreamChannels: We verify that the number of channels is within the allowed range for the device + */ +static PaError ValidateParameters( const PaStreamParameters *parameters, const PaDeviceInfo *deviceInfo, StreamMode mode ) +{ + int maxChans; + + assert( parameters ); + + if( parameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + maxChans = (mode == StreamMode_In ? deviceInfo->maxInputChannels : + deviceInfo->maxOutputChannels); + if( parameters->channelCount > maxChans ) + { + return paInvalidChannelCount; + } + + return paNoError; +} + +static PaError PaOssStreamComponent_Initialize( PaOssStreamComponent *component, const PaStreamParameters *parameters, + int callbackMode, int fd, const char *deviceName ) +{ + PaError result = paNoError; + assert( component ); + + memset( component, 0, sizeof (PaOssStreamComponent) ); + + component->fd = fd; + component->devName = deviceName; + component->userChannelCount = parameters->channelCount; + component->userFormat = parameters->sampleFormat; + component->latency = parameters->suggestedLatency; + component->userInterleaved = !(parameters->sampleFormat & paNonInterleaved); + + if( !callbackMode && !component->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( component->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * component->userChannelCount ), + paInsufficientMemory ); + } + +error: + return result; +} + +static void PaOssStreamComponent_Terminate( PaOssStreamComponent *component ) +{ + assert( component ); + + if( component->fd >= 0 ) + close( component->fd ); + if( component->buffer ) + PaUtil_FreeMemory( component->buffer ); + + if( component->userBuffers ) + PaUtil_FreeMemory( component->userBuffers ); + + PaUtil_FreeMemory( component ); +} + +static PaError ModifyBlocking( int fd, int blocking ) +{ + PaError result = paNoError; + int fflags; + + ENSURE_( fflags = fcntl( fd, F_GETFL ), paUnanticipatedHostError ); + + if( blocking ) + fflags &= ~O_NONBLOCK; + else + fflags |= O_NONBLOCK; + + ENSURE_( fcntl( fd, F_SETFL, fflags ), paUnanticipatedHostError ); + +error: + return result; +} + +static PaError OpenDevices( const char *idevName, const char *odevName, int *idev, int *odev ) +{ + PaError result = paNoError; + int flags = O_NONBLOCK, duplex = 0; + int enableBits = 0; + *idev = *odev = -1; + + if( idevName && odevName ) + { + duplex = 1; + flags |= O_RDWR; + } + else if( idevName ) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + /* open first in nonblocking mode, in case it's busy... + * A: then unset the non-blocking attribute */ + assert( flags & O_NONBLOCK ); + if( idevName ) + { + ENSURE_( *idev = open( idevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *idev, 1 ) ); /* Blocking */ + + /* Initially disable */ + enableBits = ~PCM_ENABLE_INPUT; + ENSURE_( ioctl( *idev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( odevName ) + { + if( !idevName ) + { + ENSURE_( *odev = open( odevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *odev, 1 ) ); /* Blocking */ + + /* Initially disable */ + enableBits = ~PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( *odev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + ENSURE_( *odev = dup( *idev ), paUnanticipatedHostError ); + } + } + +error: + return result; +} + +static PaError PaOssStream_Initialize( PaOssStream *stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, + PaStreamCallback callback, void *userData, PaStreamFlags streamFlags, + PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + int idev, odev; + PaUtilHostApiRepresentation *hostApi = &ossApi->inheritedHostApiRep; + const char *idevName = NULL, *odevName = NULL; + + assert( stream ); + + memset( stream, 0, sizeof (PaOssStream) ); + stream->isStopped = 1; + + PA_ENSURE( PaUtil_InitializeThreading( &stream->threading ) ); + + if( inputParameters && outputParameters ) + { + if( inputParameters->device == outputParameters->device ) + stream->sharedDevice = 1; + } + + if( inputParameters ) + idevName = hostApi->deviceInfos[inputParameters->device]->name; + if( outputParameters ) + odevName = hostApi->deviceInfos[outputParameters->device]->name; + PA_ENSURE( OpenDevices( idevName, odevName, &idev, &odev ) ); + if( inputParameters ) + { + PA_UNLESS( stream->capture = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->capture, inputParameters, callback != NULL, idev, idevName ) ); + } + if( outputParameters ) + { + PA_UNLESS( stream->playback = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->playback, outputParameters, callback != NULL, odev, odevName ) ); + } + + if( callback != NULL ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->callbackStreamInterface, callback, userData ); + stream->callbackMode = 1; + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->blockingStreamInterface, callback, userData ); + } + + ENSURE_( sem_init( &stream->semaphore, 0, 0 ), paInternalError ); + +error: + return result; +} + +static void PaOssStream_Terminate( PaOssStream *stream ) +{ + assert( stream ); + + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_TerminateThreading( &stream->threading ); + + if( stream->capture ) + PaOssStreamComponent_Terminate( stream->capture ); + if( stream->playback ) + PaOssStreamComponent_Terminate( stream->playback ); + + sem_destroy( &stream->semaphore ); + + PaUtil_FreeMemory( stream ); +} + +/** Translate from PA format to OSS native. + * + */ +static PaError Pa2OssFormat( PaSampleFormat paFormat, int *ossFormat ) +{ + switch( paFormat ) + { + case paUInt8: + *ossFormat = AFMT_U8; + break; + case paInt8: + *ossFormat = AFMT_S8; + break; + case paInt16: + *ossFormat = AFMT_S16_NE; + break; + default: + return paInternalError; /* This shouldn't happen */ + } + + return paNoError; +} + +/** Return the PA-compatible formats that this device can support. + * + */ +static PaError GetAvailableFormats( PaOssStreamComponent *component, PaSampleFormat *availableFormats ) +{ + PaError result = paNoError; + int mask = 0; + PaSampleFormat frmts = 0; + + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETFMTS, &mask ), paUnanticipatedHostError ); + if( mask & AFMT_U8 ) + frmts |= paUInt8; + if( mask & AFMT_S8 ) + frmts |= paInt8; + if( mask & AFMT_S16_NE ) + frmts |= paInt16; + else + result = paSampleFormatNotSupported; + + *availableFormats = frmts; + +error: + return result; +} + +static unsigned int PaOssStreamComponent_FrameSize( PaOssStreamComponent *component ) +{ + return Pa_GetSampleSize( component->hostFormat ) * component->hostChannelCount; +} + +/** Buffer size in bytes. + * + */ +static unsigned long PaOssStreamComponent_BufferSize( PaOssStreamComponent *component ) +{ + return PaOssStreamComponent_FrameSize( component ) * component->hostFrames * component->numBufs; +} + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1<userChannelCount; + int frgmt; + int numBufs; + int bytesPerBuf; + double bufSz; + unsigned long fragSz; + audio_buf_info bufInfo; + + /* We may have a situation where only one component (the master) is configured, if both point to the same device. + * In that case, the second component will copy settings from the other */ + if( !master ) + { + /* Aspect BufferSettings: If framesPerBuffer is unspecified we have to infer a suitable fragment size. + * The hardware need not respect the requested fragment size, so we may have to adapt. + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + bufSz = component->latency * sampleRate; + fragSz = bufSz / 4; + } + else + { + fragSz = framesPerBuffer; + bufSz = component->latency * sampleRate + fragSz; /* Latency + 1 buffer */ + } + + PA_ENSURE( GetAvailableFormats( component, &availableFormats ) ); + hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, component->userFormat ); + + /* OSS demands at least 2 buffers, and 16 bytes per buffer */ + numBufs = PA_MAX( bufSz / fragSz, 2 ); + bytesPerBuf = PA_MAX( fragSz * Pa_GetSampleSize( hostFormat ) * chans, 16 ); + + /* The fragment parameters are encoded like this: + * Most significant byte: number of fragments + * Least significant byte: exponent of fragment size (i.e., for 256, 8) + */ + frgmt = (numBufs << 16) + (CalcHigherLogTwo( bytesPerBuf ) & 0xffff); + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFRAGMENT, &frgmt ), paUnanticipatedHostError ); + + /* A: according to the OSS programmer's guide parameters should be set in this order: + * format, channels, rate */ + + /* This format should be deemed good before we get this far */ + PA_ENSURE( Pa2OssFormat( hostFormat, &temp ) ); + nativeFormat = temp; + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFMT, &temp ), paUnanticipatedHostError ); + PA_UNLESS( temp == nativeFormat, paInternalError ); + + /* try to set the number of channels */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_CHANNELS, &chans ), paSampleFormatNotSupported ); /* XXX: Should be paInvalidChannelCount? */ + /* It's possible that the minimum number of host channels is greater than what the user requested */ + PA_UNLESS( chans >= component->userChannelCount, paInvalidChannelCount ); + + /* try to set the sample rate */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SPEED, &sr ), paInvalidSampleRate ); + + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( sampleRate - sr ) / sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( ioctl( component->fd, streamMode == StreamMode_In ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &bufInfo ), + paUnanticipatedHostError ); + component->numBufs = bufInfo.fragstotal; + + /* This needs to be the last ioctl call before the first read/write, according to the OSS programmer's guide */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETBLKSIZE, &bytesPerBuf ), paUnanticipatedHostError ); + + component->hostFrames = bytesPerBuf / Pa_GetSampleSize( hostFormat ) / chans; + component->hostChannelCount = chans; + component->hostFormat = hostFormat; + } + else + { + component->hostFormat = master->hostFormat; + component->hostFrames = master->hostFrames; + component->hostChannelCount = master->hostChannelCount; + component->numBufs = master->numBufs; + } + + PA_UNLESS( component->buffer = PaUtil_AllocateMemory( PaOssStreamComponent_BufferSize( component ) ), + paInsufficientMemory ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Read( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesRead; + + ENSURE_( bytesRead = read( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesRead / PaOssStreamComponent_FrameSize( component ); + /* TODO: Handle condition where number of frames read doesn't equal number of frames requested */ + +error: + return result; +} + +static PaError PaOssStreamComponent_Write( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesWritten; + + ENSURE_( bytesWritten = write( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesWritten / PaOssStreamComponent_FrameSize( component ); + /* TODO: Handle condition where number of frames written doesn't equal number of frames requested */ + +error: + return result; +} + +/** Configure the stream according to input/output parameters. + * + * Aspect StreamChannels: The minimum number of channels supported by the device may exceed that requested by + * the user, if so we'll record the actual number of host channels and adapt later. + */ +static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, unsigned long framesPerBuffer, + double *inputLatency, double *outputLatency ) +{ + PaError result = paNoError; + int duplex = stream->capture && stream->playback; + unsigned long framesPerHostBuffer = 0; + + /* We should request full duplex first thing after opening the device */ + if( duplex && stream->sharedDevice ) + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETDUPLEX, 0 ), paUnanticipatedHostError ); + + if( stream->capture ) + { + PaOssStreamComponent *component = stream->capture; + PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_In, NULL ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *inputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + if( stream->playback ) + { + PaOssStreamComponent *component = stream->playback, *master = stream->sharedDevice ? stream->capture : NULL; + PA_ENSURE( PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_Out, + master ) ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *outputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + + if( duplex ) + framesPerHostBuffer = PA_MIN( stream->capture->hostFrames, stream->playback->hostFrames ); + else if( stream->capture ) + framesPerHostBuffer = stream->capture->hostFrames; + else if( stream->playback ) + framesPerHostBuffer = stream->playback->hostFrames; + + stream->framesPerHostBuffer = framesPerHostBuffer; + stream->pollTimeout = (int) ceil( 1e6 * framesPerHostBuffer / sampleRate ); /* Period in usecs, rounded up */ + + stream->sampleRate = stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + +error: + return result; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +/** Open a PA OSS stream. + * + * Aspect StreamChannels: The number of channels is specified per direction (in/out), and can differ between the + * two. However, OSS doesn't support separate configuration spaces for capture and playback so if both + * directions are the same device we will demand the same number of channels. The number of channels can range + * from 1 to the maximum supported by the device. + * + * Aspect BufferSettings: If framesPerBuffer != paFramesPerBufferUnspecified the number of frames per callback + * must reflect this, in addition the host latency per device should approximate the corresponding + * suggestedLatency. Based on these constraints we need to determine a number of frames per host buffer that + * both capture and playback can agree on (they can be different devices), the buffer processor can adapt + * between host and user buffer size, but the ratio should preferably be integral. + */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + PaOssStream *stream = NULL; + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0, inputHostFormat = 0, outputHostFormat = 0; + const PaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; + int bpInitialized = 0; + double inLatency, outLatency; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + if( inputParameters ) + { + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + inputDeviceInfo = hostApi->deviceInfos[inputParameters->device]; + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, StreamMode_In ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + outputDeviceInfo = hostApi->deviceInfos[outputParameters->device]; + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, StreamMode_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* Aspect StreamChannels: We currently demand that number of input and output channels are the same, if the same + * device is opened for both directions + */ + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + if( inputParameters->device == outputParameters->device ) + { + if( inputParameters->channelCount != outputParameters->channelCount ) + return paInvalidChannelCount; + } + } + + /* allocate and do basic initialization of the stream structure */ + PA_UNLESS( stream = (PaOssStream*)PaUtil_AllocateMemory( sizeof(PaOssStream) ), paInsufficientMemory ); + PA_ENSURE( PaOssStream_Initialize( stream, inputParameters, outputParameters, streamCallback, userData, streamFlags, ossHostApi ) ); + + PA_ENSURE( PaOssStream_Configure( stream, sampleRate, framesPerBuffer, &inLatency, &outLatency ) ); + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + if( inputParameters ) + { + inputHostFormat = stream->capture->hostFormat; + stream->streamRepresentation.streamInfo.inputLatency = inLatency + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate; + } + if( outputParameters ) + { + outputHostFormat = stream->playback->hostFormat; + stream->streamRepresentation.streamInfo.outputLatency = outLatency + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate; + } + + /* Initialize buffer processor with fixed host buffer size. + * Aspect StreamSampleFormat: Here we commit the user and host sample formats, PA infrastructure will + * convert between the two. + */ + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, inputHostFormat, outputChannelCount, outputSampleFormat, + outputHostFormat, sampleRate, streamFlags, framesPerBuffer, stream->framesPerHostBuffer, + paUtilFixedHostBufferSize, streamCallback, userData ) ); + bpInitialized = 1; + + *s = (PaStream*)stream; + + return result; + +error: + if( bpInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( stream ) + PaOssStream_Terminate( stream ); + + return result; +} + +/*! Poll on I/O filedescriptors. + + Poll till we've determined there's data for read or write. In the full-duplex case, + we don't want to hang around forever waiting for either input or output frames, so + whenever we have a timed out filedescriptor we check if we're nearing under/overrun + for the other direction (critical limit set at one buffer). If so, we exit the waiting + state, and go on with what we got. We align the number of frames on a host buffer + boundary because it is possible that the buffer size differs for the two directions and + the host buffer size is a compromise between the two. + */ +static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *frames ) +{ + PaError result = paNoError; + int pollPlayback = 0, pollCapture = 0; + int captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; + audio_buf_info bufInfo; + /* int ofs = 0, nfds = stream->nfds; */ + fd_set readFds, writeFds; + int nfds = 0; + struct timeval selectTimeval = {0, 0}; + unsigned long timeout = stream->pollTimeout; /* In usecs */ + int captureFd = -1, playbackFd = -1; + + assert( stream ); + assert( frames ); + + if( stream->capture ) + { + pollCapture = 1; + captureFd = stream->capture->fd; + /* stream->capture->pfd->events = POLLIN; */ + } + if( stream->playback ) + { + pollPlayback = 1; + playbackFd = stream->playback->fd; + /* stream->playback->pfd->events = POLLOUT; */ + } + + FD_ZERO( &readFds ); + FD_ZERO( &writeFds ); + + while( pollPlayback || pollCapture ) + { + pthread_testcancel(); + + /* select may modify the timeout parameter */ + selectTimeval.tv_usec = timeout; + nfds = 0; + + if( pollCapture ) + { + FD_SET( captureFd, &readFds ); + nfds = captureFd + 1; + } + if( pollPlayback ) + { + FD_SET( playbackFd, &writeFds ); + nfds = PA_MAX( nfds, playbackFd + 1 ); + } + ENSURE_( select( nfds, &readFds, &writeFds, NULL, &selectTimeval ), paUnanticipatedHostError ); + /* + if( poll( stream->pfds + ofs, nfds, stream->pollTimeout ) < 0 ) + { + + ENSURE_( -1, paUnanticipatedHostError ); + } + */ + pthread_testcancel(); + + if( pollCapture ) + { + if( FD_ISSET( captureFd, &readFds ) ) + { + FD_CLR( captureFd, &readFds ); + pollCapture = 0; + } + /* + if( stream->capture->pfd->revents & POLLIN ) + { + --nfds; + ++ofs; + pollCapture = 0; + } + */ + else if( stream->playback ) /* Timed out, go on with playback? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for capture frames, pollTimeout: %d\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + if( pollPlayback ) + { + if( FD_ISSET( playbackFd, &writeFds ) ) + { + FD_CLR( playbackFd, &writeFds ); + pollPlayback = 0; + } + /* + if( stream->playback->pfd->revents & POLLOUT ) + { + --nfds; + pollPlayback = 0; + } + */ + else if( stream->capture ) /* Timed out, go on with capture? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for playback frames, pollTimeout: %d\n\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + } + + if( stream->capture ) + { + ENSURE_( ioctl( captureFd, SNDCTL_DSP_GETISPACE, &bufInfo ), paUnanticipatedHostError ); + captureAvail = bufInfo.fragments * stream->capture->hostFrames; + if( !captureAvail ) + PA_DEBUG(( "%s: captureAvail: 0\n", __FUNCTION__ )); + + captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ + } + if( stream->playback ) + { + ENSURE_( ioctl( playbackFd, SNDCTL_DSP_GETOSPACE, &bufInfo ), paUnanticipatedHostError ); + playbackAvail = bufInfo.fragments * stream->playback->hostFrames; + if( !playbackAvail ) + { + PA_DEBUG(( "%s: playbackAvail: 0\n", __FUNCTION__ )); + } + + playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ + } + + commonAvail = PA_MIN( captureAvail, playbackAvail ); + if( commonAvail == INT_MAX ) + commonAvail = 0; + commonAvail -= commonAvail % stream->framesPerHostBuffer; + + assert( commonAvail != INT_MAX ); + assert( commonAvail >= 0 ); + *frames = commonAvail; + +error: + return result; +} + +/** Prepare stream for capture/playback. + * + * In order to synchronize capture and playback properly we use the SETTRIGGER command. + */ +static PaError PaOssStream_Prepare( PaOssStream *stream ) +{ + PaError result = paNoError; + int enableBits = 0; + + if( stream->triggered ) + return result; + + if( stream->playback ) + { + size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback ); + memset( stream->playback->buffer, 0, bufSz ); + + /* Looks like we have to turn off blocking before we try this, but if we don't fill the buffer + * OSS will complain. */ + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + while (1) + { + if( write( stream->playback->fd, stream->playback->buffer, bufSz ) < 0 ) + break; + } + PA_ENSURE( ModifyBlocking( stream->playback->fd, 1 ) ); + } + + if( stream->sharedDevice ) + { + enableBits = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + if( stream->capture ) + { + enableBits = PCM_ENABLE_INPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( stream->playback ) + { + enableBits = PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + } + + /* Ok, we have triggered the stream */ + stream->triggered = 1; + +error: + return result; +} + +/** Stop audio processing + * + */ +static PaError PaOssStream_Stop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; + + /* Looks like the only safe way to stop audio without reopening the device is SNDCTL_DSP_POST. + * Also disable capture/playback till the stream is started again */ + if( stream->capture ) + { + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + if( stream->playback && !stream->sharedDevice ) + { + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + +error: + return result; +} + +/** Clean up after thread exit. + * + * Aspect StreamState: If the user has registered a streamFinishedCallback it will be called here + */ +static void OnExit( void *data ) +{ + PaOssStream *stream = (PaOssStream *) data; + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + PaOssStream_Stop( stream, stream->callbackAbort ); + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + stream->callbackAbort = 0; /* Clear state */ + stream->isActive = 0; +} + +static PaError SetUpBuffers( PaOssStream *stream, unsigned long framesAvail ) +{ + PaError result = paNoError; + + if( stream->capture ) + { + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, + stream->capture->hostChannelCount ); + PaUtil_SetInputFrameCount( &stream->bufferProcessor, framesAvail ); + } + if( stream->playback ) + { + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, + stream->playback->hostChannelCount ); + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, framesAvail ); + } + + return result; +} + +/** Thread procedure for callback processing. + * + * Aspect StreamState: StartStream will wait on this to initiate audio processing, useful in case the + * callback should be used for buffer priming. When the stream is cancelled a separate function will + * take care of the transition to the Callback Finished state (the stream isn't considered Stopped + * before StopStream() or AbortStream() are called). + */ +static void *PaOSS_AudioThreadProc( void *userData ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)userData; + unsigned long framesAvail, framesProcessed; + int callbackResult = paContinue; + int triggered = stream->triggered; /* See if SNDCTL_DSP_TRIGGER has been issued already */ + int initiateProcessing = triggered; /* Already triggered? */ + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + + /* +#if ( SOUND_VERSION > 0x030904 ) + audio_errinfo errinfo; +#endif +*/ + + assert( stream ); + + pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + + /* The first time the stream is started we use SNDCTL_DSP_TRIGGER to accurately start capture and + * playback in sync, when the stream is restarted after being stopped we simply start by reading/ + * writing. + */ + PA_ENSURE( PaOssStream_Prepare( stream ) ); + + /* If we are to initiate processing implicitly by reading/writing data, we start off in blocking mode */ + if( initiateProcessing ) + { + /* Make sure devices are in blocking mode */ + if( stream->capture ) + ModifyBlocking( stream->capture->fd, 1 ); + if( stream->playback ) + ModifyBlocking( stream->playback->fd, 1 ); + } + + while( 1 ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* TODO: IMPLEMENT ME */ + + pthread_testcancel(); + + if( stream->callbackStop && callbackResult == paContinue ) + { + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; + } + + /* Aspect StreamState: Because of the messy OSS scheme we can't explicitly trigger device start unless + * the stream has been recently started, we will have to go right ahead and read/write in blocking + * fashion to trigger operation. Therefore we begin with processing one host buffer before we switch + * to non-blocking mode. + */ + if( !initiateProcessing ) + { + PA_ENSURE( PaOssStream_WaitForFrames( stream, &framesAvail ) ); /* Wait on available frames */ + assert( framesAvail % stream->framesPerHostBuffer == 0 ); + } + else + { + framesAvail = stream->framesPerHostBuffer; + } + + while( framesAvail > 0 ) + { + unsigned long frames = framesAvail; + + pthread_testcancel(); + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* Read data */ + if ( stream->capture ) + { + PA_ENSURE( PaOssStreamComponent_Read( stream->capture, &frames ) ); + assert( frames == framesAvail ); + } + +#if ( SOUND_VERSION >= 0x030904 ) + /* + Check with OSS to see if there have been any under/overruns + since last time we checked. + */ + /* + if( ioctl( stream->deviceHandle, SNDCTL_DSP_GETERROR, &errinfo ) >= 0 ) + { + if( errinfo.play_underruns ) + cbFlags |= paOutputUnderflow ; + if( errinfo.record_underruns ) + cbFlags |= paInputUnderflow ; + } + else + PA_DEBUG(( "SNDCTL_DSP_GETERROR command failed: %s\n", strerror( errno ) )); + */ +#endif + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + cbFlags = 0; + PA_ENSURE( SetUpBuffers( stream, framesAvail ) ); + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + assert( framesProcessed == framesAvail ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if ( stream->playback ) + { + frames = framesAvail; + + PA_ENSURE( PaOssStreamComponent_Write( stream->playback, &frames ) ); + assert( frames == framesAvail ); + + /* TODO: handle bytesWritten != bytesRequested (slippage?) */ + } + + framesAvail -= framesProcessed; + stream->framesProcessed += framesProcessed; + + if( callbackResult != paContinue ) + break; + } + + if( initiateProcessing || !triggered ) + { + /* Non-blocking */ + if( stream->capture ) + PA_ENSURE( ModifyBlocking( stream->capture->fd, 0 ) ); + if( stream->playback && !stream->sharedDevice ) + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + + initiateProcessing = 0; + sem_post( &stream->semaphore ); + } + + if( callbackResult != paContinue ) + { + stream->callbackAbort = callbackResult == paAbort; + if( stream->callbackAbort || PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + break; + } + } + + pthread_cleanup_pop( 1 ); + +error: + pthread_exit( NULL ); +} + +/** Close the stream. + * + */ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + assert( stream ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaOssStream_Terminate( stream ); + + return result; +} + +/** Start the stream. + * + * Aspect StreamState: After returning, the stream shall be in the Active state, implying that an eventual + * callback will be repeatedly called in a separate thread. If a separate thread is started this function + * will block untill it has started processing audio, otherwise audio processing is started directly. + */ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + stream->isActive = 1; + stream->isStopped = 0; + stream->lastPosPtr = 0; + stream->lastStreamBytes = 0; + stream->framesProcessed = 0; + + /* only use the thread for callback streams */ + if( stream->bufferProcessor.streamCallback ) + { + PA_ENSURE( PaUtil_StartThreading( &stream->threading, &PaOSS_AudioThreadProc, stream ) ); + sem_wait( &stream->semaphore ); + } + else + PA_ENSURE( PaOssStream_Prepare( stream ) ); + +error: + return result; +} + +static PaError RealStop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; + + if( stream->callbackMode ) + { + if( abort ) + stream->callbackAbort = 1; + else + stream->callbackStop = 1; + + PA_ENSURE( PaUtil_CancelThreading( &stream->threading, !abort, NULL ) ); + + stream->callbackStop = stream->callbackAbort = 0; + } + else + PA_ENSURE( PaOssStream_Stop( stream, abort ) ); + + stream->isStopped = 1; + +error: + return result; +} + +/** Stop the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, playing all enqueued + * buffers. + */ +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 0 ); +} + +/** Abort the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, discarding all enqueued + * buffers. Note that the buffers are not currently correctly discarded, this is difficult without closing + * the OSS device. + */ +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 1 ); +} + +/** Is the stream in the Stopped state. + * + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isStopped); +} + +/** Is the stream in the Active state. + * + */ +static PaError IsStreamActive( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isActive); +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + count_info info; + int delta; + + if( stream->playback ) { + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETOPTR, &info) == 0 ) { + delta = ( info.bytes - stream->lastPosPtr ) /* & 0x000FFFFF*/; + return (float)(stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->playback ) / stream->sampleRate; + } + } + else { + if (ioctl( stream->capture->fd, SNDCTL_DSP_GETIPTR, &info) == 0) { + delta = (info.bytes - stream->lastPosPtr) /*& 0x000FFFFF*/; + return (float)(stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->capture ) / stream->sampleRate; + } + } + + /* the ioctl failed, but we can still give a coarse estimate */ + + return stream->framesProcessed / stream->sampleRate; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesRead; + unsigned long framesRequested; + void *userBuffer; + + /* If user input is non-interleaved, PaUtil_CopyInput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userInputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->capture->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->capture->userChannelCount ); + } + + while( frames ) + { + framesRequested = PA_MIN( frames, stream->capture->hostFrames ); + + bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture ); + bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested ); + if ( bytesRequested != bytesRead ) + return paUnanticipatedHostError; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->capture->hostFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, stream->capture->hostChannelCount ); + PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesRequested ); + frames -= framesRequested; + } + return paNoError; +} + + +static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesWritten; + unsigned long framesConverted; + const void *userBuffer; + + /* If user output is non-interleaved, PaUtil_CopyOutput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + userBuffer = buffer; + else + { + /* Copy channels into local array */ + userBuffer = stream->playback->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback->userChannelCount ); + } + + while( frames ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, stream->playback->hostFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, stream->playback->hostChannelCount ); + + framesConverted = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames ); + frames -= framesConverted; + + bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback ); + bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested ); + + if ( bytesRequested != bytesWritten ) + return paUnanticipatedHostError; + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + audio_buf_info info; + + if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 ) + return paUnanticipatedHostError; + return info.fragments * stream->capture->hostFrames; +} + + +/* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + int delay = 0; + + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 ) + return paUnanticipatedHostError; + + return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback ); +} + diff --git a/portaudio-v19/pa_unix_oss/pa_unix_oss.c.orig b/portaudio-v19/pa_unix_oss/pa_unix_oss.c.orig new file mode 100644 index 000000000..3d81433cb --- /dev/null +++ b/portaudio-v19/pa_unix_oss/pa_unix_oss.c.orig @@ -0,0 +1,1922 @@ +/* + * $Id: pa_unix_oss.c,v 1.6.2.26 2005/12/10 11:35:24 aknudsen Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * OSS implementation by: + * Douglas Repetto + * Phil Burk + * Dominic Mazzoni + * Arve Knudsen + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +# include +# define DEVICE_NAME_BASE "/dev/dsp" +#elif defined __linux__ +# include +# define DEVICE_NAME_BASE "/dev/dsp" +#else +# include /* JH20010905 */ +# define DEVICE_NAME_BASE "/dev/audio" +#endif + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "../pa_unix/pa_unix_util.h" + +static int sysErr_; +static pthread_t mainThread_; + +/* Check return value of system call, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (sysErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \ + } \ + \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); + +#ifndef AFMT_S16_NE +#define AFMT_S16_NE Get_AFMT_S16_NE() +/********************************************************************* + * Some versions of OSS do not define AFMT_S16_NE. So check CPU. + * PowerPC is Big Endian. X86 is Little Endian. + */ +static int Get_AFMT_S16_NE( void ) +{ + long testData = 1; + char *ptr = (char *) &testData; + int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ + return isLittle ? AFMT_S16_LE : AFMT_S16_BE; +} +#endif + +/* PaOSSHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaOSSHostApiRepresentation; + +/** Per-direction structure for PaOssStream. + * + * Aspect StreamChannels: In case the user requests to open the same device for both capture and playback, + * but with different number of channels we will have to adapt between the number of user and host + * channels for at least one direction, since the configuration space is the same for both directions + * of an OSS device. + */ +typedef struct +{ + int fd; + const char *devName; + int userChannelCount, hostChannelCount; + int userInterleaved; + void *buffer; + PaSampleFormat userFormat, hostFormat; + double latency; + unsigned long hostFrames, numBufs; + void **userBuffers; /* For non-interleaved blocking */ +} PaOssStreamComponent; + +/** Implementation specific representation of a PaStream. + * + */ +typedef struct PaOssStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaUtilThreading threading; + + int sharedDevice; + unsigned long framesPerHostBuffer; + int triggered; /* Have the devices been triggered yet (first start) */ + + int isActive; + int isStopped; + + int lastPosPtr; + double lastStreamBytes; + + int framesProcessed; + + double sampleRate; + + int callbackMode; + int callbackStop, callbackAbort; + + PaOssStreamComponent *capture, *playback; + unsigned long pollTimeout; + sem_t semaphore; +} +PaOssStream; + +typedef enum { + StreamMode_In, + StreamMode_Out +} StreamMode; + +/* prototypes for functions declared in this file */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); +static PaError BuildDeviceList( PaOSSHostApiRepresentation *hostApi ); + + +/** Initialize the OSS API implementation. + * + * This function will initialize host API datastructures and query host devices for information. + * + * Aspect DeviceCapabilities: Enumeration of host API devices is initiated from here + * + * Aspect FreeResources: If an error is encountered under way we have to free each resource allocated in this function, + * this happens with the usual "error" label. + */ +PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = NULL; + + PA_UNLESS( ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) ), + paInsufficientMemory ); + PA_UNLESS( ossHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + ossHostApi->hostApiIndex = hostApiIndex; + + /* Initialize host API structure */ + *hostApi = &ossHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paOSS; + (*hostApi)->info.name = "OSS"; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PA_ENSURE( BuildDeviceList( ossHostApi ) ); + + PaUtil_InitializeStreamInterface( &ossHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &ossHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + mainThread_ = pthread_self(); + + return result; + +error: + if( ossHostApi ) + { + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); + } + return result; +} + +PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, PaHostApiIndex hostApiIndex, int maxInputChannels, + int maxOutputChannels, PaTime defaultLowInputLatency, PaTime defaultLowOutputLatency, PaTime defaultHighInputLatency, + PaTime defaultHighOutputLatency, double defaultSampleRate, PaUtilAllocationGroup *allocations ) +{ + PaError result = paNoError; + + deviceInfo->structVersion = 2; + if( allocations ) + { + size_t len = strlen( name ) + 1; + PA_UNLESS( deviceInfo->name = PaUtil_GroupAllocateMemory( allocations, len ), paInsufficientMemory ); + strncpy( (char *)deviceInfo->name, name, len ); + } + else + deviceInfo->name = name; + + deviceInfo->hostApi = hostApiIndex; + deviceInfo->maxInputChannels = maxInputChannels; + deviceInfo->maxOutputChannels = maxOutputChannels; + deviceInfo->defaultLowInputLatency = defaultLowInputLatency; + deviceInfo->defaultLowOutputLatency = defaultLowOutputLatency; + deviceInfo->defaultHighInputLatency = defaultHighInputLatency; + deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency; + deviceInfo->defaultSampleRate = defaultSampleRate; + +error: + return result; +} + +static PaError QueryDirection( const char *deviceName, StreamMode mode, double *defaultSampleRate, int *maxChannelCount, + double *defaultLowLatency, double *defaultHighLatency ) +{ + PaError result = paNoError; + int numChannels, maxNumChannels; + int busy = 0; + int devHandle = -1; + int sr; + *maxChannelCount = 0; /* Default value in case this fails */ + + if ( (devHandle = open( deviceName, (mode == StreamMode_In ? O_RDONLY : O_WRONLY) | O_NONBLOCK )) < 0 ) + { + if( errno == EBUSY || errno == EAGAIN ) + { + PA_DEBUG(( "%s: Device %s busy\n", __FUNCTION__, deviceName )); + } + else + { + PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) )); + } + + return paDeviceUnavailable; + } + + /* Negotiate for the maximum number of channels for this device. PLB20010927 + * Consider up to 16 as the upper number of channels. + * Variable maxNumChannels should contain the actual upper limit after the call. + * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. + */ + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + if( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ) < 0 ) + { + busy = EAGAIN == errno || EBUSY == errno; + /* ioctl() failed so bail out if we already have stereo */ + if( maxNumChannels >= 2 ) + break; + } + else + { + /* ioctl() worked but bail out if it does not support numChannels. + * We don't want to leave gaps in the numChannels supported. + */ + if( (numChannels > 2) && (temp != numChannels) ) + break; + if( temp > maxNumChannels ) + maxNumChannels = temp; /* Save maximum. */ + } + } + /* A: We're able to open a device for capture if it's busy playing back and vice versa, + * but we can't configure anything */ + if( 0 == maxNumChannels && busy ) + { + result = paDeviceUnavailable; + goto error; + } + + /* The above negotiation may fail for an old driver so try this older technique. */ + if( maxNumChannels < 1 ) + { + int stereo = 1; + if( ioctl( devHandle, SNDCTL_DSP_STEREO, &stereo ) < 0 ) + { + maxNumChannels = 1; + } + else + { + maxNumChannels = (stereo) ? 2 : 1; + } + PA_DEBUG(( "%s: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", __FUNCTION__, maxNumChannels )) + } + + /* During channel negotiation, the last ioctl() may have failed. This can + * also cause sample rate negotiation to fail. Hence the following, to return + * to a supported number of channels. SG20011005 */ + { + /* use most reasonable default value */ + int temp = PA_MIN( maxNumChannels, 2 ); + ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ), paUnanticipatedHostError ); + } + + /* Get supported sample rate closest to 44100 Hz */ + if( *defaultSampleRate < 0 ) + { + sr = 44100; + if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 ) + { + result = paUnanticipatedHostError; + goto error; + } + + *defaultSampleRate = sr; + } + + *maxChannelCount = maxNumChannels; + /* TODO */ + *defaultLowLatency = 512. / *defaultSampleRate; + *defaultHighLatency = 2048. / *defaultSampleRate; + +error: + if( devHandle >= 0 ) + close( devHandle ); + + return result; +} + +/** Query OSS device. + * + * This is where PaDeviceInfo objects are constructed and filled in with relevant information. + * + * Aspect DeviceCapabilities: The inferred device capabilities are recorded in a PaDeviceInfo object that is constructed + * in place. + */ +static PaError QueryDevice( char *deviceName, PaOSSHostApiRepresentation *ossApi, PaDeviceInfo **deviceInfo ) +{ + PaError result = paNoError; + double sampleRate = -1.; + int maxInputChannels, maxOutputChannels; + PaTime defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency; + PaError tmpRes = paNoError; + int busy = 0; + *deviceInfo = NULL; + + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settins. + e.g. a card might give you stereo at 22kHz but only mono at 44kHz. + the correct order for OSS is: format, channels, sample rate + */ + + /* Aspect StreamChannels: The number of channels supported for a device may depend on the mode it is + * opened in, it may have more channels available for capture than playback and vice versa. Therefore + * we will open the device in both read- and write-only mode to determine the supported number. + */ + if( (tmpRes = QueryDirection( deviceName, StreamMode_In, &sampleRate, &maxInputChannels, &defaultLowInputLatency, + &defaultHighInputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for capture failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + if( (tmpRes = QueryDirection( deviceName, StreamMode_Out, &sampleRate, &maxOutputChannels, &defaultLowOutputLatency, + &defaultHighOutputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for playback failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + assert( 0 <= busy && busy <= 2 ); + if( 2 == busy ) /* Both directions are unavailable to us */ + { + result = paDeviceUnavailable; + goto error; + } + + PA_UNLESS( *deviceInfo = PaUtil_GroupAllocateMemory( ossApi->allocations, sizeof (PaDeviceInfo) ), paInsufficientMemory ); + PA_ENSURE( PaUtil_InitializeDeviceInfo( *deviceInfo, deviceName, ossApi->hostApiIndex, maxInputChannels, maxOutputChannels, + defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency, sampleRate, + ossApi->allocations ) ); + +error: + return result; +} + +/** Query host devices. + * + * Loop over host devices and query their capabilitiesu + * + * Aspect DeviceCapabilities: This function calls QueryDevice on each device entry and receives a filled in PaDeviceInfo object + * per device, these are placed in the host api representation's deviceInfos array. + */ +static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *commonApi = &ossApi->inheritedHostApiRep; + int i; + int numDevices = 0, maxDeviceInfos = 1; + PaDeviceInfo **deviceInfos = NULL; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* Find devices by calling QueryDevice on each + * potential device names. When we find a valid one, + * add it to a linked list. + * A: Can there only be 10 devices? */ + + for( i = 0; i < 10; i++ ) + { + char deviceName[32]; + PaDeviceInfo *deviceInfo; + int testResult; + struct stat stbuf; + + if( i == 0 ) + snprintf(deviceName, sizeof (deviceName), "%s", DEVICE_NAME_BASE); + else + snprintf(deviceName, sizeof (deviceName), "%s%d", DEVICE_NAME_BASE, i); + + /* PA_DEBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); */ + if( stat( deviceName, &stbuf ) < 0 ) + { + if( ENOENT != errno ) + PA_DEBUG(( "%s: Error stat'ing %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) )); + continue; + } + if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError ) + { + if( testResult != paDeviceUnavailable ) + PA_ENSURE( testResult ); + + continue; + } + + ++numDevices; + if( !deviceInfos || numDevices > maxDeviceInfos ) + { + maxDeviceInfos *= 2; + PA_UNLESS( deviceInfos = (PaDeviceInfo **) realloc( deviceInfos, maxDeviceInfos * sizeof (PaDeviceInfo *) ), + paInsufficientMemory ); + } + deviceInfos[numDevices - 1] = deviceInfo; + + if( commonApi->info.defaultInputDevice == paNoDevice && deviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = i; + if( commonApi->info.defaultOutputDevice == paNoDevice && deviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = i; + } + + /* Make an array of PaDeviceInfo pointers out of the linked list */ + + PA_DEBUG(("PaOSS %s: Total number of devices found: %d\n", __FUNCTION__, numDevices)); + + commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + ossApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + memcpy( commonApi->deviceInfos, deviceInfos, numDevices * sizeof (PaDeviceInfo *) ); + + commonApi->info.deviceCount = numDevices; + +error: + free( deviceInfos ); + + return result; +} + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result = paNoError; + PaDeviceIndex device; + PaDeviceInfo *deviceInfo; + char *deviceName; + int inputChannelCount, outputChannelCount; + int tempDevHandle = -1; + int flags; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + if (inputChannelCount == 0 && outputChannelCount == 0) + return paInvalidChannelCount; + + /* if full duplex, make sure that they're the same device */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputParameters->device != outputParameters->device) + return paInvalidDevice; + + /* if full duplex, also make sure that they're the same number of channels */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputChannelCount != outputChannelCount) + return paInvalidChannelCount; + + /* open the device so we can do more tests */ + + if( inputChannelCount > 0 ) + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, inputParameters->device, hostApi); + if (result != paNoError) + return result; + } + else + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, outputParameters->device, hostApi); + if (result != paNoError) + return result; + } + + deviceInfo = hostApi->deviceInfos[device]; + deviceName = (char *)deviceInfo->name; + + flags = O_NONBLOCK; + if (inputChannelCount > 0 && outputChannelCount > 0) + flags |= O_RDWR; + else if (inputChannelCount > 0) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + ENSURE_( tempDevHandle = open( deviceInfo->name, flags ), paDeviceUnavailable ); + + /* PaOssStream_Configure will do the rest of the checking for us */ + /* PA_ENSURE( PaOssStream_Configure( tempDevHandle, deviceName, outputChannelCount, &sampleRate ) ); */ + + /* everything succeeded! */ + + error: + if( tempDevHandle >= 0 ) + close( tempDevHandle ); + + return result; +} + +/** Validate stream parameters. + * + * Aspect StreamChannels: We verify that the number of channels is within the allowed range for the device + */ +static PaError ValidateParameters( const PaStreamParameters *parameters, const PaDeviceInfo *deviceInfo, StreamMode mode ) +{ + int maxChans; + + assert( parameters ); + + if( parameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + maxChans = (mode == StreamMode_In ? deviceInfo->maxInputChannels : + deviceInfo->maxOutputChannels); + if( parameters->channelCount > maxChans ) + { + return paInvalidChannelCount; + } + + return paNoError; +} + +static PaError PaOssStreamComponent_Initialize( PaOssStreamComponent *component, const PaStreamParameters *parameters, + int callbackMode, int fd, const char *deviceName ) +{ + PaError result = paNoError; + assert( component ); + + memset( component, 0, sizeof (PaOssStreamComponent) ); + + component->fd = fd; + component->devName = deviceName; + component->userChannelCount = parameters->channelCount; + component->userFormat = parameters->sampleFormat; + component->latency = parameters->suggestedLatency; + component->userInterleaved = !(parameters->sampleFormat & paNonInterleaved); + + if( !callbackMode && !component->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( component->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * component->userChannelCount ), + paInsufficientMemory ); + } + +error: + return result; +} + +static void PaOssStreamComponent_Terminate( PaOssStreamComponent *component ) +{ + assert( component ); + + if( component->fd >= 0 ) + close( component->fd ); + if( component->buffer ) + PaUtil_FreeMemory( component->buffer ); + + if( component->userBuffers ) + PaUtil_FreeMemory( component->userBuffers ); + + PaUtil_FreeMemory( component ); +} + +static PaError ModifyBlocking( int fd, int blocking ) +{ + PaError result = paNoError; + int fflags; + + ENSURE_( fflags = fcntl( fd, F_GETFL ), paUnanticipatedHostError ); + + if( blocking ) + fflags &= ~O_NONBLOCK; + else + fflags |= O_NONBLOCK; + + ENSURE_( fcntl( fd, F_SETFL, fflags ), paUnanticipatedHostError ); + +error: + return result; +} + +static PaError OpenDevices( const char *idevName, const char *odevName, int *idev, int *odev ) +{ + PaError result = paNoError; + int flags = O_NONBLOCK, duplex = 0; + int enableBits = 0; + *idev = *odev = -1; + + if( idevName && odevName ) + { + duplex = 1; + flags |= O_RDWR; + } + else if( idevName ) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + /* open first in nonblocking mode, in case it's busy... + * A: then unset the non-blocking attribute */ + assert( flags & O_NONBLOCK ); + if( idevName ) + { + ENSURE_( *idev = open( idevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *idev, 1 ) ); /* Blocking */ + + /* Initially disable */ + enableBits = ~PCM_ENABLE_INPUT; + ENSURE_( ioctl( *idev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( odevName ) + { + if( !idevName ) + { + ENSURE_( *odev = open( odevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *odev, 1 ) ); /* Blocking */ + + /* Initially disable */ + enableBits = ~PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( *odev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + ENSURE_( *odev = dup( *idev ), paUnanticipatedHostError ); + } + } + +error: + return result; +} + +static PaError PaOssStream_Initialize( PaOssStream *stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, + PaStreamCallback callback, void *userData, PaStreamFlags streamFlags, + PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + int idev, odev; + PaUtilHostApiRepresentation *hostApi = &ossApi->inheritedHostApiRep; + const char *idevName = NULL, *odevName = NULL; + + assert( stream ); + + memset( stream, 0, sizeof (PaOssStream) ); + stream->isStopped = 1; + + PA_ENSURE( PaUtil_InitializeThreading( &stream->threading ) ); + + if( inputParameters && outputParameters ) + { + if( inputParameters->device == outputParameters->device ) + stream->sharedDevice = 1; + } + + if( inputParameters ) + idevName = hostApi->deviceInfos[inputParameters->device]->name; + if( outputParameters ) + odevName = hostApi->deviceInfos[outputParameters->device]->name; + PA_ENSURE( OpenDevices( idevName, odevName, &idev, &odev ) ); + if( inputParameters ) + { + PA_UNLESS( stream->capture = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->capture, inputParameters, callback != NULL, idev, idevName ) ); + } + if( outputParameters ) + { + PA_UNLESS( stream->playback = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->playback, outputParameters, callback != NULL, odev, odevName ) ); + } + + if( callback != NULL ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->callbackStreamInterface, callback, userData ); + stream->callbackMode = 1; + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->blockingStreamInterface, callback, userData ); + } + + ENSURE_( sem_init( &stream->semaphore, 0, 0 ), paInternalError ); + +error: + return result; +} + +static void PaOssStream_Terminate( PaOssStream *stream ) +{ + assert( stream ); + + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_TerminateThreading( &stream->threading ); + + if( stream->capture ) + PaOssStreamComponent_Terminate( stream->capture ); + if( stream->playback ) + PaOssStreamComponent_Terminate( stream->playback ); + + sem_destroy( &stream->semaphore ); + + PaUtil_FreeMemory( stream ); +} + +/** Translate from PA format to OSS native. + * + */ +static PaError Pa2OssFormat( PaSampleFormat paFormat, int *ossFormat ) +{ + switch( paFormat ) + { + case paUInt8: + *ossFormat = AFMT_U8; + break; + case paInt8: + *ossFormat = AFMT_S8; + break; + case paInt16: + *ossFormat = AFMT_S16_NE; + break; + default: + return paInternalError; /* This shouldn't happen */ + } + + return paNoError; +} + +/** Return the PA-compatible formats that this device can support. + * + */ +static PaError GetAvailableFormats( PaOssStreamComponent *component, PaSampleFormat *availableFormats ) +{ + PaError result = paNoError; + int mask = 0; + PaSampleFormat frmts = 0; + + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETFMTS, &mask ), paUnanticipatedHostError ); + if( mask & AFMT_U8 ) + frmts |= paUInt8; + if( mask & AFMT_S8 ) + frmts |= paInt8; + if( mask & AFMT_S16_NE ) + frmts |= paInt16; + else + result = paSampleFormatNotSupported; + + *availableFormats = frmts; + +error: + return result; +} + +static unsigned int PaOssStreamComponent_FrameSize( PaOssStreamComponent *component ) +{ + return Pa_GetSampleSize( component->hostFormat ) * component->hostChannelCount; +} + +/** Buffer size in bytes. + * + */ +static unsigned long PaOssStreamComponent_BufferSize( PaOssStreamComponent *component ) +{ + return PaOssStreamComponent_FrameSize( component ) * component->hostFrames * component->numBufs; +} + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1<userChannelCount; + int frgmt; + int numBufs; + int bytesPerBuf; + double bufSz; + unsigned long fragSz; + audio_buf_info bufInfo; + + /* We may have a situation where only one component (the master) is configured, if both point to the same device. + * In that case, the second component will copy settings from the other */ + if( !master ) + { + /* Aspect BufferSettings: If framesPerBuffer is unspecified we have to infer a suitable fragment size. + * The hardware need not respect the requested fragment size, so we may have to adapt. + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + bufSz = component->latency * sampleRate; + fragSz = bufSz / 4; + } + else + { + fragSz = framesPerBuffer; + bufSz = component->latency * sampleRate + fragSz; /* Latency + 1 buffer */ + } + + PA_ENSURE( GetAvailableFormats( component, &availableFormats ) ); + hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, component->userFormat ); + + /* OSS demands at least 2 buffers, and 16 bytes per buffer */ + numBufs = PA_MAX( bufSz / fragSz, 2 ); + bytesPerBuf = PA_MAX( fragSz * Pa_GetSampleSize( hostFormat ) * chans, 16 ); + + /* The fragment parameters are encoded like this: + * Most significant byte: number of fragments + * Least significant byte: exponent of fragment size (i.e., for 256, 8) + */ + frgmt = (numBufs << 16) + (CalcHigherLogTwo( bytesPerBuf ) & 0xffff); + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFRAGMENT, &frgmt ), paUnanticipatedHostError ); + + /* A: according to the OSS programmer's guide parameters should be set in this order: + * format, channels, rate */ + + /* This format should be deemed good before we get this far */ + PA_ENSURE( Pa2OssFormat( hostFormat, &temp ) ); + nativeFormat = temp; + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFMT, &temp ), paUnanticipatedHostError ); + PA_UNLESS( temp == nativeFormat, paInternalError ); + + /* try to set the number of channels */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_CHANNELS, &chans ), paSampleFormatNotSupported ); /* XXX: Should be paInvalidChannelCount? */ + /* It's possible that the minimum number of host channels is greater than what the user requested */ + PA_UNLESS( chans >= component->userChannelCount, paInvalidChannelCount ); + + /* try to set the sample rate */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SPEED, &sr ), paInvalidSampleRate ); + + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( sampleRate - sr ) / sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( ioctl( component->fd, streamMode == StreamMode_In ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &bufInfo ), + paUnanticipatedHostError ); + component->numBufs = bufInfo.fragstotal; + + /* This needs to be the last ioctl call before the first read/write, according to the OSS programmer's guide */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETBLKSIZE, &bytesPerBuf ), paUnanticipatedHostError ); + + component->hostFrames = bytesPerBuf / Pa_GetSampleSize( hostFormat ) / chans; + component->hostChannelCount = chans; + component->hostFormat = hostFormat; + } + else + { + component->hostFormat = master->hostFormat; + component->hostFrames = master->hostFrames; + component->hostChannelCount = master->hostChannelCount; + component->numBufs = master->numBufs; + } + + PA_UNLESS( component->buffer = PaUtil_AllocateMemory( PaOssStreamComponent_BufferSize( component ) ), + paInsufficientMemory ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Read( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesRead; + + ENSURE_( bytesRead = read( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesRead / PaOssStreamComponent_FrameSize( component ); + /* TODO: Handle condition where number of frames read doesn't equal number of frames requested */ + +error: + return result; +} + +static PaError PaOssStreamComponent_Write( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesWritten; + + ENSURE_( bytesWritten = write( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesWritten / PaOssStreamComponent_FrameSize( component ); + /* TODO: Handle condition where number of frames written doesn't equal number of frames requested */ + +error: + return result; +} + +/** Configure the stream according to input/output parameters. + * + * Aspect StreamChannels: The minimum number of channels supported by the device may exceed that requested by + * the user, if so we'll record the actual number of host channels and adapt later. + */ +static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, unsigned long framesPerBuffer, + double *inputLatency, double *outputLatency ) +{ + PaError result = paNoError; + int duplex = stream->capture && stream->playback; + unsigned long framesPerHostBuffer = 0; + + /* We should request full duplex first thing after opening the device */ + if( duplex && stream->sharedDevice ) + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETDUPLEX, 0 ), paUnanticipatedHostError ); + + if( stream->capture ) + { + PaOssStreamComponent *component = stream->capture; + PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_In, NULL ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *inputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + if( stream->playback ) + { + PaOssStreamComponent *component = stream->playback, *master = stream->sharedDevice ? stream->capture : NULL; + PA_ENSURE( PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_Out, + master ) ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *outputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + + if( duplex ) + framesPerHostBuffer = PA_MIN( stream->capture->hostFrames, stream->playback->hostFrames ); + else if( stream->capture ) + framesPerHostBuffer = stream->capture->hostFrames; + else if( stream->playback ) + framesPerHostBuffer = stream->playback->hostFrames; + + stream->framesPerHostBuffer = framesPerHostBuffer; + stream->pollTimeout = (int) ceil( 1e6 * framesPerHostBuffer / sampleRate ); /* Period in usecs, rounded up */ + + stream->sampleRate = stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + +error: + return result; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +/** Open a PA OSS stream. + * + * Aspect StreamChannels: The number of channels is specified per direction (in/out), and can differ between the + * two. However, OSS doesn't support separate configuration spaces for capture and playback so if both + * directions are the same device we will demand the same number of channels. The number of channels can range + * from 1 to the maximum supported by the device. + * + * Aspect BufferSettings: If framesPerBuffer != paFramesPerBufferUnspecified the number of frames per callback + * must reflect this, in addition the host latency per device should approximate the corresponding + * suggestedLatency. Based on these constraints we need to determine a number of frames per host buffer that + * both capture and playback can agree on (they can be different devices), the buffer processor can adapt + * between host and user buffer size, but the ratio should preferably be integral. + */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + PaOssStream *stream = NULL; + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0, inputHostFormat = 0, outputHostFormat = 0; + const PaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; + int bpInitialized = 0; + double inLatency, outLatency; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + if( inputParameters ) + { + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + inputDeviceInfo = hostApi->deviceInfos[inputParameters->device]; + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, StreamMode_In ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + outputDeviceInfo = hostApi->deviceInfos[outputParameters->device]; + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, StreamMode_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* Aspect StreamChannels: We currently demand that number of input and output channels are the same, if the same + * device is opened for both directions + */ + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + if( inputParameters->device == outputParameters->device ) + { + if( inputParameters->channelCount != outputParameters->channelCount ) + return paInvalidChannelCount; + } + } + + /* allocate and do basic initialization of the stream structure */ + PA_UNLESS( stream = (PaOssStream*)PaUtil_AllocateMemory( sizeof(PaOssStream) ), paInsufficientMemory ); + PA_ENSURE( PaOssStream_Initialize( stream, inputParameters, outputParameters, streamCallback, userData, streamFlags, ossHostApi ) ); + + PA_ENSURE( PaOssStream_Configure( stream, sampleRate, framesPerBuffer, &inLatency, &outLatency ) ); + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + if( inputParameters ) + { + inputHostFormat = stream->capture->hostFormat; + stream->streamRepresentation.streamInfo.inputLatency = inLatency + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate; + } + if( outputParameters ) + { + outputHostFormat = stream->playback->hostFormat; + stream->streamRepresentation.streamInfo.outputLatency = outLatency + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate; + } + + /* Initialize buffer processor with fixed host buffer size. + * Aspect StreamSampleFormat: Here we commit the user and host sample formats, PA infrastructure will + * convert between the two. + */ + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, inputHostFormat, outputChannelCount, outputSampleFormat, + outputHostFormat, sampleRate, streamFlags, framesPerBuffer, stream->framesPerHostBuffer, + paUtilFixedHostBufferSize, streamCallback, userData ) ); + bpInitialized = 1; + + *s = (PaStream*)stream; + + return result; + +error: + if( bpInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( stream ) + PaOssStream_Terminate( stream ); + + return result; +} + +/*! Poll on I/O filedescriptors. + + Poll till we've determined there's data for read or write. In the full-duplex case, + we don't want to hang around forever waiting for either input or output frames, so + whenever we have a timed out filedescriptor we check if we're nearing under/overrun + for the other direction (critical limit set at one buffer). If so, we exit the waiting + state, and go on with what we got. We align the number of frames on a host buffer + boundary because it is possible that the buffer size differs for the two directions and + the host buffer size is a compromise between the two. + */ +static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *frames ) +{ + PaError result = paNoError; + int pollPlayback = 0, pollCapture = 0; + int captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; + audio_buf_info bufInfo; + /* int ofs = 0, nfds = stream->nfds; */ + fd_set readFds, writeFds; + int nfds = 0; + struct timeval selectTimeval = {0, 0}; + unsigned long timeout = stream->pollTimeout; /* In usecs */ + int captureFd = -1, playbackFd = -1; + + assert( stream ); + assert( frames ); + + if( stream->capture ) + { + pollCapture = 1; + captureFd = stream->capture->fd; + /* stream->capture->pfd->events = POLLIN; */ + } + if( stream->playback ) + { + pollPlayback = 1; + playbackFd = stream->playback->fd; + /* stream->playback->pfd->events = POLLOUT; */ + } + + FD_ZERO( &readFds ); + FD_ZERO( &writeFds ); + + while( pollPlayback || pollCapture ) + { + pthread_testcancel(); + + /* select may modify the timeout parameter */ + selectTimeval.tv_usec = timeout; + nfds = 0; + + if( pollCapture ) + { + FD_SET( captureFd, &readFds ); + nfds = captureFd + 1; + } + if( pollPlayback ) + { + FD_SET( playbackFd, &writeFds ); + nfds = PA_MAX( nfds, playbackFd + 1 ); + } + ENSURE_( select( nfds, &readFds, &writeFds, NULL, &selectTimeval ), paUnanticipatedHostError ); + /* + if( poll( stream->pfds + ofs, nfds, stream->pollTimeout ) < 0 ) + { + + ENSURE_( -1, paUnanticipatedHostError ); + } + */ + pthread_testcancel(); + + if( pollCapture ) + { + if( FD_ISSET( captureFd, &readFds ) ) + { + FD_CLR( captureFd, &readFds ); + pollCapture = 0; + } + /* + if( stream->capture->pfd->revents & POLLIN ) + { + --nfds; + ++ofs; + pollCapture = 0; + } + */ + else if( stream->playback ) /* Timed out, go on with playback? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for capture frames, pollTimeout: %d\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + if( pollPlayback ) + { + if( FD_ISSET( playbackFd, &writeFds ) ) + { + FD_CLR( playbackFd, &writeFds ); + pollPlayback = 0; + } + /* + if( stream->playback->pfd->revents & POLLOUT ) + { + --nfds; + pollPlayback = 0; + } + */ + else if( stream->capture ) /* Timed out, go on with capture? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for playback frames, pollTimeout: %d\n\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + } + + if( stream->capture ) + { + ENSURE_( ioctl( captureFd, SNDCTL_DSP_GETISPACE, &bufInfo ), paUnanticipatedHostError ); + captureAvail = bufInfo.fragments * stream->capture->hostFrames; + if( !captureAvail ) + PA_DEBUG(( "%s: captureAvail: 0\n", __FUNCTION__ )); + + captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ + } + if( stream->playback ) + { + ENSURE_( ioctl( playbackFd, SNDCTL_DSP_GETOSPACE, &bufInfo ), paUnanticipatedHostError ); + playbackAvail = bufInfo.fragments * stream->playback->hostFrames; + if( !playbackAvail ) + { + PA_DEBUG(( "%s: playbackAvail: 0\n", __FUNCTION__ )); + } + + playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ + } + + commonAvail = PA_MIN( captureAvail, playbackAvail ); + if( commonAvail == INT_MAX ) + commonAvail = 0; + commonAvail -= commonAvail % stream->framesPerHostBuffer; + + assert( commonAvail != INT_MAX ); + assert( commonAvail >= 0 ); + *frames = commonAvail; + +error: + return result; +} + +/** Prepare stream for capture/playback. + * + * In order to synchronize capture and playback properly we use the SETTRIGGER command. + */ +static PaError PaOssStream_Prepare( PaOssStream *stream ) +{ + PaError result = paNoError; + int enableBits = 0; + + if( stream->triggered ) + return result; + + if( stream->playback ) + { + size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback ); + memset( stream->playback->buffer, 0, bufSz ); + + /* Looks like we have to turn off blocking before we try this, but if we don't fill the buffer + * OSS will complain. */ + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + while (1) + { + if( write( stream->playback->fd, stream->playback->buffer, bufSz ) < 0 ) + break; + } + PA_ENSURE( ModifyBlocking( stream->playback->fd, 1 ) ); + } + + if( stream->sharedDevice ) + { + enableBits = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + if( stream->capture ) + { + enableBits = PCM_ENABLE_INPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( stream->playback ) + { + enableBits = PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + } + + /* Ok, we have triggered the stream */ + stream->triggered = 1; + +error: + return result; +} + +/** Stop audio processing + * + */ +static PaError PaOssStream_Stop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; + + /* Looks like the only safe way to stop audio without reopening the device is SNDCTL_DSP_POST. + * Also disable capture/playback till the stream is started again */ + if( stream->capture ) + { + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + if( stream->playback && !stream->sharedDevice ) + { + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + +error: + return result; +} + +/** Clean up after thread exit. + * + * Aspect StreamState: If the user has registered a streamFinishedCallback it will be called here + */ +static void OnExit( void *data ) +{ + PaOssStream *stream = (PaOssStream *) data; + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + PaOssStream_Stop( stream, stream->callbackAbort ); + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + stream->callbackAbort = 0; /* Clear state */ + stream->isActive = 0; +} + +static PaError SetUpBuffers( PaOssStream *stream, unsigned long framesAvail ) +{ + PaError result = paNoError; + + if( stream->capture ) + { + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, + stream->capture->hostChannelCount ); + PaUtil_SetInputFrameCount( &stream->bufferProcessor, framesAvail ); + } + if( stream->playback ) + { + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, + stream->playback->hostChannelCount ); + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, framesAvail ); + } + + return result; +} + +/** Thread procedure for callback processing. + * + * Aspect StreamState: StartStream will wait on this to initiate audio processing, useful in case the + * callback should be used for buffer priming. When the stream is cancelled a separate function will + * take care of the transition to the Callback Finished state (the stream isn't considered Stopped + * before StopStream() or AbortStream() are called). + */ +static void *PaOSS_AudioThreadProc( void *userData ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)userData; + unsigned long framesAvail, framesProcessed; + int callbackResult = paContinue; + int triggered = stream->triggered; /* See if SNDCTL_DSP_TRIGGER has been issued already */ + int initiateProcessing = triggered; /* Already triggered? */ + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + + /* +#if ( SOUND_VERSION > 0x030904 ) + audio_errinfo errinfo; +#endif +*/ + + assert( stream ); + + pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + + /* The first time the stream is started we use SNDCTL_DSP_TRIGGER to accurately start capture and + * playback in sync, when the stream is restarted after being stopped we simply start by reading/ + * writing. + */ + PA_ENSURE( PaOssStream_Prepare( stream ) ); + + /* If we are to initiate processing implicitly by reading/writing data, we start off in blocking mode */ + if( initiateProcessing ) + { + /* Make sure devices are in blocking mode */ + if( stream->capture ) + ModifyBlocking( stream->capture->fd, 1 ); + if( stream->playback ) + ModifyBlocking( stream->playback->fd, 1 ); + } + + while( 1 ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* TODO: IMPLEMENT ME */ + + pthread_testcancel(); + + if( stream->callbackStop && callbackResult == paContinue ) + { + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; + } + + /* Aspect StreamState: Because of the messy OSS scheme we can't explicitly trigger device start unless + * the stream has been recently started, we will have to go right ahead and read/write in blocking + * fashion to trigger operation. Therefore we begin with processing one host buffer before we switch + * to non-blocking mode. + */ + if( !initiateProcessing ) + { + PA_ENSURE( PaOssStream_WaitForFrames( stream, &framesAvail ) ); /* Wait on available frames */ + assert( framesAvail % stream->framesPerHostBuffer == 0 ); + } + else + { + framesAvail = stream->framesPerHostBuffer; + } + + while( framesAvail > 0 ) + { + unsigned long frames = framesAvail; + + pthread_testcancel(); + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* Read data */ + if ( stream->capture ) + { + PA_ENSURE( PaOssStreamComponent_Read( stream->capture, &frames ) ); + assert( frames == framesAvail ); + } + +#if ( SOUND_VERSION >= 0x030904 ) + /* + Check with OSS to see if there have been any under/overruns + since last time we checked. + */ + /* + if( ioctl( stream->deviceHandle, SNDCTL_DSP_GETERROR, &errinfo ) >= 0 ) + { + if( errinfo.play_underruns ) + cbFlags |= paOutputUnderflow ; + if( errinfo.record_underruns ) + cbFlags |= paInputUnderflow ; + } + else + PA_DEBUG(( "SNDCTL_DSP_GETERROR command failed: %s\n", strerror( errno ) )); + */ +#endif + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + cbFlags = 0; + PA_ENSURE( SetUpBuffers( stream, framesAvail ) ); + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + assert( framesProcessed == framesAvail ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if ( stream->playback ) + { + frames = framesAvail; + + PA_ENSURE( PaOssStreamComponent_Write( stream->playback, &frames ) ); + assert( frames == framesAvail ); + + /* TODO: handle bytesWritten != bytesRequested (slippage?) */ + } + + framesAvail -= framesProcessed; + stream->framesProcessed += framesProcessed; + + if( callbackResult != paContinue ) + break; + } + + if( initiateProcessing || !triggered ) + { + /* Non-blocking */ + if( stream->capture ) + PA_ENSURE( ModifyBlocking( stream->capture->fd, 0 ) ); + if( stream->playback && !stream->sharedDevice ) + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + + initiateProcessing = 0; + sem_post( &stream->semaphore ); + } + + if( callbackResult != paContinue ) + { + stream->callbackAbort = callbackResult == paAbort; + if( stream->callbackAbort || PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + break; + } + } + + pthread_cleanup_pop( 1 ); + +error: + pthread_exit( NULL ); +} + +/** Close the stream. + * + */ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + assert( stream ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaOssStream_Terminate( stream ); + + return result; +} + +/** Start the stream. + * + * Aspect StreamState: After returning, the stream shall be in the Active state, implying that an eventual + * callback will be repeatedly called in a separate thread. If a separate thread is started this function + * will block untill it has started processing audio, otherwise audio processing is started directly. + */ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + stream->isActive = 1; + stream->isStopped = 0; + stream->lastPosPtr = 0; + stream->lastStreamBytes = 0; + stream->framesProcessed = 0; + + /* only use the thread for callback streams */ + if( stream->bufferProcessor.streamCallback ) + { + PA_ENSURE( PaUtil_StartThreading( &stream->threading, &PaOSS_AudioThreadProc, stream ) ); + sem_wait( &stream->semaphore ); + } + else + PA_ENSURE( PaOssStream_Prepare( stream ) ); + +error: + return result; +} + +static PaError RealStop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; + + if( stream->callbackMode ) + { + if( abort ) + stream->callbackAbort = 1; + else + stream->callbackStop = 1; + + PA_ENSURE( PaUtil_CancelThreading( &stream->threading, !abort, NULL ) ); + + stream->callbackStop = stream->callbackAbort = 0; + } + else + PA_ENSURE( PaOssStream_Stop( stream, abort ) ); + + stream->isStopped = 1; + +error: + return result; +} + +/** Stop the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, playing all enqueued + * buffers. + */ +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 0 ); +} + +/** Abort the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, discarding all enqueued + * buffers. Note that the buffers are not currently correctly discarded, this is difficult without closing + * the OSS device. + */ +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 1 ); +} + +/** Is the stream in the Stopped state. + * + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isStopped); +} + +/** Is the stream in the Active state. + * + */ +static PaError IsStreamActive( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isActive); +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + count_info info; + int delta; + + if( stream->playback ) { + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETOPTR, &info) == 0 ) { + delta = ( info.bytes - stream->lastPosPtr ) /* & 0x000FFFFF*/; + return (float)(stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->playback ) / stream->sampleRate; + } + } + else { + if (ioctl( stream->capture->fd, SNDCTL_DSP_GETIPTR, &info) == 0) { + delta = (info.bytes - stream->lastPosPtr) /*& 0x000FFFFF*/; + return (float)(stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->capture ) / stream->sampleRate; + } + } + + /* the ioctl failed, but we can still give a coarse estimate */ + + return stream->framesProcessed / stream->sampleRate; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesRead; + unsigned long framesRequested; + void *userBuffer; + + /* If user input is non-interleaved, PaUtil_CopyInput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userInputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->capture->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->capture->userChannelCount ); + } + + while( frames ) + { + framesRequested = PA_MIN( frames, stream->capture->hostFrames ); + + bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture ); + bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested ); + if ( bytesRequested != bytesRead ) + return paUnanticipatedHostError; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->capture->hostFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, stream->capture->hostChannelCount ); + PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesRequested ); + frames -= framesRequested; + } + return paNoError; +} + + +static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesWritten; + unsigned long framesConverted; + const void *userBuffer; + + /* If user output is non-interleaved, PaUtil_CopyOutput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + userBuffer = buffer; + else + { + /* Copy channels into local array */ + userBuffer = stream->playback->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback->userChannelCount ); + } + + while( frames ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, stream->playback->hostFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, stream->playback->hostChannelCount ); + + framesConverted = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames ); + frames -= framesConverted; + + bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback ); + bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested ); + + if ( bytesRequested != bytesWritten ) + return paUnanticipatedHostError; + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + audio_buf_info info; + + if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 ) + return paUnanticipatedHostError; + return info.fragments * stream->capture->hostFrames; +} + + +/* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + int delay = 0; + + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 ) + return paUnanticipatedHostError; + + return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback ); +} + diff --git a/portaudio-v19/pa_unix_oss/recplay.c b/portaudio-v19/pa_unix_oss/recplay.c new file mode 100644 index 000000000..9d4c78cfa --- /dev/null +++ b/portaudio-v19/pa_unix_oss/recplay.c @@ -0,0 +1,114 @@ +/* + * recplay.c + * Phil Burk + * Minimal record and playback test. + * + */ +#include +#include +#include +#ifndef __STDC__ +/* #include */ +#endif /* __STDC__ */ +#include +#ifdef __STDC__ +#include +#else /* __STDC__ */ +#include +#endif /* __STDC__ */ +#include + +#define NUM_BYTES (64*1024) +#define BLOCK_SIZE (4*1024) + +#define AUDIO "/dev/dsp" + +char buffer[NUM_BYTES]; + +int audioDev = 0; + +main (int argc, char *argv[]) +{ + int numLeft; + char *ptr; + int num; + int samplesize; + + /********** RECORD ********************/ + /* Open audio device. */ + audioDev = open (AUDIO, O_RDONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Record in blocks */ + printf("Begin recording.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = read (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Read %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); + + /********** PLAYBACK ********************/ + /* Open audio device for writing. */ + audioDev = open (AUDIO, O_WRONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Play in blocks */ + printf("Begin playing.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = write (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Wrote %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); +} diff --git a/portaudio-v19/pa_win/dev-cpp/Makefile-dll b/portaudio-v19/pa_win/dev-cpp/Makefile-dll new file mode 100644 index 000000000..856af638e --- /dev/null +++ b/portaudio-v19/pa_win/dev-cpp/Makefile-dll @@ -0,0 +1,78 @@ +# Project: portaudio-dll +# Makefile created by Dev-C++ 4.9.8.2 + +CPP = g++.exe +CC = gcc.exe +WINDRES = windres.exe +RES = +OBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LINKOBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LIBS = -L"C:/Dev-CPP/lib" -fmessage-length=0 --no-export-all-symbols --add-stdcall-alias ../../../asiosdk2/asiosdk2.a -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -lwinmm -O3 -s +INCS = -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +CXXINCS = -I"C:/Dev-CPP/include/c++" -I"C:/Dev-CPP/include/c++/mingw32" -I"C:/Dev-CPP/include/c++/backward" -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +BIN = portaudio-dll.dll +CXXFLAGS = $(CXXINCS)-O3 -fmessage-length=0 -Wall +CFLAGS = $(INCS)-DBUILDING_DLL=1 -O3 -fmessage-length=0 -Wall + +.PHONY: all all-before all-after clean clean-custom + +all: all-before portaudio-dll.dll all-after + + +clean: clean-custom + rm -f $(OBJ) $(BIN) + +DLLWRAP=dllwrap.exe +DEFFILE=libportaudio-dll.def +STATICLIB=libportaudio-dll.a + +$(BIN): $(LINKOBJ) + $(DLLWRAP) --output-def $(DEFFILE) --driver-name c++ --implib $(STATICLIB) $(LINKOBJ) $(LIBS) -o $(BIN) + +./pa_skeleton.o: ../../pa_common/pa_skeleton.c + $(CPP) -c ../../pa_common/pa_skeleton.c -o ./pa_skeleton.o $(CXXFLAGS) + +./pa_stream.o: ../../pa_common/pa_stream.c + $(CPP) -c ../../pa_common/pa_stream.c -o ./pa_stream.o $(CXXFLAGS) + +./pa_trace.o: ../../pa_common/pa_trace.c + $(CPP) -c ../../pa_common/pa_trace.c -o ./pa_trace.o $(CXXFLAGS) + +./pa_allocation.o: ../../pa_common/pa_allocation.c + $(CPP) -c ../../pa_common/pa_allocation.c -o ./pa_allocation.o $(CXXFLAGS) + +./pa_converters.o: ../../pa_common/pa_converters.c + $(CPP) -c ../../pa_common/pa_converters.c -o ./pa_converters.o $(CXXFLAGS) + +./pa_cpuload.o: ../../pa_common/pa_cpuload.c + $(CPP) -c ../../pa_common/pa_cpuload.c -o ./pa_cpuload.o $(CXXFLAGS) + +./pa_dither.o: ../../pa_common/pa_dither.c + $(CPP) -c ../../pa_common/pa_dither.c -o ./pa_dither.o $(CXXFLAGS) + +./pa_front.o: ../../pa_common/pa_front.c + $(CPP) -c ../../pa_common/pa_front.c -o ./pa_front.o $(CXXFLAGS) + +./pa_process.o: ../../pa_common/pa_process.c + $(CPP) -c ../../pa_common/pa_process.c -o ./pa_process.o $(CXXFLAGS) + +./pa_asio.o: ../../pa_asio/pa_asio.cpp + $(CPP) -c ../../pa_asio/pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +./pa_win_util.o: ../pa_win_util.c + $(CPP) -c ../pa_win_util.c -o ./pa_win_util.o $(CXXFLAGS) + +./pa_win_hostapis.o: ../pa_win_hostapis.c + $(CPP) -c ../pa_win_hostapis.c -o ./pa_win_hostapis.o $(CXXFLAGS) + +./pa_win_ds.o: ../../pa_win_ds/pa_win_ds.c + $(CPP) -c ../../pa_win_ds/pa_win_ds.c -o ./pa_win_ds.o $(CXXFLAGS) + +./dsound_wrapper.o: ../../pa_win_ds/dsound_wrapper.c + $(CPP) -c ../../pa_win_ds/dsound_wrapper.c -o ./dsound_wrapper.o $(CXXFLAGS) + +./pa_win_wmme.o: ../../pa_win_wmme/pa_win_wmme.c + $(CPP) -c ../../pa_win_wmme/pa_win_wmme.c -o ./pa_win_wmme.o $(CXXFLAGS) + +./iasiothiscallresolver.o: ../../pa_asio/iasiothiscallresolver.cpp + $(CPP) -c ../../pa_asio/iasiothiscallresolver.cpp -o ./iasiothiscallresolver.o $(CXXFLAGS) diff --git a/portaudio-v19/pa_win/dev-cpp/Makefile-static b/portaudio-v19/pa_win/dev-cpp/Makefile-static new file mode 100644 index 000000000..2a1647ddc --- /dev/null +++ b/portaudio-v19/pa_win/dev-cpp/Makefile-static @@ -0,0 +1,75 @@ +# Project: portaudio-static +# Makefile created by Dev-C++ 4.9.8.2 + +CPP = g++.exe +CC = gcc.exe +WINDRES = windres.exe +RES = +OBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LINKOBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LIBS = -L"C:/Dev-CPP/lib" -fmessage-length=0 -O3 -s +INCS = -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +CXXINCS = -I"C:/Dev-CPP/include/c++" -I"C:/Dev-CPP/include/c++/mingw32" -I"C:/Dev-CPP/include/c++/backward" -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +BIN = portaudio-static.a +CXXFLAGS = $(CXXINCS)-O3 -fmessage-length=0 -Wall +CFLAGS = $(INCS)-O3 -fmessage-length=0 -Wall + +.PHONY: all all-before all-after clean clean-custom + +all: all-before portaudio-static.a all-after + + +clean: clean-custom + rm -f $(OBJ) $(BIN) + +$(BIN): $(LINKOBJ) + ar r $(BIN) $(LINKOBJ) + ranlib $(BIN) + +./pa_skeleton.o: ../../pa_common/pa_skeleton.c + $(CPP) -c ../../pa_common/pa_skeleton.c -o ./pa_skeleton.o $(CXXFLAGS) + +./pa_stream.o: ../../pa_common/pa_stream.c + $(CPP) -c ../../pa_common/pa_stream.c -o ./pa_stream.o $(CXXFLAGS) + +./pa_trace.o: ../../pa_common/pa_trace.c + $(CPP) -c ../../pa_common/pa_trace.c -o ./pa_trace.o $(CXXFLAGS) + +./pa_allocation.o: ../../pa_common/pa_allocation.c + $(CPP) -c ../../pa_common/pa_allocation.c -o ./pa_allocation.o $(CXXFLAGS) + +./pa_converters.o: ../../pa_common/pa_converters.c + $(CPP) -c ../../pa_common/pa_converters.c -o ./pa_converters.o $(CXXFLAGS) + +./pa_cpuload.o: ../../pa_common/pa_cpuload.c + $(CPP) -c ../../pa_common/pa_cpuload.c -o ./pa_cpuload.o $(CXXFLAGS) + +./pa_dither.o: ../../pa_common/pa_dither.c + $(CPP) -c ../../pa_common/pa_dither.c -o ./pa_dither.o $(CXXFLAGS) + +./pa_front.o: ../../pa_common/pa_front.c + $(CPP) -c ../../pa_common/pa_front.c -o ./pa_front.o $(CXXFLAGS) + +./pa_process.o: ../../pa_common/pa_process.c + $(CPP) -c ../../pa_common/pa_process.c -o ./pa_process.o $(CXXFLAGS) + +./pa_asio.o: ../../pa_asio/pa_asio.cpp + $(CPP) -c ../../pa_asio/pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +./pa_win_util.o: ../pa_win_util.c + $(CPP) -c ../pa_win_util.c -o ./pa_win_util.o $(CXXFLAGS) + +./pa_win_hostapis.o: ../pa_win_hostapis.c + $(CPP) -c ../pa_win_hostapis.c -o ./pa_win_hostapis.o $(CXXFLAGS) + +./pa_win_ds.o: ../../pa_win_ds/pa_win_ds.c + $(CPP) -c ../../pa_win_ds/pa_win_ds.c -o ./pa_win_ds.o $(CXXFLAGS) + +./dsound_wrapper.o: ../../pa_win_ds/dsound_wrapper.c + $(CPP) -c ../../pa_win_ds/dsound_wrapper.c -o ./dsound_wrapper.o $(CXXFLAGS) + +./pa_win_wmme.o: ../../pa_win_wmme/pa_win_wmme.c + $(CPP) -c ../../pa_win_wmme/pa_win_wmme.c -o ./pa_win_wmme.o $(CXXFLAGS) + +./iasiothiscallresolver.o: ../../pa_asio/iasiothiscallresolver.cpp + $(CPP) -c ../../pa_asio/iasiothiscallresolver.cpp -o ./iasiothiscallresolver.o $(CXXFLAGS) diff --git a/portaudio-v19/pa_win/dev-cpp/portaudio-dll.dev b/portaudio-v19/pa_win/dev-cpp/portaudio-dll.dev new file mode 100644 index 000000000..086e109ee --- /dev/null +++ b/portaudio-v19/pa_win/dev-cpp/portaudio-dll.dev @@ -0,0 +1,209 @@ +[Project] +FileName=portaudio-dll.dev +Name=portaudio-dll +UnitCount=16 +Type=3 +Ver=1 +ObjFiles= +Includes=..\..\..\asiosdk2;..\..\..\asiosdk2\common;..\..\..\asiosdk2\host;..\..\..\asiosdk2\host\pc;..\..\pa_common +Libs= +PrivateResource= +ResourceIncludes= +MakeIncludes= +Compiler=-DBUILDING_DLL=1_@@_-O3_@@_ +CppCompiler=-O3_@@_ +Linker=--no-export-all-symbols --add-stdcall-alias_@@_../../../asiosdk2/asiosdk2.a_@@_-lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -lwinmm_@@_-O3 -s_@@_ +IsCpp=1 +Icon= +ExeOutput=. +ObjectOutput=. +OverrideOutput=0 +OverrideOutputName=portaudio.a +HostApplication= +Folders= +CommandLine= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000 +UseCustomMakefile=0 +CustomMakefile= + +[Unit1] +FileName=..\..\pa_common\pa_skeleton.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_skeleton.c -o ./pa_skeleton.o $(CFLAGS) + +[Unit2] +FileName=..\..\pa_common\pa_stream.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_stream.c -o ./pa_stream.o $(CFLAGS) + +[Unit3] +FileName=..\..\pa_common\pa_trace.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_trace.c -o ./pa_trace.o $(CFLAGS) + +[Unit4] +FileName=..\..\pa_common\pa_allocation.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_allocation.c -o ./pa_allocation.o $(CFLAGS) + +[Unit5] +FileName=..\..\pa_common\pa_converters.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_converters.c -o ./pa_converters.o $(CFLAGS) + +[Unit6] +FileName=..\..\pa_common\pa_cpuload.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_cpuload.c -o ./pa_cpuload.o $(CFLAGS) + +[Unit7] +FileName=..\..\pa_common\pa_dither.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_dither.c -o ./pa_dither.o $(CFLAGS) + +[Unit8] +FileName=..\..\pa_common\pa_front.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_front.c -o ./pa_front.o $(CFLAGS) + +[Unit9] +FileName=..\..\pa_common\pa_process.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_process.c -o ./pa_process.o $(CFLAGS) + +[VersionInfo] +Major=0 +Minor=1 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion= +FileDescription=Developed using the Dev-C++ IDE +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion= +AutoIncBuildNr=0 + +[Unit10] +FileName=..\..\pa_asio\pa_asio.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CPP) -c pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +[Unit11] +FileName=..\pa_win_util.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_util.c -o ./pa_win_util.o $(CFLAGS) + +[Unit12] +FileName=..\pa_win_hostapis.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_hostapis.c -o ./pa_win_hostapis.o $(CFLAGS) + +[Unit13] +FileName=..\..\pa_win_ds\pa_win_ds.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_ds.c -o ./pa_win_ds.o $(CFLAGS) + +[Unit14] +FileName=..\..\pa_win_ds\dsound_wrapper.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c dsound_wrapper.c -o ./dsound_wrapper.o $(CFLAGS) + +[Unit15] +FileName=..\..\pa_win_wmme\pa_win_wmme.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_wmme.c -o ./pa_win_wmme.o $(CFLAGS) + +[Unit16] +FileName=..\..\pa_asio\iasiothiscallresolver.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/portaudio-v19/pa_win/dev-cpp/portaudio-static.dev b/portaudio-v19/pa_win/dev-cpp/portaudio-static.dev new file mode 100644 index 000000000..2aae584df --- /dev/null +++ b/portaudio-v19/pa_win/dev-cpp/portaudio-static.dev @@ -0,0 +1,209 @@ +[Project] +FileName=portaudio-static.dev +Name=portaudio-static +UnitCount=16 +Type=2 +Ver=1 +ObjFiles= +Includes=..\..\..\asiosdk2;..\..\..\asiosdk2\common;..\..\..\asiosdk2\host;..\..\..\asiosdk2\host\pc;..\..\pa_common +Libs= +PrivateResource= +ResourceIncludes= +MakeIncludes= +Compiler=-O3_@@_ +CppCompiler=-O3_@@_ +Linker=-O3 -s_@@_ +IsCpp=1 +Icon= +ExeOutput=. +ObjectOutput=. +OverrideOutput=0 +OverrideOutputName=portaudio.a +HostApplication= +Folders= +CommandLine= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000 +UseCustomMakefile=0 +CustomMakefile= + +[Unit1] +FileName=..\..\pa_common\pa_skeleton.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_skeleton.c -o ./pa_skeleton.o $(CFLAGS) + +[Unit2] +FileName=..\..\pa_common\pa_stream.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_stream.c -o ./pa_stream.o $(CFLAGS) + +[Unit3] +FileName=..\..\pa_common\pa_trace.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_trace.c -o ./pa_trace.o $(CFLAGS) + +[Unit4] +FileName=..\..\pa_common\pa_allocation.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_allocation.c -o ./pa_allocation.o $(CFLAGS) + +[Unit5] +FileName=..\..\pa_common\pa_converters.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_converters.c -o ./pa_converters.o $(CFLAGS) + +[Unit6] +FileName=..\..\pa_common\pa_cpuload.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_cpuload.c -o ./pa_cpuload.o $(CFLAGS) + +[Unit7] +FileName=..\..\pa_common\pa_dither.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_dither.c -o ./pa_dither.o $(CFLAGS) + +[Unit8] +FileName=..\..\pa_common\pa_front.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_front.c -o ./pa_front.o $(CFLAGS) + +[Unit9] +FileName=..\..\pa_common\pa_process.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_process.c -o ./pa_process.o $(CFLAGS) + +[VersionInfo] +Major=0 +Minor=1 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion= +FileDescription=Developed using the Dev-C++ IDE +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion= +AutoIncBuildNr=0 + +[Unit10] +FileName=..\..\pa_asio\pa_asio.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CPP) -c pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +[Unit11] +FileName=..\..\pa_win\pa_win_util.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_util.c -o ./pa_win_util.o $(CFLAGS) + +[Unit12] +FileName=..\..\pa_win\pa_win_hostapis.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_hostapis.c -o ./pa_win_hostapis.o $(CFLAGS) + +[Unit13] +FileName=..\..\pa_win_ds\pa_win_ds.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_ds.c -o ./pa_win_ds.o $(CFLAGS) + +[Unit14] +FileName=..\..\pa_win_ds\dsound_wrapper.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c dsound_wrapper.c -o ./dsound_wrapper.o $(CFLAGS) + +[Unit15] +FileName=..\..\pa_win_wmme\pa_win_wmme.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_wmme.c -o ./pa_win_wmme.o $(CFLAGS) + +[Unit16] +FileName=..\..\pa_asio\iasiothiscallresolver.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/portaudio-v19/pa_win/dev-cpp/readme.txt b/portaudio-v19/pa_win/dev-cpp/readme.txt new file mode 100644 index 000000000..07108a7dd --- /dev/null +++ b/portaudio-v19/pa_win/dev-cpp/readme.txt @@ -0,0 +1,23 @@ +From: "Peter L Jones" +Sent: Wednesday, September 17, 2003 5:18 AM +Subject: Dev-C++ project files + +I attach two project files intended for portaudio/pa_win/dev-cpp (i.e. in +parallel with the msvc directory), if you want them. One is for a static +library build and one for a DLL. I've used the static library (in building +a single monolithic DLL) but I can't guarantee the DLL version will build a +working library (I think it's mostly there, though!). + +I also attach the resulting makefiles, which may be of use to other MinGW +users. + +They're rooted in the directory given above and drop their object and +library files in the same place. They assume the asiosdk2 files are in the +same directory as portaudio/ in a sub-directory called asiosdk2/. Oh! The +DLL is built against a static asiosdk2.a library... maybe not the best way +to do it... I ought to figure out how to link against a "home made" dll in +Dev-C++, I guess ;-) + +Cheers, + +-- Peter diff --git a/portaudio-v19/pa_win/msvc/Makefile.msvc b/portaudio-v19/pa_win/msvc/Makefile.msvc new file mode 100644 index 000000000..2f1a104e7 --- /dev/null +++ b/portaudio-v19/pa_win/msvc/Makefile.msvc @@ -0,0 +1,159 @@ +# Portaudio v1.9-devel VC6 DLL makefile 1.0 +# +# David Viens, davidv@plogue.com +# (im far from a VC6 makefile expert, so please bear with me :) +# +# For more info, look at readme.txt +# +#if you keep the ASIODIR as ".", it will use the SDK files that direclty in "pa_win/msvc" dir +ASIODIR=. +ASIOINC=/I ".\host" /I ".\host\pc" /I ".\common" +# + +LIBZ=kernel32.lib user32.lib gdi32.lib wininet.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib winmm.lib dsound.lib dxguid.lib + +CPP=cl.exe +LINK32=link.exe + +#release +CFLAGS=/nologo /MD /W3 /GX /O2 /Ob2 /I "src" /I "Win32" /I "$(MSVCDir)\Include" /D "WIN32" /D "NDEBUG" /D "_USRDLL" /YX /FD +DLL_LINK_FLAGS= /nologo /dll /incremental:no /libpath:"$(MSVCDir)\Lib" $(LIBZ) /pdb:"portaudio.pdb" /implib:".\portaudio.lib" /machine:I386 /out:"portaudio.dll" + + +COMMONINC=/I "..\..\pa_common" /I "." + +#==================================================================== +# Targets + +ALL : portaudio.dll + +CLEAN : + -@erase "*.obj" + +#==================================================================== + + +LINK32_OBJS= \ + ".\pa_allocation.obj" \ + ".\pa_converters.obj" \ + ".\pa_x86_plain_converters.obj" \ +# ".\pa_cppHelp.obj" \ + ".\pa_cpuload.obj" \ + ".\pa_dither.obj" \ + ".\pa_front.obj" \ + ".\pa_process.obj" \ + ".\pa_skeleton.obj" \ + ".\pa_stream.obj" \ + ".\pa_trace.obj" \ +# + ".\pa_win_hostapis.obj" \ + ".\pa_win_util.obj" \ +# + ".\pa_win_wmme.obj" \ +# + ".\pa_win_ds.obj" \ + ".\dsound_wrapper.obj" \ +# + ".\pa_asio.obj" \ +# + ".\asio.obj" \ + ".\ASIOConvertSamples.obj" \ + ".\asiodrivers.obj" \ + ".\asiolist.obj" \ + ".\combase.obj" \ + ".\debugmessage.obj" \ +# ".\dllentry.obj" \ + ".\register.obj" + + +portaudio.dll : $(LINK32_OBJS) ".\portaudio.def" + $(LINK32) $(DLL_LINK_FLAGS) /def:".\portaudio.def" $(LINK32_OBJS) + +#==================================================================== +# asio files (need to agree to steinberg agreement) +# this makefile assumes all files have being copied in the pa_win/msvc dir (for now) +# see readme.txt for details + +".\asio.obj" : ".\common\asio.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asio.obj" /c ".\common\asio.cpp" + +".\ASIOConvertSamples.obj" : ".\host\ASIOConvertSamples.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\ASIOConvertSamples.obj" /c ".\host\ASIOConvertSamples.cpp" + +".\asiodrivers.obj" : ".\host\asiodrivers.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asiodrivers.obj" /c ".\host\asiodrivers.cpp" + +".\asiolist.obj" : ".\host\pc\asiolist.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asiolist.obj" /c ".\host\pc\asiolist.cpp" + +".\combase.obj" : ".\common\combase.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\combase.obj" /c ".\common\combase.cpp" + +".\debugmessage.obj" : ".\common\debugmessage.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\debugmessage.obj" /c ".\common\debugmessage.cpp" + +".\register.obj" : ".\common\register.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\register.obj" /c ".\common\register.cpp" + +#==================================================================== +# Portaudio Common +# +".\pa_allocation.obj" : "..\..\pa_common\pa_allocation.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_allocation.obj" /c "..\..\pa_common\pa_allocation.c" + +".\pa_converters.obj" : "..\..\pa_common\pa_converters.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_converters.obj" /c "..\..\pa_common\pa_converters.c" + +".\pa_cppHelp.obj" : "..\..\pa_common\pa_cppHelp.cpp" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_cppHelp.obj" /c "..\..\pa_common\pa_cppHelp.cpp" + +".\pa_cpuload.obj" : "..\..\pa_common\pa_cpuload.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_cpuload.obj" /c "..\..\pa_common\pa_cpuload.c" + +".\pa_dither.obj" : "..\..\pa_common\pa_dither.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_dither.obj" /c "..\..\pa_common\pa_dither.c" + +".\pa_front.obj" : "..\..\pa_common\pa_front.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_front.obj" /c "..\..\pa_common\pa_front.c" + +".\pa_process.obj" : "..\..\pa_common\pa_process.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_process.obj" /c "..\..\pa_common\pa_process.c" + +".\pa_skeleton.obj" : "..\..\pa_common\pa_skeleton.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_skeleton.obj" /c "..\..\pa_common\pa_skeleton.c" + +".\pa_stream.obj" : "..\..\pa_common\pa_stream.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_stream.obj" /c "..\..\pa_common\pa_stream.c" + +".\pa_trace.obj" : "..\..\pa_common\pa_trace.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_trace.obj" /c "..\..\pa_common\pa_trace.c" + +#==================================================================== +# Portaudio implementations +# + +".\pa_win_hostapis.obj" : "..\..\pa_win\pa_win_hostapis.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_hostapis.obj" /c "..\..\pa_win\pa_win_hostapis.c" + +".\pa_win_util.obj" : "..\..\pa_win\pa_win_util.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_util.obj" /c "..\..\pa_win\pa_win_util.c" + +".\pa_x86_plain_converters.obj" : "..\..\pa_win\pa_x86_plain_converters.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_x86_plain_converters.obj" /c "..\..\pa_win\pa_x86_plain_converters.c" + +".\pa_asio.obj" : "..\..\pa_asio\pa_asio.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) $(COMMONINC) /Fo".\pa_asio.obj" /c "..\..\pa_asio\pa_asio.cpp" + +".\pa_win_wmme.obj" : "..\..\pa_win_wmme\pa_win_wmme.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_wmme.obj" /c "..\..\pa_win_wmme\pa_win_wmme.c" + +".\pa_win_ds.obj" : "..\..\pa_win_ds\pa_win_ds.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_ds.obj" /c "..\..\pa_win_ds\pa_win_ds.c" + +".\dsound_wrapper.obj" : "..\..\pa_win_ds\dsound_wrapper.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\dsound_wrapper.obj" /c "..\..\pa_win_ds\dsound_wrapper.c" + + +# End of Makefile +#==================================================================== +# \ No newline at end of file diff --git a/portaudio-v19/pa_win/msvc/clean.bat b/portaudio-v19/pa_win/msvc/clean.bat new file mode 100755 index 000000000..601c0d314 --- /dev/null +++ b/portaudio-v19/pa_win/msvc/clean.bat @@ -0,0 +1,7 @@ +del *.obj +del *.dll +del *.lib +del *.exp +del *.pch +del *.idb + diff --git a/portaudio-v19/pa_win/msvc/make.bat b/portaudio-v19/pa_win/msvc/make.bat new file mode 100755 index 000000000..34d6aed48 --- /dev/null +++ b/portaudio-v19/pa_win/msvc/make.bat @@ -0,0 +1,8 @@ +CALL C:\progra~1\micros~2\VC98\bin\vcvars32 +del *.dll +del *.lib +nmake Makefile.msvc +del *.obj +del *.exp +del *.pch +del *.idb diff --git a/portaudio-v19/pa_win/msvc/portaudio.def b/portaudio-v19/pa_win/msvc/portaudio.def new file mode 100644 index 000000000..97a1c3502 --- /dev/null +++ b/portaudio-v19/pa_win/msvc/portaudio.def @@ -0,0 +1,43 @@ +LIBRARY portaudio.dll +EXPORTS + +; +Pa_GetVersion @1 +Pa_GetVersionText @2 +Pa_GetErrorText @3 +Pa_Initialize @4 +Pa_Terminate @5 +Pa_GetHostApiCount @6 +Pa_GetDefaultHostApi @7 +Pa_GetHostApiInfo @8 +Pa_HostApiTypeIdToHostApiIndex @9 +Pa_HostApiDeviceIndexToDeviceIndex @10 +Pa_GetLastHostErrorInfo @11 +Pa_GetDeviceCount @12 +Pa_GetDefaultInputDevice @13 +Pa_GetDefaultOutputDevice @14 +Pa_GetDeviceInfo @15 +Pa_IsFormatSupported @16 +Pa_OpenStream @17 +Pa_OpenDefaultStream @18 +Pa_CloseStream @19 +Pa_SetStreamFinishedCallback @20 +Pa_StartStream @21 +Pa_StopStream @22 +Pa_AbortStream @23 +Pa_IsStreamStopped @24 +Pa_IsStreamActive @25 +Pa_GetStreamInfo @26 +Pa_GetStreamTime @27 +Pa_GetStreamCpuLoad @28 +Pa_ReadStream @29 +Pa_WriteStream @30 +Pa_GetStreamReadAvailable @31 +Pa_GetStreamWriteAvailable @32 +Pa_GetSampleSize @33 +Pa_Sleep @34 +PaAsio_GetAvailableLatencyValues @50 +PaAsio_ShowControlPanel @51 +PaUtil_InitializeX86PlainConverters @52 +PaAsio_GetInputChannelName @53 +PaAsio_GetOutputChannelName @54 \ No newline at end of file diff --git a/portaudio-v19/pa_win/msvc/readme.txt b/portaudio-v19/pa_win/msvc/readme.txt new file mode 100644 index 000000000..19ced4c4c --- /dev/null +++ b/portaudio-v19/pa_win/msvc/readme.txt @@ -0,0 +1,56 @@ +Hello + + This is a small list of steps in order to build portaudio +(Currently v19-devel) into a VC6 DLL and lib file. +This DLL contains all 3 current win32 PA APIS (MM/DS/ASIO) + +1)Copy the source dirs that comes with the ASIO SDK inside pa_win\msvc + so you should now have: + + pa_win\msvc\common + pa_win\msvc\host + pa_win\msvc\host\sample + pa_win\msvc\host\pc + pa_win\msvc\host\mac (not needed) + + You dont need "driver" + + +2)execure "make.bat", this assumes VC6 is installed in + C:\Program Files\Microsoft Visual Studio\ + + if its not, + + Open a command Prompt and execute "vcvars32.bat" which sets the environment + so that you can use Microsoft's "nmake" + EX: C:\Program Files\Microsoft Visual Studio\VC98\Bin\vcvars32.bat + or (C:\progra~1\micros~2\VC98\bin\vcvars32) dumb de dumb + + You should now have seen a line that said: + "Setting environment for using Microsoft Visual C++ tools." + While in pa_win\msvc , type "nmake makefile.msvc" + this _should_ create portaudio.dll and portaudio.lib + +3)Now in any VC6 project, in which you require portaudio, + you can just link with portaudio.lib, and of course include the + relevant headers + (portaudio.h, and/or pa_asio.h , pa_x86_plain_converters.h) See (*) + +4) Your new exe should now use portaudio.dll. + + +Have fun! + +(*): you may want to add/remove some DLL entry points. +Right now those 3 entries are _not_ from portaudio.h + +(from portaudio.def) +(...) +PaAsio_GetAvailableLatencyValues @50 +PaAsio_ShowControlPanel @51 +PaUtil_InitializeX86PlainConverters @52 + + +----- +last update April 16th 2003 +David Viens, davidv@plogue.com \ No newline at end of file diff --git a/portaudio-v19/pa_win/msvc/setenv.bat b/portaudio-v19/pa_win/msvc/setenv.bat new file mode 100755 index 000000000..f4c815a78 --- /dev/null +++ b/portaudio-v19/pa_win/msvc/setenv.bat @@ -0,0 +1 @@ +C:\progra~1\micros~2\VC98\bin\vcvars32 diff --git a/portaudio-v19/pa_win/pa_win_hostapis.c b/portaudio-v19/pa_win/pa_win_hostapis.c new file mode 100644 index 000000000..5d5ac87f3 --- /dev/null +++ b/portaudio-v19/pa_win/pa_win_hostapis.c @@ -0,0 +1,86 @@ +/* + * $Id$ + * Portable Audio I/O Library Windows initialization table + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + Win32 host API initialization function table. + + @todo Consider using PA_USE_WMME etc instead of PA_NO_WMME. This is what + the Unix version does, we should consider being consistent. +*/ + + +#include "pa_hostapi.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +PaUtilHostApiInitializer *paHostApiInitializers[] = + { + +#ifndef PA_NO_WMME + PaWinMme_Initialize, +#endif + +#ifndef PA_NO_DS + PaWinDs_Initialize, +#endif + +#ifndef PA_NO_ASIO + PaAsio_Initialize, +#endif + +/* +#ifndef PA_NO_WDMKS + PaWinWdm_Initialize, +#endif +*/ + + PaSkeleton_Initialize, /* just for testing */ + + 0 /* NULL terminated array */ + }; + + +int paDefaultHostApiIndex = 0; + diff --git a/portaudio-v19/pa_win/pa_win_util.c b/portaudio-v19/pa_win/pa_win_util.c new file mode 100644 index 000000000..32d0dbb98 --- /dev/null +++ b/portaudio-v19/pa_win/pa_win_util.c @@ -0,0 +1,134 @@ +/* + * $Id$ + * Portable Audio I/O Library + * Win32 platform-specific support functions + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + Win32 platform-specific support functions. + + @todo Implement workaround for QueryPerformanceCounter() skipping forward + bug. (see msdn kb Q274323). +*/ + +#include +#include /* for timeGetTime() */ + +#include "pa_util.h" + + +/* + Track memory allocations to avoid leaks. + */ + +#if PA_TRACK_MEMORY +static int numAllocations_ = 0; +#endif + + +void *PaUtil_AllocateMemory( long size ) +{ + void *result = GlobalAlloc( GPTR, size ); + +#if PA_TRACK_MEMORY + if( result != NULL ) numAllocations_ += 1; +#endif + return result; +} + + +void PaUtil_FreeMemory( void *block ) +{ + if( block != NULL ) + { + GlobalFree( block ); +#if PA_TRACK_MEMORY + numAllocations_ -= 1; +#endif + + } +} + + +int PaUtil_CountCurrentlyAllocatedBlocks( void ) +{ +#if PA_TRACK_MEMORY + return numAllocations_; +#else + return 0; +#endif +} + + +void Pa_Sleep( long msec ) +{ + Sleep( msec ); +} + +static int usePerformanceCounter_; +static double secondsPerTick_; + +void PaUtil_InitializeClock( void ) +{ + LARGE_INTEGER ticksPerSecond; + + if( QueryPerformanceFrequency( &ticksPerSecond ) != 0 ) + { + usePerformanceCounter_ = 1; + secondsPerTick_ = 1.0 / (double)ticksPerSecond.QuadPart; + } + else + { + usePerformanceCounter_ = 0; + } +} + + +double PaUtil_GetTime( void ) +{ + LARGE_INTEGER time; + + if( usePerformanceCounter_ ) + { + /* FIXME: + according to this knowledge-base article, QueryPerformanceCounter + can skip forward by seconds! + http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323& + + it may be better to use the rtdsc instruction using inline asm, + however then a method is needed to calculate a ticks/seconds ratio. + */ + QueryPerformanceCounter( &time ); + return time.QuadPart * secondsPerTick_; + } + else + { + return timeGetTime() * .001; + } +} diff --git a/portaudio-v19/pa_win/pa_x86_plain_converters.c b/portaudio-v19/pa_win/pa_x86_plain_converters.c new file mode 100644 index 000000000..98442a8c6 --- /dev/null +++ b/portaudio-v19/pa_win/pa_x86_plain_converters.c @@ -0,0 +1,1167 @@ +#include "pa_x86_plain_converters.h" + +#include "pa_converters.h" +#include "pa_dither.h" + +/* + plain intel assemby versions of standard pa converter functions. + + the main reason these versions are faster than the equivalent C versions + is that float -> int casting is expensive in C on x86 because the rounding + mode needs to be changed for every cast. these versions only set + the rounding mode once outside the loop. + + small additional speed gains are made by the way that clamping is + implemented. + +TODO: + o- inline dither code + o- implement Dither only (no-clip) versions + o- implement int8 and uint8 versions + o- test thouroughly + + o- the packed 24 bit functions could benefit from unrolling and avoiding + byte and word sized register access. +*/ + +/* -------------------------------------------------------------------------- */ + +/* +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } +*/ + +/* + the following notes were used to determine whether a floating point + value should be saturated (ie >1 or <-1) by loading it into an integer + register. these should be rewritten so that they make sense. + + an ieee floating point value + + 1.xxxxxxxxxxxxxxxxxxxx? + + + is less than or equal to 1 and greater than or equal to -1 either: + + if the mantissa is 0 and the unbiased exponent is 0 + + OR + + if the unbiased exponent < 0 + + this translates to: + + if the mantissa is 0 and the biased exponent is 7F + + or + + if the biased exponent is less than 7F + + + therefore the value is greater than 1 or less than -1 if + + the mantissa is not 0 and the biased exponent is 7F + + or + + if the biased exponent is greater than 7F + + + in other words, if we mask out the sign bit, the value is + greater than 1 or less than -1 if its integer representation is greater than: + + 0 01111111 0000 0000 0000 0000 0000 000 + + 0011 1111 1000 0000 0000 0000 0000 0000 => 0x3F800000 +*/ + +/* -------------------------------------------------------------------------- */ + +static const short fpuControlWord_ = 0x033F; /*round to nearest, 64 bit precision, all exceptions masked*/ +static const double int32Scaler_ = 0x7FFFFFFF; +static const double ditheredInt32Scaler_ = 0x7FFFFFFE; +static const double int24Scaler_ = 0x7FFFFF; +static const double ditheredInt24Scaler_ = 0x7FFFFE; +static const double int16Scaler_ = 0x7FFF; +static const double ditheredInt16Scaler_ = 0x7FFE; + +#define PA_DITHER_BITS_ (15) +/* Multiply by PA_FLOAT_DITHER_SCALE_ to get a float between -2.0 and +1.99999 */ +#define PA_FLOAT_DITHER_SCALE_ (1.0 / ((1< source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int32Scaler_ // stack: (int)0x7FFFFFFF + + Float32_To_Int32_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFFFF, (int)0x7FFFFFFF + /* + note: we could store to a temporary qword here which would cause + wraparound distortion instead of int indefinite 0x10. that would + be more work, and given that not enabling clipping is only advisable + when you know that your signal isn't going to clip it isn't worth it. + */ + fistp dword ptr [edi] // pop st(0) into dest, stack: (int)0x7FFFFFFF + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // REVIEW + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + *dest = (signed long) scaled; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int32Scaler_ // stack: (int)0x7FFFFFFF + + Float32_To_Int32_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int32_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFFFF, (int)0x7FFFFFFF + fistp dword ptr [edi] // pop st(0) into dest, stack: (int)0x7FFFFFFF + jmp Float32_To_Int32_Clip_stored + + Float32_To_Int32_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFFFF // convert to maximum range integers + mov dword ptr [edi], edx + + Float32_To_Int32_Clip_stored: + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + /* + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + + while( count-- ) + { + // REVIEW + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + *dest = (signed long) dithered; + + + src += sourceStride; + dest += destinationStride; + } + */ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt32Scaler_ // stack: int scaler + + Float32_To_Int32_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int32_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither + value*(int scaler), int scaler + fistp dword ptr [edi] // pop st(0) into dest, stack: int scaler + jmp Float32_To_Int32_DitherClip_stored + + Float32_To_Int32_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFFFF // convert to maximum range integers + mov dword ptr [edi], edx + + Float32_To_Int32_DitherClip_stored: + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + double scaled = *src * 0x7FFFFFFF; + temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + signed long tempInt32; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int24Scaler_ // stack: (int)0x7FFFFF + + Float32_To_Int24_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFF, (int)0x7FFFFF + fistp tempInt32 // pop st(0) into tempInt32, stack: (int)0x7FFFFF + mov edx, tempInt32 + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + signed long tempInt32; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int24Scaler_ // stack: (int)0x7FFFFF + + Float32_To_Int24_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int24_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFF, (int)0x7FFFFF + fistp tempInt32 // pop st(0) into tempInt32, stack: (int)0x7FFFFF + mov edx, tempInt32 + jmp Float32_To_Int24_Clip_store + + Float32_To_Int24_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFF // convert to maximum range integers + + Float32_To_Int24_Clip_store: + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + + // FIXME: the dither amplitude here appears to be too small by 8 bits + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + + temp = (signed long) dithered; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + signed long tempInt32; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt24Scaler_ // stack: int scaler + + Float32_To_Int24_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int24_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither * value*(int scaler), int scaler + fistp tempInt32 // pop st(0) into tempInt32, stack: int scaler + mov edx, tempInt32 + jmp Float32_To_Int24_DitherClip_store + + Float32_To_Int24_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFF // convert to maximum range integers + + Float32_To_Int24_DitherClip_store: + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + + short samp = (short) (*src * (32767.0f)); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int16Scaler_ // stack: (int)0x7FFF + + Float32_To_Int16_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFF, (int)0x7FFF + fistp word ptr [edi] // store scaled int into dest, stack: (int)0x7FFF + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + long samp = (signed long) (*src * (32767.0f)); + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int16Scaler_ // stack: (int)0x7FFF + + Float32_To_Int16_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int16_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFF, (int)0x7FFF + fistp word ptr [edi] // store scaled int into dest, stack: (int)0x7FFF + jmp Float32_To_Int16_Clip_stored + + Float32_To_Int16_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add dx, 0x7FFF // convert to maximum range integers + mov word ptr [edi], dx // store clamped into into dest + + Float32_To_Int16_Clip_stored: + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + float dithered = (*src * (32766.0f)) + dither; + signed long samp = (signed long) dithered; + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt16Scaler_ // stack: int scaler + + Float32_To_Int16_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int16_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] // current = randSeed1>>x + randSeed2>>x + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither * value*(int scaler), int scaler + fistp word ptr [edi] // store scaled int into dest, stack: int scaler + jmp Float32_To_Int16_DitherClip_stored + + Float32_To_Int16_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add dx, 0x7FFF // convert to maximum range integers + mov word ptr [edi], dx // store clamped into into dest + + Float32_To_Int16_DitherClip_stored: + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +void PaUtil_InitializeX86PlainConverters( void ) +{ + paConverters.Float32_To_Int32 = Float32_To_Int32; + paConverters.Float32_To_Int32_Clip = Float32_To_Int32_Clip; + paConverters.Float32_To_Int32_DitherClip = Float32_To_Int32_DitherClip; + + paConverters.Float32_To_Int24 = Float32_To_Int24; + paConverters.Float32_To_Int24_Clip = Float32_To_Int24_Clip; + paConverters.Float32_To_Int24_DitherClip = Float32_To_Int24_DitherClip; + + paConverters.Float32_To_Int16 = Float32_To_Int16; + paConverters.Float32_To_Int16_Clip = Float32_To_Int16_Clip; + paConverters.Float32_To_Int16_DitherClip = Float32_To_Int16_DitherClip; +} + +/* -------------------------------------------------------------------------- */ diff --git a/portaudio-v19/pa_win/pa_x86_plain_converters.h b/portaudio-v19/pa_win/pa_x86_plain_converters.h new file mode 100644 index 000000000..f56c710fd --- /dev/null +++ b/portaudio-v19/pa_win/pa_x86_plain_converters.h @@ -0,0 +1,19 @@ +#ifndef PA_X86_PLAIN_CONVERTERS_H +#define PA_X86_PLAIN_CONVERTERS_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** + @brief Install optimised converter functions suitable for all IA32 processors +*/ +void PaUtil_InitializeX86PlainConverters( void ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_X86_PLAIN_CONVERTERS_H */ diff --git a/portaudio-v19/pa_win_ds/dsound_wrapper.c b/portaudio-v19/pa_win_ds/dsound_wrapper.c new file mode 100644 index 000000000..e29019a8d --- /dev/null +++ b/portaudio-v19/pa_win_ds/dsound_wrapper.c @@ -0,0 +1,616 @@ +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include + +#include "dsound_wrapper.h" +#include "pa_trace.h" + +/* + Rather than linking with dxguid.a or using "#define INITGUID" to force a + header file to instantiate the required GUID(s), we define them directly + below. +*/ +#include // needed for the DEFINE_GUID macro +DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + + +/************************************************************************************/ +DSoundEntryPoints dswDSoundEntryPoints = { 0, 0, 0, 0, 0, 0, 0 }; +/************************************************************************************/ +static HRESULT WINAPI DummyDirectSoundCreate(LPGUID lpcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter) +{ + (void)lpcGuidDevice; /* unused parameter */ + (void)ppDS; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateW(LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateA(LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureCreate(LPGUID lpcGUID, LPDIRECTSOUNDCAPTURE *lplpDSC, LPUNKNOWN pUnkOuter) +{ + (void)lpcGUID; /* unused parameter */ + (void)lplpDSC; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateW(LPDSENUMCALLBACKW lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} +/************************************************************************************/ +void DSW_InitializeDSoundEntryPoints(void) +{ + dswDSoundEntryPoints.hInstance_ = LoadLibrary("dsound.dll"); + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + dswDSoundEntryPoints.DirectSoundCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCreate" ); + if( dswDSoundEntryPoints.DirectSoundCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + + dswDSoundEntryPoints.DirectSoundEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + + dswDSoundEntryPoints.DirectSoundEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + + dswDSoundEntryPoints.DirectSoundCaptureCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureCreate" ); + if( dswDSoundEntryPoints.DirectSoundCaptureCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } + else + { + /* initialize with dummy entry points to make live easy when ds isn't present */ + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } +} +/************************************************************************************/ +void DSW_TerminateDSoundEntryPoints(void) +{ + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + FreeLibrary( dswDSoundEntryPoints.hInstance_ ); + dswDSoundEntryPoints.hInstance_ = NULL; + /* ensure that we crash reliably if the entry points arent initialised */ + dswDSoundEntryPoints.DirectSoundCreate = 0; + dswDSoundEntryPoints.DirectSoundEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundEnumerateA = 0; + dswDSoundEntryPoints.DirectSoundCaptureCreate = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = 0; + } +} +/************************************************************************************/ +void DSW_Term( DSoundWrapper *dsw ) +{ + // Cleanup the sound buffers + if (dsw->dsw_OutputBuffer) + { + IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + IDirectSoundBuffer_Release( dsw->dsw_OutputBuffer ); + dsw->dsw_OutputBuffer = NULL; + } + + if (dsw->dsw_InputBuffer) + { + IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + IDirectSoundCaptureBuffer_Release( dsw->dsw_InputBuffer ); + dsw->dsw_InputBuffer = NULL; + } + + if (dsw->dsw_pDirectSoundCapture) + { + IDirectSoundCapture_Release( dsw->dsw_pDirectSoundCapture ); + dsw->dsw_pDirectSoundCapture = NULL; + } + + if (dsw->dsw_pDirectSound) + { + IDirectSound_Release( dsw->dsw_pDirectSound ); + dsw->dsw_pDirectSound = NULL; + } +} +/************************************************************************************/ +HRESULT DSW_Init( DSoundWrapper *dsw ) +{ + memset( dsw, 0, sizeof(DSoundWrapper) ); + return 0; +} +/************************************************************************************/ +HRESULT DSW_InitOutputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + // Create the DS object + HRESULT hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DWORD dwDataLen; + DWORD playCursor; + HRESULT result; + LPDIRECTSOUNDBUFFER pPrimaryBuffer; + HWND hWnd; + HRESULT hr; + WAVEFORMATEX wfFormat; + DSBUFFERDESC primaryDesc; + DSBUFFERDESC secondaryDesc; + unsigned char* pDSBuffData; + LARGE_INTEGER counterFrequency; + + dsw->dsw_OutputSize = bytesPerBuffer; + dsw->dsw_OutputRunning = FALSE; + dsw->dsw_OutputUnderflows = 0; + dsw->dsw_FramesWritten = 0; + dsw->dsw_BytesPerOutputFrame = nChannels * sizeof(short); + + // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the + // applications's window. Also if that window is closed before the Buffer is closed + // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.) + // So we will use GetDesktopWindow() which was suggested by Miller Puckette. + // hWnd = GetForegroundWindow(); + // + // FIXME: The example code I have on the net creates a hidden window that + // is managed by our code - I think we should do that - one hidden + // window for the whole of Pa_DS + // + hWnd = GetDesktopWindow(); + + // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz. + // Exclusize also prevents unexpected sounds from other apps during a performance. + if ((hr = IDirectSound_SetCooperativeLevel( dsw->dsw_pDirectSound, + hWnd, DSSCL_EXCLUSIVE)) != DS_OK) + { + return hr; + } + + // ----------------------------------------------------------------------- + // Create primary buffer and set format just so we can specify our custom format. + // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz. + // Setup the primary buffer description + ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC)); + primaryDesc.dwSize = sizeof(DSBUFFERDESC); + primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth + primaryDesc.dwBufferBytes = 0; + primaryDesc.lpwfxFormat = NULL; + // Create the buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + // Set the primary buffer's format + if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result; + + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); + secondaryDesc.dwSize = sizeof(DSBUFFERDESC); + secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + secondaryDesc.dwBufferBytes = bytesPerBuffer; + secondaryDesc.lpwfxFormat = &wfFormat; + // Create the secondary buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &secondaryDesc, &dsw->dsw_OutputBuffer, NULL)) != DS_OK) return result; + // Lock the DS buffer + if ((result = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, 0, dsw->dsw_OutputSize, (LPVOID*)&pDSBuffData, + &dwDataLen, NULL, 0, 0)) != DS_OK) return result; + // Zero the DS buffer + ZeroMemory(pDSBuffData, dwDataLen); + // Unlock the DS buffer + if ((result = IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result; + if( QueryPerformanceFrequency( &counterFrequency ) ) + { + int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short)); + dsw->dsw_CounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate; + } + else + { + dsw->dsw_CounterTicksPerBuffer.QuadPart = 0; + } + // Let DSound set the starting write position because if we set it to zero, it looks like the + // buffer is full to begin with. This causes a long pause before sound starts when using large buffers. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &dsw->dsw_WriteOffset ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_FramesWritten = dsw->dsw_WriteOffset / dsw->dsw_BytesPerOutputFrame; + /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartOutput( DSoundWrapper *dsw ) +{ + HRESULT hr; + QueryPerformanceCounter( &dsw->dsw_LastPlayTime ); + dsw->dsw_LastPlayCursor = 0; + dsw->dsw_FramesPlayed = 0; + hr = IDirectSoundBuffer_SetCurrentPosition( dsw->dsw_OutputBuffer, 0 ); + if( hr != DS_OK ) + { + return hr; + } + // Start the buffer playback in a loop. + if( dsw->dsw_OutputBuffer != NULL ) + { + hr = IDirectSoundBuffer_Play( dsw->dsw_OutputBuffer, 0, 0, DSBPLAY_LOOPING ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_OutputRunning = TRUE; + } + + return 0; +} +/************************************************************************************/ +HRESULT DSW_StopOutput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_OutputBuffer != NULL ) + { + dsw->dsw_OutputRunning = FALSE; + return IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilledPtr ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long bytesFilled; + // Query to see where play position is. + // We don't need the writeCursor but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + bytesFilled = dsw->dsw_WriteOffset - playCursor; + if( bytesFilled < 0 ) bytesFilled += dsw->dsw_OutputSize; // unwrap offset + *bytesFilledPtr = bytesFilled; + return hr; +} + +/************************************************************************************ + * Determine how much space can be safely written to in DS buffer. + * Detect underflows and overflows. + * Does not allow writing into safety gap maintained by DirectSound. + */ +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long numBytesEmpty; + long playWriteGap; + // Query to see how much room is in buffer. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + // Determine size of gap between playIndex and WriteIndex that we cannot write into. + playWriteGap = writeCursor - playCursor; + if( playWriteGap < 0 ) playWriteGap += dsw->dsw_OutputSize; // unwrap + /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */ + /* Attempt to detect playCursor wrap-around and correct it. */ + if( dsw->dsw_OutputRunning && (dsw->dsw_CounterTicksPerBuffer.QuadPart != 0) ) + { + /* How much time has elapsed since last check. */ + LARGE_INTEGER currentTime; + LARGE_INTEGER elapsedTime; + long bytesPlayed; + long bytesExpected; + long buffersWrapped; + QueryPerformanceCounter( ¤tTime ); + elapsedTime.QuadPart = currentTime.QuadPart - dsw->dsw_LastPlayTime.QuadPart; + dsw->dsw_LastPlayTime = currentTime; + /* How many bytes does DirectSound say have been played. */ + bytesPlayed = playCursor - dsw->dsw_LastPlayCursor; + if( bytesPlayed < 0 ) bytesPlayed += dsw->dsw_OutputSize; // unwrap + dsw->dsw_LastPlayCursor = playCursor; + /* Calculate how many bytes we would have expected to been played by now. */ + bytesExpected = (long) ((elapsedTime.QuadPart * dsw->dsw_OutputSize) / dsw->dsw_CounterTicksPerBuffer.QuadPart); + buffersWrapped = (bytesExpected - bytesPlayed) / dsw->dsw_OutputSize; + if( buffersWrapped > 0 ) + { + playCursor += (buffersWrapped * dsw->dsw_OutputSize); + bytesPlayed += (buffersWrapped * dsw->dsw_OutputSize); + } + /* Maintain frame output cursor. */ + dsw->dsw_FramesPlayed += (bytesPlayed / dsw->dsw_BytesPerOutputFrame); + } + numBytesEmpty = playCursor - dsw->dsw_WriteOffset; + if( numBytesEmpty < 0 ) numBytesEmpty += dsw->dsw_OutputSize; // unwrap offset + /* Have we underflowed? */ + if( numBytesEmpty > (dsw->dsw_OutputSize - playWriteGap) ) + { + if( dsw->dsw_OutputRunning ) + { + dsw->dsw_OutputUnderflows += 1; + } + dsw->dsw_WriteOffset = writeCursor; + numBytesEmpty = dsw->dsw_OutputSize - playWriteGap; + } + *bytesEmpty = numBytesEmpty; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + long bytesEmpty; + hr = DSW_QueryOutputSpace( dsw, &bytesEmpty ); // updates dsw_FramesPlayed + if (hr != DS_OK) return hr; + if( bytesEmpty == 0 ) return DS_OK; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, bytesEmpty, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + ZeroMemory(lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + ZeroMemory(lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += bytesEmpty / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + CopyMemory(lpbuf1, buf, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory(lpbuf2, buf+dwsize1, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += numBytes / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ) +{ + DWORD status; + if (IDirectSoundBuffer_GetStatus( dsw->dsw_OutputBuffer, &status ) != DS_OK) + return( DSERR_INVALIDPARAM ); + else + return( status ); +} + +/* These routines are used to support audio input. + * Do NOT compile these calls when using NT4 because it does + * not support the entry points. + */ +/************************************************************************************/ +HRESULT DSW_InitInputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + HRESULT hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} +/************************************************************************************/ +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DSCBUFFERDESC captureDesc; + WAVEFORMATEX wfFormat; + HRESULT result; + + dsw->dsw_BytesPerInputFrame = nChannels * sizeof(short); + + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + dsw->dsw_InputSize = bytesPerBuffer; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); + captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerBuffer; + captureDesc.lpwfxFormat = &wfFormat; + // Create the capture buffer + if ((result = IDirectSoundCapture_CreateCaptureBuffer( dsw->dsw_pDirectSoundCapture, + &captureDesc, &dsw->dsw_InputBuffer, NULL)) != DS_OK) return result; + dsw->dsw_ReadOffset = 0; // reset last read position to start of buffer + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartInput( DSoundWrapper *dsw ) +{ + // Start the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Start( dsw->dsw_InputBuffer, DSCBSTART_LOOPING ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_StopInput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ) +{ + HRESULT hr; + DWORD capturePos; + DWORD readPos; + long filled; + // Query to see how much data is in buffer. + // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundCaptureBuffer_GetCurrentPosition( dsw->dsw_InputBuffer, &capturePos, &readPos ); + if( hr != DS_OK ) + { + return hr; + } + filled = readPos - dsw->dsw_ReadOffset; + if( filled < 0 ) filled += dsw->dsw_InputSize; // unwrap offset + *bytesFilled = filled; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, dsw->dsw_ReadOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy from DS to the buffer + CopyMemory( buf, lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory( buf+dwsize1, lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + dwsize1 + dwsize2) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock ( dsw->dsw_InputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + } + return hr; +} + diff --git a/portaudio-v19/pa_win_ds/dsound_wrapper.h b/portaudio-v19/pa_win_ds/dsound_wrapper.h new file mode 100644 index 000000000..713f4a71d --- /dev/null +++ b/portaudio-v19/pa_win_ds/dsound_wrapper.h @@ -0,0 +1,130 @@ +#ifndef __DSOUND_WRAPPER_H +#define __DSOUND_WRAPPER_H +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * For PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* on Borland compilers, WIN32 doesn't seem to be defined by default, which + breaks DSound.h. Adding the define here fixes the problem. - rossb. */ +#ifdef __BORLANDC__ +#if !defined(WIN32) +#define WIN32 +#endif +#endif + +/* + We are only using DX3 in here, no need to polute the namespace - davidv +*/ +#define DIRECTSOUND_VERSION 0x0300 + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct +{ + HINSTANCE hInstance_; + + HRESULT (WINAPI *DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundEnumerateA)(LPDSENUMCALLBACKA, LPVOID); + + HRESULT (WINAPI *DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID); +}DSoundEntryPoints; + +extern DSoundEntryPoints dswDSoundEntryPoints; + +void DSW_InitializeDSoundEntryPoints(void); +void DSW_TerminateDSoundEntryPoints(void); + +#define DSW_NUM_POSITIONS (4) +#define DSW_NUM_EVENTS (5) +#define DSW_TERMINATION_EVENT (DSW_NUM_POSITIONS) + +typedef struct +{ +/* Output */ + LPDIRECTSOUND dsw_pDirectSound; + LPDIRECTSOUNDBUFFER dsw_OutputBuffer; + DWORD dsw_WriteOffset; /* last write position */ + INT dsw_OutputSize; + INT dsw_BytesPerOutputFrame; + /* Try to detect play buffer underflows. */ + LARGE_INTEGER dsw_CounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ + LARGE_INTEGER dsw_LastPlayTime; + UINT dsw_LastPlayCursor; + UINT dsw_OutputUnderflows; + BOOL dsw_OutputRunning; + /* use double which lets us can play for several thousand years with enough precision */ + double dsw_FramesWritten; + double dsw_FramesPlayed; +/* Input */ + INT dsw_BytesPerInputFrame; + LPDIRECTSOUNDCAPTURE dsw_pDirectSoundCapture; + LPDIRECTSOUNDCAPTUREBUFFER dsw_InputBuffer; + UINT dsw_ReadOffset; /* last read position */ + UINT dsw_InputSize; +} DSoundWrapper; + +HRESULT DSW_Init( DSoundWrapper *dsw ); +void DSW_Term( DSoundWrapper *dsw ); +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + WORD nChannels, int bufSize ); +HRESULT DSW_StartOutput( DSoundWrapper *dsw ); +HRESULT DSW_StopOutput( DSoundWrapper *dsw ); +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ); +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ); +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ); +HRESULT DSW_Enumerate( DSoundWrapper *dsw ); + +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + WORD nChannels, int bufSize ); +HRESULT DSW_StartInput( DSoundWrapper *dsw ); +HRESULT DSW_StopInput( DSoundWrapper *dsw ); +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ); +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilled ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DSOUND_WRAPPER_H */ diff --git a/portaudio-v19/pa_win_ds/pa_win_ds.c b/portaudio-v19/pa_win_ds/pa_win_ds.c new file mode 100644 index 000000000..10f19f22b --- /dev/null +++ b/portaudio-v19/pa_win_ds/pa_win_ds.c @@ -0,0 +1,1857 @@ +/* + * $Id$ + * Portable Audio I/O Library DirectSound implementation + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + + @todo implement paInputOverflow callback status flag + + @todo implement paNeverDropInput. + + @todo implement host api specific extension to set i/o buffer sizes in frames + + @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.) + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into + a native portaudio error code. Standard DirectSound result codes are documented at msdn. + + @todo implement IsFormatSupported + + @todo check that CoInitialize() CoUninitialize() are always correctly + paired, even in error cases. + + @todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error"). + + @todo make sure all buffers have been played before stopping the stream + when the stream callback returns paComplete + + old TODOs from phil, need to work out if these have been done: + O- fix "patest_stop.c" +*/ + +#include +#include /* strlen() */ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "dsound_wrapper.h" + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment( lib, "dsound.lib" ) +#pragma comment( lib, "winmm.lib" ) +#endif + + +#define PRINT(x) PA_DEBUG(x); +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) PRINT(x) +#define DBUGX(x) PRINT(x) + +#define PA_USE_HIGH_LATENCY (0) +#if PA_USE_HIGH_LATENCY +#define PA_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif + +#define PA_WIN_WDM_LATENCY (120) + +#define SECONDS_PER_MSEC (0.001) +#define MSEC_PER_SECOND (1000) + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* FIXME: should convert hr to a string */ +#define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \ + PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" ) + +/************************************************* DX Prototypes **********/ +static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); + +/************************************************************************************/ +/********************** Structures **************************************************/ +/************************************************************************************/ +/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaWinDsDeviceInfo +{ + GUID guid; + GUID *lpGUID; + double sampleRates[3]; +} PaWinDsDeviceInfo; + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + PaWinDsDeviceInfo *winDsDeviceInfos; + +} PaWinDsHostApiRepresentation; + +/* PaWinDsStream - a stream data structure specifically for this implementation */ + +typedef struct PaWinDsStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + +/* DirectSound specific data. */ + DSoundWrapper directSoundWrapper; + MMRESULT timerID; + BOOL ifInsideCallback; /* Test for reentrancy. */ + int framesPerDSBuffer; + double framesWritten; + double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ + + PaStreamCallbackFlags callbackFlags; + +/* FIXME - move all below to PaUtilStreamRepresentation */ + volatile int isStarted; + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ +} PaWinDsStream; + + +/************************************************************************************ +** Duplicate the input string using the allocations allocator. +** A NULL string is converted to a zero length string. +** If memory cannot be allocated, NULL is returned. +**/ +static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const char* src ) +{ + char *result = 0; + + if( src != NULL ) + { + size_t len = strlen(src); + result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) ); + if( result ) + memcpy( (void *) result, src, len+1 ); + } + else + { + result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 ); + if( result ) + result[0] = '\0'; + } + + return result; +} + +/************************************************************************************ +** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary +** information during device enumeration. +*/ +typedef struct DSDeviceNameAndGUID{ + char *name; // allocated from parent's allocations, never deleted by this structure + GUID guid; + LPGUID lpGUID; +} DSDeviceNameAndGUID; + +typedef struct DSDeviceNameAndGUIDVector{ + PaUtilAllocationGroup *allocations; + PaError enumerationError; + + int count; + int free; + DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() +} DSDeviceNameAndGUIDVector; + +static PaError InitializeDSDeviceNameAndGUIDVector( + DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations ) +{ + PaError result = paNoError; + + guidVector->allocations = allocations; + guidVector->enumerationError = paNoError; + + guidVector->count = 0; + guidVector->free = 8; + guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free ); + if( guidVector->items == NULL ) + result = paInsufficientMemory; + + return result; +} + +static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) +{ + PaError result = paNoError; + DSDeviceNameAndGUID *newItems; + int i; + + /* double size of vector */ + int size = guidVector->count + guidVector->free; + guidVector->free += size; + + newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 ); + if( newItems == NULL ) + { + result = paInsufficientMemory; + } + else + { + for( i=0; i < guidVector->count; ++i ) + { + newItems[i].name = guidVector->items[i].name; + if( guidVector->items[i].lpGUID == NULL ) + { + newItems[i].lpGUID = NULL; + } + else + { + newItems[i].lpGUID = &newItems[i].guid; + memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );; + } + } + + LocalFree( guidVector->items ); + guidVector->items = newItems; + } + + return result; +} + +/* + it's safe to call DSDeviceNameAndGUIDVector multiple times +*/ +static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) +{ + PaError result = paNoError; + + if( guidVector->items != NULL ) + { + if( LocalFree( guidVector->items ) != NULL ) + result = paInsufficientMemory; /** @todo this isn't the correct error to return from a deallocation failure */ + + guidVector->items = NULL; + } + + return result; +} + +/************************************************************************************ +** Collect preliminary device information during DirectSound enumeration +*/ +static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext; + PaError error; + + (void) lpszDrvName; /* unused variable */ + + if( namesAndGUIDs->free == 0 ) + { + error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs ); + if( error != paNoError ) + { + namesAndGUIDs->enumerationError = error; + return FALSE; + } + } + + /* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */ + if( lpGUID == NULL ) + { + namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL; + } + else + { + namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = + &namesAndGUIDs->items[namesAndGUIDs->count].guid; + + memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) ); + } + + namesAndGUIDs->items[namesAndGUIDs->count].name = + DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc ); + if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL ) + { + namesAndGUIDs->enumerationError = paInsufficientMemory; + return FALSE; + } + + ++namesAndGUIDs->count; + --namesAndGUIDs->free; + + return TRUE; +} + + +/* + GUIDs for emulated devices which we blacklist below. + are there more than two of them?? +*/ + +GUID IID_IRolandVSCEmulated1 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x01}; +GUID IID_IRolandVSCEmulated2 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x02}; + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */ +static double defaultSampleRateSearchOrder_[] = + { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0, + 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + + +/************************************************************************************ +** Extract capabilities from an output device, and add it to the device info list +** if successful. This function assumes that there is enough room in the +** device info list to accomodate all entries. +** +** The device will not be added to the device list if any errors are encountered. +*/ +static PaError AddOutputDeviceInfoFromDirectSound( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUND lpDirectSound; + DSCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + int i; + + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + } + + + if( lpGUID ) + { + if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) || + IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) ) + { + PA_DEBUG(("BLACKLISTED: %s \n",name)); + return paNoError; + } + } + + /* Create a DirectSound object for the specified GUID + Note that using CoCreateInstance doesn't work on windows CE. + */ + hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); + + /** try using CoCreateInstance because DirectSoundCreate was hanging under + some circumstances - note this was probably related to the + #define BOOL short bug which has now been fixed + @todo delete this comment and the following code once we've ensured + there is no bug. + */ + /* + hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectSound, (void**)&lpDirectSound ); + + if( hr == S_OK ) + { + hr = IDirectSound_Initialize( lpDirectSound, lpGUID ); + } + */ + + if( hr != DS_OK ) + { + if (hr == DSERR_ALLOCATED) + PA_DEBUG(("AddOutputDeviceInfoFromDirectSound %s DSERR_ALLOCATED\n",name)); + DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr )); + DBUG(("%s's GUID: {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, 0x%x} \n", + name, + lpGUID->Data1, + lpGUID->Data2, + lpGUID->Data3, + lpGUID->Data4[0], + lpGUID->Data4[1], + lpGUID->Data4[2], + lpGUID->Data4[3], + lpGUID->Data4[4], + lpGUID->Data4[5], + lpGUID->Data4[6], + lpGUID->Data4[7])); + + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + memset( &caps, 0, sizeof(caps) ); + caps.dwSize = sizeof(caps); + hr = IDirectSound_GetCaps( lpDirectSound, &caps ); + if( hr != DS_OK ) + { + DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { + +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = 0; + /* Mono or stereo device? */ + deviceInfo->maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + + /* initialize defaultSampleRate */ + + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) + { + /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */ + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + + for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) + { + if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate + && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ){ + + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; + break; + } + } + } + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) + { + /* + ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + ** But it supports continuous sampling. + ** So fake range of rates, and hope it really supports it. + */ + deviceInfo->defaultSampleRate = 44100.0f; + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name )); + } + else + { + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + ** But we know that they really support a range of rates! + ** So when we see a ridiculous set of rates, assume it is a range. + */ + deviceInfo->defaultSampleRate = 44100.0f; + DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name )); + } + else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + + + //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate ); + // dwFlags | DSCAPS_CONTINUOUSRATE + } + } + + IDirectSound_Release( lpDirectSound ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + return result; +} + + +/************************************************************************************ +** Extract capabilities from an input device, and add it to the device info list +** if successful. This function assumes that there is enough room in the +** device info list to accomodate all entries. +** +** The device will not be added to the device list if any errors are encountered. +*/ +static PaError AddInputDeviceInfoFromDirectSoundCapture( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; + DSCCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + } + + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); + + /** try using CoCreateInstance because DirectSoundCreate was hanging under + some circumstances - note this was probably related to the + #define BOOL short bug which has now been fixed + @todo delete this comment and the following code once we've ensured + there is no bug. + */ + /* + hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture ); + */ + if( hr != DS_OK ) + { + DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + memset( &caps, 0, sizeof(caps) ); + caps.dwSize = sizeof(caps); + hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); + if( hr != DS_OK ) + { + DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = caps.dwChannels; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + +/* constants from a WINE patch by Francois Gouget, see: + http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html + + --- + Date: Fri, 14 May 2004 10:38:12 +0200 (CEST) + From: Francois Gouget + To: Ross Bencina + Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library + + [snip] + + I give you permission to use the patch below under the BSD license. + http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html + + [snip] +*/ +#ifndef WAVE_FORMAT_48M08 +#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_48S08 0x00002000 /* 48 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_48M16 0x00004000 /* 48 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + + /* defaultSampleRate */ + if( caps.dwChannels == 2 ) + { + if( caps.dwFormats & WAVE_FORMAT_4S16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48S16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2S16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1S16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96S16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 0.; + } + else if( caps.dwChannels == 1 ) + { + if( caps.dwFormats & WAVE_FORMAT_4M16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48M16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2M16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1M16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96M16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 0.; + } + else deviceInfo->defaultSampleRate = 0.; + } + } + + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + return result; +} + + +/***********************************************************************************/ +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinDsHostApiRepresentation *winDsHostApi; + DSDeviceNameAndGUIDVector inputNamesAndGUIDs, outputNamesAndGUIDs; + PaDeviceInfo *deviceInfoArray; + + HRESULT hr = CoInitialize(NULL); /** @todo: should uninitialize too */ + if( FAILED(hr) ){ + return paUnanticipatedHostError; + } + + /* initialise guid vectors so they can be safely deleted on error */ + inputNamesAndGUIDs.items = NULL; + outputNamesAndGUIDs.items = NULL; + + DSW_InitializeDSoundEntryPoints(); + + winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); + if( !winDsHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winDsHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winDsHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winDsHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paDirectSound; + (*hostApi)->info.name = "Windows DirectSound"; + + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + +/* DSound - enumerate devices to count them and to gather their GUIDs */ + + + result = InitializeDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + result = InitializeDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&inputNamesAndGUIDs ); + + dswDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&outputNamesAndGUIDs ); + + if( inputNamesAndGUIDs.enumerationError != paNoError ) + { + result = inputNamesAndGUIDs.enumerationError; + goto error; + } + + if( outputNamesAndGUIDs.enumerationError != paNoError ) + { + result = outputNamesAndGUIDs.enumerationError; + goto error; + } + + deviceCount = inputNamesAndGUIDs.count + outputNamesAndGUIDs.count; + + if( deviceCount > 0 ) + { + /* allocate array for pointers to PaDeviceInfo structs */ + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all PaDeviceInfo structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all DSound specific info structs in a contiguous block */ + winDsHostApi->winDsDeviceInfos = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount ); + if( !winDsHostApi->winDsDeviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + (*hostApi)->deviceInfos[i] = deviceInfo; + } + + for( i=0; i< inputNamesAndGUIDs.count; ++i ) + { + result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi, + inputNamesAndGUIDs.items[i].name, + inputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + + for( i=0; i< outputNamesAndGUIDs.count; ++i ) + { + result = AddOutputDeviceInfoFromDirectSound( winDsHostApi, + outputNamesAndGUIDs.items[i].name, + outputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + } + + result = TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + result = TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winDsHostApi ) + { + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + } + + TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + + return result; +} + + +/***********************************************************************************/ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + + DSW_TerminateDSoundEntryPoints(); + + CoUninitialize(); +} + + +/* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ +static int PaWinDS_GetMinSystemLatency( void ) +{ + int minLatencyMsec; + /* Set minimal latency based on whether NT or other OS. + * NT has higher latency. + */ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof( osvi ); + GetVersionEx( &osvi ); + DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId )); + DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion )); + DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion )); + /* Check for NT */ + if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + { + minLatencyMsec = PA_WIN_NT_LATENCY; + } + else if(osvi.dwMajorVersion >= 5) + { + minLatencyMsec = PA_WIN_WDM_LATENCY; + } + else + { + minLatencyMsec = PA_WIN_9X_LATENCY; + } + return minLatencyMsec; +} + +/***********************************************************************************/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + return paFormatIsSupported; +} + + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be determined +** based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") +#define PA_ENV_BUF_SIZE (32) + +static int PaWinDs_GetMinLatencyFrames( double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hresult; + int minLatencyMsec = 0; + + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); + } + else + { + minLatencyMsec = PaWinDS_GetMinSystemLatency(); +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + + return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC); +} + +/***********************************************************************************/ +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + PaWinDsStream *stream = 0; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate); + + /* IDEA: the following 3 checks could be performed by default by pa_front + unless some flag indicated otherwise */ + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + suggestedInputLatencyFrames = 0; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + suggestedOutputLatencyFrames = 0; + } + + + /* + IMPLEMENT ME: + + ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() ) + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if possible / necessary + + - validate suggestedInputLatency and suggestedOutputLatency parameters, + use default values where necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputParameters->sampleFormat ); + } + + if( outputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputParameters->sampleFormat ); + } + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */ + /* This next mode is required because DS can split the host buffer when it wraps around. */ + paUtilVariableHostBufferSizePartialUsageAllowed, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + +/* DirectSound specific initialization */ + { + HRESULT hr; + int bytesPerDirectSoundBuffer; + DSoundWrapper *dsw; + int userLatencyFrames; + int minLatencyFrames; + + stream->timerID = 0; + dsw = &stream->directSoundWrapper; + DSW_Init( dsw ); + + /* Get system minimum latency. */ + minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate ); + + /* Let user override latency by passing latency parameter. */ + userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames) + ? suggestedInputLatencyFrames + : suggestedOutputLatencyFrames; + if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames; + + /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* App support variable framesPerBuffer */ + stream->framesPerDSBuffer = minLatencyFrames; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate; + } + else + { + /* Round up to number of buffers needed to guarantee that latency. */ + int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer; + if( numUserBuffers < 1 ) numUserBuffers = 1; + numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */ + stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate; + } + + { + /** @todo REVIEW: this calculation seems incorrect to me - rossb. */ + int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency )); + } + + + /* ------------------ OUTPUT */ + if( outputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + + hr = dswDSoundEntryPoints.DirectSoundCreate( winDsHostApi->winDsDeviceInfos[outputParameters->device].lpGUID, + &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitOutputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + (WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + /* Calculate value used in latency calculation to avoid real-time divides. */ + stream->secondsPerHostByte = 1.0 / + (stream->bufferProcessor.bytesPerHostOutputSample * + outputChannelCount * sampleRate); + } + + /* ------------------ INPUT */ + if( inputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( winDsHostApi->winDsDeviceInfos[inputParameters->device].lpGUID, + &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitInputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + (WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + } + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + + +/***********************************************************************************/ +static PaError Pa_TimeSlice( PaWinDsStream *stream ) +{ + PaError result = 0; /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/ + DSoundWrapper *dsw; + long numFrames = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long framesToXfer = 0; + long numInFramesReady = 0; + long numOutFramesReady = 0; + long bytesProcessed; + HRESULT hresult; + double outputLatency = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */ + +/* Input */ + LPBYTE lpInBuf1 = NULL; + LPBYTE lpInBuf2 = NULL; + DWORD dwInSize1 = 0; + DWORD dwInSize2 = 0; +/* Output */ + LPBYTE lpOutBuf1 = NULL; + LPBYTE lpOutBuf2 = NULL; + DWORD dwOutSize1 = 0; + DWORD dwOutSize2 = 0; + + dsw = &stream->directSoundWrapper; + + /* How much input data is available? */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + DSW_QueryInputFilled( dsw, &bytesFilled ); + framesToXfer = numInFramesReady = bytesFilled / dsw->dsw_BytesPerInputFrame; + outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte; + + /** @todo Check for overflow */ + } + + /* How much output room is available? */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + UINT previousUnderflowCount = dsw->dsw_OutputUnderflows; + DSW_QueryOutputSpace( dsw, &bytesEmpty ); + framesToXfer = numOutFramesReady = bytesEmpty / dsw->dsw_BytesPerOutputFrame; + + /* Check for underflow */ + if( dsw->dsw_OutputUnderflows != previousUnderflowCount ) + stream->callbackFlags |= paOutputUnderflow; + } + + if( (numInFramesReady > 0) && (numOutFramesReady > 0) ) + { + framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady; + } + + if( framesToXfer > 0 ) + { + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* The outputBufferDacTime parameter should indicates the time at which + the first sample of the output buffer is heard at the DACs. */ + timeInfo.currentTime = PaUtil_GetTime(); + timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags ); + stream->callbackFlags = 0; + + /* Input */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerInputFrame; + hresult = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, + dsw->dsw_ReadOffset, bytesToXfer, + (void **) &lpInBuf1, &dwInSize1, + (void **) &lpInBuf2, &dwInSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error2; + } + + numFrames = dwInSize1 / dsw->dsw_BytesPerInputFrame; + PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 ); + /* Is input split into two regions. */ + if( dwInSize2 > 0 ) + { + numFrames = dwInSize2 / dsw->dsw_BytesPerInputFrame; + PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 ); + } + } + + /* Output */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerOutputFrame; + hresult = IDirectSoundBuffer_Lock ( dsw->dsw_OutputBuffer, + dsw->dsw_WriteOffset, bytesToXfer, + (void **) &lpOutBuf1, &dwOutSize1, + (void **) &lpOutBuf2, &dwOutSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error1; + } + + numFrames = dwOutSize1 / dsw->dsw_BytesPerOutputFrame; + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 ); + + /* Is output split into two regions. */ + if( dwOutSize2 > 0 ) + { + numFrames = dwOutSize2 / dsw->dsw_BytesPerOutputFrame; + PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 ); + } + } + + result = paContinue; + numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result ); + stream->framesWritten += numFrames; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* FIXME: an underflow could happen here */ + + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerOutputFrame; + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + bytesProcessed) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2); + dsw->dsw_FramesWritten += numFrames; + } + +error1: + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + /* FIXME: an overflow could happen here */ + + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerInputFrame; + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + bytesProcessed) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock( dsw->dsw_InputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2); + } +error2: + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames ); + + } + + return result; +} +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + PaWinDsStream *stream; + + /* suppress unused variable warnings */ + (void) uID; + (void) uMsg; + (void) dw1; + (void) dw2; + + stream = (PaWinDsStream *) dwUser; + if( stream == NULL ) return; + + if( stream->isActive ) + { + if( stream->abortProcessing ) + { + stream->isActive = 0; + } + else if( stream->stopProcessing ) + { + DSoundWrapper *dsw = &stream->directSoundWrapper; + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + DSW_ZeroEmptySpace( dsw ); + /* clear isActive when all sound played */ + if( dsw->dsw_FramesPlayed >= stream->framesWritten ) + { + stream->isActive = 0; + } + } + else + { + stream->isActive = 0; + } + } + else + { + if( Pa_TimeSlice( stream ) != 0) /* Call time slice independant of timing method. */ + { + /* FIXME implement handling of paComplete and paAbort if possible */ + stream->stopProcessing = 1; + } + } + + if( !stream->isActive ){ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + } +} + +/*********************************************************************************** + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + + DSW_Term( &stream->directSoundWrapper ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + +/***********************************************************************************/ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StartInput( &stream->directSoundWrapper ); + DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + stream->framesWritten = 0; + stream->callbackFlags = 0; + + stream->abortProcessing = 0; + stream->stopProcessing = 0; + stream->isActive = 1; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */ + result = Pa_TimeSlice( stream ); + if( result != paNoError ) return result; // FIXME - what if finished? + + hr = DSW_StartOutput( &stream->directSoundWrapper ); + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int resolution; + int framesPerWakeup = stream->framesPerDSBuffer / 4; + int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate; + if( msecPerWakeup < 10 ) msecPerWakeup = 10; + else if( msecPerWakeup > 100 ) msecPerWakeup = 100; + resolution = msecPerWakeup/4; + stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) stream, TIME_PERIODIC ); + } + if( stream->timerID == 0 ) + { + stream->isActive = 0; + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + + stream->isStarted = TRUE; + +error: + return result; +} + + +/***********************************************************************************/ +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + int timeoutMsec; + + stream->stopProcessing = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate); + while( stream->isActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( stream->timerID != 0 ) + { + timeKillEvent(stream->timerID); /* Stop callback timer. */ + stream->timerID = 0; + } + + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + hr = DSW_StopOutput( &stream->directSoundWrapper ); + } + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StopInput( &stream->directSoundWrapper ); + } + + stream->isStarted = FALSE; + + return result; +} + + +/***********************************************************************************/ +static PaError AbortStream( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + stream->abortProcessing = 1; + return StopStream( s ); +} + + +/***********************************************************************************/ +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return !stream->isStarted; +} + + +/***********************************************************************************/ +static PaError IsStreamActive( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return stream->isActive; +} + +/***********************************************************************************/ +static PaTime GetStreamTime( PaStream *s ) +{ + /* suppress unused variable warnings */ + (void) s; + + +/* + new behavior for GetStreamTime is to return a stream based seconds clock + used for the outTime parameter to the callback. + FIXME: delete this comment when the other unnecessary related code has + been cleaned from this file. + + PaWinDsStream *stream = (PaWinDsStream*)s; + DSoundWrapper *dsw; + dsw = &stream->directSoundWrapper; + return dsw->dsw_FramesPlayed; +*/ + return PaUtil_GetTime(); +} + + +/***********************************************************************************/ +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + + +/*********************************************************************************** + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +/***********************************************************************************/ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + + diff --git a/portaudio-v19/pa_win_wdmks/pa_win_wdmks.c b/portaudio-v19/pa_win_wdmks/pa_win_wdmks.c new file mode 100644 index 000000000..34d0ee6e6 --- /dev/null +++ b/portaudio-v19/pa_win_wdmks/pa_win_wdmks.c @@ -0,0 +1,3269 @@ +/* + * $Id$ + * PortAudio Windows WDM-KS interface + * + * Author: Andrew Baldwin + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief Portaudio WDM-KS host API. + + @note This is the implementation of the Portaudio host API using the + Windows WDM/Kernel Streaming API in order to enable very low latency + playback and recording on all modern Windows platforms (e.g. 2K, XP) + Note: This API accesses the device drivers below the usual KMIXER + component which is normally used to enable multi-client mixing and + format conversion. That means that it will lock out all other users + of a device for the duration of active stream using those devices +*/ + +#include + +/* Debugging/tracing support */ + +#define PA_LOGE_ +#define PA_LOGL_ + +#ifdef __GNUC__ + #include + #define _WIN32_WINNT 0x0501 + #define WINVER 0x0501 +#endif + +#include /* strlen() */ +#include + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "portaudio.h" + +#include +#include + + +#ifdef __GNUC__ + #undef PA_LOGE_ + #define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__)) + #undef PA_LOGL_ + #define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__)) + /* These defines are set in order to allow the WIndows DirectX + * headers to compile with a GCC compiler such as MinGW + * NOTE: The headers may generate a few warning in GCC, but + * they should compile */ + #define _INC_MMSYSTEM + #define _INC_MMREG + #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ + #define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n ) + #if !defined( DEFINE_WAVEFORMATEX_GUID ) + #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + #endif + #define WAVE_FORMAT_ADPCM 0x0002 + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 + #define WAVE_FORMAT_ALAW 0x0006 + #define WAVE_FORMAT_MULAW 0x0007 + #define WAVE_FORMAT_MPEG 0x0050 + #define WAVE_FORMAT_DRM 0x0009 + #define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + #define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data) +#endif + +#ifdef _MSC_VER + #define DYNAMIC_GUID(data) {data} + #define _INC_MMREG + #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ + #undef DEFINE_GUID + #define DEFINE_GUID(n,data) EXTERN_C const GUID n = {data} + #define DEFINE_GUID_THUNK(n,data) DEFINE_GUID(n,data) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) + #if !defined( DEFINE_WAVEFORMATEX_GUID ) + #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + #endif + #define WAVE_FORMAT_ADPCM 0x0002 + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 + #define WAVE_FORMAT_ALAW 0x0006 + #define WAVE_FORMAT_MULAW 0x0007 + #define WAVE_FORMAT_MPEG 0x0050 + #define WAVE_FORMAT_DRM 0x0009 +#endif + +#include +#include +#include +#include +#include + +/* These next definitions allow the use of the KSUSER DLL */ +typedef KSDDKAPI DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE); +extern HMODULE DllKsUser; +extern KSCREATEPIN* FunctionKsCreatePin; + +/* Forward definition to break circular type reference between pin and filter */ +struct __PaWinWdmFilter; +typedef struct __PaWinWdmFilter PaWinWdmFilter; + +/* The Pin structure + * A pin is an input or output node, e.g. for audio flow */ +typedef struct __PaWinWdmPin +{ + HANDLE handle; + PaWinWdmFilter* parentFilter; + unsigned long pinId; + KSPIN_CONNECT* pinConnect; + unsigned long pinConnectSize; + KSDATAFORMAT_WAVEFORMATEX* ksDataFormatWfx; + KSPIN_COMMUNICATION communication; + KSDATARANGE* dataRanges; + KSMULTIPLE_ITEM* dataRangesItem; + KSPIN_DATAFLOW dataFlow; + KSPIN_CINSTANCES instances; + unsigned long frameSize; + int maxChannels; + unsigned long formats; + int bestSampleRate; +} +PaWinWdmPin; + +/* The Filter structure + * A filter has a number of pins and a "friendly name" */ +struct __PaWinWdmFilter +{ + HANDLE handle; + int pinCount; + PaWinWdmPin** pins; + TCHAR filterName[MAX_PATH]; + TCHAR friendlyName[MAX_PATH]; + int maxInputChannels; + int maxOutputChannels; + unsigned long formats; + int usageCount; + int bestSampleRate; +}; + +/* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */ +typedef struct __PaWinWdmHostApiRepresentation +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup* allocations; + PaWinWdmFilter** filters; + int filterCount; +} +PaWinWdmHostApiRepresentation; + +typedef struct __PaWinWdmDeviceInfo +{ + PaDeviceInfo inheritedDeviceInfo; + PaWinWdmFilter* filter; +} +PaWinWdmDeviceInfo; + +typedef struct __DATAPACKET +{ + KSSTREAM_HEADER Header; + OVERLAPPED Signal; +} DATAPACKET; + +/* PaWinWdmStream - a stream data structure specifically for this implementation */ +typedef struct __PaWinWdmStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaWinWdmPin* recordingPin; + PaWinWdmPin* playbackPin; + char* hostBuffer; + unsigned long framesPerHostIBuffer; + unsigned long framesPerHostOBuffer; + int bytesPerInputFrame; + int bytesPerOutputFrame; + int streamStarted; + int streamActive; + int streamStop; + int streamAbort; + int oldProcessPriority; + HANDLE streamThread; + HANDLE events[5]; /* 2 play + 2 record packets + abort events */ + DATAPACKET packets[4]; /* 2 play + 2 record */ + PaStreamFlags streamFlags; + /* These values handle the case where the user wants to use fewer + * channels than the device has */ + int userInputChannels; + int deviceInputChannels; + int userOutputChannels; + int deviceOutputChannels; + int inputSampleSize; + int outputSampleSize; +} +PaWinWdmStream; + +#include + +HMODULE DllKsUser = NULL; +KSCREATEPIN* FunctionKsCreatePin = NULL; + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +/* Low level I/O functions */ +static PaError WdmSyncIoctl(HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned); +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount); +static PaError WdmSetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount); +static PaError WdmGetPinPropertySimple(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount); +static PaError WdmGetPinPropertyMulti(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem); + +/** Pin management functions */ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error); +static void PinFree(PaWinWdmPin* pin); +static void PinClose(PaWinWdmPin* pin); +static PaError PinInstantiate(PaWinWdmPin* pin); +/*static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state); NOT USED */ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state); +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format); +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format); + +/* Filter management functions */ +static PaWinWdmFilter* FilterNew( + TCHAR* filterName, + TCHAR* friendlyName, + PaError* error); +static void FilterFree(PaWinWdmFilter* filter); +static PaWinWdmPin* FilterCreateRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaWinWdmPin* FilterFindViableRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaError FilterCanCreateRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex); +static PaWinWdmPin* FilterCreateCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaWinWdmPin* FilterFindViableCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaError FilterCanCreateCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* pwfx); +static PaError FilterUse( + PaWinWdmFilter* filter); +static void FilterRelease( + PaWinWdmFilter* filter); + +/* Interface functions */ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( + struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( + struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( + PaStream* stream, + void *buffer, + unsigned long frames ); +static PaError WriteStream( + PaStream* stream, + const void *buffer, + unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +/* Utility functions */ +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex); +static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi); +static BOOL PinWrite(HANDLE h, DATAPACKET* p); +static BOOL PinRead(HANDLE h, DATAPACKET* p); +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples); +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples); +static DWORD WINAPI ProcessingThread(LPVOID pParam); + +/* Function bodies */ + +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex) +{ + if( wfex->wFormatTag == WAVE_FORMAT_PCM ) + { + return sizeof( WAVEFORMATEX ); + } + else + { + return (sizeof( WAVEFORMATEX ) + wfex->cbSize); + } +} + +/* +Low level pin/filter access functions +*/ +static PaError WdmSyncIoctl( + HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned) +{ + PaError result = paNoError; + OVERLAPPED overlapped; + int boolResult; + unsigned long dummyBytesReturned; + unsigned long error; + + if( !bytesReturned ) + { + /* User a dummy as the caller hasn't supplied one */ + bytesReturned = &dummyBytesReturned; + } + + FillMemory((void *)&overlapped,sizeof(overlapped),0); + overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); + if( !overlapped.hEvent ) + { + result = paInsufficientMemory; + goto error; + } + overlapped.hEvent = (HANDLE)((DWORD_PTR)overlapped.hEvent | 0x1); + + boolResult = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount, + outBuffer, outBufferCount, bytesReturned, &overlapped); + if( !boolResult ) + { + error = GetLastError(); + if( error == ERROR_IO_PENDING ) + { + error = WaitForSingleObject(overlapped.hEvent,INFINITE); + if( error != WAIT_OBJECT_0 ) + { + result = paUnanticipatedHostError; + goto error; + } + } + else if((( error == ERROR_INSUFFICIENT_BUFFER ) || + ( error == ERROR_MORE_DATA )) && + ( ioctlNumber == IOCTL_KS_PROPERTY ) && + ( outBufferCount == 0 )) + { + boolResult = TRUE; + } + else + { + result = paUnanticipatedHostError; + } + } + if( !boolResult ) + *bytesReturned = 0; + +error: + if( overlapped.hEvent ) + { + CloseHandle( overlapped.hEvent ); + } + return result; +} + +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount) +{ + PaError result; + KSPROPERTY* ksProperty; + unsigned long propertyCount; + + propertyCount = sizeof(KSPROPERTY) + instanceCount; + ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount ); + if( !ksProperty ) + { + return paInsufficientMemory; + } + + FillMemory((void*)ksProperty,sizeof(ksProperty),0); + ksProperty->Set = *guidPropertySet; + ksProperty->Id = property; + ksProperty->Flags = KSPROPERTY_TYPE_GET; + + if( instance ) + { + memcpy( (void*)(((char*)ksProperty)+sizeof(KSPROPERTY)), instance, instanceCount ); + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + ksProperty, + propertyCount, + value, + valueCount, + NULL); + + PaUtil_FreeMemory( ksProperty ); + return result; +} + +static PaError WdmSetPropertySimple( + HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount) +{ + PaError result; + KSPROPERTY* ksProperty; + unsigned long propertyCount = 0; + + propertyCount = sizeof(KSPROPERTY) + instanceCount; + ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount ); + if( !ksProperty ) + { + return paInsufficientMemory; + } + + ksProperty->Set = *guidPropertySet; + ksProperty->Id = property; + ksProperty->Flags = KSPROPERTY_TYPE_SET; + + if( instance ) + { + memcpy((void*)((char*)ksProperty + sizeof(KSPROPERTY)), instance, instanceCount); + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + ksProperty, + propertyCount, + value, + valueCount, + NULL); + + PaUtil_FreeMemory( ksProperty ); + return result; +} + +static PaError WdmGetPinPropertySimple( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount) +{ + PaError result; + + KSP_PIN ksPProp; + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + value, + valueCount, + NULL); + + return result; +} + +static PaError WdmGetPinPropertyMulti( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem) +{ + PaError result; + unsigned long multipleItemSize = 0; + KSP_PIN ksPProp; + + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp.Property, + sizeof(KSP_PIN), + NULL, + 0, + &multipleItemSize); + if( result != paNoError ) + { + return result; + } + + *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize ); + if( !*ksMultipleItem ) + { + return paInsufficientMemory; + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + (void*)*ksMultipleItem, + multipleItemSize, + NULL); + + if( result != paNoError ) + { + PaUtil_FreeMemory( ksMultipleItem ); + } + + return result; +} + + +/* +Create a new pin object belonging to a filter +The pin object holds all the configuration information about the pin +before it is opened, and then the handle of the pin after is opened +*/ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error) +{ + PaWinWdmPin* pin; + PaError result; + unsigned long i; + KSMULTIPLE_ITEM* item = NULL; + KSIDENTIFIER* identifier; + KSDATARANGE* dataRange; + + PA_LOGE_; + PA_DEBUG(("Creating pin %d:\n",pinId)); + + /* Allocate the new PIN object */ + pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) ); + if( !pin ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the pin object */ + /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */ + + pin->parentFilter = parentFilter; + pin->pinId = pinId; + + /* Allocate a connect structure */ + pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize ); + if( !pin->pinConnect ) + { + result = paInsufficientMemory; + goto error; + } + + /* Configure the connect structure with default values */ + pin->pinConnect->Interface.Set = KSINTERFACESETID_Standard; + pin->pinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; + pin->pinConnect->Interface.Flags = 0; + pin->pinConnect->Medium.Set = KSMEDIUMSETID_Standard; + pin->pinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; + pin->pinConnect->Medium.Flags = 0; + pin->pinConnect->PinId = pinId; + pin->pinConnect->PinToHandle = NULL; + pin->pinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; + pin->pinConnect->Priority.PrioritySubClass = 1; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->ksDataFormatWfx->DataFormat.Flags = 0; + pin->ksDataFormatWfx->DataFormat.Reserved = 0; + pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO; + pin->ksDataFormatWfx->DataFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pin->ksDataFormatWfx->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX; + + pin->frameSize = 0; /* Unknown until we instantiate pin */ + + /* Get the COMMUNICATION property */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_COMMUNICATION, + &pin->communication, + sizeof(KSPIN_COMMUNICATION)); + if( result != paNoError ) + goto error; + + if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/ + (pin->communication != KSPIN_COMMUNICATION_SINK) && + (pin->communication != KSPIN_COMMUNICATION_BOTH) ) + { + PA_DEBUG(("Not source/sink\n")); + result = paInvalidDevice; + goto error; + } + + /* Get dataflow information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATAFLOW, + &pin->dataFlow, + sizeof(KSPIN_DATAFLOW)); + + if( result != paNoError ) + goto error; + + /* Get the INTERFACE property list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_INTERFACES, + &item); + + if( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); + + /* Check that at least one interface is STANDARD_STREAMING */ + result = paUnanticipatedHostError; + for( i = 0; i < item->Count; i++ ) + { + if( !memcmp( (void*)&identifier[i].Set, (void*)&KSINTERFACESETID_Standard, sizeof( GUID ) ) && + ( identifier[i].Id == KSINTERFACE_STANDARD_STREAMING ) ) + { + result = paNoError; + break; + } + } + + if( result != paNoError ) + { + PA_DEBUG(("No standard streaming\n")); + goto error; + } + + /* Don't need interfaces any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get the MEDIUM properties list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_MEDIUMS, + &item); + + if( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); /* Not actually necessary... */ + + /* Check that at least one medium is STANDARD_DEVIO */ + result = paUnanticipatedHostError; + for( i = 0; i < item->Count; i++ ) + { + if( !memcmp( (void*)&identifier[i].Set, (void*)&KSMEDIUMSETID_Standard, sizeof( GUID ) ) && + ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) ) + { + result = paNoError; + break; + } + } + + if( result != paNoError ) + { + PA_DEBUG(("No standard devio\n")); + goto error; + } + /* Don't need mediums any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get DATARANGES */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATARANGES, + &pin->dataRangesItem); + + if( result != paNoError ) + goto error; + + pin->dataRanges = (KSDATARANGE*)(pin->dataRangesItem +1); + + /* Check that at least one datarange supports audio */ + result = paUnanticipatedHostError; + dataRange = pin->dataRanges; + pin->maxChannels = 0; + pin->bestSampleRate = 0; + pin->formats = 0; + for( i = 0; i dataRangesItem->Count; i++) + { + PA_DEBUG(("DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat)))); + /* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */ + if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) || + !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_PCM, sizeof ( GUID ) ) || + ( !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof ( GUID ) ) && + ( !memcmp((void*)&dataRange->MajorFormat, (void*)&KSDATAFORMAT_TYPE_AUDIO, sizeof ( GUID ) ) ) ) ) + { + result = paNoError; + /* Record the maximum possible channels with this pin */ + PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels)); + if( (int)((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels ) + { + pin->maxChannels = ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels; + /*PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));*/ + } + /* Record the formats (bit depths) that are supported */ + if( ((KSDATARANGE_AUDIO*)dataRange)->MinimumBitsPerSample <= 16 ) + { + pin->formats |= paInt16; + PA_DEBUG(("Format 16 bit supported\n")); + } + if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumBitsPerSample >= 24 ) + { + pin->formats |= paInt24; + PA_DEBUG(("Format 24 bit supported\n")); + } + if( ( pin->bestSampleRate != 48000) && + (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 48000) && + (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 48000) ) + { + pin->bestSampleRate = 48000; + PA_DEBUG(("48kHz supported\n")); + } + else if(( pin->bestSampleRate != 48000) && ( pin->bestSampleRate != 44100 ) && + (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 44100) && + (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 44100) ) + { + pin->bestSampleRate = 44100; + PA_DEBUG(("44.1kHz supported\n")); + } + else + { + pin->bestSampleRate = ((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency; + } + } + dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize); + } + + if( result != paNoError ) + goto error; + + /* Get instance information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CINSTANCES, + &pin->instances, + sizeof(KSPIN_CINSTANCES)); + + if( result != paNoError ) + goto error; + + /* Success */ + *error = paNoError; + PA_DEBUG(("Pin created successfully\n")); + PA_LOGL_; + return pin; + +error: + /* + Error cleanup + */ + PaUtil_FreeMemory( item ); + if( pin ) + { + PaUtil_FreeMemory( pin->pinConnect ); + PaUtil_FreeMemory( pin->dataRangesItem ); + PaUtil_FreeMemory( pin ); + } + *error = result; + PA_LOGL_; + return NULL; +} + +/* +Safely free all resources associated with the pin +*/ +static void PinFree(PaWinWdmPin* pin) +{ + PA_LOGE_; + if( pin ) + { + PinClose(pin); + if( pin->pinConnect ) + { + PaUtil_FreeMemory( pin->pinConnect ); + } + if( pin->dataRangesItem ) + { + PaUtil_FreeMemory( pin->dataRangesItem ); + } + PaUtil_FreeMemory( pin ); + } + PA_LOGL_; +} + +/* +If the pin handle is open, close it +*/ +static void PinClose(PaWinWdmPin* pin) +{ + PA_LOGE_; + if( pin == NULL ) + { + PA_DEBUG(("Closing NULL pin!")); + PA_LOGL_; + return; + } + if( pin->handle != NULL ) + { + PinSetState( pin, KSSTATE_PAUSE ); + PinSetState( pin, KSSTATE_STOP ); + CloseHandle( pin->handle ); + pin->handle = NULL; + FilterRelease(pin->parentFilter); + } + PA_LOGL_; +} + +/* +Set the state of this (instantiated) pin +*/ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state) +{ + PaError result; + + PA_LOGE_; + if( pin == NULL ) + return paInternalError; + if( pin->handle == NULL ) + return paInternalError; + + result = WdmSetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_STATE, + &state, + sizeof(state), + NULL, + 0); + PA_LOGL_; + return result; +} + +static PaError PinInstantiate(PaWinWdmPin* pin) +{ + PaError result; + unsigned long createResult; + KSALLOCATOR_FRAMING ksaf; + KSALLOCATOR_FRAMING_EX ksafex; + + PA_LOGE_; + + if( pin == NULL ) + return paInternalError; + if(!pin->pinConnect) + return paInternalError; + + FilterUse(pin->parentFilter); + + createResult = FunctionKsCreatePin( + pin->parentFilter->handle, + pin->pinConnect, + GENERIC_WRITE | GENERIC_READ, + &pin->handle + ); + + PA_DEBUG(("Pin create result = %x\n",createResult)); + if( createResult != ERROR_SUCCESS ) + { + FilterRelease(pin->parentFilter); + pin->handle = NULL; + return paInvalidDevice; + } + + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING, + &ksaf, + sizeof(ksaf), + NULL, + 0); + + if( result != paNoError ) + { + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX, + &ksafex, + sizeof(ksafex), + NULL, + 0); + if( result == paNoError ) + { + pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize; + } + } + else + { + pin->frameSize = ksaf.FrameSize; + } + + PA_LOGL_; + + return paNoError; +} + +/* NOT USED +static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state) +{ + PaError result; + + if( state == NULL ) + return paInternalError; + if( pin == NULL ) + return paInternalError; + if( pin->handle == NULL ) + return paInternalError; + + result = WdmGetPropertySimple( + pin->handle, + KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_STATE, + state, + sizeof(KSSTATE), + NULL, + 0); + + return result; +} +*/ +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + unsigned long size; + void* newConnect; + + PA_LOGE_; + + if( pin == NULL ) + return paInternalError; + if( format == NULL ) + return paInternalError; + + size = GetWfexSize(format) + sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX) - sizeof(WAVEFORMATEX); + + if( pin->pinConnectSize != size ) + { + newConnect = PaUtil_AllocateMemory( size ); + if( newConnect == NULL ) + return paInsufficientMemory; + memcpy( newConnect, (void*)pin->pinConnect, min(pin->pinConnectSize,size) ); + PaUtil_FreeMemory( pin->pinConnect ); + pin->pinConnect = (KSPIN_CONNECT*)newConnect; + pin->pinConnectSize = size; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)((KSPIN_CONNECT*)newConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = size - sizeof(KSPIN_CONNECT); + } + + memcpy( (void*)&(pin->ksDataFormatWfx->WaveFormatEx), format, GetWfexSize(format) ); + pin->ksDataFormatWfx->DataFormat.SampleSize = (unsigned short)(format->nChannels * (format->wBitsPerSample / 8)); + + PA_LOGL_; + + return paNoError; +} + +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + KSDATARANGE_AUDIO* dataRange; + unsigned long count; + GUID guid = DYNAMIC_GUID( DEFINE_WAVEFORMATEX_GUID(format->wFormatTag) ); + PaError result = paInvalidDevice; + + PA_LOGE_; + + if( format->wFormatTag == WAVE_FORMAT_EXTENSIBLE ) + { + guid = ((WAVEFORMATEXTENSIBLE*)format)->SubFormat; + } + dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges; + for(count = 0; countdataRangesItem->Count; count++) + { + if(( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_AUDIO,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_WILDCARD,sizeof(GUID)) )) + { + /* This is an audio or wildcard datarange... */ + if(( !memcmp(&(dataRange->DataRange.SubFormat),&KSDATAFORMAT_SUBTYPE_WILDCARD,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.SubFormat),&guid,sizeof(GUID)) )) + { + if(( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WILDCARD,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,sizeof(GUID) ))) + { + + PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count)); + PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize)); + PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels)); + PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample)); + PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency)); + + if( dataRange->MaximumChannels < format->nChannels ) + { + result = paInvalidChannelCount; + continue; + } + if( dataRange->MinimumBitsPerSample > format->wBitsPerSample ) + { + result = paSampleFormatNotSupported; + continue; + } + if( dataRange->MaximumBitsPerSample < format->wBitsPerSample ) + { + result = paSampleFormatNotSupported; + continue; + } + if( dataRange->MinimumSampleFrequency > format->nSamplesPerSec ) + { + result = paInvalidSampleRate; + continue; + } + if( dataRange->MaximumSampleFrequency < format->nSamplesPerSec ) + { + result = paInvalidSampleRate; + continue; + } + /* Success! */ + PA_LOGL_; + return paNoError; + } + } + } + dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize); + } + + PA_LOGL_; + + return result; +} + +/** + * Create a new filter object + */ +static PaWinWdmFilter* FilterNew(TCHAR* filterName, TCHAR* friendlyName, PaError* error) +{ + PaWinWdmFilter* filter; + PaError result; + int pinId; + int valid; + + + /* Allocate the new filter object */ + filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) ); + if( !filter ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the filter object - done by AllocateMemory */ + /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */ + + /* Copy the filter name */ + _tcsncpy(filter->filterName, filterName, MAX_PATH); + + /* Copy the friendly name */ + _tcsncpy(filter->friendlyName, friendlyName, MAX_PATH); + + /* Open the filter handle */ + result = FilterUse(filter); + if( result != paNoError ) + { + goto error; + } + + /* Get pin count */ + result = WdmGetPinPropertySimple + ( + filter->handle, + 0, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CTYPES, + &filter->pinCount, + sizeof(filter->pinCount) + ); + + if( result != paNoError) + { + goto error; + } + + /* Allocate pointer array to hold the pins */ + filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount ); + if( !filter->pins ) + { + result = paInsufficientMemory; + goto error; + } + + /* Create all the pins we can */ + filter->maxInputChannels = 0; + filter->maxOutputChannels = 0; + filter->bestSampleRate = 0; + + valid = 0; + for(pinId = 0; pinId < filter->pinCount; pinId++) + { + /* Create the pin with this Id */ + PaWinWdmPin* newPin; + newPin = PinNew(filter, pinId, &result); + if( result == paInsufficientMemory ) + goto error; + if( newPin != NULL ) + { + filter->pins[pinId] = newPin; + valid = 1; + + /* Get the max output channel count */ + if(( newPin->dataFlow == KSPIN_DATAFLOW_IN ) && + (( newPin->communication == KSPIN_COMMUNICATION_SINK) || + ( newPin->communication == KSPIN_COMMUNICATION_BOTH))) + { + if(newPin->maxChannels > filter->maxOutputChannels) + filter->maxOutputChannels = newPin->maxChannels; + filter->formats |= newPin->formats; + } + /* Get the max input channel count */ + if(( newPin->dataFlow == KSPIN_DATAFLOW_OUT ) && + (( newPin->communication == KSPIN_COMMUNICATION_SINK) || + ( newPin->communication == KSPIN_COMMUNICATION_BOTH))) + { + if(newPin->maxChannels > filter->maxInputChannels) + filter->maxInputChannels = newPin->maxChannels; + filter->formats |= newPin->formats; + } + + if(newPin->bestSampleRate > filter->bestSampleRate) + { + filter->bestSampleRate = newPin->bestSampleRate; + } + } + } + + if(( filter->maxInputChannels == 0) && ( filter->maxOutputChannels == 0)) + { + /* No input or output... not valid */ + valid = 0; + } + + if( !valid ) + { + /* No valid pin was found on this filter so we destroy it */ + result = paDeviceUnavailable; + goto error; + } + + /* Close the filter handle for now + * It will be opened later when needed */ + FilterRelease(filter); + + *error = paNoError; + return filter; + +error: + /* + Error cleanup + */ + if( filter ) + { + for( pinId = 0; pinId < filter->pinCount; pinId++ ) + PinFree(filter->pins[pinId]); + PaUtil_FreeMemory( filter->pins ); + if( filter->handle ) + CloseHandle( filter->handle ); + PaUtil_FreeMemory( filter ); + } + *error = result; + return NULL; +} + +/** + * Free a previously created filter + */ +static void FilterFree(PaWinWdmFilter* filter) +{ + int pinId; + PA_LOGL_; + if( filter ) + { + for( pinId = 0; pinId < filter->pinCount; pinId++ ) + PinFree(filter->pins[pinId]); + PaUtil_FreeMemory( filter->pins ); + if( filter->handle ) + CloseHandle( filter->handle ); + PaUtil_FreeMemory( filter ); + } + PA_LOGE_; +} + +/** + * Reopen the filter handle if necessary so it can be used + **/ +static PaError FilterUse(PaWinWdmFilter* filter) +{ + assert( filter ); + + PA_LOGE_; + if( filter->handle == NULL ) + { + /* Open the filter */ + filter->handle = CreateFile( + filter->filterName, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if( filter->handle == NULL ) + { + return paDeviceUnavailable; + } + } + filter->usageCount++; + PA_LOGL_; + return paNoError; +} + +/** + * Release the filter handle if nobody is using it + **/ +static void FilterRelease(PaWinWdmFilter* filter) +{ + assert( filter ); + assert( filter->usageCount > 0 ); + + PA_LOGE_; + filter->usageCount--; + if( filter->usageCount == 0 ) + { + if( filter->handle != NULL ) + { + CloseHandle( filter->handle ); + filter->handle = NULL; + } + } + PA_LOGL_; +} + +/** + * Create a render (playback) Pin using the supplied format + **/ +static PaWinWdmPin* FilterCreateRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + PaError result; + PaWinWdmPin* pin; + + assert( filter ); + + pin = FilterFindViableRenderPin(filter,wfex,&result); + if(!pin) + { + goto error; + } + result = PinSetFormat(pin,wfex); + if( result != paNoError ) + { + goto error; + } + result = PinInstantiate(pin); + if( result != paNoError ) + { + goto error; + } + + *error = paNoError; + return pin; + +error: + *error = result; + return NULL; +} + +/** + * Find a pin that supports the given format + **/ +static PaWinWdmPin* FilterFindViableRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + int pinId; + PaWinWdmPin* pin; + PaError result = paDeviceUnavailable; + *error = paNoError; + + assert( filter ); + + for( pinId = 0; pinIdpinCount; pinId++ ) + { + pin = filter->pins[pinId]; + if( pin != NULL ) + { + if(( pin->dataFlow == KSPIN_DATAFLOW_IN ) && + (( pin->communication == KSPIN_COMMUNICATION_SINK) || + ( pin->communication == KSPIN_COMMUNICATION_BOTH))) + { + result = PinIsFormatSupported( pin, wfex ); + if( result == paNoError ) + { + return pin; + } + } + } + } + + *error = result; + return NULL; +} + +/** + * Check if there is a pin that should playback + * with the supplied format + **/ +static PaError FilterCanCreateRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex) +{ + PaWinWdmPin* pin; + PaError result; + + assert ( filter ); + + pin = FilterFindViableRenderPin(filter,wfex,&result); + /* result will be paNoError if pin found + * or else an error code indicating what is wrong with the format + **/ + return result; +} + +/** + * Create a capture (record) Pin using the supplied format + **/ +static PaWinWdmPin* FilterCreateCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + PaError result; + PaWinWdmPin* pin; + + assert( filter ); + + pin = FilterFindViableCapturePin(filter,wfex,&result); + if(!pin) + { + goto error; + } + + result = PinSetFormat(pin,wfex); + if( result != paNoError ) + { + goto error; + } + + result = PinInstantiate(pin); + if( result != paNoError ) + { + goto error; + } + + *error = paNoError; + return pin; + +error: + *error = result; + return NULL; +} + +/** + * Find a capture pin that supports the given format + **/ +static PaWinWdmPin* FilterFindViableCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + int pinId; + PaWinWdmPin* pin; + PaError result = paDeviceUnavailable; + *error = paNoError; + + assert( filter ); + + for( pinId = 0; pinIdpinCount; pinId++ ) + { + pin = filter->pins[pinId]; + if( pin != NULL ) + { + if(( pin->dataFlow == KSPIN_DATAFLOW_OUT ) && + (( pin->communication == KSPIN_COMMUNICATION_SINK) || + ( pin->communication == KSPIN_COMMUNICATION_BOTH))) + { + result = PinIsFormatSupported( pin, wfex ); + if( result == paNoError ) + { + return pin; + } + } + } + } + + *error = result; + return NULL; +} + +/** + * Check if there is a pin that should playback + * with the supplied format + **/ +static PaError FilterCanCreateCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex) +{ + PaWinWdmPin* pin; + PaError result; + + assert ( filter ); + + pin = FilterFindViableCapturePin(filter,wfex,&result); + /* result will be paNoError if pin found + * or else an error code indicating what is wrong with the format + **/ + return result; +} + +/** + * Build the list of available filters + */ +static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi) +{ + PaError result = paNoError; + HDEVINFO handle = NULL; + int device; + int invalidDevices; + int slot; + SP_DEVICE_INTERFACE_DATA interfaceData; + SP_DEVICE_INTERFACE_DATA aliasData; + SP_DEVINFO_DATA devInfoData; + int noError; + const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR)); + unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))]; + SP_DEVICE_INTERFACE_DETAIL_DATA* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA*)interfaceDetailsArray; + TCHAR friendlyName[MAX_PATH]; + HKEY hkey; + DWORD sizeFriendlyName; + DWORD type; + PaWinWdmFilter* newFilter; + GUID* category = (GUID*)&KSCATEGORY_AUDIO; + GUID* alias_render = (GUID*)&KSCATEGORY_RENDER; + GUID* alias_capture = (GUID*)&KSCATEGORY_CAPTURE; + DWORD hasAlias; + + PA_LOGE_; + + devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + /* Open a handle to search for devices (filters) */ + handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if( handle == NULL ) + { + return paUnanticipatedHostError; + } + PA_DEBUG(("Setup called\n")); + + /* First let's count the number of devices so we can allocate a list */ + invalidDevices = 0; + for( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + PA_DEBUG(("Enum called\n")); + if( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + hasAlias = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + PA_DEBUG(("noError = %d\n",noError)); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + hasAlias |= 1; /* Has render alias */ + } + else + { + PA_DEBUG(("Device %d has no render alias\n",device)); + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + hasAlias |= 2; /* Has capture alias */ + } + else + { + PA_DEBUG(("Device %d has no capture alias\n",device)); + } + } + if(!hasAlias) + invalidDevices++; /* This was not a valid capture or render audio device */ + + } + /* Remember how many there are */ + wdmHostApi->filterCount = device-invalidDevices; + + PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices)); + + /* Now allocate the list of pointers to devices */ + wdmHostApi->filters = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * device ); + if( !wdmHostApi->filters ) + { + if(handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + return paInsufficientMemory; + } + + /* Now create filter objects for each interface found */ + slot = 0; + for( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + devInfoData.Reserved = 0; + + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + if( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + hasAlias = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + hasAlias |= 1; /* Has render alias */ + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + hasAlias |= 2; /* Has capture alias */ + } + } + if(!hasAlias) + continue; /* This was not a valid capture or render audio device */ + + noError = SetupDiGetDeviceInterfaceDetail(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData); + if( noError ) + { + /* Try to get the "friendly name" for this interface */ + sizeFriendlyName = sizeof(friendlyName); + /* Fix contributed by Ben Allison + * Removed KEY_SET_VALUE from flags on following call + * as its causes failure when running without admin rights + * and it was not required */ + hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE); + if(hkey!=INVALID_HANDLE_VALUE) + { + noError = RegQueryValueEx(hkey,TEXT("FriendlyName"),0,&type,(BYTE*)friendlyName,&sizeFriendlyName); + if( noError == ERROR_SUCCESS ) + { + PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName)); + RegCloseKey(hkey); + } + else + { + friendlyName[0] = 0; + } + } + newFilter = FilterNew(devInterfaceDetails->DevicePath,friendlyName,&result); + if( result == paNoError ) + { + PA_DEBUG(("Filter created\n")); + wdmHostApi->filters[slot] = newFilter; + slot++; + } + else + { + PA_DEBUG(("Filter NOT created\n")); + /* As there are now less filters than we initially thought + * we must reduce the count by one */ + wdmHostApi->filterCount--; + } + } + } + + /* Clean up */ + if(handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + + return paNoError; +} + +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinWdmHostApiRepresentation *wdmHostApi; + PaWinWdmDeviceInfo *deviceInfoArray; + PaWinWdmFilter* pFilter; + PaWinWdmDeviceInfo *wdmDeviceInfo; + PaDeviceInfo *deviceInfo; + + PA_LOGE_; + + /* + Attempt to load the KSUSER.DLL without which we cannot create pins + We will unload this on termination + */ + if(DllKsUser == NULL) + { + DllKsUser = LoadLibrary(TEXT("ksuser.dll")); + if(DllKsUser == NULL) + goto error; + } + + FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin"); + if(FunctionKsCreatePin == NULL) + goto error; + + wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) ); + if( !wdmHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + wdmHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !wdmHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + result = BuildFilterList( wdmHostApi ); + if( result != paNoError ) + { + goto error; + } + deviceCount = wdmHostApi->filterCount; + + *hostApi = &wdmHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paWDMKS; + (*hostApi)->info.name = "Windows WDM-KS"; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + if( deviceCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + wdmDeviceInfo = &deviceInfoArray[i]; + deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo; + pFilter = wdmHostApi->filters[i]; + if( pFilter == NULL ) + continue; + wdmDeviceInfo->filter = pFilter; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = (char*)pFilter->friendlyName; + PA_DEBUG(("Device found name: %s\n",(char*)pFilter->friendlyName)); + deviceInfo->maxInputChannels = pFilter->maxInputChannels; + if(deviceInfo->maxInputChannels > 0) + { + /* Set the default input device to the first device we find with + * more than zero input channels + **/ + if((*hostApi)->info.defaultInputDevice == paNoDevice) + { + (*hostApi)->info.defaultInputDevice = i; + } + } + + deviceInfo->maxOutputChannels = pFilter->maxOutputChannels; + if(deviceInfo->maxOutputChannels > 0) + { + /* Set the default output device to the first device we find with + * more than zero output channels + **/ + if((*hostApi)->info.defaultOutputDevice == paNoDevice) + { + (*hostApi)->info.defaultOutputDevice = i; + } + } + + /* These low values are not very useful because + * a) The lowest latency we end up with can depend on many factors such + * as the device buffer sizes/granularities, sample rate, channels and format + * b) We cannot know the device buffer sizes until we try to open/use it at + * a particular setting + * So: we give 512x48000Hz frames as the default low input latency + **/ + deviceInfo->defaultLowInputLatency = (512.0/48000.0); + deviceInfo->defaultLowOutputLatency = (512.0/48000.0); + deviceInfo->defaultHighInputLatency = (4096.0/48000.0); + deviceInfo->defaultHighOutputLatency = (4096.0/48000.0); + deviceInfo->defaultSampleRate = (double)(pFilter->bestSampleRate); + + (*hostApi)->deviceInfos[i] = deviceInfo; + } + } + + (*hostApi)->info.deviceCount = deviceCount; + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &wdmHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + PA_LOGL_; + return result; + +error: + if( DllKsUser != NULL ) + { + FreeLibrary( DllKsUser ); + DllKsUser = NULL; + } + + if( wdmHostApi ) + { + PaUtil_FreeMemory( wdmHostApi->filters ); + if( wdmHostApi->allocations ) + { + PaUtil_FreeAllAllocations( wdmHostApi->allocations ); + PaUtil_DestroyAllocationGroup( wdmHostApi->allocations ); + } + PaUtil_FreeMemory( wdmHostApi ); + } + PA_LOGL_; + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + int i; + PA_LOGE_; + + if( wdmHostApi->filters ) + { + for( i=0; ifilterCount; i++) + { + if( wdmHostApi->filters[i] != NULL ) + { + FilterFree( wdmHostApi->filters[i] ); + wdmHostApi->filters[i] = NULL; + } + } + } + PaUtil_FreeMemory( wdmHostApi->filters ); + if( wdmHostApi->allocations ) + { + PaUtil_FreeAllAllocations( wdmHostApi->allocations ); + PaUtil_DestroyAllocationGroup( wdmHostApi->allocations ); + } + PaUtil_FreeMemory( wdmHostApi ); + PA_LOGL_; +} + +static void FillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, PaSampleFormat sampleFormat, double sampleRate, int channelCount) +{ + PA_LOGE_; + PA_DEBUG(( "sampleFormat = %lx\n" , sampleFormat )); + PA_DEBUG(( "sampleRate = %f\n" , sampleRate )); + PA_DEBUG(( "chanelCount = %d\n", channelCount )); + + pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pwfext->Format.nChannels = channelCount; + pwfext->Format.nSamplesPerSec = (int)sampleRate; + if(channelCount == 1) + pwfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT; + else + pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + if(sampleFormat == paFloat32) + { + pwfext->Format.nBlockAlign = channelCount * 4; + pwfext->Format.wBitsPerSample = 32; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 32; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else if(sampleFormat == paInt32) + { + pwfext->Format.nBlockAlign = channelCount * 4; + pwfext->Format.wBitsPerSample = 32; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 32; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if(sampleFormat == paInt24) + { + pwfext->Format.nBlockAlign = channelCount * 3; + pwfext->Format.wBitsPerSample = 24; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 24; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if(sampleFormat == paInt16) + { + pwfext->Format.nBlockAlign = channelCount * 2; + pwfext->Format.wBitsPerSample = 16; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 16; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign; + + PA_LOGL_; +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmFilter* pFilter; + int result = paFormatIsSupported; + WAVEFORMATEXTENSIBLE wfx; + + PA_LOGE_; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* Check that the input format is supported */ + FillWFEXT(&wfx,paInt16,sampleRate,inputChannelCount); + + pFilter = wdmHostApi->filters[inputParameters->device]; + result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + return result; + } + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* Check that the output format is supported */ + FillWFEXT(&wfx,paInt16,sampleRate,outputChannelCount); + + pFilter = wdmHostApi->filters[outputParameters->device]; + result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + return result; + } + + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from inputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + if((inputChannelCount == 0)&&(outputChannelCount == 0)) + result = paSampleFormatNotSupported; /* Not right error */ + + PA_LOGL_; + return result; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmStream *stream = 0; + /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */ + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int userInputChannels,userOutputChannels; + int size; + PaWinWdmFilter* pFilter; + WAVEFORMATEXTENSIBLE wfx; + + PA_LOGE_; + PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate)); + PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerBuffer)); + + if( inputParameters ) + { + userInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support stream->userInputChannels */ + if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + } + else + { + userInputChannels = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + userOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support stream->userInputChannels */ + if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + } + else + { + userOutputChannels = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */ + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaWinWdmStream*)PaUtil_AllocateMemory( sizeof(PaWinWdmStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + /* Zero the stream object */ + /* memset((void*)stream,0,sizeof(PaWinWdmStream)); */ + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* Instantiate the input pin if necessary */ + if(userInputChannels > 0) + { + result = paSampleFormatNotSupported; + pFilter = wdmHostApi->filters[inputParameters->device]; + stream->userInputChannels = userInputChannels; + + if(((inputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0) + { /* inputSampleFormat is supported, so try to use it */ + hostInputSampleFormat = inputSampleFormat; + FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels); + stream->bytesPerInputFrame = wfx.Format.nBlockAlign; + stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result); + stream->deviceInputChannels = stream->userInputChannels; + } + + if(result != paNoError) + { /* Search through all PaSampleFormats to find one that works */ + hostInputSampleFormat = paFloat32; + + do { + FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels); + stream->bytesPerInputFrame = wfx.Format.nBlockAlign; + stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result); + stream->deviceInputChannels = stream->userInputChannels; + + if(stream->recordingPin == NULL) result = paSampleFormatNotSupported; + if(result != paNoError) hostInputSampleFormat <<= 1; + } + while(result != paNoError && hostInputSampleFormat <= paUInt8); + } + + if(result != paNoError) + { /* None of the PaSampleFormats worked. Set the hostInputSampleFormat to the best fit + * and try a PCM format. + **/ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( pFilter->formats, inputSampleFormat ); + + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + if(stream->recordingPin == NULL) result = paSampleFormatNotSupported; + } + + if( result != paNoError ) + { + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + **/ + if( stream->userInputChannels < pFilter->maxInputChannels ) + { + FillWFEXT(&wfx,hostInputSampleFormat,sampleRate,pFilter->maxInputChannels); + stream->bytesPerInputFrame = wfx.Format.nBlockAlign; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + stream->deviceInputChannels = pFilter->maxInputChannels; + + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + } + } + } + + if(stream->recordingPin == NULL) + { + goto error; + } + + switch(hostInputSampleFormat) + { + case paInt16: stream->inputSampleSize = 2; break; + case paInt24: stream->inputSampleSize = 3; break; + case paInt32: + case paFloat32: stream->inputSampleSize = 4; break; + } + + stream->recordingPin->frameSize /= stream->bytesPerInputFrame; + PA_DEBUG(("Pin output frames: %d\n",stream->recordingPin->frameSize)); + } + else + { + stream->recordingPin = NULL; + stream->bytesPerInputFrame = 0; + } + + /* Instantiate the output pin if necessary */ + if(userOutputChannels > 0) + { + result = paSampleFormatNotSupported; + pFilter = wdmHostApi->filters[outputParameters->device]; + stream->userOutputChannels = userOutputChannels; + + if(((outputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0) + { + hostOutputSampleFormat = outputSampleFormat; + FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels); + stream->bytesPerOutputFrame = wfx.Format.nBlockAlign; + stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result); + stream->deviceOutputChannels = stream->userOutputChannels; + } + + if(result != paNoError) + { + hostOutputSampleFormat = paFloat32; + + do { + FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels); + stream->bytesPerOutputFrame = wfx.Format.nBlockAlign; + stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result); + stream->deviceOutputChannels = stream->userOutputChannels; + + if(stream->playbackPin == NULL) result = paSampleFormatNotSupported; + if(result != paNoError) hostOutputSampleFormat <<= 1; + } + while(result != paNoError && hostOutputSampleFormat <= paUInt8); + } + + if(result != paNoError) + { + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( pFilter->formats, outputSampleFormat ); + + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result); + if(stream->playbackPin == NULL) result = paSampleFormatNotSupported; + } + + if( result != paNoError ) + { + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + **/ + if( stream->userOutputChannels < pFilter->maxOutputChannels ) + { + FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,pFilter->maxOutputChannels); + stream->bytesPerOutputFrame = wfx.Format.nBlockAlign; + stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + stream->deviceOutputChannels = pFilter->maxOutputChannels; + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + } + } + } + + if(stream->playbackPin == NULL) + { + goto error; + } + + switch(hostOutputSampleFormat) + { + case paInt16: stream->outputSampleSize = 2; break; + case paInt24: stream->outputSampleSize = 3; break; + case paInt32: + case paFloat32: stream->outputSampleSize = 4; break; + } + + stream->playbackPin->frameSize /= stream->bytesPerOutputFrame; + PA_DEBUG(("Pin output frames: %d\n",stream->playbackPin->frameSize)); + } + else + { + stream->playbackPin = NULL; + stream->bytesPerOutputFrame = 0; + } + + /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */ + + /* Record the buffer length */ + if(inputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->framesPerHostIBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001); + if(stream->framesPerHostIBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->framesPerHostIBuffer = (unsigned long)sampleRate; + } + else if(stream->framesPerHostIBuffer < stream->recordingPin->frameSize) + { + stream->framesPerHostIBuffer = stream->recordingPin->frameSize; + } + PA_DEBUG(("Input frames chosen:%ld\n",stream->framesPerHostIBuffer)); + } + + if(outputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->framesPerHostOBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001); + if(stream->framesPerHostOBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->framesPerHostOBuffer = (unsigned long)sampleRate; + } + else if(stream->framesPerHostOBuffer < stream->playbackPin->frameSize) + { + stream->framesPerHostOBuffer = stream->playbackPin->frameSize; + } + PA_DEBUG(("Output frames chosen:%ld\n",stream->framesPerHostOBuffer)); + } + + /* Host buffer size is bounded to the largest of the input and output + frame sizes */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + stream->userInputChannels, inputSampleFormat, hostInputSampleFormat, + stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + max(stream->framesPerHostOBuffer,stream->framesPerHostIBuffer), + paUtilBoundedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + stream->streamRepresentation.streamInfo.inputLatency = + ((double)stream->framesPerHostIBuffer) / sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = + ((double)stream->framesPerHostOBuffer) / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + PA_DEBUG(("BytesPerInputFrame = %d\n",stream->bytesPerInputFrame)); + PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->bytesPerOutputFrame)); + + /* Allocate all the buffers for host I/O */ + size = 2 * (stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame); + PA_DEBUG(("Buffer size = %d\n",size)); + stream->hostBuffer = (char*)PaUtil_AllocateMemory(size); + PA_DEBUG(("Buffer allocated\n")); + if( !stream->hostBuffer ) + { + PA_DEBUG(("Cannot allocate host buffer!\n")); + result = paInsufficientMemory; + goto error; + } + PA_DEBUG(("Buffer start = %p\n",stream->hostBuffer)); + /* memset(stream->hostBuffer,0,size); */ + + /* Set up the packets */ + stream->events[0] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[0]); /* Record buffer 1 */ + stream->events[1] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[1]); /* Record buffer 2 */ + stream->events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[2]); /* Play buffer 1 */ + stream->events[3] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[3]); /* Play buffer 2 */ + stream->events[4] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[4]); /* Abort event */ + if(stream->userInputChannels > 0) + { + DATAPACKET *p = &(stream->packets[0]); + p->Signal.hEvent = stream->events[0]; + p->Header.Data = stream->hostBuffer; + p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + p = &(stream->packets[1]); + p->Signal.hEvent = stream->events[1]; + p->Header.Data = stream->hostBuffer + stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + if(stream->userOutputChannels > 0) + { + DATAPACKET *p = &(stream->packets[2]); + p->Signal.hEvent = stream->events[2]; + p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + p = &(stream->packets[3]); + p->Signal.hEvent = stream->events[3]; + p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + + stream->streamStarted = 0; + stream->streamActive = 0; + stream->streamStop = 0; + stream->streamAbort = 0; + stream->streamFlags = streamFlags; + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + + *s = (PaStream*)stream; + + PA_LOGL_; + return result; + +error: + size = 5; + while(size--) + { + if(stream->events[size] != NULL) + { + CloseHandle(stream->events[size]); + stream->events[size] = NULL; + } + } + if(stream->hostBuffer) + PaUtil_FreeMemory( stream->hostBuffer ); + + if(stream->playbackPin) + PinClose(stream->playbackPin); + if(stream->recordingPin) + PinClose(stream->recordingPin); + + if( stream ) + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int size; + + PA_LOGE_; + + assert(!stream->streamStarted); + assert(!stream->streamActive); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + size = 5; + while(size--) + { + if(stream->events[size] != NULL) + { + CloseHandle(stream->events[size]); + stream->events[size] = NULL; + } + } + if(stream->hostBuffer) + PaUtil_FreeMemory( stream->hostBuffer ); + + if(stream->playbackPin) + PinClose(stream->playbackPin); + if(stream->recordingPin) + PinClose(stream->recordingPin); + + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* +Write the supplied packet to the pin +Asynchronous +Should return false on success +*/ +static BOOL PinWrite(HANDLE h, DATAPACKET* p) +{ + unsigned long cbReturned = 0; + return DeviceIoControl(h,IOCTL_KS_WRITE_STREAM,NULL,0, + &p->Header,p->Header.Size,&cbReturned,&p->Signal); +} + +/* +Read to the supplied packet from the pin +Asynchronous +Should return false on success +*/ +static BOOL PinRead(HANDLE h, DATAPACKET* p) +{ + unsigned long cbReturned = 0; + return DeviceIoControl(h,IOCTL_KS_READ_STREAM,NULL,0, + &p->Header,p->Header.Size,&cbReturned,&p->Signal); +} + +/* +Copy the first interleaved channel of 16 bit data to the other channels +*/ +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples) +{ + unsigned short* data = (unsigned short*)buffer; + int channel; + unsigned short sourceSample; + while( samples-- ) + { + sourceSample = *data++; + channel = channels-1; + while( channel-- ) + { + *data++ = sourceSample; + } + } +} + +/* +Copy the first interleaved channel of 24 bit data to the other channels +*/ +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples) +{ + unsigned char* data = (unsigned char*)buffer; + int channel; + unsigned char sourceSample[3]; + while( samples-- ) + { + sourceSample[0] = data[0]; + sourceSample[1] = data[1]; + sourceSample[2] = data[2]; + data += 3; + channel = channels-1; + while( channel-- ) + { + data[0] = sourceSample[0]; + data[1] = sourceSample[1]; + data[2] = sourceSample[2]; + data += 3; + } + } +} + +/* +Copy the first interleaved channel of 32 bit data to the other channels +*/ +static void DuplicateFirstChannelInt32(void* buffer, int channels, int samples) +{ + unsigned long* data = (unsigned long*)buffer; + int channel; + unsigned long sourceSample; + while( samples-- ) + { + sourceSample = *data++; + channel = channels-1; + while( channel-- ) + { + *data++ = sourceSample; + } + } +} + +static DWORD WINAPI ProcessingThread(LPVOID pParam) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)pParam; + PaStreamCallbackTimeInfo ti; + int cbResult = paContinue; + int inbuf = 0; + int outbuf = 0; + int pending = 0; + PaError result; + unsigned long wait; + unsigned long eventSignaled; + int fillPlaybuf = 0; + int emptyRecordbuf = 0; + int framesProcessed; + unsigned long timeout; + int i; + int doChannelCopy; + int priming = 0; + PaStreamCallbackFlags underover = 0; + + PA_LOGE_; + + ti.inputBufferAdcTime = 0.0; + ti.currentTime = 0.0; + ti.outputBufferDacTime = 0.0; + + /* Get double buffering going */ + + /* Submit buffers */ + if(stream->playbackPin) + { + result = PinSetState(stream->playbackPin, KSSTATE_RUN); + + PA_DEBUG(("play state run = %d;",(int)result)); + SetEvent(stream->events[outbuf+2]); + outbuf = (outbuf+1)&1; + SetEvent(stream->events[outbuf+2]); + outbuf = (outbuf+1)&1; + pending += 2; + priming += 4; + } + if(stream->recordingPin) + { + result = PinSetState(stream->recordingPin, KSSTATE_RUN); + + PA_DEBUG(("recording state run = %d;",(int)result)); + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; /* Increment and wrap */ + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; /* Increment and wrap */ + /* FIXME - do error checking */ + pending += 2; + } + PA_DEBUG(("Out buffer len:%f\n",(2000*stream->framesPerHostOBuffer) / stream->streamRepresentation.streamInfo.sampleRate)); + PA_DEBUG(("In buffer len:%f\n",(2000*stream->framesPerHostIBuffer) / stream->streamRepresentation.streamInfo.sampleRate)); + timeout = max( + ((2000*(DWORD)stream->framesPerHostOBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate), + ((2000*(DWORD)stream->framesPerHostIBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate)); + timeout = max(timeout,1); + PA_DEBUG(("Timeout = %ld\n",timeout)); + + while(!stream->streamAbort) + { + fillPlaybuf = 0; + emptyRecordbuf = 0; + + /* Wait for next input or output buffer to be finished with*/ + assert(pending>0); + + if(stream->streamStop) + { + PA_DEBUG(("ss1:pending=%d ",pending)); + } + wait = WaitForMultipleObjects(5, stream->events, FALSE, 0); + if( wait == WAIT_TIMEOUT ) + { + /* No (under|over)flow has ocurred */ + wait = WaitForMultipleObjects(5, stream->events, FALSE, timeout); + eventSignaled = wait - WAIT_OBJECT_0; + } + else + { + eventSignaled = wait - WAIT_OBJECT_0; + if( eventSignaled < 2 ) + { + underover |= paInputOverflow; + PA_DEBUG(("Input overflow\n")); + } + else if(( eventSignaled < 4 )&&(!priming)) + { + underover |= paOutputUnderflow; + PA_DEBUG(("Output underflow\n")); + } + } + + if(stream->streamStop) + { + PA_DEBUG(("ss2:wait=%ld",wait)); + } + if(wait == WAIT_FAILED) + { + PA_DEBUG(("Wait fail = %ld! ",wait)); + break; + } + if(wait == WAIT_TIMEOUT) + { + continue; + } + + if(eventSignaled < 2) + { /* Recording input buffer has been filled */ + if(stream->playbackPin) + { + /* First check if also the next playback buffer has been signaled */ + wait = WaitForSingleObject(stream->events[outbuf+2],0); + if(wait == WAIT_OBJECT_0) + { + /* Yes, so do both buffers at same time */ + fillPlaybuf = 1; + pending--; + /* Was this an underflow situation? */ + if( underover ) + underover |= paOutputUnderflow; /* Yes! */ + } + } + emptyRecordbuf = 1; + pending--; + } + else if(eventSignaled < 4) + { /* Playback output buffer has been emptied */ + if(stream->recordingPin) + { + /* First check if also the next recording buffer has been signaled */ + wait = WaitForSingleObject(stream->events[inbuf],0); + if(wait == WAIT_OBJECT_0) + { /* Yes, so do both buffers at same time */ + emptyRecordbuf = 1; + pending--; + /* Was this an overflow situation? */ + if( underover ) + underover |= paInputOverflow; /* Yes! */ + } + } + fillPlaybuf = 1; + pending--; + } + else + { + /* Abort event! */ + assert(stream->streamAbort); /* Should have been set */ + PA_DEBUG(("ABORTING ")); + break; + } + ResetEvent(stream->events[eventSignaled]); + + if(stream->streamStop) + { + PA_DEBUG(("Stream stop! pending=%d",pending)); + cbResult = paComplete; /* Stop, but play remaining buffers */ + } + + /* Do necessary buffer processing (which will invoke user callback if necessary */ + doChannelCopy = 0; + if(cbResult==paContinue) + { + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) == + (stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) ) + PaUtil_BeginBufferProcessing(&stream->bufferProcessor,&ti,underover); + underover = 0; /* Reset the (under|over)flow status */ + if(fillPlaybuf) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor,0); + if( stream->userOutputChannels == 1 ) + { + /* Write the single user channel to the first interleaved block */ + PaUtil_SetOutputChannel(&stream->bufferProcessor,0,stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels); + /* We will do a copy to the other channels after the data has been written */ + doChannelCopy = 1; + } + else + { + for(i=0;iuserOutputChannels;i++) + { + /* Only write the user output channels. Leave the rest blank */ + PaUtil_SetOutputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[outbuf+2].Header.Data))+(i*stream->outputSampleSize),stream->deviceOutputChannels); + } + } + } + if(emptyRecordbuf) + { + PaUtil_SetInputFrameCount(&stream->bufferProcessor,stream->packets[inbuf].Header.DataUsed/stream->bytesPerInputFrame); + for(i=0;iuserInputChannels;i++) + { + /* Only read as many channels as the user wants */ + PaUtil_SetInputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[inbuf].Header.Data))+(i*stream->inputSampleSize),stream->deviceInputChannels); + } + } + /* Only call the EndBufferProcessing function is the total input frames == total output frames */ + if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) == + (stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) ) + framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor,&cbResult); + else framesProcessed = 0; + if( doChannelCopy ) + { + /* Copy the first output channel to the other channels */ + switch(stream->outputSampleSize) + { + case 2: + DuplicateFirstChannelInt16(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer); + break; + case 3: + DuplicateFirstChannelInt24(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer); + break; + case 4: + DuplicateFirstChannelInt32(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer); + break; + default: + assert(0); /* Unsupported format! */ + break; + } + } + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + } + else + { + fillPlaybuf = 0; + emptyRecordbuf = 0; + } + + /* + if(cbResult != paContinue) + { + PA_DEBUG(("cbResult=%d, pending=%d:",cbResult,pending)); + } + */ + /* Submit buffers */ + if((fillPlaybuf)&&(cbResult!=paAbort)) + { + if(!PinWrite(stream->playbackPin->handle,&stream->packets[outbuf+2])) + outbuf = (outbuf+1)&1; /* Increment and wrap */ + pending++; + if( priming ) + priming--; /* Have to prime twice */ + } + if((emptyRecordbuf)&&(cbResult==paContinue)) + { + stream->packets[inbuf].Header.DataUsed = 0; /* Reset for reuse */ + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; /* Increment and wrap */ + pending++; + } + if(pending==0) + { + PA_DEBUG(("pending==0 finished...;")); + break; + } + if((!stream->playbackPin)&&(cbResult!=paContinue)) + { + PA_DEBUG(("record only cbResult=%d...;",cbResult)); + break; + } + } + + PA_DEBUG(("Finished thread")); + + /* Finished, either normally or aborted */ + if(stream->playbackPin) + { + result = PinSetState(stream->playbackPin, KSSTATE_PAUSE); + result = PinSetState(stream->playbackPin, KSSTATE_STOP); + } + if(stream->recordingPin) + { + result = PinSetState(stream->recordingPin, KSSTATE_PAUSE); + result = PinSetState(stream->recordingPin, KSSTATE_STOP); + } + + stream->streamActive = 0; + + if((!stream->streamStop)&&(!stream->streamAbort)) + { + /* Invoke the user stream finished callback */ + /* Only do it from here if not being stopped/aborted by user */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + stream->streamStop = 0; + stream->streamAbort = 0; + + /* Reset process priority if necessary */ + if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + PA_LOGL_; + ExitThread(0); + return 0; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + DWORD dwID; + BOOL ret; + int size; + + PA_LOGE_; + + stream->streamStop = 0; + stream->streamAbort = 0; + size = 5; + while(size--) + { + if(stream->events[size] != NULL) + { + ResetEvent(stream->events[size]); + } + } + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + stream->oldProcessPriority = GetPriorityClass(GetCurrentProcess()); + /* Uncomment the following line to enable dynamic boosting of the process + * priority to real time for best low latency support + * Disabled by default because RT processes can easily block the OS */ + /*ret = SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS); + PA_DEBUG(("Class ret = %d;",ret));*/ + + stream->streamStarted = 1; + stream->streamThread = CreateThread(NULL, 0, ProcessingThread, stream, 0, &dwID); + if(stream->streamThread == NULL) + { + stream->streamStarted = 0; + result = paInsufficientMemory; + goto end; + } + ret = SetThreadPriority(stream->streamThread,THREAD_PRIORITY_TIME_CRITICAL); + PA_DEBUG(("Priority ret = %d;",ret)); + /* Make the stream active */ + stream->streamActive = 1; + +end: + PA_LOGL_; + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int doCb = 0; + + PA_LOGE_; + + if(stream->streamActive) + { + doCb = 1; + stream->streamStop = 1; + while(stream->streamActive) + { + PA_DEBUG(("W.")); + Sleep(10); /* Let thread sleep for 10 msec */ + } + } + + PA_DEBUG(("Terminating thread")); + if(stream->streamStarted && stream->streamThread) + { + TerminateThread(stream->streamThread,0); + stream->streamThread = NULL; + } + + stream->streamStarted = 0; + + if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + if(doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + PA_LOGL_; + return result; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int doCb = 0; + + PA_LOGE_; + + if(stream->streamActive) + { + doCb = 1; + stream->streamAbort = 1; + SetEvent(stream->events[4]); /* Signal immediately */ + while(stream->streamActive) + { + Sleep(10); + } + } + + if(stream->streamStarted && stream->streamThread) + { + TerminateThread(stream->streamThread,0); + stream->streamThread = NULL; + } + + stream->streamStarted = 0; + + if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + if(doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + stream->streamActive = 0; + stream->streamStarted = 0; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if(!stream->streamStarted) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if(stream->streamActive) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaTime GetStreamTime( PaStream* s ) +{ + PA_LOGE_; + PA_LOGL_; + (void)s; + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + double result; + PA_LOGE_; + result = PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); + PA_LOGL_; + return result; +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} \ No newline at end of file diff --git a/portaudio-v19/pa_win_wdmks/readme.txt b/portaudio-v19/pa_win_wdmks/readme.txt new file mode 100644 index 000000000..1a381fe79 --- /dev/null +++ b/portaudio-v19/pa_win_wdmks/readme.txt @@ -0,0 +1,82 @@ +Notes about WDM-KS host API +--------------------------- + +Status history +-------------- +10th November 2005: +Made following changes: + * OpenStream: Try all PaSampleFormats internally if the the chosen + format is not supported natively. This fixed several problems + with soundcards that soundcards that did not take kindly to + using 24-bit 3-byte formats. + * OpenStream: Make the minimum framesPerHostIBuffer (and framesPerHostOBuffer) + the default frameSize for the playback/recording pin. + * ProcessingThread: Added a switch to only call PaUtil_EndBufferProcessing + if the total input frames equals the total output frames + +5th September 2004: +This is the first public version of the code. It should be considered +an alpha release with zero guarantee not to crash on any particular +system. So far it has only been tested in the author's development +environment, which means a Win2k/SP2 PIII laptop with integrated +SoundMAX driver and USB Tascam US-428 compiled with both MinGW +(GCC 3.3) and MSVC++6 using the MS DirectX 9 SDK. +It has been most widely tested with the MinGW build, with most of the +test programs (particularly paqa_devs and paqa_errs) passing. +There are some notable failures: patest_out_underflow and both of the +blocking I/O tests (as blocking I/O is not implemented). +At this point the code needs to be tested with a much wider variety +of configurations and feedback provided from testers regarding +both working and failing cases. + +What is the WDM-KS host API? +---------------------------- +PortAudio for Windows currently has 3 functional host implementations. +MME uses the oldest Windows audio API which does not offer good +play/record latency. +DirectX improves this, but still imposes a penalty +of 10s of milliseconds due to the system mixing of streams from +multiple applications. +ASIO offers very good latency, but requires special drivers which are +not always available for cheaper audio hardware. Also, when ASIO +drivers are available, they are not always so robust because they +bypass all of the standardised Windows device driver architecture +and hit the hardware their own way. +Alternatively there are a couple of free (but closed source) ASIO +implementations which connect to the lower level Windows +"Kernel Streaming" API, but again these require special installation +by the user, and can be limited in functionality or difficult to use. + +This is where the PortAudio "WDM-KS" host implementation comes in. +It directly connects PortAudio to the same Kernel Streaming API which +those ASIO bridges use. This avoids the mixing penatly of DirectX, +giving at least as good latency as any ASIO driver, but it has the +advantage of working with ANY Windows audio hardware which is available +through the normal MME/DirectX routes without the user requiring +any additional device drivers to be installed, and allowing all +device selection to be done through the normal PortAudio API. + +Note that in general you should only be using this host API if your +application has a real requirement for very low latency audio (<20ms), +either because you are generating sounds in real-time based upon +user input, or you a processing recorded audio in real time. + +The only thing to be aware of is that using the KS interface will +block that device from being used by the rest of system through +the higher level APIs, or conversely, if the system is using +a device, the KS API will not be able to use it. MS recommend that +you should keep the device open only when your application has focus. +In PortAudio terms, this means having a stream Open on a WDMKS device. + +Usage +----- +To add the WDMKS backend to your program which is already using +PortAudio, you must undefine PA_NO_WDMKS from your build file, +and include the pa_win_wdmks\pa_win_wdmks.c into your build. +The file should compile in both C and C++. +You will need a DirectX SDK installed on your system for the +ks.h and ksmedia.h header files. +You will need to link to the system "setupapi" library. +Note that if you use MinGW, you will get more warnings from +the DX header files when using GCC(C), and still a few warnings +with G++(CPP). \ No newline at end of file diff --git a/portaudio-v19/pa_win_wmme/pa_win_wmme.c b/portaudio-v19/pa_win_wmme/pa_win_wmme.c new file mode 100644 index 000000000..86b6cdf14 --- /dev/null +++ b/portaudio-v19/pa_win_wmme/pa_win_wmme.c @@ -0,0 +1,3623 @@ +/* + * $Id$ + * pa_win_wmme.c + * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification History: + PLB = Phil Burk + JM = Julien Maillard + RDB = Ross Bencina + PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer) + PLB20010413 - check for excessive numbers of channels + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + including conditional inclusion of memory.h, + and explicit typecasting on memory allocation + PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory + PLB20010816 - pass process instead of thread to SetPriorityClass() + PLB20010927 - use number of frames instead of real-time for CPULoad calculation. + JM20020118 - prevent hung thread when buffers underflow. + PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount + RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init + RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices + refactoring, renaming and fixed a few edge case bugs + RDB20020531 - converted to V19 framework + ** NOTE maintanance history is now stored in CVS ** +*/ + +/** @file + + @todo Fix buffer catch up code, can sometimes get stuck (perhaps fixed now, + needs to be reviewed and tested.) + + @todo implement paInputUnderflow, paOutputOverflow streamCallback statusFlags, paNeverDropInput. + + @todo BUG: PA_MME_SET_LAST_WAVEIN/OUT_ERROR is used in functions which may + be called asynchronously from the callback thread. this is bad. + + @todo implement inputBufferAdcTime in callback thread + + @todo review/fix error recovery and cleanup in marked functions + + @todo implement timeInfo for stream priming + + @todo handle the case where the callback returns paAbort or paComplete during stream priming. + + @todo review input overflow and output underflow handling in ReadStream and WriteStream + +Non-critical stuff for the future: + + @todo Investigate supporting host buffer formats > 16 bits + + @todo define UNICODE and _UNICODE in the project settings and see what breaks + +*/ + +/* + How it works: + + For both callback and blocking read/write streams we open the MME devices + in CALLBACK_EVENT mode. In this mode, MME signals an Event object whenever + it has finished with a buffer (either filled it for input, or played it + for output). Where necessary we block waiting for Event objects using + WaitMultipleObjects(). + + When implementing a PA callback stream, we set up a high priority thread + which waits on the MME buffer Events and drains/fills the buffers when + they are ready. + + When implementing a PA blocking read/write stream, we simply wait on these + Events (when necessary) inside the ReadStream() and WriteStream() functions. +*/ + +#include +#include +#include +#include +#include +#include +#include +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifndef __MWERKS__ +#include +#include +#endif /* __MWERKS__ */ + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_win_wmme.h" + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment(lib, "winmm.lib") +#endif + +/************************************************* Constants ********/ + +#define PA_MME_USE_HIGH_DEFAULT_LATENCY_ (0) /* For debugging glitches. */ + +#if PA_MME_USE_HIGH_DEFAULT_LATENCY_ + #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.4) + #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (4) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (4) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (4) + #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ (16) + #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.3) /* Do not exceed unless user buffer exceeds */ + #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */ +#else + #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.2) + #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (2) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (3) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (2) + #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ (16) + #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.1) /* Do not exceed unless user buffer exceeds */ + #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */ +#endif + +/* Use higher latency for NT because it is even worse at real-time + operation than Win9x. +*/ +#define PA_MME_WIN_NT_DEFAULT_LATENCY_ (PA_MME_WIN_9X_DEFAULT_LATENCY_ * 2) +#define PA_MME_WIN_WDM_DEFAULT_LATENCY_ (PA_MME_WIN_9X_DEFAULT_LATENCY_) + + +#define PA_MME_MIN_TIMEOUT_MSEC_ (1000) + +static const char constInputMapperSuffix_[] = " - Input"; +static const char constOutputMapperSuffix_[] = " - Output"; + +/********************************************************************/ + +typedef struct PaWinMmeStream PaWinMmeStream; /* forward declaration */ + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* macros for setting last host error information */ + +#ifdef UNICODE + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#else /* !UNICODE */ + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#endif /* UNICODE */ + + +static void PaMme_SetLastSystemError( DWORD errorCode ) +{ + char *lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( paMME, errorCode, lpMsgBuf ); + LocalFree( lpMsgBuf ); +} + +#define PA_MME_SET_LAST_SYSTEM_ERROR( errorCode ) \ + PaMme_SetLastSystemError( errorCode ) + + +/* PaError returning wrappers for some commonly used win32 functions + note that we allow passing a null ptr to have no effect. +*/ + +static PaError CreateEventWithPaError( HANDLE *handle, + LPSECURITY_ATTRIBUTES lpEventAttributes, + BOOL bManualReset, + BOOL bInitialState, + LPCTSTR lpName ) +{ + PaError result = paNoError; + + *handle = NULL; + + *handle = CreateEvent( lpEventAttributes, bManualReset, bInitialState, lpName ); + if( *handle == NULL ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + return result; +} + + +static PaError ResetEventWithPaError( HANDLE handle ) +{ + PaError result = paNoError; + + if( handle ) + { + if( ResetEvent( handle ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + } + + return result; +} + + +static PaError CloseHandleWithPaError( HANDLE handle ) +{ + PaError result = paNoError; + + if( handle ) + { + if( CloseHandle( handle ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + } + + return result; +} + + +/* PaWinMmeHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + int inputDeviceCount, outputDeviceCount; + + /** winMmeDeviceIds is an array of WinMme device ids. + fields in the range [0, inputDeviceCount) are input device ids, + and [inputDeviceCount, inputDeviceCount + outputDeviceCount) are output + device ids. + */ + UINT *winMmeDeviceIds; +} +PaWinMmeHostApiRepresentation; + + +typedef struct +{ + PaDeviceInfo inheritedDeviceInfo; + DWORD dwFormats; /**<< standard formats bitmask from the WAVEINCAPS and WAVEOUTCAPS structures */ +} +PaWinMmeDeviceInfo; + + +/************************************************************************* + * Returns recommended device ID. + * On the PC, the recommended device can be specified by the user by + * setting an environment variable. For example, to use device #1. + * + * set PA_RECOMMENDED_OUTPUT_DEVICE=1 + * + * The user should first determine the available device ID by using + * the supplied application "pa_devs". + */ +#define PA_ENV_BUF_SIZE_ (32) +#define PA_REC_IN_DEV_ENV_NAME_ ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME_ ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceIndex GetEnvDefaultDeviceID( char *envName ) +{ + PaDeviceIndex recommendedIndex = paNoDevice; + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE_]; + +#ifndef WIN32_PLATFORM_PSPC /* no GetEnvironmentVariable on PocketPC */ + + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE_ ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE_) ) + { + recommendedIndex = atoi( envbuf ); + } +#endif + + return recommendedIndex; +} + + +static void InitializeDefaultDeviceIdsFromEnv( PaWinMmeHostApiRepresentation *hostApi ) +{ + PaDeviceIndex device; + + /* input */ + device = GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxInputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultInputDevice = device; + } + + /* output */ + device = GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxOutputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultOutputDevice = device; + } +} + + +/** Convert external PA ID to a windows multimedia device ID +*/ +static UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device ) +{ + assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount ); + + return hostApi->winMmeDeviceIds[ device ]; +} + + +static PaError QueryInputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx ) +{ + MMRESULT mmresult; + + switch( mmresult = waveInOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) ) + { + case MMSYSERR_NOERROR: + return paNoError; + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + return paDeviceUnavailable; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + return paDeviceUnavailable; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + return paInsufficientMemory; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + return paSampleFormatNotSupported; + + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + default: + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } +} + + +static PaError QueryOutputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx ) +{ + MMRESULT mmresult; + + switch( mmresult = waveOutOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) ) + { + case MMSYSERR_NOERROR: + return paNoError; + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + return paDeviceUnavailable; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + return paDeviceUnavailable; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + return paInsufficientMemory; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + return paSampleFormatNotSupported; + + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + default: + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } +} + + +static PaError QueryFormatSupported( PaDeviceInfo *deviceInfo, + PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), + int winMmeDeviceId, int channels, double sampleRate ) +{ + PaWinMmeDeviceInfo *winMmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfo; + WAVEFORMATEX waveFormatEx; + + if( sampleRate == 11025.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){ + + return paNoError; + } + + if( sampleRate == 22050.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){ + + return paNoError; + } + + if( sampleRate == 44100.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){ + + return paNoError; + } + + waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; + waveFormatEx.nChannels = (WORD)channels; + waveFormatEx.nSamplesPerSec = (DWORD)sampleRate; + waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * channels * sizeof(short); + waveFormatEx.nBlockAlign = (WORD)(channels * sizeof(short)); + waveFormatEx.wBitsPerSample = 16; + waveFormatEx.cbSize = 0; + + return waveFormatExQueryFunction( winMmeDeviceId, &waveFormatEx ); +} + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */ +static double defaultSampleRateSearchOrder_[] = + { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0, + 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + +static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int winMmeDeviceId, + PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), int maxChannels ) +{ + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + int i; + + deviceInfo->defaultSampleRate = 0.; + + for( i=0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) + { + double sampleRate = defaultSampleRateSearchOrder_[ i ]; + PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate ); + if( paerror == paNoError ) + { + deviceInfo->defaultSampleRate = sampleRate; + break; + } + } +} + + +static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEINCAPS wic; + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + + *success = 0; + + mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeInputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + strcat( deviceName, constInputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + } + deviceInfo->name = deviceName; + + deviceInfo->maxInputChannels = wic.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * If that happens, then force it to 2 channels. PLB20010413 + */ + if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxInputChannels )); + deviceInfo->maxInputChannels = 2; + } + + winMmeDeviceInfo->dwFormats = wic.dwFormats; + + DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, + QueryInputWaveFormatEx, deviceInfo->maxInputChannels ); + + *success = 1; + +error: + return result; +} + + +static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEOUTCAPS woc; + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + + *success = 0; + + mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeOutputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + strcat( deviceName, constOutputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + } + deviceInfo->name = deviceName; + + deviceInfo->maxOutputChannels = woc.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * It also happens on Win XP! + */ + if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxOutputChannels = 2; + } + + winMmeDeviceInfo->dwFormats = woc.dwFormats; + + DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, + QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels ); + + *success = 1; + +error: + return result; +} + + +static void GetDefaultLatencies( PaTime *defaultLowLatency, PaTime *defaultHighLatency ) +{ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof( osvi ); + GetVersionEx( &osvi ); + + /* Check for NT */ + if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + { + *defaultLowLatency = PA_MME_WIN_NT_DEFAULT_LATENCY_; + } + else if(osvi.dwMajorVersion >= 5) + { + *defaultLowLatency = PA_MME_WIN_WDM_DEFAULT_LATENCY_; + } + else + { + *defaultLowLatency = PA_MME_WIN_9X_DEFAULT_LATENCY_; + } + + *defaultHighLatency = *defaultLowLatency * 2; +} + + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + PaWinMmeHostApiRepresentation *winMmeHostApi; + int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount; + PaWinMmeDeviceInfo *deviceInfoArray; + int deviceInfoInitializationSucceeded; + PaTime defaultLowLatency, defaultHighLatency; + + winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) ); + if( !winMmeHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winMmeHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winMmeHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paMME; + (*hostApi)->info.name = "MME"; + + + /* initialise device counts and default devices under the assumption that + there are no devices. These values are incremented below if and when + devices are successfully initialized. + */ + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + winMmeHostApi->inputDeviceCount = 0; + winMmeHostApi->outputDeviceCount = 0; + + + maximumPossibleDeviceCount = 0; + + inputDeviceCount = waveInGetNumDevs(); + if( inputDeviceCount > 0 ) + maximumPossibleDeviceCount += inputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ + + outputDeviceCount = waveOutGetNumDevs(); + if( outputDeviceCount > 0 ) + maximumPossibleDeviceCount += outputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ + + + if( maximumPossibleDeviceCount > 0 ){ + + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinMmeDeviceInfo*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaWinMmeDeviceInfo) * maximumPossibleDeviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->winMmeDeviceIds = (UINT*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(int) * maximumPossibleDeviceCount ); + if( !winMmeHostApi->winMmeDeviceIds ) + { + result = paInsufficientMemory; + goto error; + } + + GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency ); + + if( inputDeviceCount > 0 ){ + /* -1 is the WAVE_MAPPER */ + for( i = -1; i < inputDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeInputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultInputDevice == paNoDevice ) + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->inputDeviceCount++; + (*hostApi)->info.deviceCount++; + } + } + } + + if( outputDeviceCount > 0 ){ + /* -1 is the WAVE_MAPPER */ + for( i = -1; i < outputDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeOutputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultOutputDevice == paNoDevice ) + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->outputDeviceCount++; + (*hostApi)->info.deviceCount++; + } + } + } + } + + + InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &winMmeHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winMmeHostApi ) + { + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); + } + + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo; + int inputChannelCount, outputChannelCount; + int inputMultipleDeviceChannelCount, outputMultipleDeviceChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + UINT winMmeInputDeviceId, winMmeOutputDeviceId; + unsigned int i; + PaError paerror; + + /* The calls to QueryFormatSupported below are intended to detect invalid + sample rates. If we assume that the channel count and format are OK, + then the only thing that could fail is the sample rate. This isn't + strictly true, but I can't think of a better way to test that the + sample rate is valid. + */ + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification + && inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + { + inputMultipleDeviceChannelCount = 0; + for( i=0; i< inputStreamInfo->deviceCount; ++i ) + { + inputMultipleDeviceChannelCount += inputStreamInfo->devices[i].channelCount; + + inputDeviceInfo = hostApi->deviceInfos[ inputStreamInfo->devices[i].device ]; + + /* check that input device can support inputChannelCount */ + if( inputStreamInfo->devices[i].channelCount <= 0 + || inputStreamInfo->devices[i].channelCount > inputDeviceInfo->maxInputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputStreamInfo->devices[i].device ); + paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + + if( inputMultipleDeviceChannelCount != inputChannelCount ) + return paIncompatibleHostApiSpecificStreamInfo; + } + else + { + if( inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the input device */ + + inputDeviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > inputDeviceInfo->maxInputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device ); + paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputChannelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification + && outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + { + outputMultipleDeviceChannelCount = 0; + for( i=0; i< outputStreamInfo->deviceCount; ++i ) + { + outputMultipleDeviceChannelCount += outputStreamInfo->devices[i].channelCount; + + outputDeviceInfo = hostApi->deviceInfos[ outputStreamInfo->devices[i].device ]; + + /* check that output device can support outputChannelCount */ + if( outputStreamInfo->devices[i].channelCount <= 0 + || outputStreamInfo->devices[i].channelCount > outputDeviceInfo->maxOutputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputStreamInfo->devices[i].device ); + paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + + if( outputMultipleDeviceChannelCount != outputChannelCount ) + return paIncompatibleHostApiSpecificStreamInfo; + } + else + { + if( outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the output device */ + + outputDeviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > outputDeviceInfo->maxOutputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device ); + paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputChannelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + } + + /* + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + for mme all we can do is test that the input and output devices + support the requested sample rate and number of channels. we + cannot test for full duplex compatibility. + */ + + return paFormatIsSupported; +} + + + +static void SelectBufferSizeAndCount( unsigned long baseBufferSize, + unsigned long requestedLatency, + unsigned long baseBufferCount, unsigned long minimumBufferCount, + unsigned long maximumBufferSize, unsigned long *hostBufferSize, + unsigned long *hostBufferCount ) +{ + unsigned long sizeMultiplier, bufferCount, latency; + unsigned long nextLatency, nextBufferSize; + int baseBufferSizeIsPowerOfTwo; + + sizeMultiplier = 1; + bufferCount = baseBufferCount; + + /* count-1 below because latency is always determined by one less + than the total number of buffers. + */ + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + + if( latency > requestedLatency ) + { + + /* reduce number of buffers without falling below suggested latency */ + + nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2); + while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency ) + { + --bufferCount; + nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2); + } + + }else if( latency < requestedLatency ){ + + baseBufferSizeIsPowerOfTwo = (! (baseBufferSize & (baseBufferSize - 1))); + if( baseBufferSizeIsPowerOfTwo ){ + + /* double size of buffers without exceeding requestedLatency */ + + nextBufferSize = (baseBufferSize * (sizeMultiplier*2)); + nextLatency = nextBufferSize * (bufferCount-1); + while( nextBufferSize <= maximumBufferSize + && nextLatency < requestedLatency ) + { + sizeMultiplier *= 2; + nextBufferSize = (baseBufferSize * (sizeMultiplier*2)); + nextLatency = nextBufferSize * (bufferCount-1); + } + + }else{ + + /* increase size of buffers upto first excess of requestedLatency */ + + nextBufferSize = (baseBufferSize * (sizeMultiplier+1)); + nextLatency = nextBufferSize * (bufferCount-1); + while( nextBufferSize <= maximumBufferSize + && nextLatency < requestedLatency ) + { + ++sizeMultiplier; + nextBufferSize = (baseBufferSize * (sizeMultiplier+1)); + nextLatency = nextBufferSize * (bufferCount-1); + } + + if( nextLatency < requestedLatency ) + ++sizeMultiplier; + } + + /* increase number of buffers until requestedLatency is reached */ + + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + while( latency < requestedLatency ) + { + ++bufferCount; + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + } + } + + *hostBufferSize = baseBufferSize * sizeMultiplier; + *hostBufferCount = bufferCount; +} + + +static void ReselectBufferCount( unsigned long bufferSize, + unsigned long requestedLatency, + unsigned long baseBufferCount, unsigned long minimumBufferCount, + unsigned long *hostBufferCount ) +{ + unsigned long bufferCount, latency; + unsigned long nextLatency; + + bufferCount = baseBufferCount; + + /* count-1 below because latency is always determined by one less + than the total number of buffers. + */ + latency = bufferSize * (bufferCount-1); + + if( latency > requestedLatency ) + { + /* reduce number of buffers without falling below suggested latency */ + + nextLatency = bufferSize * (bufferCount-2); + while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency ) + { + --bufferCount; + nextLatency = bufferSize * (bufferCount-2); + } + + }else if( latency < requestedLatency ){ + + /* increase number of buffers until requestedLatency is reached */ + + latency = bufferSize * (bufferCount-1); + while( latency < requestedLatency ) + { + ++bufferCount; + latency = bufferSize * (bufferCount-1); + } + } + + *hostBufferCount = bufferCount; +} + + +/* CalculateBufferSettings() fills the framesPerHostInputBuffer, hostInputBufferCount, + framesPerHostOutputBuffer and hostOutputBufferCount parameters based on the values + of the other parameters. +*/ + +static PaError CalculateBufferSettings( + unsigned long *framesPerHostInputBuffer, unsigned long *hostInputBufferCount, + unsigned long *framesPerHostOutputBuffer, unsigned long *hostOutputBufferCount, + int inputChannelCount, PaSampleFormat hostInputSampleFormat, + PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo, + int outputChannelCount, PaSampleFormat hostOutputSampleFormat, + PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo, + double sampleRate, unsigned long framesPerBuffer ) +{ + PaError result = paNoError; + int effectiveInputChannelCount, effectiveOutputChannelCount; + int hostInputFrameSize = 0; + unsigned int i; + + if( inputChannelCount > 0 ) + { + int hostInputSampleSize = Pa_GetSampleSize( hostInputSampleFormat ); + if( hostInputSampleSize < 0 ) + { + result = hostInputSampleSize; + goto error; + } + + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseMultipleDevices ) ) + { + /* set effectiveInputChannelCount to the largest number of + channels on any one device. + */ + effectiveInputChannelCount = 0; + for( i=0; i< inputStreamInfo->deviceCount; ++i ) + { + if( inputStreamInfo->devices[i].channelCount > effectiveInputChannelCount ) + effectiveInputChannelCount = inputStreamInfo->devices[i].channelCount; + } + } + else + { + effectiveInputChannelCount = inputChannelCount; + } + + hostInputFrameSize = hostInputSampleSize * effectiveInputChannelCount; + + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + if( inputStreamInfo->bufferCount <= 0 + || inputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostInputBuffer = inputStreamInfo->framesPerBuffer; + *hostInputBufferCount = inputStreamInfo->bufferCount; + } + else + { + unsigned long hostBufferSizeBytes, hostBufferCount; + unsigned long minimumBufferCount = (outputChannelCount > 0) + ? PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ + : PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_; + + unsigned long maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostInputFrameSize); + if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ ) + maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_; + + /* compute the following in bytes, then convert back to frames */ + + SelectBufferSizeAndCount( + ((framesPerBuffer == paFramesPerBufferUnspecified) + ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ + : framesPerBuffer ) * hostInputFrameSize, /* baseBufferSize */ + ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, maximumBufferSize, + &hostBufferSizeBytes, &hostBufferCount ); + + *framesPerHostInputBuffer = hostBufferSizeBytes / hostInputFrameSize; + *hostInputBufferCount = hostBufferCount; + } + } + else + { + *framesPerHostInputBuffer = 0; + *hostInputBufferCount = 0; + } + + if( outputChannelCount > 0 ) + { + if( outputStreamInfo + && ( outputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + if( outputStreamInfo->bufferCount <= 0 + || outputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostOutputBuffer = outputStreamInfo->framesPerBuffer; + *hostOutputBufferCount = outputStreamInfo->bufferCount; + + + if( inputChannelCount > 0 ) /* full duplex */ + { + if( *framesPerHostInputBuffer != *framesPerHostOutputBuffer ) + { + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + /* a custom StreamInfo was used for specifying both input + and output buffer sizes, the larger buffer size + must be a multiple of the smaller buffer size */ + + if( *framesPerHostInputBuffer < *framesPerHostOutputBuffer ) + { + if( *framesPerHostOutputBuffer % *framesPerHostInputBuffer != 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + } + else + { + assert( *framesPerHostInputBuffer > *framesPerHostOutputBuffer ); + if( *framesPerHostInputBuffer % *framesPerHostOutputBuffer != 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + } + } + else + { + /* a custom StreamInfo was not used for specifying the input buffer size, + so use the output buffer size, and approximately the same latency. */ + + *framesPerHostInputBuffer = *framesPerHostOutputBuffer; + *hostInputBufferCount = (((unsigned long)(suggestedInputLatency * sampleRate)) / *framesPerHostInputBuffer) + 1; + + if( *hostInputBufferCount < PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ ) + *hostInputBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_; + } + } + } + } + else + { + unsigned long hostBufferSizeBytes, hostBufferCount; + unsigned long minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_; + unsigned long maximumBufferSize; + int hostOutputFrameSize; + int hostOutputSampleSize; + + hostOutputSampleSize = Pa_GetSampleSize( hostOutputSampleFormat ); + if( hostOutputSampleSize < 0 ) + { + result = hostOutputSampleSize; + goto error; + } + + if( outputStreamInfo + && ( outputStreamInfo->flags & paWinMmeUseMultipleDevices ) ) + { + /* set effectiveOutputChannelCount to the largest number of + channels on any one device. + */ + effectiveOutputChannelCount = 0; + for( i=0; i< outputStreamInfo->deviceCount; ++i ) + { + if( outputStreamInfo->devices[i].channelCount > effectiveOutputChannelCount ) + effectiveOutputChannelCount = outputStreamInfo->devices[i].channelCount; + } + } + else + { + effectiveOutputChannelCount = outputChannelCount; + } + + hostOutputFrameSize = hostOutputSampleSize * effectiveOutputChannelCount; + + maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostOutputFrameSize); + if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ ) + maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_; + + + /* compute the following in bytes, then convert back to frames */ + + SelectBufferSizeAndCount( + ((framesPerBuffer == paFramesPerBufferUnspecified) + ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ + : framesPerBuffer ) * hostOutputFrameSize, /* baseBufferSize */ + ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + maximumBufferSize, + &hostBufferSizeBytes, &hostBufferCount ); + + *framesPerHostOutputBuffer = hostBufferSizeBytes / hostOutputFrameSize; + *hostOutputBufferCount = hostBufferCount; + + + if( inputChannelCount > 0 ) + { + /* ensure that both input and output buffer sizes are the same. + if they don't match at this stage, choose the smallest one + and use that for input and output + */ + + if( *framesPerHostOutputBuffer != *framesPerHostInputBuffer ) + { + if( framesPerHostInputBuffer < framesPerHostOutputBuffer ) + { + unsigned long framesPerHostBuffer = *framesPerHostInputBuffer; + + minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_; + ReselectBufferCount( + framesPerHostBuffer * hostOutputFrameSize, /* bufferSize */ + ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + &hostBufferCount ); + + *framesPerHostOutputBuffer = framesPerHostBuffer; + *hostOutputBufferCount = hostBufferCount; + } + else + { + unsigned long framesPerHostBuffer = *framesPerHostOutputBuffer; + + minimumBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_; + ReselectBufferCount( + framesPerHostBuffer * hostInputFrameSize, /* bufferSize */ + ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + &hostBufferCount ); + + *framesPerHostInputBuffer = framesPerHostBuffer; + *hostInputBufferCount = hostBufferCount; + } + } + } + } + } + else + { + *framesPerHostOutputBuffer = 0; + *hostOutputBufferCount = 0; + } + +error: + return result; +} + + +typedef struct +{ + HANDLE bufferEvent; + void *waveHandles; + unsigned int deviceCount; + /* unsigned int channelCount; */ + WAVEHDR **waveHeaders; /* waveHeaders[device][buffer] */ + unsigned int bufferCount; + unsigned int currentBufferIndex; + unsigned int framesPerBuffer; + unsigned int framesUsedInCurrentBuffer; +}PaWinMmeSingleDirectionHandlesAndBuffers; + +/* prototypes for functions operating on PaWinMmeSingleDirectionHandlesAndBuffers */ + +static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ); +static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long bytesPerHostSample, + double sampleRate, PaWinMmeDeviceAndChannelCount *devices, + unsigned int deviceCount, int isInput ); +static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); +static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long hostBufferCount, + PaSampleFormat hostSampleFormat, + unsigned long framesPerHostBuffer, + PaWinMmeDeviceAndChannelCount *devices, + int isInput ); +static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput ); + + +static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + handlesAndBuffers->bufferEvent = 0; + handlesAndBuffers->waveHandles = 0; + handlesAndBuffers->deviceCount = 0; + handlesAndBuffers->waveHeaders = 0; + handlesAndBuffers->bufferCount = 0; +} + +static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long bytesPerHostSample, + double sampleRate, PaWinMmeDeviceAndChannelCount *devices, + unsigned int deviceCount, int isInput ) +{ + PaError result; + MMRESULT mmresult; + unsigned long bytesPerFrame; + WAVEFORMATEX wfx; + signed int i; + + /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() + has already been called to zero some fields */ + + result = CreateEventWithPaError( &handlesAndBuffers->bufferEvent, NULL, FALSE, FALSE, NULL ); + if( result != paNoError ) goto error; + + if( isInput ) + handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEIN) * deviceCount ); + else + handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEOUT) * deviceCount ); + if( !handlesAndBuffers->waveHandles ) + { + result = paInsufficientMemory; + goto error; + } + + handlesAndBuffers->deviceCount = deviceCount; + + for( i = 0; i < (signed int)deviceCount; ++i ) + { + if( isInput ) + ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] = 0; + else + ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0; + } + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = (DWORD) sampleRate; + wfx.cbSize = 0; + + for( i = 0; i < (signed int)deviceCount; ++i ) + { + UINT winMmeDeviceId; + + winMmeDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, devices[i].device ); + wfx.nChannels = (WORD)devices[i].channelCount; + + bytesPerFrame = wfx.nChannels * bytesPerHostSample; + + wfx.nAvgBytesPerSec = (DWORD)(bytesPerFrame * sampleRate); + wfx.nBlockAlign = (WORD)bytesPerFrame; + wfx.wBitsPerSample = (WORD)((bytesPerFrame/wfx.nChannels) * 8); + + /* REVIEW: consider not firing an event for input when a full duplex + stream is being used. this would probably depend on the + neverDropInput flag. */ + + if( isInput ) + mmresult = waveInOpen( &((HWAVEIN*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, &wfx, + (DWORD)handlesAndBuffers->bufferEvent, (DWORD)0, CALLBACK_EVENT ); + else + mmresult = waveOutOpen( &((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, &wfx, + (DWORD)handlesAndBuffers->bufferEvent, (DWORD)0, CALLBACK_EVENT ); + + if( mmresult != MMSYSERR_NOERROR ) + { + switch( mmresult ) + { + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + result = paInsufficientMemory; + break; + + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + /* falls through */ + default: + result = paUnanticipatedHostError; + if( isInput ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + else + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + goto error; + } + } + + return result; + +error: + TerminateWaveHandles( handlesAndBuffers, isInput, 1 /* currentlyProcessingAnError */ ); + + return result; +} + + +static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ) +{ + PaError result = paNoError; + MMRESULT mmresult; + signed int i; + + if( handlesAndBuffers->waveHandles ) + { + for( i = handlesAndBuffers->deviceCount-1; i >= 0; --i ) + { + if( isInput ) + { + if( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] ) + mmresult = waveInClose( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] ); + } + else + { + if( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] ) + mmresult = waveOutClose( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] ); + } + + if( mmresult != MMSYSERR_NOERROR && + !currentlyProcessingAnError ) /* don't update the error state if we're already processing an error */ + { + result = paUnanticipatedHostError; + if( isInput ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + else + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + /* note that we don't break here, we try to continue closing devices */ + } + } + + PaUtil_FreeMemory( handlesAndBuffers->waveHandles ); + handlesAndBuffers->waveHandles = 0; + } + + if( handlesAndBuffers->bufferEvent ) + { + result = CloseHandleWithPaError( handlesAndBuffers->bufferEvent ); + handlesAndBuffers->bufferEvent = 0; + } + + return result; +} + + +static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long hostBufferCount, + PaSampleFormat hostSampleFormat, + unsigned long framesPerHostBuffer, + PaWinMmeDeviceAndChannelCount *devices, + int isInput ) +{ + PaError result = paNoError; + MMRESULT mmresult; + WAVEHDR *deviceWaveHeaders; + signed int i, j; + + /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() + has already been called to zero some fields */ + + + /* allocate an array of pointers to arrays of wave headers, one array of + wave headers per device */ + handlesAndBuffers->waveHeaders = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * handlesAndBuffers->deviceCount ); + if( !handlesAndBuffers->waveHeaders ) + { + result = paInsufficientMemory; + goto error; + } + + for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i ) + handlesAndBuffers->waveHeaders[i] = 0; + + handlesAndBuffers->bufferCount = hostBufferCount; + + for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i ) + { + int bufferBytes = Pa_GetSampleSize( hostSampleFormat ) * + framesPerHostBuffer * devices[i].channelCount; + if( bufferBytes < 0 ) + { + result = paInternalError; + goto error; + } + + /* Allocate an array of wave headers for device i */ + deviceWaveHeaders = (WAVEHDR *) PaUtil_AllocateMemory( sizeof(WAVEHDR)*hostBufferCount ); + if( !deviceWaveHeaders ) + { + result = paInsufficientMemory; + goto error; + } + + for( j=0; j < (signed int)hostBufferCount; ++j ) + deviceWaveHeaders[j].lpData = 0; + + handlesAndBuffers->waveHeaders[i] = deviceWaveHeaders; + + /* Allocate a buffer for each wave header */ + for( j=0; j < (signed int)hostBufferCount; ++j ) + { + deviceWaveHeaders[j].lpData = (char *)PaUtil_AllocateMemory( bufferBytes ); + if( !deviceWaveHeaders[j].lpData ) + { + result = paInsufficientMemory; + goto error; + } + deviceWaveHeaders[j].dwBufferLength = bufferBytes; + deviceWaveHeaders[j].dwUser = 0xFFFFFFFF; /* indicates that *PrepareHeader() has not yet been called, for error clean up code */ + + if( isInput ) + { + mmresult = waveInPrepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + else /* output */ + { + mmresult = waveOutPrepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + deviceWaveHeaders[j].dwUser = devices[i].channelCount; + } + } + + return result; + +error: + TerminateWaveHeaders( handlesAndBuffers, isInput ); + + return result; +} + + +static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput ) +{ + signed int i, j; + WAVEHDR *deviceWaveHeaders; + + if( handlesAndBuffers->waveHeaders ) + { + for( i = handlesAndBuffers->deviceCount-1; i >= 0 ; --i ) + { + deviceWaveHeaders = handlesAndBuffers->waveHeaders[i]; /* wave headers for device i */ + if( deviceWaveHeaders ) + { + for( j = handlesAndBuffers->bufferCount-1; j >= 0; --j ) + { + if( deviceWaveHeaders[j].lpData ) + { + if( deviceWaveHeaders[j].dwUser != 0xFFFFFFFF ) + { + if( isInput ) + waveInUnprepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + else + waveOutUnprepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + } + + PaUtil_FreeMemory( deviceWaveHeaders[j].lpData ); + } + } + + PaUtil_FreeMemory( deviceWaveHeaders ); + } + } + + PaUtil_FreeMemory( handlesAndBuffers->waveHeaders ); + handlesAndBuffers->waveHeaders = 0; + } +} + + + +/* PaWinMmeStream - a stream data structure specifically for this implementation */ +/* note that struct PaWinMmeStream is typedeffed to PaWinMmeStream above. */ +struct PaWinMmeStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + int primeStreamUsingCallback; + + PaWinMmeSingleDirectionHandlesAndBuffers input; + PaWinMmeSingleDirectionHandlesAndBuffers output; + + /* Processing thread management -------------- */ + HANDLE abortEvent; + HANDLE processingThread; + DWORD processingThreadId; + + char throttleProcessingThreadOnOverload; /* 0 -> don't throtte, non-0 -> throttle */ + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + unsigned long throttledSleepMsecs; + + int isStopped; + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ + + DWORD allBuffersDurationMs; /* used to calculate timeouts */ +}; + +/* updates deviceCount if PaWinMmeUseMultipleDevices is used */ + +static PaError ValidateWinMmeSpecificStreamInfo( + const PaStreamParameters *streamParameters, + const PaWinMmeStreamInfo *streamInfo, + char *throttleProcessingThreadOnOverload, + unsigned long *deviceCount ) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaWinMmeStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( streamInfo->flags & paWinMmeDontThrottleOverloadedProcessingThread ) + *throttleProcessingThreadOnOverload = 0; + + if( streamInfo->flags & paWinMmeUseMultipleDevices ) + { + if( streamParameters->device != paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + *deviceCount = streamInfo->deviceCount; + } + } + + return paNoError; +} + +static PaError RetrieveDevicesFromStreamParameters( + struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *streamParameters, + const PaWinMmeStreamInfo *streamInfo, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + PaError result = paNoError; + unsigned int i; + int totalChannelCount; + PaDeviceIndex hostApiDevice; + + if( streamInfo && streamInfo->flags & paWinMmeUseMultipleDevices ) + { + totalChannelCount = 0; + for( i=0; i < deviceCount; ++i ) + { + /* validate that the device number is within range */ + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + streamInfo->devices[i].device, hostApi ); + if( result != paNoError ) + return result; + + devices[i].device = hostApiDevice; + devices[i].channelCount = streamInfo->devices[i].channelCount; + + totalChannelCount += devices[i].channelCount; + } + + if( totalChannelCount != streamParameters->channelCount ) + { + /* channelCount must match total channels specified by multiple devices */ + return paInvalidChannelCount; /* REVIEW use of this error code */ + } + } + else + { + devices[0].device = streamParameters->device; + devices[0].channelCount = streamParameters->channelCount; + } + + return result; +} + +static PaError ValidateInputChannelCounts( + struct PaUtilHostApiRepresentation *hostApi, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( devices[i].channelCount < 1 || devices[i].channelCount + > hostApi->deviceInfos[ devices[i].device ]->maxInputChannels ) + return paInvalidChannelCount; + } + + return paNoError; +} + +static PaError ValidateOutputChannelCounts( + struct PaUtilHostApiRepresentation *hostApi, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( devices[i].channelCount < 1 || devices[i].channelCount + > hostApi->deviceInfos[ devices[i].device ]->maxOutputChannels ) + return paInvalidChannelCount; + } + + return paNoError; +} + + +/* the following macros are intended to improve the readability of the following code */ +#define PA_IS_INPUT_STREAM_( stream ) ( stream ->input.waveHandles ) +#define PA_IS_OUTPUT_STREAM_( stream ) ( stream ->output.waveHandles ) +#define PA_IS_FULL_DUPLEX_STREAM_( stream ) ( stream ->input.waveHandles && stream ->output.waveHandles ) +#define PA_IS_HALF_DUPLEX_STREAM_( stream ) ( !(stream ->input.waveHandles && stream ->output.waveHandles) ) + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaWinMmeStream *stream = 0; + int bufferProcessorIsInitialized = 0; + int streamRepresentationIsInitialized = 0; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + double suggestedInputLatency, suggestedOutputLatency; + PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + unsigned long framesPerHostInputBuffer; + unsigned long hostInputBufferCount; + unsigned long framesPerHostOutputBuffer; + unsigned long hostOutputBufferCount; + unsigned long framesPerBufferProcessorCall; + PaWinMmeDeviceAndChannelCount *inputDevices = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ + unsigned long inputDeviceCount = 0; + PaWinMmeDeviceAndChannelCount *outputDevices = 0; + unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ + char throttleProcessingThreadOnOverload = 1; + + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatency = inputParameters->suggestedLatency; + + inputDeviceCount = 1; + + /* validate input hostApiSpecificStreamInfo */ + inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + result = ValidateWinMmeSpecificStreamInfo( inputParameters, inputStreamInfo, + &throttleProcessingThreadOnOverload, + &inputDeviceCount ); + if( result != paNoError ) return result; + + inputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * inputDeviceCount ); + if( !inputDevices ) return paInsufficientMemory; + + result = RetrieveDevicesFromStreamParameters( hostApi, inputParameters, inputStreamInfo, inputDevices, inputDeviceCount ); + if( result != paNoError ) return result; + + result = ValidateInputChannelCounts( hostApi, inputDevices, inputDeviceCount ); + if( result != paNoError ) return result; + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + } + else + { + inputChannelCount = 0; + inputSampleFormat = 0; + suggestedInputLatency = 0.; + inputStreamInfo = 0; + hostInputSampleFormat = 0; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatency = outputParameters->suggestedLatency; + + outputDeviceCount = 1; + + /* validate output hostApiSpecificStreamInfo */ + outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + result = ValidateWinMmeSpecificStreamInfo( outputParameters, outputStreamInfo, + &throttleProcessingThreadOnOverload, + &outputDeviceCount ); + if( result != paNoError ) return result; + + outputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * outputDeviceCount ); + if( !outputDevices ) return paInsufficientMemory; + + result = RetrieveDevicesFromStreamParameters( hostApi, outputParameters, outputStreamInfo, outputDevices, outputDeviceCount ); + if( result != paNoError ) return result; + + result = ValidateOutputChannelCounts( hostApi, outputDevices, outputDeviceCount ); + if( result != paNoError ) return result; + + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + } + else + { + outputChannelCount = 0; + outputSampleFormat = 0; + outputStreamInfo = 0; + hostOutputSampleFormat = 0; + suggestedOutputLatency = 0.; + } + + + /* + IMPLEMENT ME: + - alter sampleRate to a close allowable rate if possible / necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + result = CalculateBufferSettings( &framesPerHostInputBuffer, &hostInputBufferCount, + &framesPerHostOutputBuffer, &hostOutputBufferCount, + inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo, + outputChannelCount, hostOutputSampleFormat, suggestedOutputLatency, outputStreamInfo, + sampleRate, framesPerBuffer ); + if( result != paNoError ) goto error; + + + stream = (PaWinMmeStream*)PaUtil_AllocateMemory( sizeof(PaWinMmeStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + InitializeSingleDirectionHandlesAndBuffers( &stream->input ); + InitializeSingleDirectionHandlesAndBuffers( &stream->output ); + + stream->abortEvent = 0; + stream->processingThread = 0; + + stream->throttleProcessingThreadOnOverload = throttleProcessingThreadOnOverload; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + ( (streamCallback) + ? &winMmeHostApi->callbackStreamInterface + : &winMmeHostApi->blockingStreamInterface ), + streamCallback, userData ); + streamRepresentationIsInitialized = 1; + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters && outputParameters ) /* full duplex */ + { + if( framesPerHostInputBuffer < framesPerHostOutputBuffer ) + { + assert( (framesPerHostOutputBuffer % framesPerHostInputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */ + + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else + { + assert( (framesPerHostInputBuffer % framesPerHostOutputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */ + + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + } + else if( inputParameters ) + { + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else if( outputParameters ) + { + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + + stream->input.framesPerBuffer = framesPerHostInputBuffer; + stream->output.framesPerBuffer = framesPerHostOutputBuffer; + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBufferProcessorCall, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) goto error; + + bufferProcessorIsInitialized = 1; + + stream->streamRepresentation.streamInfo.inputLatency = + (double)(PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) + +(framesPerHostInputBuffer * (hostInputBufferCount-1))) / sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = + (double)(PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) + +(framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->primeStreamUsingCallback = ( (streamFlags&paPrimeOutputBuffersUsingStreamCallback) && streamCallback ) ? 1 : 0; + + /* time to sleep when throttling due to >100% cpu usage. + -a quater of a buffer's duration */ + stream->throttledSleepMsecs = + (unsigned long)(stream->bufferProcessor.framesPerHostBuffer * + stream->bufferProcessor.samplePeriod * .25 * 1000); + + stream->isStopped = 1; + stream->isActive = 0; + + + /* for maximum compatibility with multi-device multichannel drivers, + we first open all devices, then we prepare all buffers, finally + we start all devices ( in StartStream() ). teardown in reverse order. + */ + + if( inputParameters ) + { + result = InitializeWaveHandles( winMmeHostApi, &stream->input, + stream->bufferProcessor.bytesPerHostInputSample, sampleRate, + inputDevices, inputDeviceCount, 1 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( outputParameters ) + { + result = InitializeWaveHandles( winMmeHostApi, &stream->output, + stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, + outputDevices, outputDeviceCount, 0 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( inputParameters ) + { + result = InitializeWaveHeaders( &stream->input, hostInputBufferCount, + hostInputSampleFormat, framesPerHostInputBuffer, inputDevices, 1 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( outputParameters ) + { + result = InitializeWaveHeaders( &stream->output, hostOutputBufferCount, + hostOutputSampleFormat, framesPerHostOutputBuffer, outputDevices, 0 /* not isInput */ ); + if( result != paNoError ) goto error; + + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostOutputBuffer * stream->output.bufferCount) / sampleRate); + } + else + { + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostInputBuffer * stream->input.bufferCount) / sampleRate); + } + + + if( streamCallback ) + { + /* abort event is only needed for callback streams */ + result = CreateEventWithPaError( &stream->abortEvent, NULL, TRUE, FALSE, NULL ); + if( result != paNoError ) goto error; + } + + *s = (PaStream*)stream; + + return result; + +error: + + if( stream ) + { + if( stream->abortEvent ) + CloseHandle( stream->abortEvent ); + + TerminateWaveHeaders( &stream->output, 0 /* not isInput */ ); + TerminateWaveHeaders( &stream->input, 1 /* isInput */ ); + + TerminateWaveHandles( &stream->output, 0 /* not isInput */, 1 /* currentlyProcessingAnError */ ); + TerminateWaveHandles( &stream->input, 1 /* isInput */, 1 /* currentlyProcessingAnError */ ); + + if( bufferProcessorIsInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + if( streamRepresentationIsInitialized ) + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaUtil_FreeMemory( stream ); + } + + return result; +} + + +/* return non-zero if all current buffers are done */ +static int BuffersAreDone( WAVEHDR **waveHeaders, unsigned int deviceCount, int bufferIndex ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( !(waveHeaders[i][ bufferIndex ].dwFlags & WHDR_DONE) ) + { + return 0; + } + } + + return 1; +} + +static int CurrentInputBuffersAreDone( PaWinMmeStream *stream ) +{ + return BuffersAreDone( stream->input.waveHeaders, stream->input.deviceCount, stream->input.currentBufferIndex ); +} + +static int CurrentOutputBuffersAreDone( PaWinMmeStream *stream ) +{ + return BuffersAreDone( stream->output.waveHeaders, stream->output.deviceCount, stream->output.currentBufferIndex ); +} + + +/* return non-zero if any buffers are queued */ +static int NoBuffersAreQueued( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + unsigned int i, j; + + if( handlesAndBuffers->waveHandles ) + { + for( i=0; i < handlesAndBuffers->bufferCount; ++i ) + { + for( j=0; j < handlesAndBuffers->deviceCount; ++j ) + { + if( !( handlesAndBuffers->waveHeaders[ j ][ i ].dwFlags & WHDR_DONE) ) + { + return 0; + } + } + } + } + + return 1; +} + + +#define PA_CIRCULAR_INCREMENT_( current, max )\ + ( (((current) + 1) >= (max)) ? (0) : (current+1) ) + +#define PA_CIRCULAR_DECREMENT_( current, max )\ + ( ((current) == 0) ? ((max)-1) : (current-1) ) + + +static signed long GetAvailableFrames( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + signed long result = 0; + unsigned int i; + + if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, handlesAndBuffers->currentBufferIndex ) ) + { + /* we could calculate the following in O(1) if we kept track of the + last done buffer */ + result = handlesAndBuffers->framesPerBuffer - handlesAndBuffers->framesUsedInCurrentBuffer; + + i = PA_CIRCULAR_INCREMENT_( handlesAndBuffers->currentBufferIndex, handlesAndBuffers->bufferCount ); + while( i != handlesAndBuffers->currentBufferIndex ) + { + if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, i ) ) + { + result += handlesAndBuffers->framesPerBuffer; + i = PA_CIRCULAR_INCREMENT_( i, handlesAndBuffers->bufferCount ); + } + else + break; + } + } + + return result; +} + + +static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[i], + &stream->input.waveHeaders[i][ stream->input.currentBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + + stream->input.currentBufferIndex = + PA_CIRCULAR_INCREMENT_( stream->input.currentBufferIndex, stream->input.bufferCount ); + + stream->input.framesUsedInCurrentBuffer = 0; + + return result; +} + + +static PaError AdvanceToNextOutputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[i], + &stream->output.waveHeaders[i][ stream->output.currentBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + + stream->output.currentBufferIndex = + PA_CIRCULAR_INCREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount ); + + stream->output.framesUsedInCurrentBuffer = 0; + + return result; +} + + +/* requeue all but the most recent input with the driver. Used for catching + up after a total input buffer underrun */ +static PaError CatchUpInputBuffers( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + unsigned int i; + + for( i=0; i < stream->input.bufferCount - 1; ++i ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + break; + } + + return result; +} + + +/* take the most recent output and duplicate it to all other output buffers + and requeue them. Used for catching up after a total output buffer underrun. +*/ +static PaError CatchUpOutputBuffers( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + unsigned int i, j; + unsigned int previousBufferIndex = + PA_CIRCULAR_DECREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount ); + + for( i=0; i < stream->output.bufferCount - 1; ++i ) + { + for( j=0; j < stream->output.deviceCount; ++j ) + { + if( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData + != stream->output.waveHeaders[j][ previousBufferIndex ].lpData ) + { + CopyMemory( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData, + stream->output.waveHeaders[j][ previousBufferIndex ].lpData, + stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].dwBufferLength ); + } + } + + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + break; + } + + return result; +} + + +static DWORD WINAPI ProcessingThreadProc( void *pArg ) +{ + PaWinMmeStream *stream = (PaWinMmeStream *)pArg; + HANDLE events[3]; + int eventCount = 0; + DWORD result = paNoError; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + int hostBuffersAvailable; + signed int hostInputBufferIndex, hostOutputBufferIndex; + PaStreamCallbackFlags statusFlags; + int callbackResult; + int done = 0; + unsigned int channel, i; + unsigned long framesProcessed; + + /* prepare event array for call to WaitForMultipleObjects() */ + if( stream->input.bufferEvent ) + events[eventCount++] = stream->input.bufferEvent; + if( stream->output.bufferEvent ) + events[eventCount++] = stream->output.bufferEvent; + events[eventCount++] = stream->abortEvent; + + statusFlags = 0; /** @todo support paInputUnderflow, paOutputOverflow and paNeverDropInput */ + + /* loop until something causes us to stop */ + do{ + /* wait for MME to signal that a buffer is available, or for + the PA abort event to be signaled. + + When this indicates that one or more buffers are available + NoBuffersAreQueued() and Current*BuffersAreDone are used below to + poll for additional done buffers. NoBuffersAreQueued() will fail + to identify an underrun/overflow if the driver doesn't mark all done + buffers prior to signalling the event. Some drivers do this + (eg RME Digi96, and others don't eg VIA PC 97 input). This isn't a + huge problem, it just means that we won't always be able to detect + underflow/overflow. + */ + waitResult = WaitForMultipleObjects( eventCount, events, FALSE /* wait all = FALSE */, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + /** @todo FIXME/REVIEW: can't return host error info from an asyncronous thread */ + done = 1; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue */ + } + + if( stream->abortProcessing ) + { + /* Pa_AbortStream() has been called, stop processing immediately */ + done = 1; + } + else if( stream->stopProcessing ) + { + /* Pa_StopStream() has been called or the user callback returned + non-zero, processing will continue until all output buffers + are marked as done. The stream will stop immediately if it + is input-only. + */ + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( NoBuffersAreQueued( &stream->output ) ) + done = 1; /* Will cause thread to return. */ + } + else + { + /* input only stream */ + done = 1; /* Will cause thread to return. */ + } + } + else + { + hostBuffersAvailable = 1; + + /* process all available host buffers */ + do + { + hostInputBufferIndex = -1; + hostOutputBufferIndex = -1; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + if( CurrentInputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo + if all of the other buffers are also ready then + we discard all but the most recent. This is an + input buffer overflow. FIXME: these buffers should + be passed to the callback in a paNeverDropInput + stream. + + note that it is also possible for an input overflow + to happen while the callback is processing a buffer. + that is handled further down. + */ + result = CatchUpInputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paInputOverflow; + } + + hostInputBufferIndex = stream->input.currentBufferIndex; + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( CurrentOutputBuffersAreDone( stream ) ) + { + /* ok, we have an output buffer */ + + if( NoBuffersAreQueued( &stream->output ) ) + { + /* + if all of the other buffers are also ready, catch up by copying + the most recently generated buffer into all but one of the output + buffers. + + note that this catch up code only handles the case where all + buffers have been played out due to this thread not having + woken up at all. a more common case occurs when this thread + is woken up, processes one buffer, but takes too long, and as + a result all the other buffers have become un-queued. that + case is handled further down. + */ + + result = CatchUpOutputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paOutputUnderflow; + } + + hostOutputBufferIndex = stream->output.currentBufferIndex; + } + } + + + if( (PA_IS_FULL_DUPLEX_STREAM_(stream) && hostInputBufferIndex != -1 && hostOutputBufferIndex != -1) || + (PA_IS_HALF_DUPLEX_STREAM_(stream) && ( hostInputBufferIndex != -1 || hostOutputBufferIndex != -1 ) ) ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */ + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + /* set timeInfo.currentTime and calculate timeInfo.outputBufferDacTime + from the current wave out position */ + MMTIME mmtime; + double timeBeforeGetPosition, timeAfterGetPosition; + double time; + long framesInBufferRing; + long writePosition; + long playbackPosition; + HWAVEOUT firstWaveOutDevice = ((HWAVEOUT*)stream->output.waveHandles)[0]; + + mmtime.wType = TIME_SAMPLES; + timeBeforeGetPosition = PaUtil_GetTime(); + waveOutGetPosition( firstWaveOutDevice, &mmtime, sizeof(MMTIME) ); + timeAfterGetPosition = PaUtil_GetTime(); + + timeInfo.currentTime = timeAfterGetPosition; + + /* approximate time at which wave out position was measured + as half way between timeBeforeGetPosition and timeAfterGetPosition */ + time = timeBeforeGetPosition + (timeAfterGetPosition - timeBeforeGetPosition) * .5; + + framesInBufferRing = stream->output.bufferCount * stream->bufferProcessor.framesPerHostBuffer; + playbackPosition = mmtime.u.sample % framesInBufferRing; + + writePosition = stream->output.currentBufferIndex * stream->bufferProcessor.framesPerHostBuffer + + stream->output.framesUsedInCurrentBuffer; + + if( playbackPosition >= writePosition ){ + timeInfo.outputBufferDacTime = + time + ((double)( writePosition + (framesInBufferRing - playbackPosition) ) * stream->bufferProcessor.samplePeriod ); + }else{ + timeInfo.outputBufferDacTime = + time + ((double)( writePosition - playbackPosition ) * stream->bufferProcessor.samplePeriod ); + } + } + + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, statusFlags ); + + /* reset status flags once they have been passed to the buffer processor */ + statusFlags = 0; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; iinput.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, + stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + + stream->input.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostInputSample, + channelCount ); + + + channel += channelCount; + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; ioutput.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + } + + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + + stream->input.framesUsedInCurrentBuffer += framesProcessed; + stream->output.framesUsedInCurrentBuffer += framesProcessed; + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + stream->abortProcessing = 1; + done = 1; + /** @todo FIXME: should probably reset the output device immediately once the callback returns paAbort */ + result = paNoError; + } + else + { + /* User callback has asked us to stop with paComplete or other non-zero value */ + stream->stopProcessing = 1; /* stop once currently queued audio has finished */ + result = paNoError; + } + + + if( PA_IS_INPUT_STREAM_(stream) + && stream->stopProcessing == 0 && stream->abortProcessing == 0 + && stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo need to handle PaNeverDropInput here where necessary */ + result = CatchUpInputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paInputOverflow; + } + + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + + + if( PA_IS_OUTPUT_STREAM_(stream) && !stream->abortProcessing ) + { + if( stream->stopProcessing && + stream->output.framesUsedInCurrentBuffer < stream->output.framesPerBuffer ) + { + /* zero remaining samples in output output buffer and flush */ + + stream->output.framesUsedInCurrentBuffer += PaUtil_ZeroOutput( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + /* we send the entire buffer to the output devices, but we could + just send a partial buffer, rather than zeroing the unused + samples. + */ + } + + if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer ) + { + /* check for underflow before enquing the just-generated buffer, + but recover from underflow after enquing it. This ensures + that the most recent audio segment is repeated */ + int outputUnderflow = NoBuffersAreQueued( &stream->output ); + + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + done = 1; + + if( outputUnderflow && !done && !stream->stopProcessing ) + { + /* Recover from underflow in the case where the + underflow occured while processing the buffer + we just finished */ + + result = CatchUpOutputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paOutputUnderflow; + } + } + } + + if( stream->throttleProcessingThreadOnOverload != 0 ) + { + if( stream->stopProcessing || stream->abortProcessing ) + { + if( stream->processingThreadPriority != stream->highThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->highThreadPriority ); + stream->processingThreadPriority = stream->highThreadPriority; + } + } + else if( PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) > 1. ) + { + if( stream->processingThreadPriority != stream->throttledThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->throttledThreadPriority ); + stream->processingThreadPriority = stream->throttledThreadPriority; + } + + /* sleep to give other processes a go */ + Sleep( stream->throttledSleepMsecs ); + } + else + { + if( stream->processingThreadPriority != stream->highThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->highThreadPriority ); + stream->processingThreadPriority = stream->highThreadPriority; + } + } + } + } + else + { + hostBuffersAvailable = 0; + } + } + while( hostBuffersAvailable && + stream->stopProcessing == 0 && + stream->abortProcessing == 0 && + !done ); + } + } + while( !done ); + + stream->isActive = 0; + + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + result = CloseHandleWithPaError( stream->abortEvent ); + if( result != paNoError ) goto error; + + TerminateWaveHeaders( &stream->output, 0 /* not isInput */ ); + TerminateWaveHeaders( &stream->input, 1 /* isInput */ ); + + TerminateWaveHandles( &stream->output, 0 /* not isInput */, 0 /* not currentlyProcessingAnError */ ); + TerminateWaveHandles( &stream->input, 1 /* isInput */, 0 /* not currentlyProcessingAnError */ ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + +error: + /** @todo REVIEW: what is the best way to clean up a stream if an error is detected? */ + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + MMRESULT mmresult; + unsigned int i, j; + int callbackResult; + unsigned int channel; + unsigned long framesProcessed; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement this for stream priming */ + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; iinput.bufferCount; ++i ) + { + for( j=0; jinput.deviceCount; ++j ) + { + mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[j], &stream->input.waveHeaders[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + stream->input.currentBufferIndex = 0; + stream->input.framesUsedInCurrentBuffer = 0; + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; ioutput.deviceCount; ++i ) + { + if( (mmresult = waveOutPause( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + + for( i=0; ioutput.bufferCount; ++i ) + { + if( stream->primeStreamUsingCallback ) + { + + stream->output.framesUsedInCurrentBuffer = 0; + do{ + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, + &timeInfo, + paPrimingOutput | ((stream->input.bufferCount > 0 ) ? paInputUnderflow : 0)); + + if( stream->input.bufferCount > 0 ) + PaUtil_SetNoInput( &stream->bufferProcessor ); + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( j=0; joutput.deviceCount; ++j ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[j][i].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[j][i].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + /* we have stored the number of channels in the buffer in dwUser */ + channel += channelCount; + } + + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + stream->output.framesUsedInCurrentBuffer += framesProcessed; + + if( callbackResult != paContinue ) + { + /** @todo fix this, what do we do if callback result is non-zero during stream + priming? + + for complete: play out primed waveHeaders as usual + for abort: clean up immediately. + */ + } + + }while( stream->output.framesUsedInCurrentBuffer != stream->output.framesPerBuffer ); + + } + else + { + for( j=0; joutput.deviceCount; ++j ) + { + ZeroMemory( stream->output.waveHeaders[j][i].lpData, stream->output.waveHeaders[j][i].dwBufferLength ); + } + } + + /* we queue all channels of a single buffer frame (accross all + devices, because some multidevice multichannel drivers work + better this way */ + for( j=0; joutput.deviceCount; ++j ) + { + mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[j], &stream->output.waveHeaders[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + stream->output.currentBufferIndex = 0; + stream->output.framesUsedInCurrentBuffer = 0; + } + + + stream->isStopped = 0; + stream->isActive = 1; + stream->stopProcessing = 0; + stream->abortProcessing = 0; + + result = ResetEventWithPaError( stream->input.bufferEvent ); + if( result != paNoError ) goto error; + + result = ResetEventWithPaError( stream->output.bufferEvent ); + if( result != paNoError ) goto error; + + + if( stream->streamRepresentation.streamCallback ) + { + /* callback stream */ + + result = ResetEventWithPaError( stream->abortEvent ); + if( result != paNoError ) goto error; + + /* Create thread that waits for audio buffers to be ready for processing. */ + stream->processingThread = CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ); + if( !stream->processingThread ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + /** @todo could have mme specific stream parameters to allow the user + to set the callback thread priorities */ + stream->highThreadPriority = THREAD_PRIORITY_TIME_CRITICAL; + stream->throttledThreadPriority = THREAD_PRIORITY_NORMAL; + + if( !SetThreadPriority( stream->processingThread, stream->highThreadPriority ) ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + stream->processingThreadPriority = stream->highThreadPriority; + } + else + { + /* blocking read/write stream */ + + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInStart( ((HWAVEIN*)stream->input.waveHandles)[i] ); + PA_DEBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult)); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; i < stream->output.deviceCount; ++i ) + { + if( (mmresult = waveOutRestart( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + + return result; + +error: + /** @todo FIXME: implement recovery as best we can + This should involve rolling back to a state as-if this function had never been called + */ + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + signed int hostOutputBufferIndex; + unsigned int channel, waitCount, i; + + /** @todo + REVIEW: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + if( stream->processingThread ) + { + /* callback stream */ + + /* Tell processing thread to stop generating more data and to let current data play out. */ + stream->stopProcessing = 1; + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = (int)(stream->allBuffersDurationMs * 1.5); + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + /* try to abort */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n")); + result = paTimedOut; + } + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + } + else + { + /* blocking read / write stream */ + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( stream->output.framesUsedInCurrentBuffer > 0 ) + { + /* there are still unqueued frames in the current buffer, so flush them */ + + hostOutputBufferIndex = stream->output.currentBufferIndex; + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; ioutput.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + + PaUtil_ZeroOutput( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + /* we send the entire buffer to the output devices, but we could + just send a partial buffer, rather than zeroing the unused + samples. + */ + AdvanceToNextOutputBuffer( stream ); + } + + + timeout = (stream->allBuffersDurationMs / stream->output.bufferCount) + 1; + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + waitCount = 0; + while( !NoBuffersAreQueued( &stream->output ) && waitCount <= stream->output.bufferCount ) + { + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* keep waiting */ + } + + ++waitCount; + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + } + + stream->isStopped = 1; + stream->isActive = 0; + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + unsigned int i; + + /** @todo + REVIEW: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + if( stream->processingThread ) + { + /* callback stream */ + + /* Tell processing thread to abort immediately */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + } + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + + if( stream->processingThread ) + { + /* callback stream */ + + PA_DEBUG(("WinMME AbortStream: waiting for background thread.\n")); + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = (int)(stream->allBuffersDurationMs * 1.5); + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME AbortStream: timed out while waiting for background thread to finish.\n")); + return paTimedOut; + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + } + + stream->isStopped = 1; + stream->isActive = 0; + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return stream->isStopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + (void) s; /* unused parameter */ + + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + void *userBuffer; + unsigned long framesRead = 0; + unsigned long framesProcessed; + signed int hostInputBufferIndex; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + unsigned int channel, i; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + /* make a local copy of the user buffer pointer(s). this is necessary + because PaUtil_CopyInput() advances these pointers every time + it is called. + */ + if( stream->bufferProcessor.userInputIsInterleaved ) + { + userBuffer = buffer; + } + else + { + userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount ); + if( !userBuffer ) + return paInsufficientMemory; + for( i = 0; ibufferProcessor.inputChannelCount; ++i ) + ((void**)userBuffer)[i] = ((void**)buffer)[i]; + } + + do{ + if( CurrentInputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo REVIEW: consider what to do if the input overflows. + do we requeue all of the buffers? should we be running + a thread to make sure they are always queued? */ + + result = paInputOverflowed; + } + + hostInputBufferIndex = stream->input.currentBufferIndex; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, + stream->input.framesPerBuffer - stream->input.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; iinput.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, + stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + + stream->input.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostInputSample, + channelCount ); + + channel += channelCount; + } + + framesProcessed = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, frames - framesRead ); + + stream->input.framesUsedInCurrentBuffer += framesProcessed; + if( stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + break; + } + + framesRead += framesProcessed; + + }else{ + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->input.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue, + perhaps we should give up eventually + */ + } + } + }while( framesRead < frames ); + } + else + { + result = paCanNotReadFromAnOutputOnlyStream; + } + + return result; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + const void *userBuffer; + unsigned long framesWritten = 0; + unsigned long framesProcessed; + signed int hostOutputBufferIndex; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + unsigned int channel, i; + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + /* make a local copy of the user buffer pointer(s). this is necessary + because PaUtil_CopyOutput() advances these pointers every time + it is called. + */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + { + userBuffer = buffer; + } + else + { + userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount ); + if( !userBuffer ) + return paInsufficientMemory; + for( i = 0; ibufferProcessor.outputChannelCount; ++i ) + ((const void**)userBuffer)[i] = ((const void**)buffer)[i]; + } + + do{ + if( CurrentOutputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->output ) ) + { + /** @todo REVIEW: consider what to do if the output + underflows. do we requeue all the existing buffers with + zeros? should we run a separate thread to keep the buffers + enqueued at all times? */ + + result = paOutputUnderflowed; + } + + hostOutputBufferIndex = stream->output.currentBufferIndex; + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; ioutput.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + + framesProcessed = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames - framesWritten ); + + stream->output.framesUsedInCurrentBuffer += framesProcessed; + if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer ) + { + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + break; + } + + framesWritten += framesProcessed; + } + else + { + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue, + perhaps we should give up eventually + */ + } + } + }while( framesWritten < frames ); + } + else + { + result = paCanNotWriteToAnInputOnlyStream; + } + + return result; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + if( PA_IS_INPUT_STREAM_(stream) ) + return GetAvailableFrames( &stream->input ); + else + return paCanNotReadFromAnOutputOnlyStream; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + if( PA_IS_OUTPUT_STREAM_(stream) ) + return GetAvailableFrames( &stream->output ); + else + return paCanNotWriteToAnInputOnlyStream; +} + + +/* NOTE: the following functions are MME-stream specific, and are called directly + by client code. We need to check for many more error conditions here because + we don't have the benefit of pa_front.c's parameter checking. +*/ + +static PaError GetWinMMEStreamPointer( PaWinMmeStream **stream, PaStream *s ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaWinMmeHostApiRepresentation *winMmeHostApi; + + result = PaUtil_ValidateStreamPointer( s ); + if( result != paNoError ) + return result; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paMME ); + if( result != paNoError ) + return result; + + winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + /* note, the following would be easier if there was a generic way of testing + that a stream belongs to a specific host API */ + + if( PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->blockingStreamInterface ) + { + /* s is a WinMME stream */ + *stream = (PaWinMmeStream *)s; + return paNoError; + } + else + { + return paIncompatibleStreamHostApi; + } +} + + +int PaWinMME_GetStreamInputHandleCount( PaStream* s ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError ) + return (PA_IS_INPUT_STREAM_(stream)) ? stream->input.deviceCount : 0; + else + return result; +} + + +HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* s, int handleIndex ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError + && PA_IS_INPUT_STREAM_(stream) + && handleIndex >= 0 + && (unsigned int)handleIndex < stream->input.deviceCount ) + return ((HWAVEIN*)stream->input.waveHandles)[handleIndex]; + else + return 0; +} + + +int PaWinMME_GetStreamOutputHandleCount( PaStream* s) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError ) + return (PA_IS_OUTPUT_STREAM_(stream)) ? stream->output.deviceCount : 0; + else + return result; +} + + +HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* s, int handleIndex ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError + && PA_IS_OUTPUT_STREAM_(stream) + && handleIndex >= 0 + && (unsigned int)handleIndex < stream->output.deviceCount ) + return ((HWAVEOUT*)stream->output.waveHandles)[handleIndex]; + else + return 0; +} + + + + + diff --git a/portaudio-v19/pa_win_wmme/pa_win_wmme.h b/portaudio-v19/pa_win_wmme/pa_win_wmme.h new file mode 100644 index 000000000..458f88f99 --- /dev/null +++ b/portaudio-v19/pa_win_wmme/pa_win_wmme.h @@ -0,0 +1,160 @@ +#ifndef PA_WIN_WMME_H +#define PA_WIN_WMME_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * MME specific extensions + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** @file + @brief WMME-specific PortAudio API extension header file. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#define paWinMmeUseLowLevelLatencyParameters (0x01) +#define paWinMmeUseMultipleDevices (0x02) /* use mme specific multiple device feature */ + + +/* By default, the mme implementation drops the processing thread's priority + to THREAD_PRIORITY_NORMAL and sleeps the thread if the CPU load exceeds 100% + This flag disables any priority throttling. The processing thread will always + run at THREAD_PRIORITY_TIME_CRITICAL. +*/ +#define paWinMmeDontThrottleOverloadedProcessingThread (0x08) + + +typedef struct PaWinMmeDeviceAndChannelCount{ + PaDeviceIndex device; + int channelCount; +}PaWinMmeDeviceAndChannelCount; + + +typedef struct PaWinMmeStreamInfo{ + unsigned long size; /**< sizeof(PaWinMmeStreamInfo) */ + PaHostApiTypeId hostApiType; /**< paMME */ + unsigned long version; /**< 1 */ + + unsigned long flags; + + /* low-level latency setting support + These settings control the number and size of host buffers in order + to set latency. They will be used instead of the generic parameters + to Pa_OpenStream() if flags contains the PaWinMmeUseLowLevelLatencyParameters + flag. + + If PaWinMmeStreamInfo structures with PaWinMmeUseLowLevelLatencyParameters + are supplied for both input and output in a full duplex stream, then the + input and output framesPerBuffer must be the same, or the larger of the + two must be a multiple of the smaller, otherwise a + paIncompatibleHostApiSpecificStreamInfo error will be returned from + Pa_OpenStream(). + */ + unsigned long framesPerBuffer; + unsigned long bufferCount; /* formerly numBuffers */ + + /* multiple devices per direction support + If flags contains the PaWinMmeUseMultipleDevices flag, + this functionality will be used, otherwise the device parameter to + Pa_OpenStream() will be used instead. + If devices are specified here, the corresponding device parameter + to Pa_OpenStream() should be set to paUseHostApiSpecificDeviceSpecification, + otherwise an paInvalidDevice error will result. + The total number of channels accross all specified devices + must agree with the corresponding channelCount parameter to + Pa_OpenStream() otherwise a paInvalidChannelCount error will result. + */ + PaWinMmeDeviceAndChannelCount *devices; + unsigned long deviceCount; + +}PaWinMmeStreamInfo; + + +/** Retrieve the number of wave in handles used by a PortAudio WinMME stream. + Returns zero if the stream is output only. + + @return A non-negative value indicating the number of wave in handles + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaWinMME_GetStreamInputHandle +*/ +int PaWinMME_GetStreamInputHandleCount( PaStream* stream ); + + +/** Retrieve a wave in handle used by a PortAudio WinMME stream. + + @param stream The stream to query. + @param handleIndex The zero based index of the wave in handle to retrieve. This + should be in the range [0, PaWinMME_GetStreamInputHandle(stream)-1]. + + @return A valid wave in handle, or NULL if an error occurred. + + @see PaWinMME_GetStreamInputHandle +*/ +HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* stream, int handleIndex ); + + +/** Retrieve the number of wave out handles used by a PortAudio WinMME stream. + Returns zero if the stream is input only. + + @return A non-negative value indicating the number of wave out handles + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaWinMME_GetStreamOutputHandle +*/ +int PaWinMME_GetStreamOutputHandleCount( PaStream* stream ); + + +/** Retrieve a wave out handle used by a PortAudio WinMME stream. + + @param stream The stream to query. + @param handleIndex The zero based index of the wave out handle to retrieve. + This should be in the range [0, PaWinMME_GetStreamOutputHandleCount(stream)-1]. + + @return A valid wave out handle, or NULL if an error occurred. + + @see PaWinMME_GetStreamOutputHandleCount +*/ +HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* stream, int handleIndex ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_WIN_WMME_H */ diff --git a/portaudio-v19/pablio/README.txt b/portaudio-v19/pablio/README.txt new file mode 100644 index 000000000..99c7d146e --- /dev/null +++ b/portaudio-v19/pablio/README.txt @@ -0,0 +1,39 @@ +README for PABLIO +Portable Audio Blocking I/O Library +Author: Phil Burk + +PABLIO is a simplified interface to PortAudio that provide +read/write style blocking I/O. + +Please see the .DOC file for documentation. + +/* + * More information on PortAudio at: http://www.portaudio.com + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + diff --git a/portaudio-v19/pablio/pablio.c b/portaudio-v19/pablio/pablio.c new file mode 100644 index 000000000..452eccc4d --- /dev/null +++ b/portaudio-v19/pablio/pablio.c @@ -0,0 +1,307 @@ +/* + * $Id$ + * pablio.c + * Portable Audio Blocking Input/Output utility. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include "pablio.h" +#include + +/************************************************************************/ +/******** Constants *****************************************************/ +/************************************************************************/ + +#define FRAMES_PER_BUFFER (256) + +/************************************************************************/ +/******** Prototypes ****************************************************/ +/************************************************************************/ + +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ); +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ); + +/************************************************************************/ +/******** Functions *****************************************************/ +/************************************************************************/ + +/* Called from PortAudio. + * Read and write data only if there is room in FIFOs. + */ +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + PABLIO_Stream *data = (PABLIO_Stream*)userData; + long numBytes = data->bytesPerFrame * framesPerBuffer; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int i; + int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + for( i=numRead; ibuffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesWritten; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesRead; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ) +{ + int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + return bytesEmpty / aStream->bytesPerFrame; +} + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ) +{ + int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO ); + return bytesFull / aStream->bytesPerFrame; +} + +/************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2; + aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame; + + /* Initialize PortAudio */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Warning: numFrames must be larger than amount of data processed per interrupt + * inside PA to prevent glitches. Just to be safe, adjust size upwards. + */ + minNumBuffers = 2 * Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + numFrames = minNumBuffers * FRAMES_PER_BUFFER; + numFrames = RoundUpToNextPowerOf2( numFrames ); + + /* Initialize Ring Buffers */ + doRead = ((flags & PABLIO_READ) != 0); + doWrite = ((flags & PABLIO_WRITE) != 0); + if(doRead) + { + err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + } + if(doWrite) + { + long numBytes; + err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes ); + } + + /* Open a PortAudio stream that we will use to communicate with the underlying + * audio drivers. */ + err = Pa_OpenStream( + &aStream->stream, + (doRead ? Pa_GetDefaultInputDeviceID() : paNoDevice), + (doRead ? aStream->samplesPerFrame : 0 ), + format, + NULL, + (doWrite ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + (doWrite ? aStream->samplesPerFrame : 0 ), + format, + NULL, + sampleRate, + FRAMES_PER_BUFFER, + minNumBuffers, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + blockingIOCallback, + aStream ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( aStream->stream ); + if( err != paNoError ) goto error; + + *rwblPtr = aStream; + return paNoError; + +error: + CloseAudioStream( aStream ); + *rwblPtr = NULL; + return err; +} + +/************************************************************/ +PaError CloseAudioStream( PABLIO_Stream *aStream ) +{ + PaError err; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( bytesEmpty < byteSize ) + { + Pa_Sleep( 10 ); + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + +error: + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/portaudio-v19/pablio/pablio.def b/portaudio-v19/pablio/pablio.def new file mode 100644 index 000000000..a10f95291 --- /dev/null +++ b/portaudio-v19/pablio/pablio.def @@ -0,0 +1,35 @@ +LIBRARY PABLIO +DESCRIPTION 'PABLIO Portable Audio Blocking I/O' + +EXPORTS + ; Explicit exports can go here + Pa_Initialize @1 + Pa_Terminate @2 + Pa_GetHostError @3 + Pa_GetErrorText @4 + Pa_CountDevices @5 + Pa_GetDefaultInputDeviceID @6 + Pa_GetDefaultOutputDeviceID @7 + Pa_GetDeviceInfo @8 + Pa_OpenStream @9 + Pa_OpenDefaultStream @10 + Pa_CloseStream @11 + Pa_StartStream @12 + Pa_StopStream @13 + Pa_StreamActive @14 + Pa_StreamTime @15 + Pa_GetCPULoad @16 + Pa_GetMinNumBuffers @17 + Pa_Sleep @18 + + OpenAudioStream @19 + CloseAudioStream @20 + WriteAudioStream @21 + ReadAudioStream @22 + + Pa_GetSampleSize @23 + + ;123456789012345678901234567890123456 + ;000000000111111111122222222223333333 + + diff --git a/portaudio-v19/pablio/pablio.h b/portaudio-v19/pablio/pablio.h new file mode 100644 index 000000000..c1ad84a83 --- /dev/null +++ b/portaudio-v19/pablio/pablio.h @@ -0,0 +1,109 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * PABLIO.h + * Portable Audio Blocking read/write utility. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * Include file for PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include + +typedef struct +{ + RingBuffer inFIFO; + RingBuffer outFIFO; + PortAudioStream *stream; + int bytesPerFrame; + int samplesPerFrame; +} +PABLIO_Stream; + +/* Values for flags for OpenAudioStream(). */ +#define PABLIO_READ (1<<0) +#define PABLIO_WRITE (1<<1) +#define PABLIO_READ_WRITE (PABLIO_READ|PABLIO_WRITE) +#define PABLIO_MONO (1<<2) +#define PABLIO_STEREO (1<<3) + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ); + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ); + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + * and either PABLIO_MONO or PABLIO_STEREO + */ +PaError OpenAudioStream( PABLIO_Stream **aStreamPtr, double sampleRate, + PaSampleFormat format, long flags ); + +PaError CloseAudioStream( PABLIO_Stream *aStream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PABLIO_H */ diff --git a/portaudio-v19/pablio/ringbuffer.c b/portaudio-v19/pablio/ringbuffer.c new file mode 100644 index 000000000..16031fef6 --- /dev/null +++ b/portaudio-v19/pablio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id$ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +/*************************************************************************** + * Initialize FIFO. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + rbuf->bigMask = (numBytes*2)-1; + rbuf->smallMask = (numBytes)-1; + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/portaudio-v19/pablio/ringbuffer.h b/portaudio-v19/pablio/ringbuffer.h new file mode 100644 index 000000000..dd769bee6 --- /dev/null +++ b/portaudio-v19/pablio/ringbuffer.h @@ -0,0 +1,101 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * ringbuffer.h + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +typedef struct +{ + long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by RingBuffer_Init. */ + long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + long readIndex; /* Index of next readable byte. Set by RingBuffer_AdvanceReadIndex. */ + long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ + long smallMask; /* Used for fitting indices to buffer. */ + char *buffer; +} +RingBuffer; +/* + * Initialize Ring Buffer. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ); + +/* Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ); + +/* Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ); +/* Return number of bytes available for read. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ); +/* Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ); +/* Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); + +/* Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ); + +/* Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); + +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _RINGBUFFER_H */ diff --git a/portaudio-v19/pablio/test_rw.c b/portaudio-v19/pablio/test_rw.c new file mode 100644 index 000000000..7f7e886ac --- /dev/null +++ b/portaudio-v19/pablio/test_rw.c @@ -0,0 +1,99 @@ +/* + * $Id$ + * test_rw.c + * Read input from one stream and write it to another. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "pablio.h" + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (5) +#define SAMPLES_PER_FRAME (2) +#define FRAMES_PER_BLOCK (64) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + SAMPLE samples[SAMPLES_PER_FRAME * FRAMES_PER_BLOCK]; + PaError err; + PABLIO_Stream *aStream; + + printf("Full duplex sound test using PortAudio and RingBuffers\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Process samples in the foreground. */ + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Read one block of data into sample array from audio input. */ + ReadAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + /* Write that same block of data to output. */ + WriteAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aStream ); + + printf("Full duplex sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/portaudio-v19/pablio/test_rw_echo.c b/portaudio-v19/pablio/test_rw_echo.c new file mode 100644 index 000000000..9ba8c8d12 --- /dev/null +++ b/portaudio-v19/pablio/test_rw_echo.c @@ -0,0 +1,123 @@ +/* + * $Id$ + * test_rw_echo.c + * Echo delayed input to output. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * Note that if you need low latency, you should not use PABLIO. + * Use the PA_OpenStream callback technique which is lower level + * than PABLIO. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (20) +#define SAMPLES_PER_FRAME (2) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +#define NUM_ECHO_FRAMES (2*SAMPLE_RATE) +SAMPLE samples[NUM_ECHO_FRAMES][SAMPLES_PER_FRAME] = {0.0}; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + PABLIO_Stream *aInStream; + PABLIO_Stream *aOutStream; + int index; + + printf("Full duplex sound test using PABLIO\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + /* Open input first so it can start to fill buffers. */ + err = OpenAudioStream( &aInStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened input\n"); fflush(stdout); /**/ + + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened output\n"); fflush(stdout); /**/ + + /* Process samples in the foreground. */ + index = 0; + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i++ ) + { + /* Write old frame of data to output. */ + /* samples[index][1] = (i&256) * (1.0f/256.0f); /* sawtooth */ + WriteAudioStream( aOutStream, &samples[index][0], 1 ); + + /* Read one frame of data into sample array for later output. */ + ReadAudioStream( aInStream, &samples[index][0], 1 ); + index += 1; + if( index >= NUM_ECHO_FRAMES ) index = 0; + + if( (i & 0xFFFF) == 0 ) printf("i = %d\n", i ); fflush(stdout); /**/ + } + + CloseAudioStream( aOutStream ); + CloseAudioStream( aInStream ); + + printf("R/W echo sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PortAudio\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/portaudio-v19/pablio/test_w_saw.c b/portaudio-v19/pablio/test_w_saw.c new file mode 100644 index 000000000..d5d4e9be5 --- /dev/null +++ b/portaudio-v19/pablio/test_w_saw.c @@ -0,0 +1,108 @@ +/* + * $Id$ + * test_w_saw.c + * Generate stereo sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + +#define FREQUENCY (220.0f) +#define PHASE_INCREMENT (2.0f * FREQUENCY / SAMPLE_RATE) +#define FRAMES_PER_BLOCK (100) + +float samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +float phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paFloat32, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases. */ + phases[0] = 0.0; + phases[1] = 0.0; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j 1.0f ) phases[0] -= 2.0f; + samples[j][0] = phases[0]; + + /* On the second channel, generate a sawtooth wave a fifth higher. */ + phases[1] += PHASE_INCREMENT * (3.0f / 2.0f); + if( phases[1] > 1.0f ) phases[1] -= 2.0f; + samples[j][1] = phases[1]; + } + + /* Write samples to output. */ + WriteAudioStream( aOutStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aOutStream ); + + printf("Sawtooth sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PABLIO\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/portaudio-v19/pablio/test_w_saw8.c b/portaudio-v19/pablio/test_w_saw8.c new file mode 100644 index 000000000..0a9d05abd --- /dev/null +++ b/portaudio-v19/pablio/test_w_saw8.c @@ -0,0 +1,106 @@ +/* + * $Id$ + * test_w_saw8.c + * Generate stereo 8 bit sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + + +#define FRAMES_PER_BLOCK (100) + +unsigned char samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +unsigned char phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate unsigned 8 bit sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paUInt8, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases to "ground" level for paUInt8. */ + phases[0] = 128; + phases[1] = 128; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j