Commits

Anonymous committed 4ea561f

Added pygame2.resources - resource handling routines.
Added stream support for freetype.Font files.
Added generic CPyStreamWrapper object for stream management within the C API.
Added IsRead/WriteableStreamObj() C API methods for stream checking.
Added bufferproxy unit tests.
Added sdlext.fastevents documentation.
Added more sdl.video.Surface unit tests.
Added LongFromObj() and UlongFromObj() C API methods.
Completed most math module portage (from trunk), except for swizzles and elementwise() methods.
Fixed some memory leaks in freetype module.
Fixed error escalation in threads.tmap() for Python 3.x
Fixed msys library configuration bug for Python 3.x
Fixed doc string recognition for sdlext.fastevents, sdlext.scrap, sdlext.numericsurfarray, sdl.wm
Fixed sdl.video.Surface.set_alpha() default alpha flag.
Fixed some sdl.time threading and blocking behaviour.
Fixed lock reference removals, if an SDL surface is already deallocated.

Comments (0)

Files changed (65)

 
 testall:
 	@python2.4 test/run_tests.py
+	@rm -rf test/*.pyc
 	@python2.5 test/run_tests.py
+	@rm -rf test/*.pyc
 	@python2.6 test/run_tests.py
+	@rm -rf test/*.pyc
 	@python3.1 test/run_tests.py
+	@rm -rf test/*.pyc
 
 testall2:
 	@python2.4 -c "import pygame2.test; pygame2.test.run ()"
 * Fix eval(repr(xyz)) for most types.
 * >8 bpp surfaces with palettes - it is supported (although it might be
   pointless) by SDL and so should we.
+* Fix possible dead locks on long running timer callbacks in sdl.time module.
+
 
 * freetype: render to buffer buffer support (like new bytearray, but
   write to a buffer instead)

config/config_msys.py

             for g in self._libdirs:
                 p = msys_obj.msys_to_windows (os.path.join (d, g))
                 f = msys_obj.msys_to_windows (os.path.join (p, name))
-                if filter (os.path.isfile, glob.glob (f + '*')):
+                if list (filter (os.path.isfile, glob.glob (f + '*'))):
                     return p
 
     def _configure_libconfig(self):

doc/capi/base.rst

   stored in *convobj* and needs to be freed by the caller, once *text* is not
   required anymore. This returns 1 on success and 0 on failure.
 
+.. cfunction:: int IsReadableStreamObj (PyObject *obj)
+
+  Checks, whether the passed object supports the most important stream
+  operation for reading data, such as ``read``, ``seek`` and ``tell``.
+  This returns 1 on success and 0 on failure.
+
+.. cfunction:: int IsWriteableStreamObj (PyObject *obj)
+
+  Checks, whether the passed object supports the most important stream
+  operation for writing data, such as ``write``, ``seek`` and ``tell``.
+  This returns 1 on success and 0 on failure.
+
+.. cfunction:: int IsReadWriteableStreamObj (PyObject *obj)
+
+  Checks, whether the passed object supports the most important stream
+  operation for reading and writing data, such as ``read``, ``write``,
+  ``seek`` and ``tell``. This returns 1 on success and 0 on failure.
+
+
 PyColor
 -------
 .. ctype:: PyColor
 .. ctype:: PySurface
 .. ctype:: PySurface_Type
 
-The PySurface object an abstract base class, to be used by inheriting classes
-and other interfaces, so it is guaranteed that surface-like objects contain a
-set of same attributes and methods.
+The PySurface object is an abstract base class, to be used by inheriting
+classes and other interfaces, so it is guaranteed that surface-like
+objects contain a set of same attributes and methods.
 
 Members
 ^^^^^^^
 
   Creates a new, empty :ctype:`PySurface` object, which's members are set to
   NULL. On failure, this returns NULL.
+
+CPyStreamWrapper
+----------------
+.. ctype:: CPyStreamWrapper
+
+CPyStreamWrapper is a C API only class type for reading and writing
+Python stream objects in a threaded or non-threaded manner. It
+encapsules the important underlying stream methods.
+
+Members
+^^^^^^^
+.. cmember:: PyObject* CPyStreamWrapper.read
+
+  The method pointer to the underlying Python object's ``read`` method.
+  This will be NULL, if the Python object does not support read access.
+
+.. cmember:: PyObject* CPyStreamWrapper.write
+
+  The method pointer to the underlying Python object's ``write`` method.
+  This will be NULL, if the Python object does not support write access.
+
+.. cmember:: PyObject* CPyStreamWrapper.seek
+
+  The method pointer to the underlying Python object's ``seek`` method.
+  This will be NULL, if the Python object does not support seeking the
+  stream.
+
+.. cmember:: PyObject* CPyStreamWrapper.tell
+
+  The method pointer to the underlying Python object's ``tell`` method.
+  This will be NULL, if the Python object does not support seeking the
+  stream.
+
+.. cmember:: PyObject* CPyStreamWrapper.close
+
+  The method pointer to the underlying Python object's ``close`` method.
+  This will be NULL, if the Python object does not support closing the
+  stream.
+
+.. cmember:: PyThreadState* CPyStreamWrapper.thread
+
+  If Python was built with thread support, this will contain the
+  preserved Python thread state to allow concurrent external threads
+  to access the interpreter state and perform stream operations on the
+  underlying Python object.
+
+Functions
+^^^^^^^^^
+.. cfunction:: CPyStreamWrapper* CPyStreamWrapper_New (PyObject *obj)
+
+  Creates a new :ctype:`CPyStreamWrapper` object encapsuling the passed
+  *obj*. This will not perform any checks, whether the *obj* actually is
+  a stream-like object and supports all required methods. If it is not a
+  stream-like object or does not implement the methods, the according
+  :ctype:`CPyStreamWrapper` members will be NULL.
+   
+  Use :cfunc:`IsReadableStreamObj`, :cfunc:`IsWriteableStreamObj` or
+  :cfunc:`IsReadWritebbleStreamObj` beforehand, to check whether *obj*
+  implements all wanted methods.
+
+  On failure, this returns NULL.
+
+.. cfunction:: CPyStreamWrapper_Free (CPyStreamWrapper *wrapper)
+
+  Releases all resources hold by the passed :ctype:`CPyStreamWrapper`
+  instance.
+
+.. cfunction:: int CPyStreamWrapper_Read_Threaded (CPyStreamWrapper *wrapper, void *buf, pguint32 offset, pguint32 count, pguint32 *read_)
+
+  Reads a maximum of *count* bytes from the passed
+  :ctype:`CPyStreamWrapper`, starting at *offset*. The read data will be
+  stored in *buf*, which must be large enough to hold the data. The
+  amount of bytes actually written to *buf* will be stored in *read_*.
+  If *offset* is 0, the stream will not be repositioned. Otherwise,
+  *offset* denotes a position relative to the start of the stream.
+  Returns 1 on succes and 0 on failure.
+
+  This will swap the Python interpreter thread state to gain access to
+  the underlying Python stream object. It **should** not be called from
+  within the same interpreter thread, as it locks the interpreter stat
+  (and thus itself).
+
+.. cfunction:: int CPyStreamWrapper_Read (CPyStreamWrapper *wrapper, void *buf, pguint32 offset, pguint32 count, pguint32 *read_)
+
+  Same as :cfunc:`CPyStreamWrapper_Read_Threaded`, but this will not
+  swap the thread state of the Python interpreter and thus is safe
+  to be called from within the interpreter thread.
+
+.. cfunction:: int CPyStreamWrapper_Write_Threaded (CPyStreamWrapper *wrapper, const void *buf, pguint32 num, pguint32 size, pguint32 *written)
+
+  Writes at least *num* elements of size *size* from the passed *buf* to
+  the stream of the passed :ctype:`CPyStreamWrapper`. The actual amount
+  of written elements will be stored in *written*.
+
+  This will swap the Python interpreter thread state to gain access to
+  the underlying Python stream object. It **should** not be called from
+  within the same interpreter thread, as it locks the interpreter stat
+  (and thus itself).
+
+.. cfunction:: int CPyStreamWrapper_Write (CPyStreamWrapper *wrapper, const void *buf, pguint32 num, pguint32 size, pguint32 *written)
+
+  Same as :cfunc:`CPyStreamWrapper_Write_Threaded`, but this will not
+  swap the thread state of the Python interpreter and thus is safe
+  to be called from within the interpreter thread.
+
+.. cfunction:: int CPyStreamWrapper_Seek_Threaded (CPyStreamWrapper *wrapper, pgint32 offset, int whence)
+
+  Moves to a new stream position. *offset* is the position in
+  bytes. *whence* indicates, how the movement should be performed and
+  can be a valid value of
+
+    * SEEK_SET - *offset* is relative to the start of the stream
+    * SEEK_CUR - *offset* is relative to the current stream position
+    * SEEK_END - *offset* is relative to the end of the stream.
+
+  .. note:: 
+
+    Seeking beyond the end of the stream boundaries might result in an
+    undefined behaviour.
+
+  Returns 1 on succes and 0 on failure.
+
+  This will swap the Python interpreter thread state to gain access to
+  the underlying Python stream object. It **should** not be called from
+  within the same interpreter thread, as it locks the interpreter stat
+  (and thus itself).
+
+.. cfunction:: int CPyStreamWrapper_Seek (CPyStreamWrapper *wrapper, pgint32 offset, int whence)
+
+  Same as :cfunc:`CPyStreamWrapper_Seek_Threaded`, but this will not
+  swap the thread state of the Python interpreter and thus is safe
+  to be called from within the interpreter thread.
+
+.. cfunction:: pgint32 CPyStreamWrapper_Tell_Threaded (CPyStreamWrapper *wrapper)
+
+  Returns the current stream position or -1 if an error occured.
+
+  This will swap the Python interpreter thread state to gain access to
+  the underlying Python stream object. It **should** not be called from
+  within the same interpreter thread, as it locks the interpreter stat
+  (and thus itself).
+
+.. cfunction:: pgint32 CPyStreamWrapper_Tell (CPyStreamWrapper *wrapper)
+
+  Same as :cfunc:`CPyStreamWrapper_Tell_Threaded`, but this will not
+  swap the thread state of the Python interpreter and thus is safe
+  to be called from within the interpreter thread.
+
+.. cfunction:: int CPyStreamWrapper_Close_Threaded (CPyStreamWrapper *wrapper)
+
+  Closes the underlying stream. This leaves the *wrapper* itself intact.
+  Returns 1 on success and 0 on failure.
+
+  This will swap the Python interpreter thread state to gain access to
+  the underlying Python stream object. It **should** not be called from
+  within the same interpreter thread, as it locks the interpreter stat
+  (and thus itself).
+
+.. cfunction:: int CPyStreamWrapper_Close (CPyStreamWrapper *wrapper)
+
+  Same as :cfunc:`CPyStreamWrapper_Close_Threaded`, but this will not
+  swap the thread state of the Python interpreter and thus is safe
+  to be called from within the interpreter thread.

doc/src/sdlbase.xml

       case it was not done before, :func:`init` will be
       called implicitly with the passed *flags*.
 
+      .. note::
+        
+        Please be aware, that timers need a special treatment, which is not
+        be met by using :data:`INIT_TIMER` alone. For more details, take a look
+        at the :mod:`pygame2.sdl.time` module documentation.
+
       In case an error occured, False will be returned. The detailled
       error can be received using :func:`get_error`.
     </desc>
       After calling this function, you should not invoke any SDL related
       class, method or function as they are likely to fail or might give
       unpredictable results.
+    
+      .. note::
+        
+        Please be aware, that timers need a special treatment, which is not
+        be met by using :data:`INIT_TIMER` alone. For more details, take a look
+        at the :mod:`pygame2.sdl.time` module documentation.
     </desc>
   </func>
   <func name="quit_subsystem">
       After calling this function, you should not invoke any class,
       method or function related to the specified subsystems as they are
       likely to fail or might give unpredictable results.
+    
+      .. note::
+        
+        Please be aware, that timers need a special treatment, which is not
+        be met by using :data:`INIT_TIMER` alone. For more details, take a look
+        at the :mod:`pygame2.sdl.time` module documentation.
     </desc>
   </func>
   <func name="was_init">

doc/src/sdlextfastevent.xml

   <func name="get">
     <call>get () -> [:class:`Event`, :class:`Event`, ...]</call>
     <desc>
-      Gets the events from the event queue.
+      Gets events from the event queue.
+
+      Gets the current events from the event queue. If no argument is
+      passed, all currently available events are received from the event
+      queue and returned as list. Otherwise, the argument can be a
+      sequence or a bitmask combination of event types to receive from
+      the queue.
+
+      If no matching events are found on the queue, an empty list will be
+      returned.
     </desc>
   </func>
   <func name="init">
     </desc>
   </func>
   <func name="poll">
-    <call></call>
-    <desc></desc>
+    <call>poll () -> :class:`Event`</call>
+    <desc>
+      Gets a single event from the event queue.
+
+      Returns a single event from the queue. If the event queue is
+      empty, None will be returned. If an event is available and
+      returned, it will be removed from the queue.
+    </desc>
   </func>
   <func name="push">
-    <call></call>
-    <desc></desc>
+    <call>push (event) -> None</call>
+    <desc>
+      Places a new event at the end of the event queue.
+
+      This is usually used for placing user defined events on the event
+      queue. You also can push user created device events on the queue,
+      but this will not change the state of the device itself.
+    </desc>
   </func>
   <func name="quit">
-    <call></call>
-    <desc></desc>
+    <call>quit () -> None</call>
+    <desc>
+      Quits the underlying fastevents system and releases all hold resources.
+    </desc>
   </func>
   <func name="wait">
-    <call></call>
-    <desc></desc>
+    <call>wait () -> :class:`Event`</call>
+    <desc>
+      Waits indefinitely for the next available event.
+
+      This is a blocking method, that only returns, if an event occurs
+      on the event queue. Once an event occurs, it will be returned to
+      the caller and removed from the queue. While the program is
+      waiting it will sleep in an idle state.
+    </desc>
   </func>
 </module>
 

doc/src/sdltime.xml

 
 <module name="pygame2.sdl.time">
   <short>SDL time handling and measurement wrapper module</short>
-  <desc>SDL time handling and measurement wrapper module</desc>
+  <desc>SDL time handling and measurement wrapper module.
+  
+    If you are planning to use the timer functionality (:func:`set_timer`,
+    :func:`add_timer`, :func:`remove_timer`) of this module, be aware that
+    simply initializing the SDL subsystem itself (e.g. by calling
+    ``pygame2.sdl.init_subsystem(pygame2.sdl.constants.INIT_TIMER)`` is *not*
+    enough. Instead, explicitly use the :func:`init` and :func:`quit` functions
+    provided by this module, so that threaded timers can work as supposed and
+    you are able to manage them properly.
+  
+    .. todo::
+    
+      Add note about synchronization and deadlock behaviour for the interpreter
+      thread(s).
+  </desc>
 
   <func name="add_timer">
     <call>add_timer (interval, callable[, data]) -> CObject</call>
       passed object is not a matching timer object.
     </desc>
   </func>
-  <func name="set_timer">
-    <call>set_timer (interval, callable) -> None</call>
-    <desc>
-      Sets a single timer callback to be called periodically.
-      
-      Sets a single timer callback to be called periodically using the specified
-      *interval* in milliseconds. The timer callback can be reset by passing
-      None as *callable* object.
-      
-      .. note::
-      
-        Any set timer will be removed automatically on calling :func:`quit`.
-    </desc>
-  </func>
   <func name="was_init">
     <call>was_init () -> bool</call>
     <desc>

doc/src/sdlwm.xml

     <call>get_info () -> dict</call>
     <desc>
       Gets operating system and window manager specific information about the
-      current SDL window .
+      current SDL window.
     </desc>
   </func>
   <func name="grab_input">

examples/__init__.py

 """
 
 import os
+from pygame2.resources import Resources
 
-_filepath = os.path.abspath (__file__)
-RESOURCEDIR = os.path.join (os.path.dirname (_filepath), "resources")
-FONTDIR = os.path.join (os.path.dirname (_filepath), "resources")
-IMAGEDIR = os.path.join (os.path.dirname (_filepath), "resources")
+_filepath = os.path.dirname (os.path.abspath (__file__))
+RESOURCES = Resources (os.path.join (_filepath, "resources"), ".*\.svn\.*")

examples/freetype/sdlfont.py

     video.init ()
     freetype.init (8)
 
-    fontfile = os.path.join (pygame2.examples.RESOURCEDIR, "sans.ttf")
-    font = freetype.Font (fontfile)
+    font = freetype.Font (pygame2.examples.RESOURCES.get ("sans.ttf"))
 
     screen = video.set_mode (800, 600)
     screen.fill (colors["grey_light"])

examples/sdl/hello_world.py

 
     surface = None
     if hassdlimage:
-        surface = image.load (os.path.join
-                              (pygame2.examples.IMAGEDIR, "logo.gif"))
+        surface = image.load (pygame2.examples.RESOURCES.get ("logo.gif"))
     else:
-        surface = image.load_bmp (os.path.join
-                                  (pygame2.examples.IMAGEDIR, "logo.bmp"))
+        surface = image.load_bmp (pygame2.examples.RESOURCES.get ("logo.bmp"))
 
     screen = video.set_mode (surface.w + 10, surface.h + 10)
     screen.fill (pygame2.Color (255, 255, 255))

examples/sdl/keyboard.py

 
     video.init ()
 
-    imgfont = image.load_bmp (os.path.join
-                              (pygame2.examples.IMAGEDIR, "font.bmp"))
+    imgfont = image.load_bmp (pygame2.examples.RESOURCES.get ("font.bmp"))
     bmpfont = BitmapFont (imgfont, (32, 32), fontmap)
     
     screen = video.set_mode (640, 480)

examples/sdl/surface_blit.py

     imgdir = os.path.dirname (os.path.abspath (__file__))
     logo = None
     if hassdlimage:
-        logo = image.load (os.path.join (pygame2.examples.IMAGEDIR, "logo.gif"))
+        logo = image.load (pygame2.examples.RESOURCES.get ("logo.gif"))
     else:
-        logo = image.load_bmp (os.path.join
-                               (pygame2.examples.IMAGEDIR, "logo.bmp"))
+        logo = image.load_bmp (pygame2.examples.RESOURCES.get ("logo.bmp"))
     
     screen.fill (color)
     screen.blit (logo, (-10, 140))

examples/sdl/surface_fill.py

     video.init ()
     
     screen = video.set_mode (760, 300, 32)
-    imgdir = os.path.dirname (os.path.abspath (__file__))
-    surface = image.load_bmp (os.path.join
-                              (pygame2.examples.IMAGEDIR, "logo.bmp"))
+    surface = image.load_bmp (pygame2.examples.RESOURCES.get ("logo.bmp"))
     surface = surface.convert (flags=sdlconst.SRCALPHA)
     
     color = white

examples/sdlext/pixelarray.py

     screen = video.set_mode (320, 240, 32)
     screen.fill (black)
     
-    imgdir = os.path.dirname (os.path.abspath (__file__))
-    surface = image.load_bmp (os.path.join
-                              (pygame2.examples.IMAGEDIR, "array.bmp"))
+    surface = image.load_bmp (pygame2.examples.RESOURCES.get ("array.bmp"))
     surface = surface.convert (flags=sdlconst.SRCALPHA)
     screen.blit (surface)
     screen.flip ()

examples/sdlgfx/fpsmanager.py

     video.init ()
     freetype.init ()
 
-    fontfile = os.path.join (pygame2.examples.RESOURCEDIR, "sans.ttf")
-    font = freetype.Font (fontfile)
+    font = freetype.Font (pygame2.examples.RESOURCES.get ("sans.ttf"))
 
     fpsmanager = sdlgfx.FPSmanager (2)
 

lib/colorpalettes.py

 ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 ##
 
-## 
+##
 ## This file is placed under the Public Domain.
 ##
 

lib/dll/__init__.py

-# DO NOT REMOVE
-# This is a placeholder to make packaging systems happy
-"""
-DLL inclusion package for Win32 platforms.
-"""
+# DO NOT REMOVE
+# This is a placeholder to make packaging systems happy
+"""
+DLL inclusion package for Win32 platforms.
+"""
-##    pygame - Python Game Library
-##    Copyright (C) 2008  Rene Dudfield
-##
-##    This library is free software; you can redistribute it and/or
-##    modify it under the terms of the GNU Library General Public
-##    License as published by the Free Software Foundation; either
-##    version 2 of the License, or (at your option) any later version.
-##
-##    This library 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
-##    Library General Public License for more details.
-##
-##    You should have received a copy of the GNU Library General Public
-##    License along with this library; if not, write to the Free
-##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-##
-
-"""
-PyPortMidi interface methods to ease interaction with MIDI devices.
-"""
-
-import pygame2
-import atexit
-
-# Necessary globals
-_pypm = None    # The PyPortMidi module binding
-_init = False   # Initialization flag.
-
-def _check_init ():
-    if not _init:
-        raise pygame2.Error ("pygame2.midi is not initialized")
-
-def init ():
-    """init () -> None
-    
-    Initializes the midi module.
-
-    Raises a pygame2.Error on failure.
-    """
-    global _pypm, _init
-    if not _init:
-        try:
-            import pygame2.midi.pypm
-            _pypm = pygame2.midi.pypm
-        except ImportError:
-            raise pygame2.Error ("pygame2.midi.pypm not usable")
-        _pypm.Initialize ()
-        _init = True
-        atexit.register (quit)
-
-def quit ():
-    """quit () -> None
-    
-    Uninitializes the midi module and releases all hold resources.
-    """
-    global _pypm, _init
-    if _pypm:
-        _pypm.Terminate ()
-        _pypm = None
-        _init = False
-
-def was_init ():
-    """was_init () -> bool
-    
-    Gets, whether the midi module was already initialized.
-    """
-    return _init
-
-def time ():
-    """time () -> int
-    
-    Gets the time in milliseconds since the midi module was initialized.
-    """
-    return _pypm.Time ()
-
-def get_count ():
-    """get_count () -> int
-    
-    Gets the number of available midi devices.
-    
-    Raises a pygame2.Error, if the midi module is not initialized..
-    """
-    _check_init ()
-    return _pypm.CountDevices ()
-
-
-def get_default_input_id ():
-    """get_default_input_id () -> int
-    
-    Returns the default device ID or -1 if there are no devices.
-    The result can be passed to the Input()/Ouput() class.
-    
-    On the PC, the user can specify a default device by
-    setting an environment variable. For example, to use device #1.
-    
-        set PM_RECOMMENDED_INPUT_DEVICE=1
-    
-    The user should first determine the available device ID by using
-    the supplied application "testin" or "testout".
-    
-    In general, the registry is a better place for this kind of info,
-    and with USB devices that can come and go, using integers is not
-    very reliable for device identification. Under Windows, if
-    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
-    *NOT* found in the environment, then the default device is obtained
-    by looking for a string in the registry under:
-        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
-    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
-    for a string. The number of the first device with a substring that
-    matches the string exactly is returned. For example, if the string
-    in the registry is "USB", and device 1 is named
-    "In USB MidiSport 1x1", then that will be the default
-    input because it contains the string "USB".
-    
-    In addition to the name, get_device_info() returns "interf", which
-    is the interface name. (The "interface" is the underlying software
-    system or API used by PortMidi to access devices. Examples are
-    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
-    At present, the only Win32 interface is "MMSystem", the only Linux
-    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
-    To specify both the interface and the device name in the registry,
-    separate the two with a comma and a space, e.g.:
-        MMSystem, In USB MidiSport 1x1
-    In this case, the string before the comma must be a substring of
-    the "interf" string, and the string after the space must be a
-    substring of the "name" name string in order to match the device.
-    
-    Note: in the current release, the default is simply the first device
-    (the input or output device with the lowest PmDeviceID).
-
-    Raises a pygame2.Error, if the midi module is not initialized.
-    """
-    _check_init ()
-    return _pypm.GetDefaultInputDeviceID ()
-
-def get_default_output_id ():
-    """get_default_output_id () -> int
-    
-    Return the default device ID or -1 if there are no devices.
-    The result can be passed to the Input()/Ouput() class.
-    
-    On the PC, the user can specify a default device by
-    setting an environment variable. For example, to use device #1.
-    
-        set PM_RECOMMENDED_OUTPUT_DEVICE=1
-    
-    The user should first determine the available device ID by using
-    the supplied application "testin" or "testout".
-    
-    In general, the registry is a better place for this kind of info,
-    and with USB devices that can come and go, using integers is not
-    very reliable for device identification. Under Windows, if
-    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
-    *NOT* found in the environment, then the default device is obtained
-    by looking for a string in the registry under:
-        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
-    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
-    for a string. The number of the first device with a substring that
-    matches the string exactly is returned. For example, if the string
-    in the registry is "USB", and device 1 is named
-    "In USB MidiSport 1x1", then that will be the default
-    input because it contains the string "USB".
-    
-    In addition to the name, get_device_info() returns "interf", which
-    is the interface name. (The "interface" is the underlying software
-    system or API used by PortMidi to access devices. Examples are
-    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
-    At present, the only Win32 interface is "MMSystem", the only Linux
-    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
-    To specify both the interface and the device name in the registry,
-    separate the two with a comma and a space, e.g.:
-        MMSystem, In USB MidiSport 1x1
-    In this case, the string before the comma must be a substring of
-    the "interf" string, and the string after the space must be a
-    substring of the "name" name string in order to match the device.
-    
-    Note: in the current release, the default is simply the first device
-    (the input or output device with the lowest PmDeviceID).
-
-    Raises a pygame2.Error, if the midi module is not initialized.
-    """
-    _check_init ()
-    return _pypm.GetDefaultOutputDeviceID()
-
-def get_device_info (id):
-    """get_device_info (id) -> string, string, bool, bool, bool
-    
-    Gets information about a midi device.
-    
-    Gets enhanced information about a midi device. The return values are
-    
-    * the name of the device, e.g. 'ALSA'
-    * the enhanced description of the device, e.g. 'Midi Through Port-0'
-    * a boolean indicating, whether the device is an input device
-    * a boolean indicating, whether the device is an output device
-    * a boolean indicating, whether the device is opened
-    
-    in this order.
-    
-    Raises a TypeError, if the *id* is not a integer value.
-    Raises a ValueError, if the *id* is not within the range of available
-    devices.
-    Raises a pygame2.Error, if the midi module is not initialized.
-    """
-    _check_init ()
-    _id = int (id)
-    if _id < 0 or _id >= get_count ():
-        raise ValueError ("id must be in the range of available devices")
-    name, desc, input, output, opened = _pypm.GetDeviceInfo (_id)
-    return name, desc, input == 1, output == 1, opened == 1
-
-class Input (object):
-    """Input (id, bufsize=4096) -> Input
-    
-    Creates a new Input instance for a specific device.
-    
-    The Input class gives read access to a specific midi device, which allows
-    input, with buffering support.
-    
-    Raises a ValueError, if the *id* is not within the range of available
-    devices.
-    Raises a pygame2.Error, if the midi module is not initialized.
-    """
-    def __init__ (self, id, bufsize=4096):
-        _check_init ()
-        interf, name, input, output, opened = get_device_info (id)
-        if not input:
-            raise pygame2.Error ("device is not an input device")
-        
-        self._input = _pypm.Input(device_id, buffer_size)
-        self._id = id
-
-    def close (self):
-        """I.close () -> None
-        
-        Closes the Input device.
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if self._input:
-            self._input.Close ()
-        self._input = None
-    
-    def read (self, amount):
-        """I.read (amount) -> list
-
-        Reads a certain *amount* of midi events from the buffer.
-        
-        Reads from the Input buffer and gives back midi events in the form
-        
-        [ [[status,data1,data2,data3],timestamp],
-          [[status,data1,data2,data3],timestamp], ...]
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if not self._input:
-            raise pygame2.Error ("device is not opened")
-        return self._input.Read (amount)
-    
-    def poll (self):
-        """I.poll () -> bool
-
-        Gets, whether data is available on the buffer or not.
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if not self._input:
-            raise pygame2.Error ("device is not opened")
-
-        r = self._input.Poll ()
-        if r == _pypm.TRUE:
-            return True
-        elif r == _pypm.FALSE:
-            return False
-        else:
-            err_text = _pypm.GetErrorText (r)
-            raise pygame2.Error (err_text)
-
-class Output (object):
-    """Output (id, latency=0, bufsize=4096) -> Output
-    
-    Creates a new Output instance for a specific device.
-    
-    The Output class gives write access to a specific midi device, which allows
-    output.
-
-    *latency* is the delay in milliseconds applied to timestamps to determine
-    when the output should actually occur. (If *latency* is < 0, 0 is 
-    assumed.)
-
-    If *latency* is zero, timestamps are ignored and all output is delivered
-    immediately. If *latency* is greater than zero, output is delayed until
-    the message timestamp plus the latency. (NOTE: time is measured 
-    relative to the time source indicated by time_proc. Timestamps are 
-    absolute, not relative delays or offsets.) In some cases, PortMidi 
-    can obtain better timing than your application by passing timestamps 
-    along to the device driver or hardware. Latency may also help you 
-    to synchronize midi data to audio data by matching midi latency to 
-    the audio buffer latency.
-    
-    Raises a ValueError, if the *id* is not within the range of available
-    devices.
-    Raises a pygame2.Error, if the midi module is not initialized.
-    """
-    def __init__ (self, id, latency=0):
-        _check_init ()
-        interf, name, input, output, opened = get_device_info (id)
-        if not output:
-            raise pygame2.Error ("device is not an output device")
-        
-        self.output = _pypm.Output (id, latency)
-        self._id = id
-    
-    def close (self):
-        """O.close () -> None
-        
-        Closes the Output device.
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if self._input:
-            self._input.Close ()
-        self._input = None
-    
-    def abort (self):
-        """O.abort () -> None
-        
-        Aborts outgoing messages immediately.
-        
-        The caller should immediately close the output port;
-        this call may result in transmission of a partial midi message.
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init()
-        if self._output:
-            self._output.Abort ()
-    
-    def write (self, data):
-        """O.write (data) -> None
-        
-        Writes midi data to the output device.
-        
-        Writes series of MIDI information in the form of a list:
-        
-             write([[[status <,data1><,data2><,data3>],timestamp],
-                    [[status <,data1><,data2><,data3>],timestamp],...])
-        
-        <data> fields are optional.
-        
-        Example: choose program change 1 at time 20000 and
-        send note 65 with velocity 100 500 ms later.
-             
-             write([[[0xc0,0,0],20000],[[0x90,60,100],20500]])
-        
-        Notes:
-          1. timestamps will be ignored if latency = 0.
-          2. To get a note to play immediately, send MIDI info with
-             timestamp read from function time().
-          3. understanding optional data fields:
-               
-               write([[[0xc0,0,0],20000]])
-             
-             is equivalent to
-               
-               write([[[0xc0],20000]])
-
-        This can send up to 1024 elements in your data list, otherwise an 
-        IndexError is raised.
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if not self._output:
-            raise pygame2.Error ("device is not opened")
-        self._output.Write (data)
-    
-    def write_short (self, status, data1=0, data2=0):
-        """O.write_short (status <, data1><, data2>) -> None
-
-        Writes MIDI information of 3 bytes or less.
-
-        Writes a short MIDI information to the device. The data fields are
-        optional and assumed 0, if omitted. The *status* byte could be:
-
-            0xc0 = program change
-            0x90 = note on
-            ...
-            
-        Example: note 65 on with velocity 100
-            
-            write_short(0x90,65,100)
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if not self._output:
-            raise pygame2.Error ("device is not opened")
-        self._output.WriteShort (status, data1, data2)
-
-    def write_sys_ex (self, timestamp, msg):
-        """O.write_sys_ex (timestamp, msg) -> None
-        
-        Writes a timestamped, system-exclusive message.
-        
-        Writes a system-exclusive message *msg*, which can be either a byte
-        buffer or string  - or - list of bytes.
-        
-        Example:
-        
-            write_sys_ex (0, '\\xF0\\x7D\\x10\\x11\\x12\\x13\\xF7')
-        
-        is equivalent to
-        
-            write_sys_ex (pygame2.midi.time (),
-                          [0xF0,0x7D,0x10,0x11,0x12,0x13,0xF7])
-        
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        _check_init ()
-        if not self._output:
-            raise pygame2.Error ("device is not opened")
-        self._output.WriteSysExc (timestamp, msg)
-
-    def note_on (self, note, velocity=None, channel=0):
-        """O.note_on (note, velocity=None, channel=0) -> None
-        
-        Turn a note on in the output stream.
-
-        Turn a note on in the output stream. The note must already be off for
-        this to work correctly.
-        
-        Raises a ValueError, if *channel* is not in the range [0, 15].
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        if velocity is None:
-            velocity = 0
-
-        if not (0 <= channel <= 15):
-            raise ValueError ("Channel not between 0 and 15")
-
-        self.write_short (0x90 + channel, note, velocity)
-    
-    def note_off (self, note, velocity=None, channel=0):
-        """O.note_off (note, velocity=None, channel = 0) -> None
-
-        Turn a note off in the output stream.
-
-        Turn a note off in the output stream. The note must already
-        be on for this to work correctly.
-        
-        Raises a ValueError, if *channel* is not in the range [0, 15].
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        if velocity is None:
-            velocity = 0
-
-        if not (0 <= channel <= 15):
-            raise ValueError ("Channel not between 0 and 15")
-
-        self.write_short (0x80 + channel, note, velocity)
-
-    def set_instrument (self, id, channel=0):
-        """O.set_instrument (id, channel=0) -> None
-        
-        Select an instrument, with a value between 0 and 127.
-
-        Selects an instrument, where the *id* is in a range of [0, 127].
-        
-        Raises a ValueError, if *id* is not in the range [0, 127].
-        Raises a ValueError, if *channel* is not in the range [0, 15].
-        Raises a pygame2.Error, if the midi module is not initialized.
-        """
-        if not (0 <= id <= 127):
-            raise ValueError ("instrument id not between 0 and 127")
-        if not (0 <= channel <= 15):
-            raise ValueError ("Channel not between 0 and 15")
-        self.write_short (0xc0 + channel, id)
+##    pygame - Python Game Library
+##    Copyright (C) 2008  Rene Dudfield
+##
+##    This library is free software; you can redistribute it and/or
+##    modify it under the terms of the GNU Library General Public
+##    License as published by the Free Software Foundation; either
+##    version 2 of the License, or (at your option) any later version.
+##
+##    This library 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
+##    Library General Public License for more details.
+##
+##    You should have received a copy of the GNU Library General Public
+##    License along with this library; if not, write to the Free
+##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+##
+
+"""
+PyPortMidi interface methods to ease interaction with MIDI devices.
+"""
+
+import pygame2
+import atexit
+
+# Necessary globals
+_pypm = None    # The PyPortMidi module binding
+_init = False   # Initialization flag.
+
+def _check_init ():
+    if not _init:
+        raise pygame2.Error ("pygame2.midi is not initialized")
+
+def init ():
+    """init () -> None
+    
+    Initializes the midi module.
+
+    Raises a pygame2.Error on failure.
+    """
+    global _pypm, _init
+    if not _init:
+        try:
+            import pygame2.midi.pypm
+            _pypm = pygame2.midi.pypm
+        except ImportError:
+            raise pygame2.Error ("pygame2.midi.pypm not usable")
+        _pypm.Initialize ()
+        _init = True
+        atexit.register (quit)
+
+def quit ():
+    """quit () -> None
+    
+    Uninitializes the midi module and releases all hold resources.
+    """
+    global _pypm, _init
+    if _pypm:
+        _pypm.Terminate ()
+        _pypm = None
+        _init = False
+
+def was_init ():
+    """was_init () -> bool
+    
+    Gets, whether the midi module was already initialized.
+    """
+    return _init
+
+def time ():
+    """time () -> int
+    
+    Gets the time in milliseconds since the midi module was initialized.
+    """
+    return _pypm.Time ()
+
+def get_count ():
+    """get_count () -> int
+    
+    Gets the number of available midi devices.
+    
+    Raises a pygame2.Error, if the midi module is not initialized..
+    """
+    _check_init ()
+    return _pypm.CountDevices ()
+
+
+def get_default_input_id ():
+    """get_default_input_id () -> int
+    
+    Returns the default device ID or -1 if there are no devices.
+    The result can be passed to the Input()/Ouput() class.
+    
+    On the PC, the user can specify a default device by
+    setting an environment variable. For example, to use device #1.
+    
+        set PM_RECOMMENDED_INPUT_DEVICE=1
+    
+    The user should first determine the available device ID by using
+    the supplied application "testin" or "testout".
+    
+    In general, the registry is a better place for this kind of info,
+    and with USB devices that can come and go, using integers is not
+    very reliable for device identification. Under Windows, if
+    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
+    *NOT* found in the environment, then the default device is obtained
+    by looking for a string in the registry under:
+        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
+    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
+    for a string. The number of the first device with a substring that
+    matches the string exactly is returned. For example, if the string
+    in the registry is "USB", and device 1 is named
+    "In USB MidiSport 1x1", then that will be the default
+    input because it contains the string "USB".
+    
+    In addition to the name, get_device_info() returns "interf", which
+    is the interface name. (The "interface" is the underlying software
+    system or API used by PortMidi to access devices. Examples are
+    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
+    At present, the only Win32 interface is "MMSystem", the only Linux
+    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
+    To specify both the interface and the device name in the registry,
+    separate the two with a comma and a space, e.g.:
+        MMSystem, In USB MidiSport 1x1
+    In this case, the string before the comma must be a substring of
+    the "interf" string, and the string after the space must be a
+    substring of the "name" name string in order to match the device.
+    
+    Note: in the current release, the default is simply the first device
+    (the input or output device with the lowest PmDeviceID).
+
+    Raises a pygame2.Error, if the midi module is not initialized.
+    """
+    _check_init ()
+    return _pypm.GetDefaultInputDeviceID ()
+
+def get_default_output_id ():
+    """get_default_output_id () -> int
+    
+    Return the default device ID or -1 if there are no devices.
+    The result can be passed to the Input()/Ouput() class.
+    
+    On the PC, the user can specify a default device by
+    setting an environment variable. For example, to use device #1.
+    
+        set PM_RECOMMENDED_OUTPUT_DEVICE=1
+    
+    The user should first determine the available device ID by using
+    the supplied application "testin" or "testout".
+    
+    In general, the registry is a better place for this kind of info,
+    and with USB devices that can come and go, using integers is not
+    very reliable for device identification. Under Windows, if
+    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
+    *NOT* found in the environment, then the default device is obtained
+    by looking for a string in the registry under:
+        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
+    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
+    for a string. The number of the first device with a substring that
+    matches the string exactly is returned. For example, if the string
+    in the registry is "USB", and device 1 is named
+    "In USB MidiSport 1x1", then that will be the default
+    input because it contains the string "USB".
+    
+    In addition to the name, get_device_info() returns "interf", which
+    is the interface name. (The "interface" is the underlying software
+    system or API used by PortMidi to access devices. Examples are
+    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
+    At present, the only Win32 interface is "MMSystem", the only Linux
+    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
+    To specify both the interface and the device name in the registry,
+    separate the two with a comma and a space, e.g.:
+        MMSystem, In USB MidiSport 1x1
+    In this case, the string before the comma must be a substring of
+    the "interf" string, and the string after the space must be a
+    substring of the "name" name string in order to match the device.
+    
+    Note: in the current release, the default is simply the first device
+    (the input or output device with the lowest PmDeviceID).
+
+    Raises a pygame2.Error, if the midi module is not initialized.
+    """
+    _check_init ()
+    return _pypm.GetDefaultOutputDeviceID()
+
+def get_device_info (id):
+    """get_device_info (id) -> string, string, bool, bool, bool
+    
+    Gets information about a midi device.
+    
+    Gets enhanced information about a midi device. The return values are
+    
+    * the name of the device, e.g. 'ALSA'
+    * the enhanced description of the device, e.g. 'Midi Through Port-0'
+    * a boolean indicating, whether the device is an input device
+    * a boolean indicating, whether the device is an output device
+    * a boolean indicating, whether the device is opened
+    
+    in this order.
+    
+    Raises a TypeError, if the *id* is not a integer value.
+    Raises a ValueError, if the *id* is not within the range of available
+    devices.
+    Raises a pygame2.Error, if the midi module is not initialized.
+    """
+    _check_init ()
+    _id = int (id)
+    if _id < 0 or _id >= get_count ():
+        raise ValueError ("id must be in the range of available devices")
+    name, desc, input, output, opened = _pypm.GetDeviceInfo (_id)
+    return name, desc, input == 1, output == 1, opened == 1
+
+class Input (object):
+    """Input (id, bufsize=4096) -> Input
+    
+    Creates a new Input instance for a specific device.
+    
+    The Input class gives read access to a specific midi device, which allows
+    input, with buffering support.
+    
+    Raises a ValueError, if the *id* is not within the range of available
+    devices.
+    Raises a pygame2.Error, if the midi module is not initialized.
+    """
+    def __init__ (self, id, bufsize=4096):
+        _check_init ()
+        interf, name, input, output, opened = get_device_info (id)
+        if not input:
+            raise pygame2.Error ("device is not an input device")
+        
+        self._input = _pypm.Input(device_id, buffer_size)
+        self._id = id
+
+    def close (self):
+        """I.close () -> None
+        
+        Closes the Input device.
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if self._input:
+            self._input.Close ()
+        self._input = None
+    
+    def read (self, amount):
+        """I.read (amount) -> list
+
+        Reads a certain *amount* of midi events from the buffer.
+        
+        Reads from the Input buffer and gives back midi events in the form
+        
+        [ [[status,data1,data2,data3],timestamp],
+          [[status,data1,data2,data3],timestamp], ...]
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if not self._input:
+            raise pygame2.Error ("device is not opened")
+        return self._input.Read (amount)
+    
+    def poll (self):
+        """I.poll () -> bool
+
+        Gets, whether data is available on the buffer or not.
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if not self._input:
+            raise pygame2.Error ("device is not opened")
+
+        r = self._input.Poll ()
+        if r == _pypm.TRUE:
+            return True
+        elif r == _pypm.FALSE:
+            return False
+        else:
+            err_text = _pypm.GetErrorText (r)
+            raise pygame2.Error (err_text)
+
+class Output (object):
+    """Output (id, latency=0, bufsize=4096) -> Output
+    
+    Creates a new Output instance for a specific device.
+    
+    The Output class gives write access to a specific midi device, which allows
+    output.
+
+    *latency* is the delay in milliseconds applied to timestamps to determine
+    when the output should actually occur. (If *latency* is < 0, 0 is 
+    assumed.)
+
+    If *latency* is zero, timestamps are ignored and all output is delivered
+    immediately. If *latency* is greater than zero, output is delayed until
+    the message timestamp plus the latency. (NOTE: time is measured 
+    relative to the time source indicated by time_proc. Timestamps are 
+    absolute, not relative delays or offsets.) In some cases, PortMidi 
+    can obtain better timing than your application by passing timestamps 
+    along to the device driver or hardware. Latency may also help you 
+    to synchronize midi data to audio data by matching midi latency to 
+    the audio buffer latency.
+    
+    Raises a ValueError, if the *id* is not within the range of available
+    devices.
+    Raises a pygame2.Error, if the midi module is not initialized.
+    """
+    def __init__ (self, id, latency=0):
+        _check_init ()
+        interf, name, input, output, opened = get_device_info (id)
+        if not output:
+            raise pygame2.Error ("device is not an output device")
+        
+        self.output = _pypm.Output (id, latency)
+        self._id = id
+    
+    def close (self):
+        """O.close () -> None
+        
+        Closes the Output device.
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if self._input:
+            self._input.Close ()
+        self._input = None
+    
+    def abort (self):
+        """O.abort () -> None
+        
+        Aborts outgoing messages immediately.
+        
+        The caller should immediately close the output port;
+        this call may result in transmission of a partial midi message.
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init()
+        if self._output:
+            self._output.Abort ()
+    
+    def write (self, data):
+        """O.write (data) -> None
+        
+        Writes midi data to the output device.
+        
+        Writes series of MIDI information in the form of a list:
+        
+             write([[[status <,data1><,data2><,data3>],timestamp],
+                    [[status <,data1><,data2><,data3>],timestamp],...])
+        
+        <data> fields are optional.
+        
+        Example: choose program change 1 at time 20000 and
+        send note 65 with velocity 100 500 ms later.
+             
+             write([[[0xc0,0,0],20000],[[0x90,60,100],20500]])
+        
+        Notes:
+          1. timestamps will be ignored if latency = 0.
+          2. To get a note to play immediately, send MIDI info with
+             timestamp read from function time().
+          3. understanding optional data fields:
+               
+               write([[[0xc0,0,0],20000]])
+             
+             is equivalent to
+               
+               write([[[0xc0],20000]])
+
+        This can send up to 1024 elements in your data list, otherwise an 
+        IndexError is raised.
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if not self._output:
+            raise pygame2.Error ("device is not opened")
+        self._output.Write (data)
+    
+    def write_short (self, status, data1=0, data2=0):
+        """O.write_short (status <, data1><, data2>) -> None
+
+        Writes MIDI information of 3 bytes or less.
+
+        Writes a short MIDI information to the device. The data fields are
+        optional and assumed 0, if omitted. The *status* byte could be:
+
+            0xc0 = program change
+            0x90 = note on
+            ...
+            
+        Example: note 65 on with velocity 100
+            
+            write_short(0x90,65,100)
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if not self._output:
+            raise pygame2.Error ("device is not opened")
+        self._output.WriteShort (status, data1, data2)
+
+    def write_sys_ex (self, timestamp, msg):
+        """O.write_sys_ex (timestamp, msg) -> None
+        
+        Writes a timestamped, system-exclusive message.
+        
+        Writes a system-exclusive message *msg*, which can be either a byte
+        buffer or string  - or - list of bytes.
+        
+        Example:
+        
+            write_sys_ex (0, '\\xF0\\x7D\\x10\\x11\\x12\\x13\\xF7')
+        
+        is equivalent to
+        
+            write_sys_ex (pygame2.midi.time (),
+                          [0xF0,0x7D,0x10,0x11,0x12,0x13,0xF7])
+        
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        _check_init ()
+        if not self._output:
+            raise pygame2.Error ("device is not opened")
+        self._output.WriteSysExc (timestamp, msg)
+
+    def note_on (self, note, velocity=None, channel=0):
+        """O.note_on (note, velocity=None, channel=0) -> None
+        
+        Turn a note on in the output stream.
+
+        Turn a note on in the output stream. The note must already be off for
+        this to work correctly.
+        
+        Raises a ValueError, if *channel* is not in the range [0, 15].
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        if velocity is None:
+            velocity = 0
+
+        if not (0 <= channel <= 15):
+            raise ValueError ("Channel not between 0 and 15")
+
+        self.write_short (0x90 + channel, note, velocity)
+    
+    def note_off (self, note, velocity=None, channel=0):
+        """O.note_off (note, velocity=None, channel = 0) -> None
+
+        Turn a note off in the output stream.
+
+        Turn a note off in the output stream. The note must already
+        be on for this to work correctly.
+        
+        Raises a ValueError, if *channel* is not in the range [0, 15].
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        if velocity is None:
+            velocity = 0
+
+        if not (0 <= channel <= 15):
+            raise ValueError ("Channel not between 0 and 15")
+
+        self.write_short (0x80 + channel, note, velocity)
+
+    def set_instrument (self, id, channel=0):
+        """O.set_instrument (id, channel=0) -> None
+        
+        Select an instrument, with a value between 0 and 127.
+
+        Selects an instrument, where the *id* is in a range of [0, 127].
+        
+        Raises a ValueError, if *id* is not in the range [0, 127].
+        Raises a ValueError, if *channel* is not in the range [0, 15].
+        Raises a pygame2.Error, if the midi module is not initialized.
+        """
+        if not (0 <= id <= 127):
+            raise ValueError ("instrument id not between 0 and 127")
+        if not (0 <= channel <= 15):
+            raise ValueError ("Channel not between 0 and 15")
+        self.write_short (0xc0 + channel, id)
+##    pygame - Python Game Library
+##    Copyright (C) 2010 Marcus von Appen
+##
+##    This library is free software; you can redistribute it and/or
+##    modify it under the terms of the GNU Library General Public
+##    License as published by the Free Software Foundation; either
+##    version 2 of the License, or (at your option) any later version.
+##
+##    This library 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
+##    Library General Public License for more details.
+##
+##    You should have received a copy of the GNU Library General Public
+##    License along with this library; if not, write to the Free
+##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+##
+
+##
+## This file is placed under the Public Domain.
+##
+
+"""
+Resource management methods.
+"""
+
+import os, re
+import zipfile
+import tarfile
+import urlparse
+import urllib2
+
+try:
+    import cStringIO as stringio
+except ImportError:
+    import StringIO as stringio
+
+def open_zipfile (archive, filename, dir=None):
+    """open_zipfile (archive, filename, dir=None) -> StringIO
+    """
+    data = None
+    opened = False
+    
+    if not isinstance (archive, zipfile.ZipFile):
+        if not zipfile.is_zipfile (archive):
+            raise TypeError ("passed file does not seem to be a ZIP archive")
+        else:
+            archive = zipfile.ZipFile (archive, 'r')
+            opened = True
+    
+    apath = filename
+    if dir:
+        apath = "%s/%s" % (dir, filename)
+    
+    try:
+        dmpdata = archive.open (apath)
+        data = stringio.StringIO (dmpdata.read ())
+    finally:
+        if opened:
+            archive.close ()
+    return data
+
+def open_tarfile (archive, filename, dir=None, type=None):
+    """open_tarfile (archive, filename, dir=None, type=None) -> StringIO
+    """
+    data = None
+    opened = False
+    
+    mode = 'r'
+    if type:
+        if type not in ('gz', 'bz2'):
+            raise TypeError ("invalid TAR compression type")
+        mode = "r:%s" % type
+    
+    if not isinstance (archive, tarfile.TarFile):
+        if not tarfile.is_tarfile (archive):
+            raise TypeError ("passed file does not seem to be a TAR archive")
+        else:
+            archive = tarfile.open (archive, mode)
+            opened = True
+    
+    apath = filename
+    if dir:
+        apath = "%s/%s" % (dir, filename)
+    
+    try:
+        dmpdata = archive.extractfile (apath)
+        data = stringio.StringIO (dmpdata.read ())
+    finally:
+        if opened:
+            archive.close ()
+    return data
+    
+def open_url (filename, basepath=None):
+    """open_url (filename) -> file
+    """
+    url = filename
+    if basepath:
+        url = urlparse.urljoin (basepath, filename)
+    return urllib2.urlopen (url)
+
+class Resources (object):
+    """Resources () -> Resources
+    
+    Creates a new resource container instance.
+    
+    The Resources class manages a set of file resources and eases accessing
+    them by using relative paths, scanning archives automatically and so on.
+    """
+    def __init__ (self, path=None, excludepattern=None):
+        self.files = {}
+        if path:
+            self.scan (path, excludepattern)
+
+    def _scanzip (self, filename):
+        """
+        """
+        if not zipfile.is_zipfile (filename):
+            raise TypeError ("file '%s' is not a valid ZIP archive" % filename)
+        archname = os.path.abspath (filename)
+        zip = zipfile.ZipFile (filename, 'r')
+        for path in zip.namelist ():
+            dirname, fname = os.path.split (path)
+            if fname:
+                self.files[fname] = (archname, 'zip', path)
+        zip.close ()
+    
+    def _scantar (self, filename, type=None):
+        """
+        """
+        if not tarfile.is_tarfile (filename):
+            raise TypeError ("file '%s' is not a valid TAR archive" % filename)
+        mode = 'r'
+        if type:
+            if type not in ('gz', 'bz2'):
+                raise TypeError ("invalid TAR compression type")
+            mode = "r:%s" % type
+        archname = os.path.abspath (filename)
+        archtype = 'tar'
+        if type:
+            archtype = 'tar%s' % type
+        tar = tarfile.open (filename, mode)
+        for path in tar.getnames ():
+            dirname, fname = os.path.split (path)
+            self.files[fname] = (archname, archtype, path)
+        tar.close ()
+    
+    def add (self, filename):
+        """add (filename) -> None
+        
+        Adds a file to the Resources container.
+        
+        Depending on the file type (determined by the file suffix or name),
+        the file will be automatically scanned (if it is an archive) or
+        checked for availability (if it is a stream/network resource).
+        """
+        if zipfile.is_zipfile (filename):
+            self.add_archive (filename)
+        elif tarfile.is_tarfile (filename):
+            self.add_archive (filename, 'tar')
+        else:
+            self.add_file (filename)
+
+    def add_file (self, filename):
+        """add_file (self, filename) -> None
+        
+        Adds a file to the Resources container.
+        
+        This will only add the passed file and do not scan an archive or check
+        a stream for availability.
+        """
+        abspath = os.path.abspath (filename)
+        dirname, fname = os.path.split (abspath)
+        if not fname:
+            raise ValueError ("invalid file path")
+        self.files[fname] = (None, None, abspath)
+    
+    def add_archive (self, filename, typehint='zip'):
+        """add_archive (self, filename, typehint='zip') -> None
+        
+        Adds an archive file to the Resources container.
+        
+        This will scan the passed archive and add its contents to the list
+        of available resources.
+        """
+        if typehint == 'zip':
+            self._scanzip (filename)
+        elif typehint == 'tar':
+            self._scantar (filename)
+        elif typehint == 'tarbz2':
+            self._scantar (filename, 'bz2')
+        elif typehint == 'targz':
+            self._scantar (filename, 'gz')
+        else:
+            raise ValueError ("unsupported archive type")
+
+    def get (self, filename):
+        """get (filename) -> file
+       
+        Gets the specified file from the Resources.
+        """
+        archive, type, pathname = self.files[filename]
+        if archive:
+            if type == 'zip':
+                return open_zipfile (archive, pathname)
+            elif type == 'tar':
+                return open_tarfile (archive, pathname)
+            elif type == 'tarbz2':
+                return open_tarfile (archive, pathname, 'bz2')
+            elif type == 'targz':
+                return open_tarfile (archive, pathname, 'gz')
+            else:
+                raise ValueError ("unsupported archive type")
+        dmpdata = open (pathname, 'rb')
+        data = stringio.StringIO (dmpdata.read ())
+        dmpdata.close ()
+        return data
+    
+    def get_filelike (self, filename):
+        """get_filelike (filename) -> file
+        
+        Like get(), but tries to return the original file handle, if possible.
+        """
+        archive, type, pathname = self.files[filename]
+        if archive:
+            if type == 'zip':
+                return open_zipfile (archive, pathname)
+            elif type == 'tar':
+                return open_tarfile (archive, pathname)
+            elif type == 'tarbz2':
+                return open_tarfile (archive, pathname, 'bz2')
+            elif type == 'targz':
+                return open_tarfile (archive, pathname, 'gz')
+            else:
+                raise ValueError ("unsupported archive type")
+        return open (pathname, 'rb')
+    
+    def get_path (self, filename):
+        """get_path (filename) -> str
+        """
+        archive, type, pathname = self.files[filename]
+        if archive:
+            return '%s@%s' % (pathname, archive)
+        return pathname
+
+    def scan (self, path, excludepattern=None):
+        """scan (path) -> None
+        """
+        match = None
+        if excludepattern:
+            match = re.compile (excludepattern).match
+        join = os.path.join
+        add = self.add
+        abspath = os.path.abspath (path)
+        for (p, dirnames, filenames) in os.walk (abspath):
+            if match and match(p) is not None:
+                continue
+            for fname in filenames:
+                add (join (p, fname))

lib/threads/__init__.py

         # NOTE: TODO: we might want to return the results anyway?  This
         # should be an option.
         if stop_on_error:
-            error_ones = filter(lambda x:x.exception, results)
+            error_ones = list (filter(lambda x:x.exception, results))
             if error_ones:
                 raise error_ones[0].exception
         
             "src/base/color.c",
             "src/base/floatrect.c",
             "src/base/rect.c",
+            "src/base/streamwrapper.c",
             "src/base/surface.c",
             "src/base/font.c"],
 

src/base/basemod.c

 }
 
 int
+LongFromObj (PyObject* obj, long* val)
+{
+    PyObject* longobj;
+    long tmp;
+
+    if (!obj || !val)
+    {
+        PyErr_SetString (PyExc_TypeError, "argument is NULL");
+        return 0;
+    }
+    
+    if (PyNumber_Check (obj))
+    {
+        if (!(longobj = PyNumber_Long (obj)))
+            return 0;
+        tmp = PyLong_AsLong (longobj);
+        Py_DECREF (longobj);
+        if (tmp == -1 && PyErr_Occurred ())
+            return 0;
+        *val = tmp;
+        return 1;
+    }
+    PyErr_SetString (PyExc_TypeError, "value must be a number object");
+    return 0;
+}
+
+unsigned long
+UlongFromObj (PyObject* obj, long* val)
+{
+    PyObject* longobj;
+    unsigned long tmp;
+    
+    if (!obj || !val)
+    {
+        PyErr_SetString (PyExc_TypeError, "argument is NULL");
+        return 0;
+    }
+    
+    if (PyNumber_Check (obj))
+    {
+        if (!(longobj = PyNumber_Long (obj)))
+            return 0;
+        tmp = PyLong_AsUnsignedLong (longobj);
+        Py_DECREF (longobj);
+        if (PyErr_Occurred ())
+            return 0;
+        *val = tmp;
+        return 1;
+    }
+    PyErr_SetString (PyExc_TypeError, "value must be a number object");
+    return 0;
+}
+
+int
 UintFromObj (PyObject* obj, unsigned int* val)
 {
     PyObject* intobj;
     state = BASE_MOD_STATE(mod);
 
     /* Setup the pygame exeption */
-    state->error = PyErr_NewException ("base.Error", NULL, NULL);
+    state->error = PyErr_NewException ("pygame2.Error", NULL, NULL);
     if (!state->error)
     {
         Py_DECREF (mod);
     c_api[PYGAME_BASE_FIRSTSLOT+10] = FSizeFromObject;
     c_api[PYGAME_BASE_FIRSTSLOT+11] = ASCIIFromObject;
     c_api[PYGAME_BASE_FIRSTSLOT+12] = UTF8FromObject;
+    c_api[PYGAME_BASE_FIRSTSLOT+13] = UlongFromObj;
+    c_api[PYGAME_BASE_FIRSTSLOT+14] = LongFromObj;
 
+    streamwrapper_export_capi (c_api);
     color_export_capi (c_api);
     rect_export_capi (c_api);
     floatrect_export_capi (c_api);

src/base/floatrect.c

 _frect_repr (PyObject *self)
 {
     PyFRect *r = (PyFRect*) self;
+#ifdef IS_PYTHON_3
+    PyObject *retval;
+    int i;
+    char *b[4] = { NULL, NULL, NULL, NULL };
+    
+    b[0] = PyOS_double_to_string (r->x, 'f', 8, 0, NULL);
+    if (!b[0])
+        goto failure;
+    b[1] = PyOS_double_to_string (r->y, 'f', 8, 0, NULL);
+    if (!b[1])
+        goto failure;
+    b[2] = PyOS_double_to_string (r->w, 'f', 8, 0, NULL);
+    if (!b[2])
+        goto failure;
+    b[3] = PyOS_double_to_string (r->h, 'f', 8, 0, NULL);
+    if (!b[3])
+        goto failure;
+
+    retval = Text_FromFormat ("FRect(%s, %s, %s, %s)", b[0], b[1], b[2], b[3]);
+    goto success;
+
+failure:
+    retval = Text_FromUTF8 ("FRect(???)");
+success:
+    for (i = 0; i < 4; i++)
+    {
+        if (b[i])
+            PyMem_Free (b[i]);
+    }
+    return retval;
+#else /* !IS_PYTHON_3 */
     char b1[32], b2[32], b3[32], b4[32];
-
     if (!PyOS_ascii_formatd (b1, 32, "%.8f", r->x) ||
         !PyOS_ascii_formatd (b2, 32, "%.8f", r->y) ||
         !PyOS_ascii_formatd (b3, 32, "%.8f", r->w) ||
         !PyOS_ascii_formatd (b4, 32, "%.8f", r->h))
         return Text_FromUTF8 ("FRect(???)");
     return Text_FromFormat ("FRect(%s, %s, %s, %s)", b1, b2, b3, b4);
+#endif
 }
 
 /* FRect getters/setters */

src/base/internals.h

 #define PYGAME_RECT_INTERNAL
 #define PYGAME_FRECT_INTERNAL
 #define PYGAME_BUFFERPROXY_INTERNAL
+#define PYGAME_STREAMWRAPPER_INTERNAL
 #define PYGAME_SURFACE_INTERNAL
 #define PYGAME_FONT_INTERNAL
 
 
 extern PyTypeObject PyFont_Type;
 #define PyFont_Check(x) (PyObject_TypeCheck(x, &PyFont_Type))
-PyObject* PyFont_New(void);
-void font_export_capi(void **capi);
+PyObject* PyFont_New (void);
+void font_export_capi (void **capi);
+
+void streamwrapper_export_capi (void **capi);
 
 #endif /* _PYGAME_BASE_INTERNALS_H_ */

src/base/pgbase.h

 extern "C" {
 #endif
 
+/* Stream handling */
+
 #define PYGAME_BASE_FIRSTSLOT 0
-#define PYGAME_BASE_NUMSLOTS 13
+#define PYGAME_BASE_NUMSLOTS 15
 #ifndef PYGAME_BASE_INTERNAL
 #define PyExc_PyGameError ((PyObject*)PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT])
 #define DoubleFromObj                                                   \
     (*(int(*)(PyObject*, double*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+1])
 #define IntFromObj                                                      \
     (*(int(*)(PyObject*, int*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+2])
-#define UintFromObj                                                      \
+#define UintFromObj                                                     \
     (*(int(*)(PyObject*, unsigned int*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+3])
 #define DoubleFromSeqIndex                                              \
     (*(int(*)(PyObject*, Py_ssize_t, double*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+4])
 #define IntFromSeqIndex                                                 \
     (*(int(*)(PyObject*, Py_ssize_t, int*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+5])
-#define UintFromSeqIndex                                                 \
+#define UintFromSeqIndex                                                \
     (*(int(*)(PyObject*, Py_ssize_t, unsigned int*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+6])
 #define PointFromObject                                                 \
     (*(int(*)(PyObject*, int*, int*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+7])
 #define SizeFromObject                                                  \
     (*(int(*)(PyObject*, pgint32*, pgint32*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+8])
-#define FPointFromObject                                                 \
+#define FPointFromObject                                                \
     (*(int(*)(PyObject*, double*, double*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+9])
-#define FSizeFromObject                                                  \
+#define FSizeFromObject                                                 \
     (*(int(*)(PyObject*, double*, double*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+10])
 #define ASCIIFromObject                                                 \
     (*(int(*)(PyObject*, char**, PyObject**))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+11])
 #define UTF8FromObject                                                  \
     (*(int(*)(PyObject*, char**, PyObject**))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+12])
+#define UlongFromObj                                                    \
+    (*(int(*)(PyObject*, unsigned long*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+13])
+#define LongFromObj                                                    \
+    (*(int(*)(PyObject*, long*))PyGameBase_C_API[PYGAME_BASE_FIRSTSLOT+14])
 #endif /* PYGAME_BASE_INTERNAL */
 
+/*
+ * C-only stream access wrapper with threading support. 
+ */
+typedef struct
+{
+    PyObject *read;
+    PyObject *write;
+    PyObject *seek;
+    PyObject *tell;
+    PyObject *close;
+#ifdef WITH_THREAD
+    PyThreadState *thread;
+#endif
+} CPyStreamWrapper;
+
+#define PYGAME_STREAMWRAPPER_FIRSTSLOT \
+    (PYGAME_BASE_FIRSTSLOT + PYGAME_BASE_NUMSLOTS)
+#define PYGAME_STREAMWRAPPER_NUMSLOTS 15
+#ifndef PYGAME_STREAMWRAPPER_INTERNAL
+#define CPyStreamWrapper_New                                            \
+    (*(CPyStreamWrapper*(*)(PyObject*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+0])
+#define CPyStreamWrapper_Free                                           \
+    (*(void(*)(CPyStreamWrapper*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+1])
+#define CPyStreamWrapper_Read_Threaded                                  \
+    (*(int(*)(CPyStreamWrapper*, void*, pguint32, pguint32, pguint32*)) \
+        PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+2])
+#define CPyStreamWrapper_Read                                           \
+    (*(int(*)(CPyStreamWrapper*, void*, pguint32, pguint32, pguint32*)) \
+        PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+3])
+#define CPyStreamWrapper_Write_Threaded                                 \
+    (*(int(*)(CPyStreamWrapper*, const void*, pguint32, pguint32, pguint32*)) \
+        PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+4])
+#define CPyStreamWrapper_Write                                          \
+    (*(int(*)(CPyStreamWrapper*, const void*, pguint32, pguint32, pguint32*)) \
+        PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+5])
+#define CPyStreamWrapper_Seek_Threaded                                  \
+    (*(int(*)(CPyStreamWrapper*, pgint32, int)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+6])
+#define CPyStreamWrapper_Seek                                         \
+    (*(int(*)(CPyStreamWrapper*, pgint32, int)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+7])
+#define CPyStreamWrapper_Tell_Threaded                                  \
+    (*(pgint32(*)(CPyStreamWrapper*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+8])
+#define CPyStreamWrapper_Tell                                           \
+    (*(pgint32(*)(CPyStreamWrapper*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+9])
+#define CPyStreamWrapper_Close_Threaded                                 \
+    (*(int(*)(CPyStreamWrapper*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+10])
+#define CPyStreamWrapper_Close                                          \
+    (*(int(*)(CPyStreamWrapper*)) PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+11)
+#define IsReadableStreamObj                                             \
+    (*(int(*)(PyObject*))PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+12])
+#define IsWriteableStreamObj                                            \
+    (*(int(*)(PyObject*))PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+13])
+#define IsReadWriteableStreamObj                                        \
+    (*(int(*)(PyObject*))PyGameBase_C_API[PYGAME_STREAMWRAPPER_FIRSTSLOT+14])
+#endif /* PYGAME_STREAMWRAPPER_INTERNAL */
+
 typedef struct