# Copyright 2020 Andrzej Cichocki

# This file is part of outport.
#
# outport 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 3 of the License, or
# (at your option) any later version.
#
# outport 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 outport.  If not, see <http://www.gnu.org/licenses/>.

# This file incorporates work covered by the following copyright and
# permission notice:

# Copyright (c) 2006 Hubert Pham
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# PyAudio : Python Bindings for PortAudio.

# Copyright (c) 2006 Hubert Pham

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

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


"""
PyAudio provides Python bindings for PortAudio, the cross-platform
audio I/O library. With PyAudio, you can easily use Python to play and
record audio on a variety of platforms.  PyAudio is inspired by:

* pyPortAudio/fastaudio: Python bindings for PortAudio v18 API.
* tkSnack: cross-platform sound toolkit for Tcl/Tk and Python.

.. include:: ../sphinx/examples.rst

Overview
--------

**Classes**
  :py:class:`PyAudio`, :py:class:`Stream`

.. only:: pamac

   **Host Specific Classes**
     :py:class:`PaMacCoreStreamInfo`

**Stream Conversion Convenience Functions**
  :py:func:`get_sample_size`, :py:func:`get_format_from_width`

**PortAudio version**
  :py:func:`get_portaudio_version`, :py:func:`get_portaudio_version_text`

.. |PaSampleFormat| replace:: :ref:`PortAudio Sample Format <PaSampleFormat>`
.. _PaSampleFormat:

**Portaudio Sample Formats**
  :py:data:`paFloat32`, :py:data:`paInt32`, :py:data:`paInt24`,
  :py:data:`paInt16`, :py:data:`paInt8`, :py:data:`paUInt8`,
  :py:data:`paCustomFormat`

.. |PaHostAPI| replace:: :ref:`PortAudio Host API <PaHostAPI>`
.. _PaHostAPI:

**PortAudio Host APIs**
  :py:data:`paInDevelopment`, :py:data:`paDirectSound`, :py:data:`paMME`,
  :py:data:`paASIO`, :py:data:`paSoundManager`, :py:data:`paCoreAudio`,
  :py:data:`paOSS`, :py:data:`paALSA`, :py:data:`paAL`, :py:data:`paBeOS`,
  :py:data:`paWDMKS`, :py:data:`paJACK`, :py:data:`paWASAPI`,
  :py:data:`paNoDevice`

.. |PaErrorCode| replace:: :ref:`PortAudio Error Code <PaErrorCode>`
.. _PaErrorCode:

**PortAudio Error Codes**
  :py:data:`paNoError`, :py:data:`paNotInitialized`,
  :py:data:`paUnanticipatedHostError`, :py:data:`paInvalidChannelCount`,
  :py:data:`paInvalidSampleRate`, :py:data:`paInvalidDevice`,
  :py:data:`paInvalidFlag`, :py:data:`paSampleFormatNotSupported`,
  :py:data:`paBadIODeviceCombination`, :py:data:`paInsufficientMemory`,
  :py:data:`paBufferTooBig`, :py:data:`paBufferTooSmall`,
  :py:data:`paNullCallback`, :py:data:`paBadStreamPtr`,
  :py:data:`paTimedOut`, :py:data:`paInternalError`,
  :py:data:`paDeviceUnavailable`,
  :py:data:`paIncompatibleHostApiSpecificStreamInfo`,
  :py:data:`paStreamIsStopped`, :py:data:`paStreamIsNotStopped`,
  :py:data:`paInputOverflowed`, :py:data:`paOutputUnderflowed`,
  :py:data:`paHostApiNotFound`, :py:data:`paInvalidHostApi`,
  :py:data:`paCanNotReadFromACallbackStream`,
  :py:data:`paCanNotWriteToACallbackStream`,
  :py:data:`paCanNotReadFromAnOutputOnlyStream`,
  :py:data:`paCanNotWriteToAnInputOnlyStream`,
  :py:data:`paIncompatibleStreamHostApi`

.. |PaCallbackReturnCodes| replace:: :ref:`PortAudio Callback Return Code <PaCallbackReturnCodes>`
.. _PaCallbackReturnCodes:

**PortAudio Callback Return Codes**
  :py:data:`paContinue`, :py:data:`paComplete`, :py:data:`paAbort`

.. |PaCallbackFlags| replace:: :ref:`PortAutio Callback Flag <PaCallbackFlags>`
.. _PaCallbackFlags:

**PortAudio Callback Flags**
  :py:data:`paInputUnderflow`, :py:data:`paInputOverflow`,
  :py:data:`paOutputUnderflow`, :py:data:`paOutputOverflow`,
  :py:data:`paPrimingOutput`
"""

from __future__ import division

__author__ = "Hubert Pham"
__version__ = "0.2.11"
__docformat__ = "restructuredtext en"

# attempt to import PortAudio
try:
    import _portaudio as pa
except ImportError:
    print("Could not import the PyAudio C module '_portaudio'.")
    raise

############################################################
# GLOBALS
############################################################

##### PaSampleFormat Sample Formats #####

paFloat32      = pa.paFloat32      #: 32 bit float
paInt32        = pa.paInt32        #: 32 bit int
paInt24        = pa.paInt24        #: 24 bit int
paInt16        = pa.paInt16        #: 16 bit int
paInt8         = pa.paInt8         #: 8 bit int
paUInt8        = pa.paUInt8        #: 8 bit unsigned int
paCustomFormat = pa.paCustomFormat #: a custom data format

###### HostAPI TypeId #####

paInDevelopment = pa.paInDevelopment #: Still in development
paDirectSound   = pa.paDirectSound   #: DirectSound (Windows only)
paMME           = pa.paMME           #: Multimedia Extension (Windows only)
paASIO          = pa.paASIO          #: Steinberg Audio Stream Input/Output
paSoundManager  = pa.paSoundManager  #: SoundManager (OSX only)
paCoreAudio     = pa.paCoreAudio     #: CoreAudio (OSX only)
paOSS           = pa.paOSS           #: Open Sound System (Linux only)
paALSA          = pa.paALSA          #: Advanced Linux Sound Architecture (Linux only)
paAL            = pa.paAL            #: Open Audio Library
paBeOS          = pa.paBeOS          #: BeOS Sound System
paWDMKS         = pa.paWDMKS         #: Windows Driver Model (Windows only)
paJACK          = pa.paJACK          #: JACK Audio Connection Kit
paWASAPI        = pa.paWASAPI        #: Windows Vista Audio stack architecture
paNoDevice      = pa.paNoDevice      #: Not actually an audio device

###### portaudio error codes #####

paNoError                               = pa.paNoError
paNotInitialized                        = pa.paNotInitialized
paUnanticipatedHostError                = pa.paUnanticipatedHostError
paInvalidChannelCount                   = pa.paInvalidChannelCount
paInvalidSampleRate                     = pa.paInvalidSampleRate
paInvalidDevice                         = pa.paInvalidDevice
paInvalidFlag                           = pa.paInvalidFlag
paSampleFormatNotSupported              = pa.paSampleFormatNotSupported
paBadIODeviceCombination                = pa.paBadIODeviceCombination
paInsufficientMemory                    = pa.paInsufficientMemory
paBufferTooBig                          = pa.paBufferTooBig
paBufferTooSmall                        = pa.paBufferTooSmall
paNullCallback                          = pa.paNullCallback
paBadStreamPtr                          = pa.paBadStreamPtr
paTimedOut                              = pa.paTimedOut
paInternalError                         = pa.paInternalError
paDeviceUnavailable                     = pa.paDeviceUnavailable
paIncompatibleHostApiSpecificStreamInfo = pa.paIncompatibleHostApiSpecificStreamInfo
paStreamIsStopped                       = pa.paStreamIsStopped
paStreamIsNotStopped                    = pa.paStreamIsNotStopped
paInputOverflowed                       = pa.paInputOverflowed
paOutputUnderflowed                     = pa.paOutputUnderflowed
paHostApiNotFound                       = pa.paHostApiNotFound
paInvalidHostApi                        = pa.paInvalidHostApi
paCanNotReadFromACallbackStream         = pa.paCanNotReadFromACallbackStream
paCanNotWriteToACallbackStream          = pa.paCanNotWriteToACallbackStream
paCanNotReadFromAnOutputOnlyStream      = pa.paCanNotReadFromAnOutputOnlyStream
paCanNotWriteToAnInputOnlyStream        = pa.paCanNotWriteToAnInputOnlyStream
paIncompatibleStreamHostApi             = pa.paIncompatibleStreamHostApi

###### portaudio callback return codes ######

paContinue = pa.paContinue #: There is more audio data to come
paComplete = pa.paComplete #: This was the last block of audio data
paAbort    = pa.paAbort    #: An error ocurred, stop playback/recording

###### portaudio callback flags ######

paInputUnderflow  = pa.paInputUnderflow  #: Buffer underflow in input
paInputOverflow   = pa.paInputOverflow   #: Buffer overflow in input
paOutputUnderflow = pa.paOutputUnderflow #: Buffer underflow in output
paOutputOverflow  = pa.paOutputOverflow  #: Buffer overflow in output
paPrimingOutput   = pa.paPrimingOutput   #: Just priming, not playing yet

############################################################
# Convenience Functions
############################################################

def get_sample_size(format):
    """
    Returns the size (in bytes) for the specified
    sample *format*.

    :param format: A |PaSampleFormat| constant.
    :raises ValueError: on invalid specified `format`.
    :rtype: integer
    """

    return pa.get_sample_size(format)

def get_format_from_width(width, unsigned=True):
    """
    Returns a PortAudio format constant for the specified *width*.

    :param width: The desired sample width in bytes (1, 2, 3, or 4)
    :param unsigned: For 1 byte width, specifies signed or unsigned format.

    :raises ValueError: when invalid *width*
    :rtype: A |PaSampleFormat| constant
    """

    if width == 1:
        if unsigned:
            return paUInt8
        else:
            return paInt8
    elif width == 2:
        return paInt16
    elif width == 3:
        return paInt24
    elif width == 4:
        return paFloat32
    else:
        raise ValueError("Invalid width: %d" % width)


############################################################
# Versioning
############################################################

def get_portaudio_version():
    """
    Returns portaudio version.

    :rtype: string
    """

    return pa.get_version()

def get_portaudio_version_text():
    """
    Returns PortAudio version as a text string.

    :rtype: string
    """

    return pa.get_version_text()

############################################################
# Wrapper around _portaudio Stream (Internal)
############################################################

# Note: See PyAudio class below for main export.

class Stream:
    """
    PortAudio Stream Wrapper. Use :py:func:`PyAudio.open` to make a new
    :py:class:`Stream`.

    **Opening and Closing**
      :py:func:`__init__`, :py:func:`close`

    **Stream Info**
      :py:func:`get_input_latency`, :py:func:`get_output_latency`,
      :py:func:`get_time`, :py:func:`get_cpu_load`

    **Stream Management**
      :py:func:`start_stream`, :py:func:`stop_stream`, :py:func:`is_active`,
      :py:func:`is_stopped`

    **Input Output**
      :py:func:`write`, :py:func:`read`, :py:func:`get_read_available`,
      :py:func:`get_write_available`
    """

    def __init__(self,
                 PA_manager,
                 rate,
                 channels,
                 format,
                 input=False,
                 output=False,
                 input_device_index=None,
                 output_device_index=None,
                 frames_per_buffer=1024,
                 start=True,
                 input_host_api_specific_stream_info=None,
                 output_host_api_specific_stream_info=None,
                 stream_callback=None):
        """
        Initialize a stream; this should be called by
        :py:func:`PyAudio.open`. A stream can either be input, output,
        or both.

        :param PA_manager: A reference to the managing :py:class:`PyAudio`
            instance
        :param rate: Sampling rate
        :param channels: Number of channels
        :param format: Sampling size and format. See |PaSampleFormat|.
        :param input: Specifies whether this is an input stream.
            Defaults to ``False``.
        :param output: Specifies whether this is an output stream.
            Defaults to ``False``.
        :param input_device_index: Index of Input Device to use.
            Unspecified (or ``None``) uses default device.
            Ignored if `input` is ``False``.
        :param output_device_index:
            Index of Output Device to use.
            Unspecified (or ``None``) uses the default device.
            Ignored if `output` is ``False``.
        :param frames_per_buffer: Specifies the number of frames per buffer.
        :param start: Start the stream running immediately.
            Defaults to ``True``. In general, there is no reason to set
            this to ``False``.
        :param input_host_api_specific_stream_info: Specifies a host API
            specific stream information data structure for input.

            .. only:: pamac

               See :py:class:`PaMacCoreStreamInfo`.

        :param output_host_api_specific_stream_info: Specifies a host API
            specific stream information data structure for output.

            .. only:: pamac

               See :py:class:`PaMacCoreStreamInfo`.

        :param stream_callback: Specifies a callback function for
            *non-blocking* (callback) operation.  Default is
            ``None``, which indicates *blocking* operation (i.e.,
            :py:func:`Stream.read` and :py:func:`Stream.write`).  To use
            non-blocking operation, specify a callback that conforms
            to the following signature:

            .. code-block:: python

               callback(in_data,      # recorded data if input=True; else None
                        frame_count,  # number of frames
                        time_info,    # dictionary
                        status_flags) # PaCallbackFlags

            ``time_info`` is a dictionary with the following keys:
            ``input_buffer_adc_time``, ``current_time``, and
            ``output_buffer_dac_time``; see the PortAudio
            documentation for their meanings.  ``status_flags`` is one
            of |PaCallbackFlags|.

            The callback must return a tuple:

            .. code-block:: python

                (out_data, flag)

            ``out_data`` is a byte array whose length should be the
            (``frame_count * channels * bytes-per-channel``) if
            ``output=True`` or ``None`` if ``output=False``.  ``flag``
            must be either :py:data:`paContinue`, :py:data:`paComplete` or
            :py:data:`paAbort` (one of |PaCallbackReturnCodes|).
            When ``output=True`` and ``out_data`` does not contain at
            least ``frame_count`` frames, :py:data:`paComplete` is
            assumed for ``flag``.

            **Note:** ``stream_callback`` is called in a separate
            thread (from the main thread).  Exceptions that occur in
            the ``stream_callback`` will:

            1. print a traceback on standard error to aid debugging,
            2. queue the exception to be thrown (at some point) in
               the main thread, and
            3. return `paAbort` to PortAudio to stop the stream.

            **Note:** Do not call :py:func:`Stream.read` or
            :py:func:`Stream.write` if using non-blocking operation.

            **See:** PortAudio's callback signature for additional
            details: http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html#a8a60fb2a5ec9cbade3f54a9c978e2710

        :raise ValueError: Neither input nor output are set True.
        """

        # no stupidity allowed
        if not (input or output):
            raise ValueError("Must specify an input or output " + "stream.")

        # remember parent
        self._parent = PA_manager

        # remember if we are an: input, output (or both)
        self._is_input = input
        self._is_output = output

        # are we running?
        self._is_running = start

        # remember some parameters
        self._rate = rate
        self._channels = channels
        self._format = format
        self._frames_per_buffer = frames_per_buffer

        arguments = {
            'rate' : rate,
            'channels' : channels,
            'format' : format,
            'input' : input,
            'output' : output,
            'input_device_index' : input_device_index,
            'output_device_index' : output_device_index,
            'frames_per_buffer' : frames_per_buffer}

        if input_host_api_specific_stream_info:
            _l = input_host_api_specific_stream_info
            arguments[
                'input_host_api_specific_stream_info'
                ] = _l._get_host_api_stream_object()

        if output_host_api_specific_stream_info:
            _l = output_host_api_specific_stream_info
            arguments[
                'output_host_api_specific_stream_info'
                ] = _l._get_host_api_stream_object()

        if stream_callback:
            arguments['stream_callback'] = stream_callback

        # calling pa.open returns a stream object
        self._stream = pa.open(**arguments)

        self._input_latency = self._stream.inputLatency
        self._output_latency = self._stream.outputLatency

        if self._is_running:
            pa.start_stream(self._stream)

    def close(self):
        """ Close the stream """

        pa.close(self._stream)

        self._is_running = False

        self._parent._remove_stream(self)


    ############################################################
    # Stream Info
    ############################################################

    def get_input_latency(self):
        """
        Return the input latency.

        :rtype: float
        """

        return self._stream.inputLatency

    def get_output_latency(self):
        """
        Return the output latency.

        :rtype: float
        """

        return self._stream.outputLatency

    def get_time(self):
        """
        Return stream time.

        :rtype: float
        """

        return pa.get_stream_time(self._stream)

    def get_cpu_load(self):
        """
        Return the CPU load.  This is always 0.0 for the
        blocking API.

        :rtype: float
        """

        return pa.get_stream_cpu_load(self._stream)


    ############################################################
    # Stream Management
    ############################################################

    def start_stream(self):
        """ Start the stream. """

        if self._is_running:
            return

        pa.start_stream(self._stream)
        self._is_running = True

    def stop_stream(self):
        """
        Stop the stream. Once the stream is stopped, one may not call
        write or read.  Call :py:func:`start_stream` to resume the
        stream.
        """

        if not self._is_running:
            return

        pa.stop_stream(self._stream)
        self._is_running = False

    def is_active(self):
        """
        Returns whether the stream is active.

        :rtype: bool
        """

        return pa.is_stream_active(self._stream)

    def is_stopped(self):
        """
        Returns whether the stream is stopped.

        :rtype: bool
        """

        return pa.is_stream_stopped(self._stream)


    ############################################################
    # Reading/Writing
    ############################################################

    def write(self, frames, num_frames=None,
              exception_on_underflow=False):

        """
        Write samples to the stream.  Do not call when using
        *non-blocking* mode.

        :param frames:
           The frames of data.
        :param num_frames:
           The number of frames to write.
           Defaults to None, in which this value will be
           automatically computed.
        :param exception_on_underflow:
           Specifies whether an IOError exception should be thrown
           (or silently ignored) on buffer underflow. Defaults
           to False for improved performance, especially on
           slower platforms.

        :raises IOError: if the stream is not an output stream
           or if the write operation was unsuccessful.

        :rtype: `None`
        """

        if not self._is_output:
            raise IOError("Not output stream",
                          paCanNotWriteToAnInputOnlyStream)

        if num_frames == None:
            # determine how many frames to read
            width = get_sample_size(self._format)
            num_frames = int(len(frames) / (self._channels * width))
            #print len(frames), self._channels, self._width, num_frames

        pa.write_stream(self._stream, frames, num_frames,
                        exception_on_underflow)


    def read(self, num_frames, exception_on_overflow=True):
        """
        Read samples from the stream.  Do not call when using
        *non-blocking* mode.

        :param num_frames: The number of frames to read.
        :param exception_on_overflow:
           Specifies whether an IOError exception should be thrown
           (or silently ignored) on input buffer overflow. Defaults
           to True.
        :raises IOError: if stream is not an input stream
          or if the read operation was unsuccessful.
        :rtype: string
        """

        if not self._is_input:
            raise IOError("Not input stream",
                          paCanNotReadFromAnOutputOnlyStream)

        return pa.read_stream(self._stream, num_frames, exception_on_overflow)

    def get_read_available(self):
        """
        Return the number of frames that can be read without waiting.

        :rtype: integer
        """

        return pa.get_stream_read_available(self._stream)


    def get_write_available(self):
        """
        Return the number of frames that can be written without
        waiting.

        :rtype: integer

        """

        return pa.get_stream_write_available(self._stream)



############################################################
# Main Export
############################################################

class PyAudio:

    """
    Python interface to PortAudio. Provides methods to:
     - initialize and terminate PortAudio
     - open and close streams
     - query and inspect the available PortAudio Host APIs
     - query and inspect the available PortAudio audio
       devices

    Use this class to open and close streams.

    **Stream Management**
      :py:func:`open`, :py:func:`close`

    **Host API**
      :py:func:`get_host_api_count`, :py:func:`get_default_host_api_info`,
      :py:func:`get_host_api_info_by_type`,
      :py:func:`get_host_api_info_by_index`,
      :py:func:`get_device_info_by_host_api_device_index`

    **Device API**
      :py:func:`get_device_count`, :py:func:`is_format_supported`,
      :py:func:`get_default_input_device_info`,
      :py:func:`get_default_output_device_info`,
      :py:func:`get_device_info_by_index`

    **Stream Format Conversion**
      :py:func:`get_sample_size`, :py:func:`get_format_from_width`

    **Details**
    """

    ############################################################
    # Initialization and Termination
    ############################################################

    def __init__(self):
        """Initialize PortAudio."""

        pa.initialize()
        self._streams = set()

    def terminate(self):
        """
        Terminate PortAudio.

        :attention: Be sure to call this method for every instance of
          this object to release PortAudio resources.
        """

        for stream in self._streams.copy():
            stream.close()

        self._streams = set()

        pa.terminate()


    ############################################################
    # Stream Format
    ############################################################

    def get_sample_size(self, format):
        """
        Returns the size (in bytes) for the specified
        sample `format` (a |PaSampleFormat| constant).

        :param format: A |PaSampleFormat| constant.
        :raises ValueError: Invalid specified `format`.
        :rtype: integer
        """

        return pa.get_sample_size(format)

    def get_format_from_width(self, width, unsigned=True):
        """
        Returns a PortAudio format constant for the specified `width`.

        :param width: The desired sample width in bytes (1, 2, 3, or 4)
        :param unsigned: For 1 byte width, specifies signed or unsigned format.

        :raises ValueError: for invalid `width`
        :rtype: A |PaSampleFormat| constant.
        """

        if width == 1:
            if unsigned:
                return paUInt8
            else:
                return paInt8
        elif width == 2:
            return paInt16
        elif width == 3:
            return paInt24
        elif width == 4:
            return paFloat32
        else:
            raise ValueError("Invalid width: %d" % width)


    ############################################################
    # Stream Factory
    ############################################################

    def open(self, *args, **kwargs):
        """
        Open a new stream. See constructor for
        :py:func:`Stream.__init__` for parameter details.

        :returns: A new :py:class:`Stream`
        """

        stream = Stream(self, *args, **kwargs)
        self._streams.add(stream)
        return stream

    def close(self, stream):
        """
        Close a stream. Typically use :py:func:`Stream.close` instead.

        :param stream: An instance of the :py:class:`Stream` object.
        :raises ValueError: if stream does not exist.
        """

        if stream not in self._streams:
            raise ValueError("Stream `%s' not found" % str(stream))

        stream.close()

    def _remove_stream(self, stream):
        """
        Internal method. Removes a stream.

        :param stream: An instance of the :py:class:`Stream` object.
        """

        if stream in self._streams:
            self._streams.remove(stream)


    ############################################################
    # Host API Inspection
    ############################################################

    def get_host_api_count(self):
        """
        Return the number of available PortAudio Host APIs.

        :rtype: integer
        """

        return pa.get_host_api_count()

    def get_default_host_api_info(self):
        """
        Return a dictionary containing the default Host API
        parameters. The keys of the dictionary mirror the data fields
        of PortAudio's ``PaHostApiInfo`` structure.

        :raises IOError: if no default input device is available
        :rtype: dict
        """

        defaultHostApiIndex = pa.get_default_host_api()
        return self.get_host_api_info_by_index(defaultHostApiIndex)

    def get_host_api_info_by_type(self, host_api_type):
        """
        Return a dictionary containing the Host API parameters for the
        host API specified by the `host_api_type`. The keys of the
        dictionary mirror the data fields of PortAudio's ``PaHostApiInfo``
        structure.

        :param host_api_type: The desired |PaHostAPI|
        :raises IOError: for invalid `host_api_type`
        :rtype: dict
        """

        index = pa.host_api_type_id_to_host_api_index(host_api_type)
        return self.get_host_api_info_by_index(index)

    def get_host_api_info_by_index(self, host_api_index):
        """
        Return a dictionary containing the Host API parameters for the
        host API specified by the `host_api_index`. The keys of the
        dictionary mirror the data fields of PortAudio's ``PaHostApiInfo``
        structure.

        :param host_api_index: The host api index
        :raises IOError: for invalid `host_api_index`
        :rtype: dict
        """

        return self._make_host_api_dictionary(
            host_api_index,
            pa.get_host_api_info(host_api_index)
            )

    def get_device_info_by_host_api_device_index(self,
                                                 host_api_index,
                                                 host_api_device_index):
        """
        Return a dictionary containing the Device parameters for a
        given Host API's n'th device. The keys of the dictionary
        mirror the data fields of PortAudio's ``PaDeviceInfo`` structure.

        :param host_api_index: The Host API index number
        :param host_api_device_index: The n'th device of the host API
        :raises IOError: for invalid indices
        :rtype: dict
        """

        long_method_name = pa.host_api_device_index_to_device_index
        device_index = long_method_name(host_api_index,
                                        host_api_device_index)
        return self.get_device_info_by_index(device_index)

    def _make_host_api_dictionary(self, index, host_api_struct):
        """
        Internal method to create Host API dictionary that mirrors
        PortAudio's ``PaHostApiInfo`` structure.

        :rtype: dict
        """

        return {'index' : index,
                'structVersion' : host_api_struct.structVersion,
                'type' : host_api_struct.type,
                'name' : host_api_struct.name,
                'deviceCount' : host_api_struct.deviceCount,
                'defaultInputDevice' : host_api_struct.defaultInputDevice,
                'defaultOutputDevice' : host_api_struct.defaultOutputDevice}


    ############################################################
    # Device Inspection
    ############################################################

    def get_device_count(self):
        """
        Return the number of PortAudio Host APIs.

        :rtype: integer
        """

        return pa.get_device_count()

    def is_format_supported(self, rate,
                            input_device=None,
                            input_channels=None,
                            input_format=None,
                            output_device=None,
                            output_channels=None,
                            output_format=None):
        """
        Check to see if specified device configuration
        is supported. Returns True if the configuration
        is supported; throws a ValueError exception otherwise.

        :param rate:
           Specifies the desired rate (in Hz)
        :param input_device:
           The input device index. Specify ``None`` (default) for
           half-duplex output-only streams.
        :param input_channels:
           The desired number of input channels. Ignored if
           `input_device` is not specified (or ``None``).
        :param input_format:
           PortAudio sample format constant defined
           in this module
        :param output_device:
           The output device index. Specify ``None`` (default) for
           half-duplex input-only streams.
        :param output_channels:
           The desired number of output channels. Ignored if
           `input_device` is not specified (or ``None``).
        :param output_format:
           |PaSampleFormat| constant.

        :rtype: bool
        :raises ValueError: tuple containing (error string, |PaErrorCode|).
        """

        if input_device == None and output_device == None:
            raise ValueError("must specify stream format for input, " +\
                             "output, or both", paInvalidDevice);

        kwargs = {}

        if input_device != None:
            kwargs['input_device'] = input_device
            kwargs['input_channels'] = input_channels
            kwargs['input_format'] = input_format

        if output_device != None:
            kwargs['output_device'] = output_device
            kwargs['output_channels'] = output_channels
            kwargs['output_format'] = output_format

        return pa.is_format_supported(rate, **kwargs)

    def get_default_input_device_info(self):
        """
        Return the default input Device parameters as a
        dictionary. The keys of the dictionary mirror the data fields
        of PortAudio's ``PaDeviceInfo`` structure.

        :raises IOError: No default input device available.
        :rtype: dict
        """

        device_index = pa.get_default_input_device()
        return self.get_device_info_by_index(device_index)

    def get_default_output_device_info(self):
        """
        Return the default output Device parameters as a
        dictionary. The keys of the dictionary mirror the data fields
        of PortAudio's ``PaDeviceInfo`` structure.

        :raises IOError: No default output device available.
        :rtype: dict
        """

        device_index = pa.get_default_output_device()
        return self.get_device_info_by_index(device_index)


    def get_device_info_by_index(self, device_index):
        """
        Return the Device parameters for device specified in
        `device_index` as a dictionary. The keys of the dictionary
        mirror the data fields of PortAudio's ``PaDeviceInfo``
        structure.

        :param device_index: The device index
        :raises IOError: Invalid `device_index`.
        :rtype: dict
        """

        return self._make_device_info_dictionary(
            device_index,
            pa.get_device_info(device_index)
            )

    def _make_device_info_dictionary(self, index, device_info):
        """
        Internal method to create Device Info dictionary that mirrors
        PortAudio's ``PaDeviceInfo`` structure.

        :rtype: dict
        """

        device_name = device_info.name

        # Attempt to decode device_name
        for codec in ["utf-8", "cp1252"]:
            try:
                device_name = device_name.decode(codec)
                break
            except:
                pass

        # If we fail to decode, we return the raw bytes and let the caller
        # deal with the encoding.
        return {'index' : index,
                'structVersion' : device_info.structVersion,
                'name' : device_name,
                'hostApi' : device_info.hostApi,
                'maxInputChannels' : device_info.maxInputChannels,
                'maxOutputChannels' : device_info.maxOutputChannels,
                'defaultLowInputLatency' :
                device_info.defaultLowInputLatency,
                'defaultLowOutputLatency' :
                device_info.defaultLowOutputLatency,
                'defaultHighInputLatency' :
                device_info.defaultHighInputLatency,
                'defaultHighOutputLatency' :
                device_info.defaultHighOutputLatency,
                'defaultSampleRate' :
                device_info.defaultSampleRate
                }


######################################################################
# Host Specific Stream Info
######################################################################

try:
    paMacCoreStreamInfo = pa.paMacCoreStreamInfo
except AttributeError:
    pass
else:
    class PaMacCoreStreamInfo:
        """
        Mac OS X-only: PaMacCoreStreamInfo is a PortAudio Host API
        Specific Stream Info data structure for specifying Mac OS
        X-only settings. Instantiate this class (if desired) and pass
        the instance as the argument in :py:func:`PyAudio.open` to parameters
        ``input_host_api_specific_stream_info`` or
        ``output_host_api_specific_stream_info``.
        (See :py:func:`Stream.__init__`.)

        :note: Mac OS X only.

        .. |PaMacCoreFlags| replace:: :ref:`PortAudio Mac Core Flags <PaMacCoreFlags>`
        .. _PaMacCoreFlags:

        **PortAudio Mac Core Flags**
          :py:data:`paMacCoreChangeDeviceParameters`,
          :py:data:`paMacCoreFailIfConversionRequired`,
          :py:data:`paMacCoreConversionQualityMin`,
          :py:data:`paMacCoreConversionQualityMedium`,
          :py:data:`paMacCoreConversionQualityLow`,
          :py:data:`paMacCoreConversionQualityHigh`,
          :py:data:`paMacCoreConversionQualityMax`,
          :py:data:`paMacCorePlayNice`,
          :py:data:`paMacCorePro`,
          :py:data:`paMacCoreMinimizeCPUButPlayNice`,
          :py:data:`paMacCoreMinimizeCPU`

        **Settings**
          :py:func:`get_flags`, :py:func:`get_channel_map`
        """

        paMacCoreChangeDeviceParameters   = pa.paMacCoreChangeDeviceParameters
        paMacCoreFailIfConversionRequired = pa.paMacCoreFailIfConversionRequired
        paMacCoreConversionQualityMin     = pa.paMacCoreConversionQualityMin
        paMacCoreConversionQualityMedium  = pa.paMacCoreConversionQualityMedium
        paMacCoreConversionQualityLow     = pa.paMacCoreConversionQualityLow
        paMacCoreConversionQualityHigh    = pa.paMacCoreConversionQualityHigh
        paMacCoreConversionQualityMax     = pa.paMacCoreConversionQualityMax
        paMacCorePlayNice                 = pa.paMacCorePlayNice
        paMacCorePro                      = pa.paMacCorePro
        paMacCoreMinimizeCPUButPlayNice   = pa.paMacCoreMinimizeCPUButPlayNice
        paMacCoreMinimizeCPU              = pa.paMacCoreMinimizeCPU

        def __init__(self, flags=None, channel_map=None):
            """
            Initialize with flags and channel_map. See PortAudio
            documentation for more details on these parameters; they are
            passed almost verbatim to the PortAudio library.

            :param flags: |PaMacCoreFlags| OR'ed together.
                See :py:class:`PaMacCoreStreamInfo`.
            :param channel_map: An array describing the channel mapping.
                See PortAudio documentation for usage.
            """

            kwargs = {"flags" : flags,
                      "channel_map" : channel_map}

            if flags == None:
                del kwargs["flags"]
            if channel_map == None:
                del kwargs["channel_map"]

            self._paMacCoreStreamInfo = paMacCoreStreamInfo(**kwargs)

        def get_flags(self):
            """
            Return the flags set at instantiation.

            :rtype: integer
            """

            return self._paMacCoreStreamInfo.flags

        def get_channel_map(self):
            """
            Return the channel map set at instantiation.

            :rtype: tuple or None
            """

            return self._paMacCoreStreamInfo.channel_map

        def _get_host_api_stream_object(self):
            """Private method."""

            return self._paMacCoreStreamInfo
