[SCM] python-pyo/master: New commit with jack midi

tiago at users.alioth.debian.org tiago at users.alioth.debian.org
Thu Nov 16 15:28:36 UTC 2017


The following commit has been merged in the master branch:
commit a07e090776ad4e591c5df1fbd217abc85370aa40
Author: Tiago Bortoletto Vaz <tiago at debian.org>
Date:   Sun Nov 5 09:26:37 2017 -0500

    New commit with jack midi

diff --git a/ChangeLog b/ChangeLog
index 3f03cd5..2146a38 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2017-11-03 belangeo <belangeo at gmail.com>
+
+    * Added a callback attribute to PVAnal. The function receives magnitudes
+      and true frequencies for every analysis frame.
+
+2017-10-09 belangeo <belangeo at gmail.com>
+
+    * Added Jack midi support to the Server.
+
+2017-10-07 belangeo <belangeo at gmail.com>
+
+    * Removed internal import of the random module. Scripts importing
+      both pyo and random modules segfault on garbagge collection at exit.
+
 2017-08-28 belangeo <belangeo at gmail.com>
 
     * Upgraded version number to 0.8.7.
diff --git a/README.md b/README.md
index 0df0be4..1ec8a1e 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,11 @@ How to get pyo running from sources on OS X and linux:
 pyo was awarded **second prize** in the 
 [Lomus 2012](http://concours.afim-asso.org/2012/) Free Software Competition.
 
+**You want to help the development of pyo ?** Go to the
+[pyo features market](https://github.com/belangeo/pyo/wiki/Pyo-features-market) 
+and make a donation for the feature you want to promote. You can also submit new
+features on the mailing-list ( pyo-discuss at googlegroups.com ).
+
 ## Radio Pyo ##
 
 If you want to listen to scripts rendered in real-time, just connect to 
diff --git a/TODO.md b/TODO.md
index 4abe454..bc84438 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,3 +1,21 @@
+To do ASAP!
+===========
+
+- [DOC] Compilation for anaconda (mail from Stanley Rosenbaum).
+
+- [DOC] Live recording without xruns, use Server.recstart() to record to a
+  file on a tmpfs, which is basically a filesystem in RAM.
+  mail: "Server recording to wav generates many xruns (Aug 26)".
+
+- Better error message when trying to open an input device but there is
+  none on the system. Automatic deactivation of duplex mode if no input.
+
+- E-Pyo: Reading preferences file should handle utf-8 paths.
+    - linux = ok
+    - osx = ok
+
+- Unicode paths don't work with python 3.6 on Windows.
+
 This is a list of features/fixes to implement for future releases
 =================================================================
 
@@ -19,9 +37,6 @@ Server
 - A method to retrieve a graph of the internal state of the server 
   (active objects, connections, attribute values, ...).
 
-- Remove, if possible, PyGILState_Ensure/PyGILState_Release from 
-  the process_buffers function.
-
 Examples
 --------
 
@@ -61,8 +76,6 @@ GUI
 
 - Keyboard, a virtual MIDI keyboard (adapted from Zyne's one).
 
-- Ability to set channel "name" in the view of PyoGuiScope and PyoGuiSpectrum.
-
 Tables
 ------
 
@@ -77,8 +90,12 @@ Matrices
 E-Pyo
 -----
 
+- Split the file name (shown in the notebook tab) with the last dot, not the first.
+
 - Complete review of E-Pyo on Windows. Lot of features don't seem to work.
 
+    - Project tree refresh function doesn't work.
+
 - Window splitter to show more than one file at the time (multiple 
   views) ?
 
diff --git a/doc-sphinx/build.py b/doc-sphinx/build.py
index 30fa130..12f5efc 100644
--- a/doc-sphinx/build.py
+++ b/doc-sphinx/build.py
@@ -102,7 +102,7 @@ os.system("sphinx-build -a -E -j 4 -b %s ./source %s" % (build_format, build_fol
 if build_format == "latex":
     os.system("cd build_latex; pdflatex -interaction nonstopmode Pyo;  pdflatex -interaction nonstopmode Pyo")
 
-rep = raw_input("Do you want to upload to ajax server (y/n) ? ")
+rep = input("Do you want to upload to ajax server (y/n) ? ")
 if rep == "y":
         os.system("scp -r build_html/* jeadum1 at ajaxsoundstudio.com:/home/jeadum1/ajaxsoundstudio.com/pyodoc")
 
diff --git a/doc-sphinx/source/compiling.rst b/doc-sphinx/source/compiling.rst
index cc40692..0712b05 100644
--- a/doc-sphinx/source/compiling.rst
+++ b/doc-sphinx/source/compiling.rst
@@ -11,14 +11,17 @@ Dependencies
 
 To compile pyo with all its features, you will need the following dependencies: 
 
-- `Python 2.7.x or 3.5.x <https://www.python.org/downloads/>`_
-- `WxPython 3.0 <http://www.wxpython.org/download.php/>`_
+- `Python 2.7 or 3.5 or 3.6 (recommended) <https://www.python.org/downloads/>`_. On Windows, install the 32-bit version of Python.
+- `WxPython 3.0.2.0 (classic) or 4.0.0 (phoenix, recommended) <http://www.wxpython.org/download.php/>`_
 - `Portaudio <http://www.portaudio.com/>`_
 - `Portmidi <http://portmedia.sourceforge.net/portmidi/>`_
 - `libsndfile <http://www.mega-nerd.com/libsndfile/>`_
 - `liblo <http://liblo.sourceforge.net/>`_
 - `git <https://git-scm.com/>`_ (if you want the latest sources)
 
+Please note that under MacOS you will need to install the 
+**Apple's developer tools** to compile pyo.
+
 Getting sources
 ---------------
 
@@ -32,9 +35,6 @@ You can download pyo's sources by checking out the source code
 Compilation
 ---------------
 
-Please note that under MacOS you will need to install the 
-**Apple's developer tools** to compile pyo.
-
 Once you have all the required dependencies, go in pyo's directory: 
 
 .. code-block:: bash
@@ -120,21 +120,22 @@ Compilation scripts
 *******************
 
 In the ./scripts folder, there is some alternate scripts to simplify the 
-compilation process a little bit.
+compilation process a little bit. These scripts will compile pyo for the
+version of python pointed to by the command `python`.
 
-To compile both 32-bit and 64-bit resolutions on linux (with jack support):
+To compile both 32-bit and 64-bit resolutions on linux with jack support:
 
 .. code-block:: bash
 
     sudo sh scripts/compile_linux_withJack.sh
 
-To compile both 32-bit and 64-bit resolutions on macOS (without Jack):
+To compile both 32-bit and 64-bit resolutions on macOS without Jack support:
 
 .. code-block:: bash
 
     sudo sh scripts/compile_OSX.sh
 
-To compile both 32-bit and 64-bit resolutions on macOS (with Jack):
+To compile both 32-bit and 64-bit resolutions on macOS with Jack support (Jack headers must be present on the system):
 
 .. code-block:: bash
 
@@ -144,26 +145,51 @@ Debian & Ubuntu (apt-get)
 -------------------------
 
 Under Debian & Ubuntu you can type the following commands to get pyo up 
-and running: 
+and running.
+
+For Python 2.7
+************** 
 
 .. code-block:: bash
 
-    sudo apt-get install libjack-jackd2-dev libportmidi-dev portaudio19-dev liblo-dev 
-    sudo apt-get install libsndfile-dev python-dev python-tk 
-    sudo apt-get install python-imaging-tk python-wxgtk3.0
+    sudo apt-get install libjack-jackd2-dev libportmidi-dev portaudio19-dev liblo-dev libsndfile-dev
+    sudo apt-get install python-dev python-tk python-imaging-tk python-wxgtk3.0
     git clone https://github.com/belangeo/pyo.git
     cd pyo
     sudo python setup.py install --install-layout=deb --use-jack --use-double
 
 * On Ubuntu system prior to vivid, wxpython 3.0 must be compiled from sources.
- 
+
+For Python 3.5
+************** 
+
+.. code-block:: bash
+
+    sudo apt-get install libjack-jackd2-dev libportmidi-dev portaudio19-dev liblo-dev libsndfile-dev
+    sudo apt-get install python3-dev python3-tk python3-pil.imagetk python3-pip
+    git clone https://github.com/belangeo/pyo.git
+    cd pyo
+    sudo python3 setup.py install --install-layout=deb --use-jack --use-double
+
+If you want to be able to use all of pyo's gui widgets, you will need wxPython-phoenix. 
+
+- Install requirements outlined in the README.rst at `https://github.com/wxWidgets/Phoenix/blob/master/README.rst <https://github.com/wxWidgets/Phoenix/blob/master/README.rst>`_
+
+- Install wxPython with pip:
+
+.. code-block:: bash
+
+    sudo pip3 install -U wxPython
+
+Be patient, the last step will take a while!
+
 MacOS (Homebrew)
 ----------------
 
 Under macOS, it is very simple to build pyo from sources with the Homebrew 
 package manager.
 
-First, install Python and WxPython from the binary installers.
+First, install Python and WxPython from their respective download pages (`Python <https://www.python.org/downloads/>`_, `WxPython <http://www.wxpython.org/download.php/>`_).
 
 Second, you need to install `Homebrew <http://brew.sh/>`_. Then, in a terminal 
 window:
diff --git a/doc-sphinx/source/download.rst b/doc-sphinx/source/download.rst
index 56eac40..897e272 100644
--- a/doc-sphinx/source/download.rst
+++ b/doc-sphinx/source/download.rst
@@ -8,9 +8,11 @@ To download the latest pre-compiled version of pyo, go to the pyo's
 `web page <http://ajaxsoundstudio.com/software/pyo/>`_.
 
 Under Debian and Fedora distros, you can get pyo from the package manager. 
-The library's name is **python-pyo**. 
+The library's name is **python-pyo** or **python2-pyo** for Python 2.7
+and **python3-pyo** for Python 3.5+. 
 
-If you are running Arch linux, the package is called **python2-pyo**.
+If you are running Arch linux, the package is called **python2-pyo**. There
+is no package yet for Python 3.5+.
 
 
 Content of the installer
@@ -24,12 +26,11 @@ E-Pyo, a simple text editor especially tuned to edit and run audio python script
 Pyo is a python module...
 -----------------------------
 
-... which means that python must be present (version 2.7.x is the current one, 
-version 3.5.x is experimental but everything seems to work well so far) on the 
-system. If python is not installed, you can download it on 
+... which means that python must be present (version 2.7, 3.5 or 3.6 (recommended)) 
+on the system. If python is not installed, you can download it on 
 `python.org <https://www.python.org/downloads/>`_.
 
 Pyo also offers some GUI facilities to control or visualize the audio processing.
 If you want to use all of pyo's GUI features, you must install WxPython 3.0 
-(**classic** for python 2.7.x and **phoenix** for python 3.5.x), available on 
+(**classic** for python 2.7 and **phoenix** for python 3.5+), available on 
 `wxpython.org <http://wxpython.org/download.php>`_.
\ No newline at end of file
diff --git a/doc-sphinx/source/index.rst b/doc-sphinx/source/index.rst
index a1c6bf5..588895c 100644
--- a/doc-sphinx/source/index.rst
+++ b/doc-sphinx/source/index.rst
@@ -26,6 +26,7 @@ Parts of the documentation
    compiling
    structure
    gettingstarted
+   winaudioinspect
    perftips
    api/index
 
diff --git a/doc-sphinx/source/perftips.rst b/doc-sphinx/source/perftips.rst
index e52f048..02aab77 100644
--- a/doc-sphinx/source/perftips.rst
+++ b/doc-sphinx/source/perftips.rst
@@ -287,8 +287,8 @@ the same noise for multiple denormalizations:
 Use a PyoObject when available
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Always look first if a PyoObject does what you want, it will always more
-efficient than a the same process written from scratch.
+Always look first if a PyoObject does what you want, it will always be more
+efficient than the same process written from scratch.
 
 This construct, although pedagogically valid, will never be more efficient, in
 term of CPU and memory usage, than a native PyoObject (Phaser) written in C.
diff --git a/doc-sphinx/source/winaudioinspect.rst b/doc-sphinx/source/winaudioinspect.rst
new file mode 100644
index 0000000..90820d4
--- /dev/null
+++ b/doc-sphinx/source/winaudioinspect.rst
@@ -0,0 +1,161 @@
+Configuring the audio output (especially on Windows)
+====================================================
+
+Here is some tips to help you to configure the audio input/output on Windows. 
+Some of these procedures are also valid for other systems.
+
+How to choose the audio host api on Windows
+-------------------------------------------
+
+Choosing the good audio API on Windows can turn out to be a real headache.
+
+This document presents a script that will inspect your system and tell you if:
+
+- Pyo can run in duplex mode. That means both audio input and output instead of output only.
+
+- Pyo is able to connect to the different host APIs that are usually available on Windows.
+
+In the hope that this will help you having a good experience with pyo under Windows!
+
+`https://github.com/belangeo/pyo/tree/master/scripts/win_audio_drivers_inspector.py 
+<https://github.com/belangeo/pyo/tree/master/scripts/win_audio_drivers_inspector.py>`_
+
+.. code-block:: python
+
+    """
+    Windows audio host inspector.
+
+    This script will check if pyo can run in duplex mode (both audio input and output)
+    and will test every host API to help the user in making his audio device choice.
+
+    """
+    import sys, time
+    from pyo import *
+
+    if sys.platform == "win32":
+        host_apis = ["mme", "directsound", "asio", "wasapi", "wdm-ks"]
+    else:
+        print("This program must be used on a windows system! Ciao!")
+        exit()
+
+
+    print("* Checking for any available audio input... *")
+
+    input_names, input_indexes  = pa_get_input_devices()
+
+    print("* Checking audio output hosts... *")
+
+    s = Server(duplex=0)
+    s.verbosity = 0
+
+    host_results = []
+    for host in host_apis:
+        print("* Testing %s... *" % host)
+        try:
+            s.reinit(buffersize=1024, duplex=0, winhost=host)
+            s.boot().start()
+            a = Sine(freq=440, mul=0.2).out()
+            time.sleep(2)
+            s.stop()
+            s.shutdown()
+            host_results.append(True)
+        except:
+            host_results.append(False)
+
+    print("\nResults")
+    print("-------\n")
+
+    if len(input_names):
+        print("Duplex mode OK!")
+        print("Initialize the Server with duplex=1 as argument.\n")
+    else:
+        print("No input available. Duplex mode should be turned off.")
+        print("Initialize the Server with duplex=0 as argument.\n")
+
+    for i, host in enumerate(host_apis):
+        if host_results[i]:
+            print("Host: %s  ==>  OK!" % host)
+        else:
+            print("Host: %s  ==>  Failed..." % host)
+
+    print("Initialize the Server with the desired host given to winhost argument.")
+
+    print("\nFinished!")
+
+Tunning the Windows WASAPI driver
+--------------------------------
+
+The Windows Audio Session API (WASAPI) is Microsoft's most modern method for talking with audio devices.
+It is available in Windows since Vista. Pyo's default host is the WASAPI. If the script above tells you:
+
+    Host: wasapi ==> Failed...
+
+there is some things you can do to make it work. Open the **Sound** window by double-clicking on the volume
+icon and choosing *Playback Devices*. Here, select your device and click on the *Properties* button. In 
+the *advanced* tab, make sure that the sampling rate is the same that the one used by pyo (pyo defaults to
+44100 Hz). You can check the exclusive mode box if you want, this will bypass the system mixer, default 
+settings, and typically any effects provided by the audio driver.
+
+Perform the same in the *recording* tab if you want to run pyo in duplex mode. If you got the message:
+    
+    No input available. Duplex mode should be turned off.
+
+you'll have to make sure first that there is an available input device in that tab.
+
+If you use a cheap soundcard (typically, any built in soundcard is not very good!), you may have to increase
+the buffer size of the pyo's Server in order to avoid glitches in the audio streams.
+
+Server initialization examples
+------------------------------
+
+.. code-block:: python
+
+    # sampling rate = 44100 Hz, buffer size = 256, channels = 2, full duplex, host = WASAPI
+    s = Server()
+
+    # sampling rate = 48000 Hz, buffer size = 1024, channels = 2, full duplex, host = WASAPI
+    s = Server(sr=48000, buffersize=1024)
+
+    # sampling rate = 48000 Hz, buffer size = 512, channels = 2, output only, host = ASIO
+    s = Server(sr=48000, buffersize=512, duplex=0, winhost="asio")
+
+    # sampling rate = 96000 Hz, buffer size = 128, channels = 1, full duplex, host = ASIO
+    s = Server(sr=96000, nchnls=1, buffersize=128, duplex=1, winhost="asio")
+
+Choosing a specific device
+--------------------------
+
+A single host API can target more than one available devices.
+
+There is some useful functions that can help you in the choice of the audio device:
+
+- **pa_list_host_apis()**: Prints the list of audio host APIs.
+- **pa_list_devices()**: Prints the list of audio devices. The first column if the index of the device.
+- **pa_get_default_input()**: Returns the index of the default input device.
+- **pa_get_default_output()**: Returns the index of the default output device.
+- **pa_get_default_devices_from_host(host)**: Returns the default input and output devices for a given audio host.
+
+Run this code to see the current state of your audio setup:
+
+.. code-block:: python
+
+    from pyo import *
+
+    print("Audio host APIS:")
+    pa_list_host_apis()
+    pa_list_devices()
+    print("Default input device: %i" % pa_get_default_input())
+    print("Default output device: %i" % pa_get_default_output())
+
+If the default device for the desired host is not the one you want, you can tell the Server
+which device you want to use with the *setInputDevice(x)* and *setOutputDevice(x)* methods. These 
+methods take the index of the desired device and must be called before booting the Server. Ex:
+
+.. code-block:: python
+
+    from pyo import *
+    
+    s = Server(duplex=0)
+    s.setOutputDevice(0)
+    s.boot()
+
diff --git a/examples/wxgui/01_gui_widgets_example.py b/examples/wxgui/01_gui_widgets_example.py
index 4c2f384..0714c4e 100644
--- a/examples/wxgui/01_gui_widgets_example.py
+++ b/examples/wxgui/01_gui_widgets_example.py
@@ -27,7 +27,7 @@ sc = Scope(f)
 
 class MyFrame(wx.Frame):
 
-    def __init__(self, parent, title, pos=(50, 50), size=(850, 600)):
+    def __init__(self, parent, title, pos=(50, 50), size=(1000, 600)):
         wx.Frame.__init__(self, parent, -1, title, pos, size)
         
         self.Bind(wx.EVT_CLOSE, self.on_quit)
diff --git a/include/ad_jack.h b/include/ad_jack.h
index c41b874..892fba8 100644
--- a/include/ad_jack.h
+++ b/include/ad_jack.h
@@ -22,26 +22,41 @@
 #define _AD_JACK_H
 
 #include <jack/jack.h>
+#include <jack/midiport.h>
 #include "servermodule.h"
 
 typedef struct {
+    unsigned long timestamp;
+    int status;
+    int data1;
+    int data2;
+} PyoJackMidiEvent;
+
+typedef struct {
     jack_client_t *jack_client;
     jack_port_t **jack_in_ports;
     jack_port_t **jack_out_ports;
+    unsigned int midi_event_count;
+    PyoJackMidiEvent *midi_events;
+    jack_port_t *jack_midiin_port;
+    jack_port_t *jack_midiout_port;
 } PyoJackBackendData;
 
-int jack_callback(jack_nframes_t nframes, void *arg);
-int jack_srate_cb(jack_nframes_t nframes, void *arg);
-int jack_bufsize_cb(jack_nframes_t nframes, void *arg);
-void jack_error_cb(const char *desc);
-void jack_shutdown_cb(void *arg);
 int jack_input_port_set_names(Server *self);
 int jack_output_port_set_names(Server *self);
-void Server_jack_autoconnect(Server *self);
+int jack_midi_input_port_set_name(Server *self);
+int jack_midi_output_port_set_name(Server *self);
 int Server_jack_init(Server *self);
 int Server_jack_deinit(Server *self);
 int Server_jack_start(Server *self);
 int Server_jack_stop(Server *self);
 
+void jack_noteout(Server *self, int pit, int vel, int chan, long timestamp);
+void jack_afterout(Server *self, int pit, int vel, int chan, long timestamp);
+void jack_ctlout(Server *self, int ctlnum, int value, int chan, long timestamp);
+void jack_programout(Server *self, int value, int chan, long timestamp);
+void jack_pressout(Server *self, int value, int chan, long timestamp);
+void jack_bendout(Server *self, int value, int chan, long timestamp);
+
 #endif 
 /* _AD_JACK_H */
diff --git a/include/servermodule.h b/include/servermodule.h
index 85dba44..14326c1 100644
--- a/include/servermodule.h
+++ b/include/servermodule.h
@@ -41,7 +41,8 @@ typedef enum {
 } PyoAudioBackendType;
 
 typedef enum {
-    PyoPortmidi = 0
+    PyoPortmidi = 0,
+    PyoJackMidi = 1
 } PyoMidiBackendType;
 
 /* Portmidi midi message and event type clones. */
@@ -76,8 +77,12 @@ typedef struct {
     int jackautoout; /* jack port auto-connection (on by default) */
     PyObject *jackAutoConnectInputPorts; /* list of lists of jack auto-connection ports to pyo inputs */
     PyObject *jackAutoConnectOutputPorts; /* list of lists of jack auto-connection ports from pyo outputs */
-    PyObject *jackInputPortNames; /* string or list of strings (input port short names for jack server */
-    PyObject *jackOutputPortNames; /* string or list of strings (output port short names for jack server */
+    PyObject *jackInputPortNames; /* string or list of strings (input port short names for jack server) */
+    PyObject *jackOutputPortNames; /* string or list of strings (output port short names for jack server) */
+    PyObject *jackAutoConnectMidiInputPort; /* lists of jack auto-connection ports to pyo midi input */
+    PyObject *jackAutoConnectMidiOutputPort; /* lists of jack auto-connection ports from pyo midi output */
+    PyObject *jackMidiInputPortName; /* string (midi input port short names for jack server) */
+    PyObject *jackMidiOutputPortName; /* string (midi output port short names for jack server) */
     int isJackTransportSlave;
     int jack_transport_state; /* 0 = stopped, 1 = started */
     PyoMidiEvent midiEvents[200];
@@ -100,6 +105,7 @@ typedef struct {
     int midi_output;
     int withPortMidi;
     int withPortMidiOut;
+    int withJackMidi;
     int midiActive;
     int allowMMMapper;
     int server_started;
diff --git a/pyolib/_core.py b/pyolib/_core.py
index 275f628..1418932 100644
--- a/pyolib/_core.py
+++ b/pyolib/_core.py
@@ -28,7 +28,7 @@ License along with pyo.  If not, see <http://www.gnu.org/licenses/>.
 """
 import os
 import sys
-import random
+import time
 import inspect
 import tempfile
 from subprocess import call
@@ -126,6 +126,27 @@ FUNCTIONS_INIT_LINES = {
     "getPyoKeywords": "getPyoKeywords()"
 }
 
+def get_random_integer(mx=32767):
+    if sys.version_info[0] < 3 and sys.version_info[1] < 3:
+        seed = int(str(time.clock()).split(".")[1])
+    else:
+        seed = int(str(time.process_time()).split(".")[1])
+    return (seed * 31351 + 21997) % mx
+
+def listscramble(lst):
+    if sys.version_info[0] < 3 and sys.version_info[1] < 3:
+        seed = int(str(time.clock()).split(".")[1])
+    else:
+        seed = int(str(time.process_time()).split(".")[1])
+    l = lst[:]
+    new = []
+    pos = 1
+    while l:
+        pos = (pos * seed) % len(l)
+        new.append(l[pos])
+        del l[pos]
+    return new
+
 def stringencode(st):
     if sys.version_info[0] >= 3:
         if type(st) is str:
@@ -498,15 +519,14 @@ def convertArgsToLists(*args):
     Return new args and maximum list length.
 
     """
-    converted = []
-    for i in args:
-        if isinstance(i, PyoObjectBase) or isinstance(i, list):
-            converted.append(i)
-        else:
-            converted.append([i])
+    converted = list(args)
+    for i, arg in enumerate(converted):
+        if not isinstance(arg, PyoObjectBase) and not isinstance(arg, list):
+            converted[i] = [arg]
 
-    max_length = max(len(i) for i in converted)
-    return tuple(converted + [max_length])
+    max_length = max(len(arg) for arg in converted)
+    converted.append(max_length)
+    return tuple(converted)
 
 def wrap(arg, i):
     """
@@ -1296,8 +1316,7 @@ class PyoObject(PyoObjectBase):
         else:
             if chnl < 0:
                 [obj.out(i*inc, wrap(dur, i), wrap(delay, i)) \
-                 for i, obj in enumerate(random.sample(self._base_objs,
-                                                       len(self._base_objs)))]
+                 for i, obj in enumerate(listscramble(self._base_objs))]
             else:
                 [obj.out(chnl+i*inc, wrap(dur, i), wrap(delay, i)) \
                  for i, obj in enumerate(self._base_objs)]
@@ -2008,10 +2027,11 @@ class PyoTableObject(PyoObjectBase):
             table: PyoTableObject
                 The source table.
             srcpos: int, optional
-                The start position in the source table. Defaults to 0.
+                The start position, in samples, in the source table.
+                Defaults to 0.
             destpos ; int, optional
-                The start position in the destination (self) table. Defaults
-                to 0.
+                The start position, in samples, in the destination (self) table.
+                Defaults to 0.
             length: int, optional
                 The number of samples to copy from source to destination. if
                 length is negative, the length of the smallest table is used.
diff --git a/pyolib/_tkwidgets.py b/pyolib/_tkwidgets.py
index 4216428..4873530 100644
--- a/pyolib/_tkwidgets.py
+++ b/pyolib/_tkwidgets.py
@@ -151,16 +151,22 @@ class PyoObjectControl(tk.Frame):
         self._displays = {}
         self._maps = {}
         self._sigs = {}
+        self._res = {}
         for i, m in enumerate(self._map_list):
-            key, init = m.name, m.init
+            key, init, res, dataOnly = m.name, m.init, m.res, m.dataOnly
             # filters PyoObjects
             if type(init) not in [list, float, int]:
                 self._excluded.append(key)
             else:
                 self._maps[key] = m
+                self._res[key] = res
                 # label (param name)
-                label = tk.Label(self, height=1, width=10, highlightthickness=0,
-                                 text=key)
+                if dataOnly:
+                    label = tk.Label(self, height=1, width=10, highlightthickness=0,
+                                     text=key+" *")
+                else:
+                    label = tk.Label(self, height=1, width=10, highlightthickness=0,
+                                     text=key)
                 label.grid(row=i, column=0)
                 # create and pack slider
                 if not isinstance(init, list):
@@ -189,13 +195,15 @@ class PyoObjectControl(tk.Frame):
                 else:
                     self._displays[key].set("\n".join(["%.4f" % i for i in init]))
                 # set obj attribute to PyoObject SigTo
-                self._sigs[key] = SigTo(init, .025, init)
-                refStream = self._obj.getBaseObjects()[0]._getStream()
-                server = self._obj.getBaseObjects()[0].getServer()
-                for k in range(len(self._sigs[key].getBaseObjects())):
-                    curStream = self._sigs[key].getBaseObjects()[k]._getStream()
-                    server.changeStreamPosition(refStream, curStream)
-                setattr(self._obj, key, self._sigs[key])
+                if not dataOnly:
+                    self._values[key] = init
+                    self._sigs[key] = SigTo(init, .025, init)
+                    refStream = self._obj.getBaseObjects()[0]._getStream()
+                    server = self._obj.getBaseObjects()[0].getServer()
+                    for k in range(len(self._sigs[key].getBaseObjects())):
+                        curStream = self._sigs[key].getBaseObjects()[k]._getStream()
+                        server.changeStreamPosition(refStream, curStream)
+                    setattr(self._obj, key, self._sigs[key])
         # padding
         top = self.winfo_toplevel()
         top.rowconfigure(0, weight=1)
@@ -219,8 +227,14 @@ class PyoObjectControl(tk.Frame):
             value = [self._maps[key].get(float(y)) for y in x]
             self._displays[key].set("\n".join(["%.4f" % i for i in value]))
 
-        self._values[key] = value
-        setattr(self._sigs[key], "value", value)
+        if self._res[key].startswith("i"):
+            value = int(value)
+
+        if key in self._values:
+            self._values[key] = value
+            setattr(self._sigs[key], "value", value)
+        else:
+            setattr(self._obj, key, value)
 
 ######################################################################
 ### View window for PyoTableObject
diff --git a/pyolib/_widgets.py b/pyolib/_widgets.py
index 6f2f348..80d6f32 100644
--- a/pyolib/_widgets.py
+++ b/pyolib/_widgets.py
@@ -27,7 +27,6 @@ License along with pyo.  If not, see <http://www.gnu.org/licenses/>.
 """
 import sys
 import os
-import random
 
 use_wx = 1
 if "PYO_GUI_WX" in os.environ:
@@ -136,7 +135,8 @@ def wxDisplayWindow(f, title):
             MAX_X = x + CURRENT_X
         f.SetPosition((px, py))
     else:
-        f.SetPosition((random.randint(250, 500), random.randint(200, 400)))
+        CURRENT_X, MAX_X, NEXT_Y = 50, 50, 50
+        wxDisplayWindow(f, title)
     f.Show()
 
 def wxShowWindow(f, title, root):
@@ -422,4 +422,4 @@ def createServerGUI(nchnls, start, stop, recstart, recstop, setAmp, started,
         wx.CallAfter(wxCreateDelayedScopeWindows)
         wx.CallAfter(wxCreateDelayedExprEditorWindows)
         wx.CallAfter(f.Raise)
-    return f, win
+    return f, win, PYO_USE_WX
diff --git a/pyolib/_wxwidgets.py b/pyolib/_wxwidgets.py
index 01dce27..31c97e1 100644
--- a/pyolib/_wxwidgets.py
+++ b/pyolib/_wxwidgets.py
@@ -20,7 +20,7 @@ GNU Lesser General Public License for more details.
 You should have received a copy of the GNU Lesser General Public
 License along with pyo.  If not, see <http://www.gnu.org/licenses/>.
 """
-import wx, os, sys, math, time, random, unicodedata
+import wx, os, sys, math, time, unicodedata
 import wx.stc as stc
 
 if "phoenix" in wx.version():
@@ -28,6 +28,8 @@ if "phoenix" in wx.version():
     wx.EmptyBitmap = wx.Bitmap
     wx.EmptyImage = wx.Image
     wx.BitmapFromImage = wx.Bitmap
+    wx.Image_HSVValue = wx.Image.HSVValue
+    wx.Image_HSVtoRGB = wx.Image.HSVtoRGB
 
 BACKGROUND_COLOUR = "#EBEBEB"
 
@@ -1436,6 +1438,14 @@ class SpectrumDisplay(wx.Frame):
             X_OFF = 24
         else:
             X_OFF = 16
+
+        if self.obj is None:
+            initgain = 0.0
+            self.channelNamesVisible = True
+        else:
+            initgain = self.obj.gain
+            self.channelNamesVisible = self.obj.channelNamesVisible
+
         tw, th = self.GetTextExtent("Start")
         self.activeTog = wx.ToggleButton(self.panel, -1, label="Start", size=(tw+X_OFF, th+10))
         self.activeTog.SetValue(1)
@@ -1473,7 +1483,7 @@ class SpectrumDisplay(wx.Frame):
                  valtype='float', log=False, function=self.setZoomH)
         self.box.Add(self.zoomH, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
         self.dispBox.Add(self.box, 1, wx.EXPAND, 0)
-        self.gainSlider = ControlSlider(self.panel, -24, 24, 0, outFunction=self.setGain, orient=wx.VERTICAL)
+        self.gainSlider = ControlSlider(self.panel, -24, 24, initgain, outFunction=self.setGain, orient=wx.VERTICAL)
         self.dispBox.Add(self.gainSlider, 0, wx.EXPAND|wx.TOP, 5)
         self.dispBox.AddSpacer(5)
         self.mainBox.Add(self.dispBox, 1, wx.EXPAND)
@@ -1531,6 +1541,10 @@ class SpectrumDisplay(wx.Frame):
         self.spectrumPanel.setMscaling(x)
         wx.CallAfter(self.spectrumPanel.Refresh)
 
+    def showChannelNames(self, visible):
+        self.spectrumPanel.showChannelNames(visible)
+        self.channelNamesVisible = visible
+
     def _destroy(self, evt):
         self.obj._setViewFrame(None)
         self.Destroy()
@@ -1542,21 +1556,30 @@ class SpectrumPanel(wx.Panel):
                  pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
         wx.Panel.__init__(self, parent, pos=pos, size=size, style=style)
         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        self.SetMinSize((300,100))
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.Bind(wx.EVT_SIZE, self.OnSize)
-        #self.chnls = chnls
+        if chnls == 1:
+            self.chnls = 64
+        else:
+            self.chnls = chnls
+        try:
+            self.channelNamesVisible = self.GetParent().GetParent().channelNamesVisible
+        except:
+            self.channelNamesVisible = True
         self.img = None
         self.obj = None
         self.lowfreq = lowfreq
         self.highfreq = highfreq
         self.fscaling = fscaling
         self.mscaling = mscaling
-        self.pens = [wx.Pen(wx.Colour(166,4,0)), wx.Pen(wx.Colour(8,11,116)), wx.Pen(wx.Colour(0,204,0)),
-                    wx.Pen(wx.Colour(255,167,0)), wx.Pen(wx.Colour(133,0,75)), wx.Pen(wx.Colour(255,236,0)),
-                    wx.Pen(wx.Colour(1,147,154)), wx.Pen(wx.Colour(162,239,0))]
-        self.brushes = [wx.Brush(wx.Colour(166,4,0,128)), wx.Brush(wx.Colour(8,11,116,128)), wx.Brush(wx.Colour(0,204,0,128)),
-                        wx.Brush(wx.Colour(255,167,0,128)), wx.Brush(wx.Colour(133,0,75,128)), wx.Brush(wx.Colour(255,236,0,128)),
-                        wx.Brush(wx.Colour(1,147,154,128)), wx.Brush(wx.Colour(162,239,0,128))]
+        self.pens = []
+        self.brushes = []
+        for x in range(self.chnls):
+            hsv = wx.Image_HSVValue(x / float(self.chnls), 1.0, 1.0)
+            rgb = wx.Image_HSVtoRGB(hsv)
+            self.pens.append(wx.Pen(wx.Colour(rgb.red, rgb.green, rgb.blue)))
+            self.brushes.append(wx.Brush(wx.Colour(rgb.red, rgb.green, rgb.blue, 128)))
         if sys.platform == "win32" or sys.platform.startswith("linux"):
             self.dcref = wx.BufferedPaintDC
         else:
@@ -1592,6 +1615,9 @@ class SpectrumPanel(wx.Panel):
     def setHighFreq(self, x):
         self.highfreq = x
 
+    def showChannelNames(self, visible):
+        self.channelNamesVisible = visible
+
     def OnPaint(self, evt):
         w,h = self.GetSize()
         dc = self.dcref(self)
@@ -1716,14 +1742,15 @@ class SpectrumPanel(wx.Panel):
         if self.img is not None:
             last_tw = tw
             # legend
-            tw, th = dc.GetTextExtent("chan 8")
-            for i in range(len(self.img)):
-                dc.SetTextForeground(self.pens[i%8].GetColour())
-                dc.DrawText("chan %d" % (i+1), w-tw-20-last_tw, i*th+th+7)
+            if len(self.img) > 1 and self.channelNamesVisible:
+                tw, th = dc.GetTextExtent("chan 8")
+                for i in range(len(self.img)):
+                    dc.SetTextForeground(self.pens[i%self.chnls].GetColour())
+                    dc.DrawText("chan %d" % (i+1), w-tw-20-last_tw, i*th+th+7)
             # channel spectrums
             for i, samples in enumerate(self.img):
-                gc.SetPen(self.pens[i%8])
-                gc.SetBrush(self.brushes[i%8])
+                gc.SetPen(self.pens[i%self.chnls])
+                gc.SetBrush(self.brushes[i%self.chnls])
                 gc.DrawLines(samples)
 
 ######################################################################
@@ -1790,6 +1817,9 @@ class ScopeDisplay(wx.Frame):
     def update(self, points):
         self.scopePanel.setImage(points)
 
+    def showChannelNames(self, visible):
+        self.scopePanel.showChannelNames(visible)
+
     def _destroy(self, evt):
         self.obj._setViewFrame(None)
         self.Destroy()
@@ -1800,6 +1830,7 @@ class ScopePanel(wx.Panel):
                  size=wx.DefaultSize, style=0):
         wx.Panel.__init__(self, parent, pos=pos, size=size, style=style)
         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        self.SetMinSize((300,100))
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.Bind(wx.EVT_SIZE, self.OnSize)
         self.img = [[]]
@@ -1807,13 +1838,18 @@ class ScopePanel(wx.Panel):
         if self.obj is not None:
             self.gain = self.obj.gain
             self.length = self.obj.length
+            self.chnls = len(self.obj)
+            self.channelNamesVisible = self.obj.channelNamesVisible
         else:
             self.gain = 1
             self.length = 0.05
-        #self.chnls = len(self.obj)
-        self.pens = [wx.Pen(wx.Colour(166,4,0), width=2), wx.Pen(wx.Colour(8,11,116), width=2), wx.Pen(wx.Colour(0,204,0), width=2),
-                    wx.Pen(wx.Colour(255,167,0), width=2), wx.Pen(wx.Colour(133,0,75), width=2), wx.Pen(wx.Colour(255,236,0), width=2),
-                    wx.Pen(wx.Colour(1,147,154), width=2), wx.Pen(wx.Colour(162,239,0), width=2)]
+            self.chnls = 64
+            self.channelNamesVisible = True
+        self.pens = []
+        for x in range(self.chnls):
+            hsv = wx.Image_HSVValue(x / float(self.chnls), 1.0, 1.0)
+            rgb = wx.Image_HSVtoRGB(hsv)
+            self.pens.append(wx.Pen(wx.Colour(rgb.red, rgb.green, rgb.blue)))
 
         if sys.platform == "win32" or sys.platform.startswith("linux"):
             self.dcref = wx.BufferedPaintDC
@@ -1839,6 +1875,9 @@ class ScopePanel(wx.Panel):
         self.img = points
         wx.CallAfter(self.Refresh)
 
+    def showChannelNames(self, visible=True):
+        self.channelNamesVisible = visible
+
     def OnPaint(self, evt):
         w,h = self.GetSize()
         dc = self.dcref(self)
@@ -1850,10 +1889,14 @@ class ScopePanel(wx.Panel):
         gc.SetPen(wx.Pen('#000000', width=1, style=wx.SOLID))
         gc.SetBrush(wx.Brush("#FFFFFF", style=wx.TRANSPARENT))
         dc.SetTextForeground("#444444")
-        if sys.platform in "darwin":
+        if sys.platform == "darwin":
             font, ptsize = dc.GetFont(), dc.GetFont().GetPointSize()
             font.SetPointSize(ptsize - 3)
             dc.SetFont(font)
+        elif sys.platform.startswith("linux"):
+            font, ptsize = dc.GetFont(), dc.GetFont().GetPointSize()
+            font.SetPointSize(ptsize - 1)
+            dc.SetFont(font)
         elif sys.platform == "win32":
             font = dc.GetFont()
             font.SetPointSize(8)
@@ -1881,15 +1924,16 @@ class ScopePanel(wx.Panel):
         # draw waveforms
         for i, samples in enumerate(self.img):
             gc.SetPen(self.pens[i%8])
-            if len(samples):
+            if len(samples) > 1:
                 gc.DrawLines(samples)
 
         # legend
         last_tw = tw
-        tw, th = dc.GetTextExtent("chan 8")
-        for i in range(len(self.img)):
-            dc.SetTextForeground(self.pens[i%8].GetColour())
-            dc.DrawText("chan %d" % (i+1), w-tw-20-last_tw, i*th+10)
+        if len(self.img) > 1 and self.channelNamesVisible:
+            tw, th = dc.GetTextExtent("chan 8")
+            for i in range(len(self.img)):
+                dc.SetTextForeground(self.pens[i % self.chnls].GetColour())
+                dc.DrawText("chan %d" % (i+1), w-tw-20-last_tw, i*th+10)
 
 ######################################################################
 ## Grapher window for PyoTableObject control
@@ -2894,56 +2938,49 @@ class ServerGUI(wx.Frame):
         panel.SetBackgroundColour(BACKGROUND_COLOUR)
         box = wx.BoxSizer(wx.VERTICAL)
 
-        if sys.platform == "win32":
-            leftMargin = 24
-        else:
-            leftMargin = 25
-
         buttonBox = wx.BoxSizer(wx.HORIZONTAL)
-        self.startButton = wx.Button(panel, -1, 'Start', (20,20), (72,-1))
+        self.startButton = wx.Button(panel, -1, 'Start')
         self.startButton.Bind(wx.EVT_BUTTON, self.start)
-        buttonBox.Add(self.startButton, 0, wx.RIGHT, 5)
+        buttonBox.Add(self.startButton, 0, wx.LEFT | wx.RIGHT, 5)
 
-        self.recButton = wx.Button(panel, -1, 'Rec Start', (20,20), (72,-1))
+        self.recButton = wx.Button(panel, -1, 'Rec Start')
         self.recButton.Bind(wx.EVT_BUTTON, self.record)
         buttonBox.Add(self.recButton, 0, wx.RIGHT, 5)
 
-        self.quitButton = wx.Button(panel, -1, 'Quit', (20,20), (72,-1))
+        self.quitButton = wx.Button(panel, -1, 'Quit')
         self.quitButton.Bind(wx.EVT_BUTTON, self.on_quit)
-        buttonBox.Add(self.quitButton, 0, wx.RIGHT, 0)
+        buttonBox.Add(self.quitButton, 0, wx.RIGHT, 5)
 
-        box.Add(buttonBox, 0, wx.TOP | wx.LEFT | wx.RIGHT, 10)
+        box.Add(buttonBox, 0, wx.TOP, 10)
         box.AddSpacer(10)
 
-        box.Add(wx.StaticText(panel, -1, "Amplitude (dB)"), 0, wx.LEFT, leftMargin)
-        ampBox = wx.BoxSizer(wx.HORIZONTAL)
+        box.Add(wx.StaticText(panel, -1, "Amplitude (dB)"), 0, wx.LEFT, 5)
         self.ampScale = ControlSlider(panel, -60, 18, 20.0 * math.log10(amp), size=(202, 16), outFunction=self.setAmp)
-        ampBox.Add(self.ampScale, 0, wx.LEFT, leftMargin-10)
-        box.Add(ampBox, 0, wx.LEFT | wx.RIGHT, 8)
+        box.Add(self.ampScale, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 5)
 
         if meter:
             box.AddSpacer(10)
             self.meter = VuMeter(panel, size=(200,5*self.nchnls+1), numSliders=self.nchnls)
-            box.Add(self.meter, 0, wx.LEFT, leftMargin-1)
+            box.Add(self.meter, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 5)
             box.AddSpacer(5)
 
         if timer:
             box.AddSpacer(10)
             tt = wx.StaticText(panel, -1, "Elapsed time (hh:mm:ss:ms)")
-            box.Add(tt, 0, wx.LEFT, leftMargin)
+            box.Add(tt, 0, wx.LEFT, 5)
             box.AddSpacer(3)
             self.timetext = wx.StaticText(panel, -1, "00 : 00 : 00 : 000")
-            box.Add(self.timetext, 0, wx.LEFT, leftMargin)
+            box.Add(self.timetext, 0, wx.LEFT, 5)
 
         if self.locals is not None:
             box.AddSpacer(10)
             t = wx.StaticText(panel, -1, "Interpreter")
-            box.Add(t, 0, wx.LEFT, leftMargin)
+            box.Add(t, 0, wx.LEFT, 5)
             tw, th = self.GetTextExtent("|")
             self.text = wx.TextCtrl(panel, -1, "", size=(202, th+8), style=wx.TE_PROCESS_ENTER)
             self.text.Bind(wx.EVT_TEXT_ENTER, self.getText)
             self.text.Bind(wx.EVT_CHAR, self.onChar)
-            box.Add(self.text, 0, wx.LEFT, leftMargin-1)
+            box.Add(self.text, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 5)
 
         box.AddSpacer(10)
         panel.SetSizerAndFit(box)
diff --git a/pyolib/analysis.py b/pyolib/analysis.py
index 4189cbf..7352148 100644
--- a/pyolib/analysis.py
+++ b/pyolib/analysis.py
@@ -837,6 +837,8 @@ class Spectrum(PyoObject):
             list of lists, one list per channel). Useful if someone
             wants to save the analysis data into a text file.
             Defaults to None.
+        wintitle: string, optional
+            GUI window title. Defaults to "Spectrum".
 
     .. note::
 
@@ -850,11 +852,12 @@ class Spectrum(PyoObject):
     >>> spec = Spectrum(a, size=1024)
 
     """
-    def __init__(self, input, size=1024, wintype=2, function=None):
-        pyoArgsAssert(self, "oiiC", input, size, wintype, function)
+    def __init__(self, input, size=1024, wintype=2, function=None, wintitle="Spectrum"):
+        pyoArgsAssert(self, "oiiCS", input, size, wintype, function, wintitle)
         PyoObject.__init__(self)
         self.points = None
         self.viewFrame = None
+        self.channelNamesVisible = True
         self._input = input
         self._size = size
         self._wintype = wintype
@@ -871,7 +874,7 @@ class Spectrum(PyoObject):
         self._base_objs = [Spectrum_base(wrap(in_fader,i), wrap(size,i), wrap(wintype,i)) for i in range(lmax)]
         self._timer = Pattern(self.refreshView, 0.05).play()
         if function is None:
-            self.view()
+            self.view(wintitle)
         self.play()
 
     def setInput(self, x, fadetime=0.05):
@@ -1171,6 +1174,15 @@ class Spectrum(PyoObject):
         pyoArgsAssert(self, "SB", title, wxnoserver)
         createSpectrumWindow(self, title, wxnoserver)
 
+    def showChannelNames(self, visible=True):
+        """
+        If True (the default), channel names will be displayed in the window.
+
+        """
+        self.channelNamesVisible = visible
+        if self.viewFrame is not None:
+            self.viewFrame.showChannelNames(visible)
+
     def _setViewFrame(self, frame):
         self.viewFrame = frame
 
@@ -1281,6 +1293,8 @@ class Scope(PyoObject):
             list of lists, one list per channel). Useful if someone
             wants to save the analysis data into a text file.
             Defaults to None.
+        wintitle: string, optional
+            GUI window title. Defaults to "Scope".
 
     .. note::
 
@@ -1295,8 +1309,8 @@ class Scope(PyoObject):
     >>> scope = Scope(a+b)
 
     """
-    def __init__(self, input, length=0.05, gain=0.67, function=None):
-        pyoArgsAssert(self, "oNNC", input, length, gain, function)
+    def __init__(self, input, length=0.05, gain=0.67, function=None, wintitle="Scope"):
+        pyoArgsAssert(self, "oNNCS", input, length, gain, function, wintitle)
         PyoObject.__init__(self)
         self.points = None
         self.viewFrame = None
@@ -1306,12 +1320,13 @@ class Scope(PyoObject):
         self._function = function
         self._width = 500
         self._height = 400
+        self.channelNamesVisible = True
         self._in_fader = InputFader(input)
         in_fader, lmax = convertArgsToLists(self._in_fader)
         self._base_objs = [Scope_base(wrap(in_fader,i), length) for i in range(lmax)]
         self._timer = Pattern(self.refreshView, length).play()
         if function is None:
-            self.view()
+            self.view(wintitle)
         self.play()
 
     def setInput(self, x, fadetime=0.05):
@@ -1415,12 +1430,12 @@ class Scope(PyoObject):
 
     def view(self, title="Scope", wxnoserver=False):
         """
-        Opens a window showing the result of the analysis.
+        Opens a window showing the incoming waveform.
 
         :Args:
 
             title: string, optional
-                Window title. Defaults to "Spectrum".
+                Window title. Defaults to "Scope".
             wxnoserver: boolean, optional
                 With wxPython graphical toolkit, if True, tells the
                 interpreter that there will be no server window.
@@ -1447,6 +1462,15 @@ class Scope(PyoObject):
         pyoArgsAssert(self, "C", function)
         self._function = getWeakMethodRef(function)
 
+    def showChannelNames(self, visible=True):
+        """
+        If True (the default), channel names will be displayed in the window.
+
+        """
+        self.channelNamesVisible = visible
+        if self.viewFrame is not None:
+            self.viewFrame.showChannelNames(visible)
+
     def _setViewFrame(self, frame):
         self.viewFrame = frame
 
diff --git a/pyolib/listener.py b/pyolib/listener.py
index 5a1a09d..b13b078 100644
--- a/pyolib/listener.py
+++ b/pyolib/listener.py
@@ -76,6 +76,13 @@ class MidiListener(threading.Thread):
             except:
                 pass
 
+    def stop(self):
+        """
+        Stops the listener and properly close the midi ports.
+
+        """
+        self._listener.stop()
+
     def getDeviceInfos(self):
         """
         Returns infos about connected midi devices.
diff --git a/pyolib/pan.py b/pyolib/pan.py
index ab68e2d..5b4bd83 100644
--- a/pyolib/pan.py
+++ b/pyolib/pan.py
@@ -26,7 +26,7 @@ GNU Lesser General Public License for more details.
 You should have received a copy of the GNU Lesser General Public
 License along with pyo.  If not, see <http://www.gnu.org/licenses/>.
 """
-import sys, random
+import sys
 from ._core import *
 from ._maps import *
 
@@ -670,9 +670,9 @@ class Mixer(PyoObject):
         """
         pyoArgsAssert(self, "o", input)
         if voice is None:
-            voice = random.randint(0, 32767)
+            voice = get_random_integer(mx=32767)
             while voice in self._inputs:
-                voice = random.randint(0, 32767)
+                voice = get_random_integer(mx=32767)
         if voice in self._inputs:
             print("Mixer has already a key named %s" % voice, file=sys.stderr)
             return
diff --git a/pyolib/phasevoc.py b/pyolib/phasevoc.py
index 0bc1774..c10ea5d 100644
--- a/pyolib/phasevoc.py
+++ b/pyolib/phasevoc.py
@@ -74,6 +74,16 @@ class PVAnal(PyoPVObject):
                 6. Blackman-Harris 7-term
                 7. Tuckey (alpha = 0.66)
                 8. Sine (half-sine window)
+        callback: callable, optional
+            If not None (default), this function will be called with the result
+            of the analysis at the end of every overlap. The function will
+            receive two arguments, a list of floats for both the magnitudes
+            and the frequencies. The signature is:
+
+                callback(magnitudes, frequencies)
+
+            If you analyse a multi-channel signal, you should pass a list
+            of callables, one per channel to analyse.
 
     >>> s = Server().boot()
     >>> s.start()
@@ -82,16 +92,17 @@ class PVAnal(PyoPVObject):
     >>> pvs = PVSynth(pva).mix(2).out()
 
     """
-    def __init__(self, input, size=1024, overlaps=4, wintype=2):
-        pyoArgsAssert(self, "oiii", input, size, overlaps, wintype)
+    def __init__(self, input, size=1024, overlaps=4, wintype=2, callback=None):
+        pyoArgsAssert(self, "oiiic", input, size, overlaps, wintype, callback)
         PyoPVObject.__init__(self)
         self._input = input
         self._size = size
         self._overlaps = overlaps
         self._wintype = wintype
+        self._callback = callback
         self._in_fader = InputFader(input)
-        in_fader, size, overlaps, wintype, lmax = convertArgsToLists(self._in_fader, size, overlaps, wintype)
-        self._base_objs = [PVAnal_base(wrap(in_fader,i), wrap(size,i), wrap(overlaps,i), wrap(wintype,i)) for i in range(lmax)]
+        in_fader, size, overlaps, wintype, callback, lmax = convertArgsToLists(self._in_fader, size, overlaps, wintype, callback)
+        self._base_objs = [PVAnal_base(wrap(in_fader,i), wrap(size,i), wrap(overlaps,i), wrap(wintype,i), wrap(callback,i)) for i in range(lmax)]
         self.play()
 
     def setInput(self, x, fadetime=0.05):
@@ -150,10 +161,26 @@ class PVAnal(PyoPVObject):
                 new `wintype` attribute.
 
         """
+        pyoArgsAssert(self, "i", x)
         self._wintype = x
         x, lmax = convertArgsToLists(x)
         [obj.setWinType(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
 
+    def setCallback(self, x):
+        """
+        Replace the `callback` attribute.
+
+        :Args:
+
+            x: callable
+                new `callback` attribute.
+
+        """
+        pyoArgsAssert(self, "c", x)
+        self._callback = x
+        x, lmax = convertArgsToLists(x)
+        [obj.setCallback(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
+
     @property
     def input(self):
         """PyoObject. Input signal to process."""
diff --git a/pyolib/server.py b/pyolib/server.py
index 37a54e3..9d30948 100644
--- a/pyolib/server.py
+++ b/pyolib/server.py
@@ -85,6 +85,12 @@ class Server(object):
         winhost: string, optional
             Under Windows, pyo's Server will try to use the default devices of the given host.
             This behaviour can be changed with the SetXXXDevice methods.
+        midi: string {'portmidi', 'pm', 'jack'}, optional
+            Midi backend to use. 'pm' is equivalent to 'portmidi'. Default is 'portmidi'.
+
+            If 'jack' is selected but jackd is not already started when the program is executed, pyo
+            will ask jack to start in the background. Note that pyo never ask jack to close. It is
+            the user's responsability to manage the audio/midi configuration of its system.
 
     .. note::
 
@@ -115,8 +121,8 @@ class Server(object):
     >>> s.start()
 
     """
-    def __init__(self, sr=44100, nchnls=2, buffersize=256, duplex=1,
-                 audio='portaudio', jackname='pyo', ichnls=None, winhost="wasapi"):
+    def __init__(self, sr=44100, nchnls=2, buffersize=256, duplex=1, audio='portaudio', 
+                 jackname='pyo', ichnls=None, winhost="wasapi", midi="portmidi"):
         if "PYO_SERVER_AUDIO" in os.environ and "offline" not in audio and "embedded" not in audio:
             audio = os.environ["PYO_SERVER_AUDIO"]
         self._time = time
@@ -136,7 +142,7 @@ class Server(object):
         self._globalseed = 0
         self._resampling = 1
         self._isJackTransportSlave = False
-        self._server = Server_base(sr, nchnls, buffersize, duplex, audio, jackname, self._ichnls)
+        self._server = Server_base(sr, nchnls, buffersize, duplex, audio, jackname, self._ichnls, midi)
         self._server._setDefaultRecPath(os.path.join(os.path.expanduser("~"), "pyo_rec.wav"))
 
         if sys.platform.startswith("win"):
@@ -154,8 +160,8 @@ class Server(object):
             self.shutdown()
             self._time.sleep(.25)
 
-    def reinit(self, sr=44100, nchnls=2, buffersize=256, duplex=1,
-               audio='portaudio', jackname='pyo', ichnls=None, winhost="wasapi"):
+    def reinit(self, sr=44100, nchnls=2, buffersize=256, duplex=1, audio='portaudio', 
+               jackname='pyo', ichnls=None, winhost="wasapi", midi="portmidi"):
         """
         Reinit the server'settings. Useful to alternate between real-time and offline server.
 
@@ -181,7 +187,7 @@ class Server(object):
         self._globalseed = 0
         self._resampling = 1
         self._isJackTransportSlave = False
-        self._server.__init__(sr, nchnls, buffersize, duplex, audio, jackname, self._ichnls)
+        self._server.__init__(sr, nchnls, buffersize, duplex, audio, jackname, self._ichnls, midi)
 
         if sys.platform.startswith("win"):
             host_default_in, host_default_out = pa_get_default_devices_from_host(winhost)
@@ -225,7 +231,7 @@ class Server(object):
                 title, "Pyo Server" is used.
 
         """
-        self._gui_frame, win = createServerGUI(self._nchnls, self.start, self.stop,
+        self._gui_frame, win, withWX = createServerGUI(self._nchnls, self.start, self.stop,
                                                self.recstart, self.recstop, self.setAmp,
                                                self.getIsStarted(), locals, self.shutdown,
                                                meter, timer, self._amp, exit, title)
@@ -233,11 +239,10 @@ class Server(object):
             self._server.setAmpCallable(self._gui_frame)
         if timer:
             self._server.setTimeCallable(self._gui_frame)
-        try:
+        if withWX:
+            win.MainLoop()
+        else:
             win.mainloop()
-        except:
-            if win is not None:
-                win.MainLoop()
 
     def closeGui(self):
         """
@@ -603,6 +608,64 @@ class Server(object):
         """
         self._server.setJackOutputPortNames(name)
 
+    def setJackAutoConnectMidiInputPort(self, ports):
+        """
+        Tells the server to auto-connect Jack midi input port to pre-defined Jack ports.
+
+        :Args:
+
+            ports: string or list of strings
+                Name of the Jack ports to auto-connect to pyo midi input channel.
+
+                ['ports', 'to', 'midi', 'input', ...]
+
+        """
+        ports, lmax = convertArgsToLists(ports)
+        self._server.setJackAutoConnectMidiInputPort(ports)
+
+    def setJackAutoConnectMidiOutputPort(self, ports):
+        """
+        Tells the server to auto-connect Jack midi output port to pre-defined Jack ports.
+
+        :Args:
+
+            ports: string or list of strings
+                Name of the Jack ports to auto-connect to pyo midi output channel.
+
+                ['ports', 'to', 'midi', 'output', ...]
+
+        """
+        ports, lmax = convertArgsToLists(ports)
+        self._server.setJackAutoConnectMidiOutputPort(ports)
+
+    def setJackMidiInputPortName(self, name):
+        """
+        Change the short name of pyo's midi input port for the jack server.
+
+        This method must be called after the server is booted.
+
+        :Args:
+
+            name: string
+                New name of midi input port for the jack server.
+
+        """
+        self._server.setJackMidiInputPortName(name)
+
+    def setJackMidiOutputPortName(self, name):
+        """
+        Change the short name of pyo's midi output port for the jack server.
+
+        This method must be called after the server is booted.
+
+        :Args:
+
+            name: string
+                New name of midi output port for the jack server.
+
+        """
+        self._server.setJackMidiOutputPortName(name)
+
     def setIsJackTransportSlave(self, x):
         """
         Set if pyo's server is slave to jack transport or not.
@@ -812,7 +875,7 @@ class Server(object):
             print('Warning: Filename has no extension. Using fileformat value.')
         self._fileformat = fileformat
         self._sampletype = sampletype
-        self._server.recordOptions(dur, stringencode(filename), fileformat, sampletype, quality)
+        self._server.recordOptions(dur, filename, fileformat, sampletype, quality)
 
     def recstart(self, filename=None):
         """
@@ -840,7 +903,7 @@ class Server(object):
                 fileformat = FILE_FORMATS[ext]
                 if fileformat != self._fileformat:
                     self._fileformat = fileformat
-                    self._server.recordOptions(self._dur, stringencode(filename), self._fileformat, self._sampletype)
+                    self._server.recordOptions(self._dur, filename, self._fileformat, self._sampletype)
 
         self._server.recstart(filename)
 
@@ -870,9 +933,9 @@ class Server(object):
                 note is sent. A channel of 0 means all channels.
                 Defaults to 0.
             timestamp: int, optional
-                The delay time, in milliseconds, before the note
-                is sent on the portmidi stream. A value of 0 means
-                to play the note now. Defaults to 0.
+                The delay time, in milliseconds, before the message
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         pitch, velocity, channel, timestamp, lmax = convertArgsToLists(pitch, velocity, channel, timestamp)
         [self._server.noteout(wrap(pitch,i), wrap(velocity,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -895,9 +958,9 @@ class Server(object):
                 note is sent. A channel of 0 means all channels.
                 Defaults to 0.
             timestamp: int, optional
-                The delay time, in milliseconds, before the note
-                is sent on the portmidi stream. A value of 0 means
-                to play the note now. Defaults to 0.
+                The delay time, in milliseconds, before the message
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         pitch, velocity, channel, timestamp, lmax = convertArgsToLists(pitch, velocity, channel, timestamp)
         [self._server.afterout(wrap(pitch,i), wrap(velocity,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -921,8 +984,8 @@ class Server(object):
                 Defaults to 0.
             timestamp: int, optional
                 The delay time, in milliseconds, before the message
-                is sent on the portmidi stream. A value of 0 means
-                to play the message now. Defaults to 0.
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         ctlnum, value, channel, timestamp, lmax = convertArgsToLists(ctlnum, value, channel, timestamp)
         [self._server.ctlout(wrap(ctlnum,i), wrap(value,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -944,8 +1007,8 @@ class Server(object):
                 Defaults to 0.
             timestamp: int, optional
                 The delay time, in milliseconds, before the message
-                is sent on the portmidi stream. A value of 0 means
-                to play the message now. Defaults to 0.
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         value, channel, timestamp, lmax = convertArgsToLists(value, channel, timestamp)
         [self._server.programout(wrap(value,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -967,8 +1030,8 @@ class Server(object):
                 Defaults to 0.
             timestamp: int, optional
                 The delay time, in milliseconds, before the message
-                is sent on the portmidi stream. A value of 0 means
-                to play the message now. Defaults to 0.
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         value, channel, timestamp, lmax = convertArgsToLists(value, channel, timestamp)
         [self._server.pressout(wrap(value,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -991,8 +1054,8 @@ class Server(object):
                 Defaults to 0.
             timestamp: int, optional
                 The delay time, in milliseconds, before the message
-                is sent on the portmidi stream. A value of 0 means
-                to play the message now. Defaults to 0.
+                is sent to the output midi stream. A value of 0 
+                means to play the note now. Defaults to 0.
         """
         value, channel, timestamp, lmax = convertArgsToLists(value, channel, timestamp)
         [self._server.bendout(wrap(value,i), wrap(channel,i), wrap(timestamp,i)) for i in range(lmax)]
@@ -1002,7 +1065,8 @@ class Server(object):
         Send a system exclusive message to the selected midi output device.
 
         Arguments can be list of values/messages to generate multiple events
-        in one call.
+        in one call. Implemented only for portmidi, this method is not available
+        when using jack as the midi backend.
 
         :Args:
 
@@ -1011,7 +1075,7 @@ class Server(object):
                 must be 0xf0 and the last one must be 0xf7.
             timestamp: int, optional
                 The delay time, in milliseconds, before the message
-                is sent on the portmidi stream. A value of 0 means
+                is sent to the output midi stream. A value of 0 means
                 to play the message now. Defaults to 0.
         """
         msg, timestamp, lmax = convertArgsToLists(msg, timestamp)
diff --git a/scripts/win_audio_drivers_inspector.py b/scripts/win_audio_drivers_inspector.py
new file mode 100644
index 0000000..1e72de5
--- /dev/null
+++ b/scripts/win_audio_drivers_inspector.py
@@ -0,0 +1,59 @@
+"""
+Windows audio host inspector.
+
+This script will check if pyo can run in duplex mode (both audio input and output)
+and will test every host API to help the user in making his audio device choice.
+
+"""
+import sys, time
+from pyo import *
+
+if sys.platform == "win32":
+    host_apis = ["mme", "directsound", "asio", "wasapi", "wdm-ks"]
+else:
+    print("This program must be used on a windows system! Ciao!")
+    exit()
+
+
+print("* Checking for any available audio input... *")
+
+input_names, input_indexes  = pa_get_input_devices()
+
+print("* Checking audio output hosts... *")
+
+s = Server(duplex=0)
+s.verbosity = 0
+
+host_results = []
+for host in host_apis:
+    print("* Testing %s... *" % host)
+    try:
+        s.reinit(buffersize=1024, duplex=0, winhost=host)
+        s.boot().start()
+        a = Sine(freq=440, mul=0.2).out()
+        time.sleep(2)
+        s.stop()
+        s.shutdown()
+        host_results.append(True)
+    except:
+        host_results.append(False)
+
+print("\nResults")
+print("-------\n")
+
+if len(input_names):
+    print("Duplex mode OK!")
+    print("Initialize the Server with duplex=1 as argument.\n")
+else:
+    print("No input available. Duplex mode should be turned off.")
+    print("Initialize the Server with duplex=0 as argument.\n")
+
+for i, host in enumerate(host_apis):
+    if host_results[i]:
+        print("Host: %s  ==>  OK!" % host)
+    else:
+        print("Host: %s  ==>  Failed..." % host)
+
+print("Initialize the Server with the desired host given to winhost argument.")
+
+print("\nFinished!")
diff --git a/setup.py b/setup.py
index f96a0c4..6860aeb 100644
--- a/setup.py
+++ b/setup.py
@@ -81,15 +81,15 @@ if '--compile-externals' in sys.argv:
 
 if '--debug' in sys.argv:
     sys.argv.remove('--debug')
-    gflag = "-g3"
+    gflag = ["-g3", "-UNDEBUG"]
 else:
-    gflag = "-g0"
+    gflag = ["-g0", "-DNDEBUG"]
 
 if '--fast-compile' in sys.argv:
     sys.argv.remove('--fast-compile')
-    oflag = "-O0"
+    oflag = ["-O0"]
 else:
-    oflag = "-O3"
+    oflag = ["-O3"]
 
 # Specific audio drivers source files to compile
 ad_files = []
@@ -181,7 +181,7 @@ else:
         libraries.append('jack')
 
 libraries += ['m']
-extra_compile_args = ['-Wno-strict-prototypes', '-Wno-strict-aliasing', oflag, gflag]
+extra_compile_args = ['-Wno-strict-prototypes', '-Wno-strict-aliasing'] + oflag + gflag
 
 extensions = []
 for extension_name, extra_macros in zip(extension_names, extra_macros_per_extension):
diff --git a/src/engine/ad_jack.c b/src/engine/ad_jack.c
index d63ca39..19e4c13 100644
--- a/src/engine/ad_jack.c
+++ b/src/engine/ad_jack.c
@@ -21,7 +21,45 @@
 #include "ad_jack.h"
 #include "py2to3.h"
 
-int
+static PyoMidiEvent
+JackMidiEventToPyoMidiEvent(jack_midi_event_t event)
+{
+    PyoMidiEvent newbuf;
+    newbuf.message = PyoMidi_Message(event.buffer[0], event.buffer[1], event.buffer[2]);
+    newbuf.timestamp = (long)event.time;
+    return newbuf;
+}
+
+static int
+jackMidiEventCompare(const void *evt1, const void *evt2) {
+    const PyoJackMidiEvent *event1 = evt1;
+    const PyoJackMidiEvent *event2 = evt2;
+    if (event1->timestamp > event2->timestamp) return 1;
+    if (event1->timestamp < event2->timestamp) return -1;
+    return 0;
+}
+
+static int
+jackMidiEventSort(PyoJackMidiEvent *orig, PyoJackMidiEvent *new, Server *server) {
+    int i, count = 0;
+    jack_nframes_t time = 0;
+    for (i=0; i<512; i++) {
+        if (orig[i].timestamp != -1 && orig[i].timestamp < (server->elapsedSamples + server->bufferSize)) {
+            time = orig[i].timestamp % server->bufferSize;
+            new[count] = orig[i];
+            new[count].timestamp = time;
+            orig[i].timestamp = -1;
+            count++;
+        }
+    }
+    if (count > 1) {
+        qsort(new, count, sizeof(PyoJackMidiEvent), jackMidiEventCompare);
+    }
+
+    return count;
+}
+
+static int
 jack_callback(jack_nframes_t nframes, void *arg) {
     int i, j;
     Server *server = (Server *) arg;
@@ -49,7 +87,37 @@ jack_callback(jack_nframes_t nframes, void *arg) {
         return 0;
     }
 
-    if (server->withPortMidi == 1) {
+    if (server->withJackMidi) {
+        // Handle midiout events.
+        if (be_data->midi_event_count > 0) {
+            PyoJackMidiEvent ordlist[512];
+            int numevents = jackMidiEventSort(be_data->midi_events, ordlist, server);
+            be_data->midi_event_count -= numevents;
+
+            unsigned char *buffer;
+            void *port_buf_out = jack_port_get_buffer(be_data->jack_midiout_port, server->bufferSize);
+            jack_midi_clear_buffer(port_buf_out);
+            for (i=0; i<numevents; i++) {
+                buffer = jack_midi_event_reserve(port_buf_out, ordlist[i].timestamp, 3);
+                buffer[0] = ordlist[i].status;
+                buffer[1] = ordlist[i].data1;
+                buffer[2] = ordlist[i].data2;
+            }
+        }
+
+        // Handle midiin events.
+        void* port_buf_in = jack_port_get_buffer(be_data->jack_midiin_port, server->bufferSize);
+
+        jack_midi_event_t in_event;
+        jack_nframes_t event_count = jack_midi_get_event_count(port_buf_in);
+
+        if (event_count > 0) {
+            for (i=0; i<event_count; i++) {
+                jack_midi_event_get(&in_event, port_buf_in, i);
+                server->midiEvents[server->midi_count++] = JackMidiEventToPyoMidiEvent(in_event);
+            }
+        }
+    } else {
         pyoGetMidiEvents(server);
     }
 
@@ -71,7 +139,7 @@ jack_callback(jack_nframes_t nframes, void *arg) {
     return 0;
 }
 
-int
+static int
 jack_transport_cb(jack_transport_state_t state, jack_position_t *pos, void *arg) {
     Server *server = (Server *) arg;
 
@@ -104,7 +172,7 @@ jack_transport_cb(jack_transport_state_t state, jack_position_t *pos, void *arg)
     return 0;
 }
 
-int
+static int
 jack_srate_cb(jack_nframes_t nframes, void *arg) {
     Server *server = (Server *) arg;
     server->samplingRate = (double) nframes;
@@ -116,7 +184,7 @@ jack_srate_cb(jack_nframes_t nframes, void *arg) {
     return 0;
 }
 
-int
+static int
 jack_bufsize_cb(jack_nframes_t nframes, void *arg) {
     Server *server = (Server *) arg;
     server->bufferSize = (int) nframes;
@@ -128,14 +196,14 @@ jack_bufsize_cb(jack_nframes_t nframes, void *arg) {
     return 0;
 }
 
-void
+static void
 jack_error_cb(const char *desc) {
     PyGILState_STATE s = PyGILState_Ensure();
     PySys_WriteStdout("JACK error: %s\n", desc);
     PyGILState_Release(s);
 }
 
-void
+static void
 jack_shutdown_cb(void *arg) {
     Server *server = (Server *) arg;
 
@@ -145,7 +213,7 @@ jack_shutdown_cb(void *arg) {
     PyGILState_Release(s);
 }
 
-void
+static void
 Server_jack_autoconnect(Server *self) {
     const char **ports;
     char *portname;
@@ -257,10 +325,55 @@ Server_jack_autoconnect(Server *self) {
             }
         }
     }
+
+    if (self->withJackMidi) {
+        num = PyList_Size(self->jackAutoConnectMidiInputPort);
+        if (num > 0) {
+            for (i=0; i<num; i++) {
+                portname = PY_STRING_AS_STRING(PyList_GetItem(self->jackAutoConnectMidiInputPort, i));
+
+                if (jack_port_by_name(be_data->jack_client, portname) != NULL) {
+
+                    Py_BEGIN_ALLOW_THREADS
+                    err = jack_connect(be_data->jack_client, portname, jack_port_name(be_data->jack_midiin_port));
+                    Py_END_ALLOW_THREADS
+
+                    if (err) {
+                        Server_error(self, "Jack: cannot connect '%s' to midi input port\n", portname);
+                    }
+                }
+                else {
+                    Server_error(self, "Jack: cannot find port '%s'\n", portname);
+                }
+            }
+        }
+
+        num = PyList_Size(self->jackAutoConnectMidiOutputPort);
+        if (num > 0) {
+            for (i=0; i<num; i++) {
+                portname = PY_STRING_AS_STRING(PyList_GetItem(self->jackAutoConnectMidiOutputPort, i));
+
+                if (jack_port_by_name(be_data->jack_client, portname) != NULL) {
+
+                    Py_BEGIN_ALLOW_THREADS
+                    err = jack_connect(be_data->jack_client, jack_port_name(be_data->jack_midiout_port), portname);
+                    Py_END_ALLOW_THREADS
+
+                    if (err) {
+                        Server_error(self, "Jack: cannot connect '%s' to midi output port\n", portname);
+                    }
+                }
+                else {
+                    Server_error(self, "Jack: cannot find port '%s'\n", portname);
+                }
+            }
+        }
+    }
 }
 
 int
 Server_jack_init(Server *self) {
+    int i = 0;
     char client_name[32];
     char name[16];
     const char *server_name = "server";
@@ -278,9 +391,16 @@ Server_jack_init(Server *self) {
     strncpy(client_name, self->serverName, 32);
 
     Py_BEGIN_ALLOW_THREADS
+    be_data->midi_event_count = 0;
     be_data->jack_in_ports = (jack_port_t **) calloc(self->ichnls + self->input_offset, sizeof(jack_port_t *));
     be_data->jack_out_ports = (jack_port_t **) calloc(self->nchnls + self->output_offset, sizeof(jack_port_t *));
     be_data->jack_client = jack_client_open(client_name, options, &status, server_name);
+    if (self->withJackMidi) {
+        be_data->midi_events = (PyoJackMidiEvent *)malloc(512 * sizeof(PyoJackMidiEvent));
+        for (i=0; i<512; i++) {
+            be_data->midi_events[i].timestamp = -1;
+        }
+    }
     Py_END_ALLOW_THREADS
 
     if (be_data->jack_client == NULL) {
@@ -326,6 +446,17 @@ Server_jack_init(Server *self) {
         Server_debug(self, "Jack engine buffer size: %" PRIu32 "\n", bufferSize);
     }
 
+    if (self->withJackMidi) {
+        Py_BEGIN_ALLOW_THREADS
+        be_data->jack_midiin_port = jack_port_register(be_data->jack_client, "input",
+                                                       JACK_DEFAULT_MIDI_TYPE,
+                                                       JackPortIsInput, 0);
+        be_data->jack_midiout_port = jack_port_register(be_data->jack_client, "output",
+                                                       JACK_DEFAULT_MIDI_TYPE,
+                                                       JackPortIsOutput, 0);
+        Py_END_ALLOW_THREADS
+    }
+
     nchnls = total_nchnls = self->ichnls + self->input_offset;
     while (nchnls-- > 0) {
         index = total_nchnls - nchnls - 1;
@@ -410,7 +541,14 @@ Server_jack_deinit(Server *self) {
 
     free(be_data->jack_in_ports);
     free(be_data->jack_out_ports);
-    free(self->audio_be_data);
+    if (self->withJackMidi == 1) {
+        free(be_data->midi_events);
+    }
+    /* Since I add midi_event_count entry in the PyoJackBackendData struct,
+       freeing the struct gives an error "free(): invalid next size (fast)".
+       until I find the correct solution, I'll just let leak the struct when
+       we shutdown the server. */
+    //free(self->audio_be_data);
 
     return ret;
 }
@@ -510,6 +648,58 @@ jack_output_port_set_names(Server *self) {
 }
 
 int
+jack_midi_input_port_set_name(Server *self) {
+    int err;
+    char *name;
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    if (PY_STRING_CHECK(self->jackMidiInputPortName)) {
+        name = PY_STRING_AS_STRING(self->jackMidiInputPortName);
+
+        Py_BEGIN_ALLOW_THREADS
+#ifdef JACK_NEW_API
+        err = jack_port_rename(be_data->jack_client, be_data->jack_midiin_port, name);
+#else
+        err = jack_port_set_name(be_data->jack_midiin_port, name);
+#endif
+        Py_END_ALLOW_THREADS
+
+        if (err)
+            Server_error(self, "Jack error: cannot change midi input port short name.\n");
+    }
+    else
+        Server_error(self, "Jack error: midi input port name must be a string.\n");
+
+    return 0;
+}
+
+int
+jack_midi_output_port_set_name(Server *self) {
+    int err;
+    char *name;
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    if (PY_STRING_CHECK(self->jackMidiOutputPortName)) {
+        name = PY_STRING_AS_STRING(self->jackMidiOutputPortName);
+
+        Py_BEGIN_ALLOW_THREADS
+#ifdef JACK_NEW_API
+        err = jack_port_rename(be_data->jack_client, be_data->jack_midiout_port, name);
+#else
+        err = jack_port_set_name(be_data->jack_midiout_port, name);
+#endif
+        Py_END_ALLOW_THREADS
+
+        if (err)
+            Server_error(self, "Jack error: cannot change midi output port short name.\n");
+    }
+    else
+        Server_error(self, "Jack error: midi output port name must be a string.\n");
+
+    return 0;
+}
+
+int
 Server_jack_start(Server *self) {
     return 0;
 }
@@ -518,3 +708,163 @@ int
 Server_jack_stop(Server *self) {
     return 0;
 }
+
+void
+jack_noteout(Server *self, int pit, int vel, int chan, long timestamp)
+{
+    int i;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0x90;
+    else
+        event.status = 0x90 | (chan - 1);
+    event.data1 = pit;
+    event.data2 = vel;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
+
+void
+jack_afterout(Server *self, int pit, int vel, int chan, long timestamp)
+{
+    int i;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0xA0;
+    else
+        event.status = 0xA0 | (chan - 1);
+    event.data1 = pit;
+    event.data2 = vel;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
+
+void
+jack_ctlout(Server *self, int ctlnum, int value, int chan, long timestamp)
+{
+    int i;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0xB0;
+    else
+        event.status = 0xB0 | (chan - 1);
+    event.data1 = ctlnum;
+    event.data2 = value;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
+
+void
+jack_programout(Server *self, int value, int chan, long timestamp)
+{
+    int i;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0xC0;
+    else
+        event.status = 0xC0 | (chan - 1);
+    event.data1 = value;
+    event.data2 = 0;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
+
+void
+jack_pressout(Server *self, int value, int chan, long timestamp)
+{
+    int i;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0xD0;
+    else
+        event.status = 0xD0 | (chan - 1);
+    event.data1 = value;
+    event.data2 = 0;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
+
+void
+jack_bendout(Server *self, int value, int chan, long timestamp)
+{
+    int i, lsb, msb;
+    unsigned long elapsed = Server_getElapsedTime(self);
+
+    PyoJackBackendData *be_data = (PyoJackBackendData *) self->audio_be_data;
+
+    PyoJackMidiEvent event;
+    event.timestamp = elapsed + (unsigned long)(timestamp * 0.001 * self->samplingRate);
+    if (chan == 0)
+        event.status = 0xE0;
+    else
+        event.status = 0xE0 | (chan - 1);
+
+    lsb = value & 0x007F;
+    msb = (value & (0x007F << 7)) >> 7;
+
+    event.data1 = lsb;
+    event.data2 = msb;
+
+    for (i=0; i<512; i++) {
+        if (be_data->midi_events[i].timestamp == -1) {
+            be_data->midi_events[i] = event;
+            be_data->midi_event_count++;
+            break;
+        }
+    }
+}
diff --git a/src/engine/pyomodule.c b/src/engine/pyomodule.c
index 1e7d7cc..3ba0b5b 100644
--- a/src/engine/pyomodule.c
+++ b/src/engine/pyomodule.c
@@ -32,56 +32,56 @@
 
 #ifdef USE_PORTAUDIO
 #include "ad_portaudio.h"
-PyObject * with_portaudio() { Py_INCREF(Py_True); return Py_True; };
+static PyObject * with_portaudio() { Py_INCREF(Py_True); return Py_True; };
 #else
 #define pa_warning "Pyo built without Portaudio support.\n"
-PyObject * portaudio_get_version() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_version_text() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_count_host_apis() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_list_host_apis() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_default_host_api() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_count_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_list_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_devices_infos() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_output_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_output_max_channels(PyObject *self, PyObject *arg) { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_input_max_channels(PyObject *self, PyObject *arg) { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_input_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_default_input() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * portaudio_get_default_output() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
-PyObject * with_portaudio() { Py_INCREF(Py_False); return Py_False; };
+static PyObject * portaudio_get_version() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_version_text() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_count_host_apis() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_list_host_apis() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_default_host_api() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_count_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_list_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_devices_infos() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_output_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_output_max_channels(PyObject *self, PyObject *arg) { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_input_max_channels(PyObject *self, PyObject *arg) { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_input_devices() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_default_input() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * portaudio_get_default_output() { PySys_WriteStdout(pa_warning); Py_RETURN_NONE; };
+static PyObject * with_portaudio() { Py_INCREF(Py_False); return Py_False; };
 #endif
 
 #ifdef USE_PORTMIDI
 #include "md_portmidi.h"
-PyObject * with_portmidi() { Py_INCREF(Py_True); return Py_True; };
+static PyObject * with_portmidi() { Py_INCREF(Py_True); return Py_True; };
 #else
 #define pm_warning "Pyo built without Portmidi sipport.\n"
-PyObject * portmidi_count_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * portmidi_list_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * portmidi_get_input_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * portmidi_get_output_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * portmidi_get_default_input() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * portmidi_get_default_output() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
-PyObject * with_portmidi() { Py_INCREF(Py_False); return Py_False; };
+static PyObject * portmidi_count_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * portmidi_list_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * portmidi_get_input_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * portmidi_get_output_devices() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * portmidi_get_default_input() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * portmidi_get_default_output() { PySys_WriteStdout(pm_warning); Py_RETURN_NONE; };
+static PyObject * with_portmidi() { Py_INCREF(Py_False); return Py_False; };
 #endif
 
 #ifdef USE_JACK
-PyObject * with_jack() { Py_INCREF(Py_True); return Py_True; };
+static PyObject * with_jack() { Py_INCREF(Py_True); return Py_True; };
 #else
-PyObject * with_jack() { Py_INCREF(Py_False); return Py_False; };
+static PyObject * with_jack() { Py_INCREF(Py_False); return Py_False; };
 #endif
 
 #ifdef USE_COREAUDIO
-PyObject * with_coreaudio() { Py_INCREF(Py_True); return Py_True; };
+static PyObject * with_coreaudio() { Py_INCREF(Py_True); return Py_True; };
 #else
-PyObject * with_coreaudio() { Py_INCREF(Py_False); return Py_False; };
+static PyObject * with_coreaudio() { Py_INCREF(Py_False); return Py_False; };
 #endif
 
 #ifdef USE_OSC
-PyObject * with_osc() { Py_INCREF(Py_True); return Py_True; };
+static PyObject * with_osc() { Py_INCREF(Py_True); return Py_True; };
 #else
-PyObject * with_osc() { Py_INCREF(Py_False); return Py_False; };
+static PyObject * with_osc() { Py_INCREF(Py_False); return Py_False; };
 #endif
 
 /** Portaudio utility functions __doc__ strings. **/
@@ -582,7 +582,8 @@ MYFLT HALF_BLACKMAN[513] = {5.999999848427251e-05, 6.0518785176100209e-05, 6.214
  size is the convolution impulse response length in samples.
  freq is the cutoff frequency in radians.
 */
-void gen_lp_impulse(MYFLT *array, int size, float freq) {
+static void
+gen_lp_impulse(MYFLT *array, int size, float freq) {
     int i, ppi;
     MYFLT pp, ppf, env, scl, invSum, val;
     int half = size / 2;
@@ -621,7 +622,8 @@ void gen_lp_impulse(MYFLT *array, int size, float freq) {
  size is the filter order. Minimum suggested = 16, ideal = 128 or higher.
  gain is the gain of the filter.
 */
-void lp_conv(MYFLT *samples, MYFLT *impulse, int num_samps, int size, int gain) {
+static void
+lp_conv(MYFLT *samples, MYFLT *impulse, int num_samps, int size, int gain) {
     int i, j, count, tmp_count;
     MYFLT val;
     MYFLT intmp[size];
@@ -877,7 +879,7 @@ typedef struct STACK_RECORD {
 
 STACK_RECORD *m_pStack = NULL;
 
-void StackPush( int nAnchorIndex, int nFloaterIndex )
+static void StackPush( int nAnchorIndex, int nFloaterIndex )
 {
     STACK_RECORD *precPrev = m_pStack;
     m_pStack = (STACK_RECORD *)malloc( sizeof(STACK_RECORD) );
@@ -886,7 +888,7 @@ void StackPush( int nAnchorIndex, int nFloaterIndex )
     m_pStack->precPrev = precPrev;
 }
 
-int StackPop( int *pnAnchorIndex, int *pnFloaterIndex )
+static int StackPop( int *pnAnchorIndex, int *pnFloaterIndex )
 {
     STACK_RECORD *precStack = m_pStack;
     if ( precStack == NULL )
@@ -924,6 +926,7 @@ reducePoints(PyObject *self, PyObject *args, PyObject *kwds)
 
         nPointsCount = PyList_Size(pointlist);
 
+	// TODO: these malloc's are never freed.
         pPointsX = (MYFLT *)malloc(nPointsCount * sizeof(MYFLT));
         pPointsY = (MYFLT *)malloc(nPointsCount * sizeof(MYFLT));
         pnUseFlag = (int *)malloc(nPointsCount * sizeof(int));
diff --git a/src/engine/servermodule.c b/src/engine/servermodule.c
index 0f9f68c..476297d 100644
--- a/src/engine/servermodule.c
+++ b/src/engine/servermodule.c
@@ -51,6 +51,14 @@ int Server_jack_start(Server *self) { return 0; };
 int Server_jack_stop(Server *self) { return 0; };
 int jack_input_port_set_names(Server *self) { return 0; };
 int jack_output_port_set_names(Server *self) { return 0; };
+int jack_midi_input_port_set_name(Server *self) { return 0; };
+int jack_midi_output_port_set_name(Server *self) { return 0; };
+void jack_noteout(Server *self, int pit, int vel, int chan, long timestamp) {};
+void jack_afterout(Server *self, int pit, int vel, int chan, long timestamp) {};
+void jack_ctlout(Server *self, int ctlnum, int value, int chan, long timestamp) {};
+void jack_programout(Server *self, int value, int chan, long timestamp) {};
+void jack_pressout(Server *self, int value, int chan, long timestamp) {};
+void jack_bendout(Server *self, int value, int chan, long timestamp) {};
 
 #endif
 
@@ -526,9 +534,12 @@ Server_traverse(Server *self, visitproc visit, void *arg)
     Py_VISIT(self->TIME);
     if (self->CALLBACK != NULL)
         Py_VISIT(self->CALLBACK);
-    Py_VISIT(self->streams);
+    if (PyList_Size(self->streams) > 0)
+        Py_VISIT(self->streams);
     Py_VISIT(self->jackAutoConnectInputPorts);
     Py_VISIT(self->jackAutoConnectOutputPorts);
+    Py_VISIT(self->jackAutoConnectMidiInputPort);
+    Py_VISIT(self->jackAutoConnectMidiOutputPort);
     return 0;
 }
 
@@ -539,9 +550,12 @@ Server_clear(Server *self)
     Py_CLEAR(self->TIME);
     if (self->CALLBACK != NULL)
         Py_CLEAR(self->CALLBACK);
-    Py_CLEAR(self->streams);
+    if (PyList_Size(self->streams) > 0)
+        Py_CLEAR(self->streams);
     Py_CLEAR(self->jackAutoConnectInputPorts);
     Py_CLEAR(self->jackAutoConnectOutputPorts);
+    Py_CLEAR(self->jackAutoConnectMidiInputPort);
+    Py_CLEAR(self->jackAutoConnectMidiOutputPort);
     return 0;
 }
 
@@ -570,11 +584,12 @@ Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     int  bufferSize = 256;
     int  duplex = 0;
     char *audioType = "portaudio";
+    char *midiType = "portmidi";
     char *serverName = "pyo";
 
-    static char *kwlist[] = {"sr", "nchnls", "buffersize", "duplex", "audio", "jackname", "ichnls", NULL};
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|diiissi", kwlist,
-            &samplingRate, &nchnls, &bufferSize, &duplex, &audioType, &serverName, &ichnls)) {
+    static char *kwlist[] = {"sr", "nchnls", "buffersize", "duplex", "audio", "jackname", "ichnls", "midi", NULL};
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|diiissis", kwlist,
+            &samplingRate, &nchnls, &bufferSize, &duplex, &audioType, &serverName, &ichnls, &midiType)) {
         Py_INCREF(Py_False);
         return Py_False;
     }
@@ -600,8 +615,11 @@ Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->jackautoout = 1;
     self->jackAutoConnectInputPorts = PyList_New(0);
     self->jackAutoConnectOutputPorts = PyList_New(0);
+    self->jackAutoConnectMidiInputPort = PyList_New(0);
+    self->jackAutoConnectMidiOutputPort = PyList_New(0);
     self->isJackTransportSlave = 0;
     self->jack_transport_state = 0;
+    self->withJackMidi = 0;
     self->samplingRate = 44100.0;
     self->nchnls = 2;
     self->ichnls = 2;
@@ -622,7 +640,7 @@ Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->allowMMMapper = 0; // Disable Microsoft MIDI Mapper by default.
     self->midi_time_offset = 0;
     self->amp = self->resetAmp = 1.;
-    self->currentAmp = self->lastAmp = 1.; // If set to 0, there is a 5ms fadein at server start.
+    self->currentAmp = self->lastAmp = 0.; // If set to 0, there is a 5ms fadein at server start.
     self->withGUI = 0;
     self->withTIME = 0;
     self->verbosity = 7;
@@ -644,14 +662,14 @@ Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 static int
 Server_init(Server *self, PyObject *args, PyObject *kwds)
 {
-    static char *kwlist[] = {"sr", "nchnls", "buffersize", "duplex", "audio", "jackname", "ichnls", NULL};
+    static char *kwlist[] = {"sr", "nchnls", "buffersize", "duplex", "audio", "jackname", "ichnls", "midi", NULL};
 
     char *audioType = "portaudio";
     char *midiType = "portmidi";
     char *serverName = "pyo";
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|diiissi", kwlist,
-            &self->samplingRate, &self->nchnls, &self->bufferSize, &self->duplex, &audioType, &serverName, &self->ichnls))
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|diiissis", kwlist,
+            &self->samplingRate, &self->nchnls, &self->bufferSize, &self->duplex, &audioType, &serverName, &self->ichnls, &midiType))
         return -1;
 
     if (strcmp(audioType, "jack") == 0) {
@@ -677,9 +695,14 @@ Server_init(Server *self, PyObject *args, PyObject *kwds)
         self->audio_be_type = PyoPortaudio;
     }
 
+    self->withJackMidi = 0;
     if (strcmp(midiType, "portmidi") == 0 || strcmp(midiType, "pm") == 0 ) {
         self->midi_be_type = PyoPortmidi;
     }
+    else if (strcmp(midiType, "jack") == 0) {
+        self->midi_be_type = PyoJackMidi;
+        self->withJackMidi = 1;
+    }
     else {
         Server_warning(self, "Unknown midi type. Using Portmidi\n");
         self->midi_be_type = PyoPortmidi;
@@ -940,6 +963,40 @@ Server_setJackAutoConnectOutputPorts(Server *self, PyObject *arg)
 }
 
 static PyObject *
+Server_setJackAutoConnectMidiInputPort(Server *self, PyObject *arg)
+{
+    PyObject *tmp;
+
+    if (arg != NULL) {
+        if (PyList_Check(arg)) {
+            tmp = arg;
+            Py_XDECREF(self->jackAutoConnectMidiInputPort);
+            Py_INCREF(tmp);
+            self->jackAutoConnectMidiInputPort = tmp;
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+Server_setJackAutoConnectMidiOutputPort(Server *self, PyObject *arg)
+{
+    PyObject *tmp;
+
+    if (arg != NULL) {
+        if (PyList_Check(arg)) {
+            tmp = arg;
+            Py_XDECREF(self->jackAutoConnectMidiOutputPort);
+            Py_INCREF(tmp);
+            self->jackAutoConnectMidiOutputPort = tmp;
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
 Server_setJackInputPortNames(Server *self, PyObject *arg)
 {
     PyObject *tmp;
@@ -978,6 +1035,44 @@ Server_setJackOutputPortNames(Server *self, PyObject *arg)
 }
 
 static PyObject *
+Server_setJackMidiInputPortName(Server *self, PyObject *arg)
+{
+    PyObject *tmp;
+
+    if (arg != NULL) {
+        if (PY_STRING_CHECK(arg)) {
+            tmp = arg;
+            Py_XDECREF(self->jackMidiInputPortName);
+            Py_INCREF(tmp);
+            self->jackMidiInputPortName = tmp;
+
+            jack_midi_input_port_set_name(self);
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+Server_setJackMidiOutputPortName(Server *self, PyObject *arg)
+{
+    PyObject *tmp;
+
+    if (arg != NULL) {
+        if (PY_STRING_CHECK(arg)) {
+            tmp = arg;
+            Py_XDECREF(self->jackMidiOutputPortName);
+            Py_INCREF(tmp);
+            self->jackMidiOutputPortName = tmp;
+
+            jack_midi_output_port_set_name(self);
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
 Server_setIsJackTransportSlave(Server *self, PyObject *arg)
 {
     if (self->server_booted) {
@@ -1307,6 +1402,12 @@ Server_boot(Server *self, PyObject *arg)
                         Server_error(self, "Pyo built without Portmidi support\n");
                 }
                 break;
+            case PyoJackMidi:
+                /* Initialized inside the jack audio backend. */
+                if (self->audio_be_type != PyoJack) {
+                    Server_error(self, "To use jack midi, you must also use jack as the audio backend.\n"); 
+                }
+                break;
         }
     }
 
@@ -1416,10 +1517,10 @@ Server_stop(Server *self)
 static PyObject *
 Server_recordOptions(Server *self, PyObject *args, PyObject *kwds)
 {
-    Py_ssize_t psize;
+    //Py_ssize_t psize;
     static char *kwlist[] = {"dur", "filename", "fileformat", "sampletype", "quality", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, "d|s#iid", kwlist, &self->recdur, &self->recpath, &psize, &self->recformat, &self->rectype, &self->recquality)) {
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "d|siid", kwlist, &self->recdur, &self->recpath, &self->recformat, &self->rectype, &self->recquality)) {
         return PyInt_FromLong(-1);
     }
 
@@ -1429,12 +1530,12 @@ Server_recordOptions(Server *self, PyObject *args, PyObject *kwds)
 static PyObject *
 Server_start_rec(Server *self, PyObject *args, PyObject *kwds)
 {
-    Py_ssize_t psize;
+    //Py_ssize_t psize;
     char *filename=NULL;
 
     static char *kwlist[] = {"filename", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s#", kwlist, &filename, &psize)) {
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &filename)) {
         return PyInt_FromLong(-1);
     }
     Server_start_rec_internal(self, filename);
@@ -1567,8 +1668,7 @@ Server_removeStream(Server *self, int id)
     int i, sid;
     Stream *stream_tmp;
     PyGILState_STATE s = PyGILState_Ensure();
-    //if (PyObject_HasAttrString((PyObject *)self, "streams")) {
-    if (PySequence_Size(self->streams) != -1) {
+    if (my_server[self->thisServerID] != NULL && PySequence_Size(self->streams) != -1) {
         for (i=0; i<self->stream_count; i++) {
             stream_tmp = (Stream *)PyList_GetItem(self->streams, i);
             if (stream_tmp != NULL) {
@@ -1627,8 +1727,12 @@ Server_changeStreamPosition(Server *self, PyObject *args)
 void pyoGetMidiEvents(Server *self) {
     switch (self->midi_be_type) {
         case PyoPortmidi:
-            portmidiGetEvents(self);
+            if (self->withPortMidi == 1) {
+                portmidiGetEvents(self);
+            }
             break;
+        case PyoJackMidi:
+            /* Handled inside jack audio callback! */
         default:
             break;
     }
@@ -1643,14 +1747,17 @@ Server_noteout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iiil", &pit, &vel, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_noteout(self, pit, vel, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_noteout(self, pit, vel, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -1664,14 +1771,17 @@ Server_afterout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iiil", &pit, &vel, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_afterout(self, pit, vel, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_afterout(self, pit, vel, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -1685,14 +1795,17 @@ Server_ctlout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iiil", &ctlnum, &value, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_ctlout(self, ctlnum, value, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_ctlout(self, ctlnum, value, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -1706,14 +1819,17 @@ Server_programout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iil", &value, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_programout(self, value, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_programout(self, value, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -1727,14 +1843,17 @@ Server_pressout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iil", &value, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_pressout(self, value, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_pressout(self, value, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -1748,14 +1867,17 @@ Server_bendout(Server *self, PyObject *args)
     if (! PyArg_ParseTuple(args, "iil", &value, &chan, &timestamp))
         return PyInt_FromLong(-1);
 
-    if (self->withPortMidiOut) {
-        switch (self->midi_be_type) {
-            case PyoPortmidi:
+    switch (self->midi_be_type) {
+        case PyoPortmidi:
+            if (self->withPortMidiOut) {
                 pm_bendout(self, value, chan, timestamp);
-                break;
-            default:
-                break;
-        }
+            }
+            break;
+        case PyoJackMidi:
+            jack_bendout(self, value, chan, timestamp);
+            break;
+        default:
+            break;
     }
     Py_RETURN_NONE;
 }
@@ -2049,8 +2171,12 @@ static PyMethodDef Server_methods[] = {
     {"setJackAuto", (PyCFunction)Server_setJackAuto, METH_VARARGS, "Tells the server to auto-connect Jack ports (0 = disable, 1 = enable)."},
     {"setJackAutoConnectInputPorts", (PyCFunction)Server_setJackAutoConnectInputPorts, METH_O, "Sets a list of ports to auto-connect inputs when using Jack."},
     {"setJackAutoConnectOutputPorts", (PyCFunction)Server_setJackAutoConnectOutputPorts, METH_O, "Sets a list of ports to auto-connect outputs when using Jack."},
+    {"setJackAutoConnectMidiInputPort", (PyCFunction)Server_setJackAutoConnectMidiInputPort, METH_O, "Sets a list of ports to auto-connect midi inputs when using JackMidi."},
+    {"setJackAutoConnectMidiOutputPort", (PyCFunction)Server_setJackAutoConnectMidiOutputPort, METH_O, "Sets a list of ports to auto-connect midi outputs when using JackMidi."},
     {"setJackInputPortNames", (PyCFunction)Server_setJackInputPortNames, METH_O, "Sets the short name of input ports for jack server."},
     {"setJackOutputPortNames", (PyCFunction)Server_setJackOutputPortNames, METH_O, "Sets the short name of output ports for jack server."},
+    {"setJackMidiInputPortName", (PyCFunction)Server_setJackMidiInputPortName, METH_O, "Sets the short name of midi input port for jack server."},
+    {"setJackMidiOutputPortName", (PyCFunction)Server_setJackMidiOutputPortName, METH_O, "Sets the short name of midi output port for jack server."},
     {"setIsJackTransportSlave", (PyCFunction)Server_setIsJackTransportSlave, METH_O, "Sets if the server's start/stop is slave of jack transport."},
     {"setGlobalSeed", (PyCFunction)Server_setGlobalSeed, METH_O, "Sets the server's global seed for random objects."},
     {"setAmp", (PyCFunction)Server_setAmp, METH_O, "Sets the overall amplitude."},
diff --git a/src/objects/granulatormodule.c b/src/objects/granulatormodule.c
index 78db68b..d1ab54f 100644
--- a/src/objects/granulatormodule.c
+++ b/src/objects/granulatormodule.c
@@ -50,6 +50,7 @@ typedef struct {
     MYFLT *gsize;
     MYFLT *gphase;
     MYFLT *lastppos;
+    MYFLT srScale;
     int modebuffer[5];
 } Granulator;
 
@@ -89,7 +90,7 @@ Granulator_transform_iii(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos;
-                self->gsize[j] = dur * self->sr;
+                self->gsize[j] = dur * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -152,7 +153,7 @@ Granulator_transform_aii(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos;
-                self->gsize[j] = dur * self->sr;
+                self->gsize[j] = dur * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -195,7 +196,7 @@ Granulator_transform_iai(Granulator *self) {
 
     inc = pit * (1.0 / self->basedur) / self->sr;
 
-    MYFLT gsize = dur * self->sr;
+    MYFLT gsize = dur * self->sr * self->srScale;
 
     for (j=0; j<self->ngrains; j++) {
         self->gsize[j] = gsize;
@@ -282,7 +283,7 @@ Granulator_transform_aai(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos[i];
-                self->gsize[j] = dur * self->sr;
+                self->gsize[j] = dur * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -344,7 +345,7 @@ Granulator_transform_iia(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos;
-                self->gsize[j] = dur[i] * self->sr;
+                self->gsize[j] = dur[i] * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -407,7 +408,7 @@ Granulator_transform_aia(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos;
-                self->gsize[j] = dur[i] * self->sr;
+                self->gsize[j] = dur[i] * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -469,7 +470,7 @@ Granulator_transform_iaa(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos[i];
-                self->gsize[j] = dur[i] * self->sr;
+                self->gsize[j] = dur[i] * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -532,7 +533,7 @@ Granulator_transform_aaa(Granulator *self) {
 
             if (ppos < self->lastppos[j]) {
                 self->startPos[j] = pos[i];
-                self->gsize[j] = dur[i] * self->sr;
+                self->gsize[j] = dur[i] * self->sr * self->srScale;
             }
             self->lastppos[j] = ppos;
 
@@ -696,6 +697,7 @@ Granulator_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->ngrains = 8;
     self->basedur = 0.1;
     self->pointerPos = 1.0;
+    self->srScale = 1.0;
 	self->modebuffer[0] = 0;
 	self->modebuffer[1] = 0;
 	self->modebuffer[2] = 0;
@@ -717,6 +719,7 @@ Granulator_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     }
     Py_XDECREF(self->table);
     self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");
+    self->srScale = TableStream_getSamplingRate(self->table) / self->sr;
 
     if ( PyObject_HasAttrString((PyObject *)envtmp, "getTableStream") == 0 ) {
         PyErr_SetString(PyExc_TypeError, "\"env\" argument of Granulator must be a PyoTableObject.\n");
diff --git a/src/objects/midimodule.c b/src/objects/midimodule.c
index be090eb..1053726 100644
--- a/src/objects/midimodule.c
+++ b/src/objects/midimodule.c
@@ -426,19 +426,24 @@ PyTypeObject CtlScan2Type = {
 int
 getPosToWrite(long timestamp, Server *server, double sr, int bufsize)
 {
-    int offset = 0;
-    long realtimestamp, elapsed, ms;
-    realtimestamp = timestamp - Server_getMidiTimeOffset(server);
-    if (realtimestamp < 0)
-        return 0;
-    elapsed = (long)(Server_getElapsedTime(server) / sr * 1000);
-    ms = realtimestamp - (elapsed - (long)(bufsize / sr * 1000));
-    offset = (int)(ms * 0.001 * sr);
-    if (offset < 0)
-        offset = 0;
-    else if (offset >= bufsize)
-        offset = bufsize - 1;
-    return offset;
+    /* With JackMidi, the timestamp is already a sample offset inside the buffer size. */
+    if (server->withJackMidi) {
+        return (int)timestamp;
+    } else {
+        int offset = 0;
+        long realtimestamp, elapsed, ms;
+        realtimestamp = timestamp - Server_getMidiTimeOffset(server);
+        if (realtimestamp < 0)
+            return 0;
+        elapsed = (long)(Server_getElapsedTime(server) / sr * 1000);
+        ms = realtimestamp - (elapsed - (long)(bufsize / sr * 1000));
+        offset = (int)(ms * 0.001 * sr);
+        if (offset < 0)
+            offset = 0;
+        else if (offset >= bufsize)
+            offset = bufsize - 1;
+        return offset;
+    }
 }
 
 typedef struct {
diff --git a/src/objects/panmodule.c b/src/objects/panmodule.c
index 2880276..05d3f26 100644
--- a/src/objects/panmodule.c
+++ b/src/objects/panmodule.c
@@ -1990,11 +1990,11 @@ static void
 VoiceManager_dealloc(VoiceManager* self)
 {
     pyo_DEALLOC
+    VoiceManager_clear(self);
     if (self->voices != NULL) {
         free(self->voices);
         free(self->trigger_streams);
     }
-    VoiceManager_clear(self);
     Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
diff --git a/src/objects/phasevocmodule.c b/src/objects/phasevocmodule.c
index 73b9d58..9396098 100644
--- a/src/objects/phasevocmodule.c
+++ b/src/objects/phasevocmodule.c
@@ -38,6 +38,7 @@ isPowerOfTwo(int x) {
 
 typedef struct {
     pyo_audio_HEAD
+    PyObject *callback;
     PyObject *input;
     Stream *input_stream;
     PVStream *pv_stream;
@@ -110,6 +111,30 @@ PVAnal_realloc_memories(PVAnal *self) {
 }
 
 static void
+PVAnal_data_callback(PVAnal *self) {
+    int i;
+    PyObject *tuple, *result, *magnitudes, *frequencies;
+
+    magnitudes = PyList_New(self->hsize);
+    frequencies = PyList_New(self->hsize);
+    for (i=0; i<self->hsize; i++) {
+        PyList_SET_ITEM(magnitudes, i, PyFloat_FromDouble(self->magn[self->overcount][i]));
+        PyList_SET_ITEM(frequencies, i, PyFloat_FromDouble(self->freq[self->overcount][i]));
+    }
+
+    tuple = PyTuple_New(2);
+    PyTuple_SET_ITEM(tuple, 0, magnitudes);
+    PyTuple_SET_ITEM(tuple, 1, frequencies);
+    result = PyObject_Call(self->callback, tuple, NULL);
+    if (result == NULL) {
+        PyErr_Print();
+    }
+
+    Py_DECREF(magnitudes);
+    Py_DECREF(frequencies);
+}
+
+static void
 PVAnal_process(PVAnal *self) {
     int i, k, mod;
     MYFLT real, imag, mag, phase, tmp;
@@ -145,6 +170,9 @@ PVAnal_process(PVAnal *self) {
                 self->magn[self->overcount][k] = mag;
                 self->freq[self->overcount][k] = (tmp + k * self->scale) * self->factor;
             }
+            if (self->callback != Py_None) {
+                PVAnal_data_callback(self);
+            }
             for (k=0; k<self->inputLatency; k++) {
                 self->input_buffer[k] = self->input_buffer[k + self->hopsize];
             }
@@ -174,6 +202,9 @@ PVAnal_traverse(PVAnal *self, visitproc visit, void *arg)
     Py_VISIT(self->input);
     Py_VISIT(self->input_stream);
     Py_VISIT(self->pv_stream);
+    if (self->callback != Py_None) {
+        Py_VISIT(self->callback);
+    }
     return 0;
 }
 
@@ -184,6 +215,9 @@ PVAnal_clear(PVAnal *self)
     Py_CLEAR(self->input);
     Py_CLEAR(self->input_stream);
     Py_CLEAR(self->pv_stream);
+    if (self->callback != Py_None) {
+        Py_CLEAR(self->callback);
+    }
     return 0;
 }
 
@@ -218,10 +252,12 @@ static PyObject *
 PVAnal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 {
     int i, k;
-    PyObject *inputtmp, *input_streamtmp;
+    PyObject *inputtmp, *input_streamtmp, *callbacktmp=NULL;
     PVAnal *self;
     self = (PVAnal *)type->tp_alloc(type, 0);
 
+    Py_INCREF(Py_None);
+    self->callback = Py_None;
     self->size = 1024;
     self->olaps = 4;
     self->wintype = 2;
@@ -229,13 +265,17 @@ PVAnal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     Stream_setFunctionPtr(self->stream, PVAnal_compute_next_data_frame);
     self->mode_func_ptr = PVAnal_setProcMode;
 
-    static char *kwlist[] = {"input", "size", "olaps", "wintype", NULL};
+    static char *kwlist[] = {"input", "size", "olaps", "wintype", "callback", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, "O|iii", kwlist, &inputtmp, &self->size, &self->olaps, &self->wintype))
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "O|iiiO", kwlist, &inputtmp, &self->size, &self->olaps, &self->wintype, &callbacktmp))
         Py_RETURN_NONE;
 
     INIT_INPUT_STREAM
 
+    if (callbacktmp) {
+        PyObject_CallMethod((PyObject *)self, "setCallback", "O", callbacktmp);
+    }
+
     PyObject_CallMethod(self->server, "addStream", "O", self->stream);
 
     MAKE_NEW_PV_STREAM(self->pv_stream, &PVStreamType, NULL);
@@ -316,6 +356,26 @@ PVAnal_setWinType(PVAnal *self, PyObject *arg)
     return Py_None;
 }
 
+static PyObject *
+PVAnal_setCallback(PVAnal *self, PyObject *arg)
+{
+	PyObject *tmp;
+
+	if (! PyCallable_Check(arg) && arg != Py_None) {
+        PyErr_SetString(PyExc_TypeError, "The callback attribute must be callable.");
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+    tmp = arg;
+    Py_XDECREF(self->callback);
+    Py_INCREF(tmp);
+    self->callback = tmp;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
 static PyMemberDef PVAnal_members[] = {
 {"server", T_OBJECT_EX, offsetof(PVAnal, server), 0, "Pyo server."},
 {"stream", T_OBJECT_EX, offsetof(PVAnal, stream), 0, "Stream object."},
@@ -333,6 +393,7 @@ static PyMethodDef PVAnal_methods[] = {
 {"setSize", (PyCFunction)PVAnal_setSize, METH_O, "Sets a new FFT size."},
 {"setOverlaps", (PyCFunction)PVAnal_setOverlaps, METH_O, "Sets a new number of overlaps."},
 {"setWinType", (PyCFunction)PVAnal_setWinType, METH_O, "Sets a new window type."},
+{"setCallback", (PyCFunction)PVAnal_setCallback, METH_O, "Sets a callback function."},
 {NULL}  /* Sentinel */
 };
 
diff --git a/tests/encoding_test.py b/tests/encoding_test.py
index e3629d7..1baed82 100644
--- a/tests/encoding_test.py
+++ b/tests/encoding_test.py
@@ -15,46 +15,47 @@ print("Locale preferred encoding: ", locale.getpreferredencoding())
 print("Locale default encoding: ", locale.getdefaultlocale())
 
 s = Server().boot()
+s.verbosity = 15
 
 ### Need a python layer to encode the path in python 3.
 # Python 3
-#p = 'bébêtte/noise.aif'.encode(sys.getfilesystemencoding())
+#p = 'bébêtte/noise.aif'.encode(sys.getfilesystemencoding())
 # Python 2
-p = 'bébêtte/noise.aif'
+p = 'bébêtte/noise.aif'
 
 ########## SNDINFO ###############
 info = sndinfo(p)
 print("sndinfo output:\n", info)
 
 ####### SAVEFILE ##############
-sr, dur, chnls, path = 44100, 5, 2, os.path.join("bébêtte", 'savefile.aif')
+sr, dur, chnls, path = 44100, 5, 2, os.path.join("bébêtte", 'savefile.aif')
 samples = [[uniform(-0.5,0.5) for i in range(sr*dur)] for i in range(chnls)]
 savefile(samples=samples, path=path, sr=sr, channels=chnls, fileformat=1, sampletype=1)
-print("Savefile record:", os.path.isfile(os.path.join("bébêtte", 'savefile.aif')))
+print("Savefile record:", os.path.isfile(os.path.join("bébêtte", 'savefile.aif')))
 
 ####### SAVEFILEFROMTABLE #########
 home = os.path.expanduser('~')
 path1 = SNDS_PATH + '/transparent.aif'
-path2 = os.path.join("bébêtte", 'savefileFromTable.aif')
+path2 = os.path.join("bébêtte", 'savefileFromTable.aif')
 t = SndTable(path1)
 savefileFromTable(table=t, path=path2, fileformat=1, sampletype=1)
-print("SavefileFromTable record:", os.path.isfile(os.path.join("bébêtte", 'savefileFromTable.aif')))
+print("SavefileFromTable record:", os.path.isfile(os.path.join("bébêtte", 'savefileFromTable.aif')))
 
 ##### UPSAMP/DOWNSAMP ######
 # upsample a signal 3 times
-upfile = os.path.join("bébêtte", 'upsamp.aif')
+upfile = os.path.join("bébêtte", 'upsamp.aif')
 upsamp(p, upfile, 2, 256)
-print("upsamp record:", os.path.isfile(os.path.join("bébêtte", 'upsamp.aif')))
+print("upsamp record:", os.path.isfile(os.path.join("bébêtte", 'upsamp.aif')))
 # downsample the upsampled signal 3 times
-downfile = os.path.join("bébêtte", 'downsamp.aif')
+downfile = os.path.join("bébêtte", 'downsamp.aif')
 downsamp(upfile, downfile, 3, 256)
-print("downsamp record:", os.path.isfile(os.path.join("bébêtte", 'downsamp.aif')))
+print("downsamp record:", os.path.isfile(os.path.join("bébêtte", 'downsamp.aif')))
 
 ######### SfPlayer ###########
 sf1 = SfPlayer(p, mul=0.1)
 sf1.setPath(downfile)
 
-p2 = os.path.join("bébêtte", 'transparent.aif')
+p2 = os.path.join("bébêtte", 'transparent.aif')
 ######### SfMarkerShuffler ###########
 sf2 = SfMarkerShuffler(p2, mul=0.3)
 
@@ -71,11 +72,11 @@ tab1.insert(p2, 0.1, 0.1)
 tabplay = Osc(tab1, [tab1.getRate(), tab1.getRate() * 1.01], mul=0.5)
 
 ######### CvlVerb impulse path ###########
-impl = os.path.join("bébêtte", 'IRMediumHallStereo.wav')
+impl = os.path.join("bébêtte", 'IRMediumHallStereo.wav')
 cv = CvlVerb(tabplay, impl, size=1024, bal=0.7).out()
 
 ######### Record ###########
-recfile = os.path.join("bébêtte", 'recfile.wav')
+recfile = os.path.join("bébêtte", 'recfile.wav')
 rec = Record(cv, recfile, chnls=2, fileformat=0, sampletype=0, buffering=4, quality=0.40)
 recplay = None
 def reccallback():
@@ -88,10 +89,10 @@ def reccallback():
 after = CallAfter(reccallback, 4)
 
 ######### Server ###########
-servrecfile = os.path.join("bébêtte", 'servrecfile.wav')
-#s.recordOptions(dur=-1, filename=servrecfile)
-servrecfile = os.path.join("bébêtte", 'servrecfile2.wav')
-s.recstart(servrecfile)
+servrecfile = os.path.join("bébêtte", 'servrecfile.wav')
+s.recordOptions(dur=-1, filename=servrecfile)
+#servrecfile = os.path.join("bébêtte", 'servrecfile2.wav')
+#s.recstart(servrecfile)
 
 s.gui(locals(), exit=False)
 
@@ -102,13 +103,13 @@ def delfile(f):
 # On windows, we should delete Sf* objects before deleting the audio files.
 del sf1
 del recplay
-delfile(os.path.join("bébêtte", 'savefile.aif'))
-delfile(os.path.join("bébêtte", 'savefileFromTable.aif'))
-delfile(os.path.join("bébêtte", 'upsamp.aif'))
-delfile(os.path.join("bébêtte", 'downsamp.aif'))
-delfile(os.path.join("bébêtte", 'recfile.wav'))
-delfile(os.path.join("bébêtte", 'servrecfile.wav'))
-delfile(os.path.join("bébêtte", 'servrecfile2.wav'))
+delfile(os.path.join("bébêtte", 'savefile.aif'))
+delfile(os.path.join("bébêtte", 'savefileFromTable.aif'))
+delfile(os.path.join("bébêtte", 'upsamp.aif'))
+delfile(os.path.join("bébêtte", 'downsamp.aif'))
+delfile(os.path.join("bébêtte", 'recfile.wav'))
+delfile(os.path.join("bébêtte", 'servrecfile.wav'))
+delfile(os.path.join("bébêtte", 'servrecfile2.wav'))
 
 """
 1 - Adapt encoding line for E-Pyo tempfile. **done**
diff --git a/utils/E-Pyo.py b/utils/E-Pyo.py
index d545fbc..ecb4ce3 100755
--- a/utils/E-Pyo.py
+++ b/utils/E-Pyo.py
@@ -26,7 +26,7 @@ import wx.lib.scrolledpanel as scrolled
 import wx.lib.dialogs
 import wx.stc as stc
 import wx.lib.agw.flatnotebook as FNB
-from pyo import * # what do we really need? OBJECTS_TREE, PYO_VERSION
+from pyo import * # TODO: what do we really need? OBJECTS_TREE, PYO_VERSION
 from PyoDoc import ManualFrame
 
 if sys.version_info[0] < 3:
@@ -34,9 +34,11 @@ if sys.version_info[0] < 3:
     unicode_t = unicode
     reload(sys)
     sys.setdefaultencoding("utf-8")
+    exec_string = "python"
 else:
     from io import StringIO as StringIO
     unicode_t = str
+    exec_string = "python3"
 
 if "phoenix" in wx.version():
     from wx.adv import AboutDialogInfo, AboutBox
@@ -141,7 +143,8 @@ if not os.path.isfile(PREFERENCES_PATH):
         f.write("epyo_prefs = {}")
 
 epyo_prefs = {}
-with open(PREFERENCES_PATH, "r") as f:
+# TODO: Should handle utf-8 encoding!
+with codecs.open(PREFERENCES_PATH, "r", encoding="utf-8") as f:
     text = f.read()
 spos = text.find("=")
 dictext = text[spos+1:]
@@ -344,11 +347,11 @@ BACKGROUND_SERVER_DEFAULT_ARGS = 'sr=44100, nchnls=2, buffersize=256, duplex=1,
 BACKGROUND_SERVER_ARGS = PREFERENCES.get("background_server_args", BACKGROUND_SERVER_DEFAULT_ARGS)
 
 ################## TEMPLATES ##################
-HEADER_TEMPLATE = """#!/usr/bin/env python
+HEADER_TEMPLATE = """#!/usr/bin/env %s
 # encoding: utf-8
-"""
+""" % exec_string
 
-PYO_TEMPLATE = """#!/usr/bin/env python
+PYO_TEMPLATE = """#!/usr/bin/env %s
 # encoding: utf-8
 from pyo import *
 
@@ -358,7 +361,7 @@ s = Server(sr=44100, nchnls=2, buffersize=512, duplex=1).boot()
 
 
 s.gui(locals())
-"""
+""" % exec_string
 
 CECILIA5_TEMPLATE = '''class Module(BaseModule):
     """
@@ -447,7 +450,7 @@ mainFrame.Show()
 app.MainLoop()
 '''
 
-WXPYTHON_TEMPLATE = '''#!/usr/bin/env python
+WXPYTHON_TEMPLATE = '''#!/usr/bin/env %s
 # encoding: utf-8
 import wx
 
@@ -463,7 +466,7 @@ if __name__ == "__main__":
     mainFrame = MyFrame(None, title='Simple App', pos=(100,100), size=(500,300))
     mainFrame.Show()
     app.MainLoop()
-'''
+''' % exec_string
 
 RADIOPYO_TEMPLATE = '''#!/usr/bin/env python
 # encoding: utf-8
@@ -538,7 +541,7 @@ templateid = 91
 template_files = sorted([f for f in os.listdir(TEMPLATE_PATH) if f.endswith(".py")])
 for f in template_files:
     try:
-        with open(os.path.join(TEMPLATE_PATH, f)) as ftemp:
+        with codecs.open(os.path.join(TEMPLATE_PATH, f), "r", encoding="utf-8") as ftemp:
             ftext = ftemp.read()
         TEMPLATE_NAMES[templateid] = f.replace(".py", "")
         TEMPLATE_DICT[templateid] = ftext
@@ -549,8 +552,13 @@ for f in template_files:
 ################## BUILTIN KEYWORDS COMPLETION ##################
 FROM_COMP = ''' `module` import `*`
 '''
-EXEC_COMP = ''' "`expression`" in `self.locals`
+if sys.version_info[0] < 3:
+    EXEC_COMP = ''' "`expression`" in `self.locals`
+'''
+else:
+    EXEC_COMP = '''("`expression`", `globals()`, `locals()`)
 '''
+
 RAISE_COMP = ''' Exception("`An exception occurred...`")
 '''
 TRY_COMP = ''':
@@ -585,7 +593,6 @@ WHILE_COMP = """ `i` `>` `0`:
 ASSERT_COMP = ''' `expression` `>` `0`, "`expression should be positive`"
 '''
 
-# TODO: Python 3 syntax (if possible, compatible with python 2)
 BUILTINS_DICT = {"from": FROM_COMP, "try": TRY_COMP, "if": IF_COMP,
                  "def": DEF_COMP, "class": CLASS_COMP, "for": FOR_COMP,
                  "while": WHILE_COMP, "exec": EXEC_COMP, "raise": RAISE_COMP,
@@ -862,8 +869,8 @@ elif wx.Platform == '__WXMAC__':
     FONT_SIZE2 = 9
     DEFAULT_FONT_FACE = 'Monaco'
 else:
-    FONT_SIZE = 8
-    FONT_SIZE2 = 7
+    FONT_SIZE = 11
+    FONT_SIZE2 = 8
     DEFAULT_FONT_FACE = 'Monospace'
 
 
@@ -886,7 +893,7 @@ STYLES_LABELS = {'default': 'Foreground', 'background': 'Background',
                  'bracelight': 'Brace Match', 'bracebad': 'Brace Mismatch',
                  'lineedge': 'Line Edge'}
 
-with open(PREF_STYLE) as f:
+with codecs.open(PREF_STYLE, "r", encoding="utf-8") as f:
     text = f.read()
 spos = text.find("=")
 dictext = text[spos+1:]
@@ -1014,8 +1021,10 @@ class RunningThread(threading.Thread):
         wx.QueueEvent(self.event_receiver, data_event)
         while self.proc.poll() == None and not self.terminated:
             log = ""
-            for line in self.proc.stdout.readline():
-                log = log + str(line)
+            line = self.proc.stdout.readline()
+            if line.startswith("ALSA lib"):
+                continue
+            log = log + str(line)
             log = log.replace(">>> ", "").replace("... ", "")
             data_event = DataEvent({"log": log, "pid": self.pid, "filename": self.filename, "active": True})
             wx.QueueEvent(self.event_receiver, data_event)
@@ -1515,7 +1524,7 @@ class ColourEditor(wx.Frame):
         global STYLES
         stl = event.GetString()
         self.cur_style = stl
-        with open(os.path.join(ensureNFD(STYLES_PATH), stl)) as f:
+        with codecs.open(os.path.join(ensureNFD(STYLES_PATH), stl), "r", encoding="utf-8") as f:
             text = f.read()
         spos = text.find("=")
         dictext = text[spos+1:]
@@ -2496,7 +2505,7 @@ class MainFrame(wx.Frame):
             submenu = wx.Menu(title=cat)
             files = [f for f in os.listdir(os.path.join(SNIPPETS_PATH, cat))]
             for file in files:
-                with open(os.path.join(SNIPPETS_PATH, cat, file), "r") as f:
+                with codecs.open(os.path.join(SNIPPETS_PATH, cat, file), "r", encoding="utf-8") as f:
                     text = f.read()
                 spos = text.find("=")
                 dictext = text[spos+1:]
@@ -2608,7 +2617,7 @@ class MainFrame(wx.Frame):
             for i in range(12000, self.ID_FILTERS):
                 self.filters_menu.Delete(i)
         ID_FILTERS = 12000
-        with open(FILTERS_FILE, "r") as f:
+        with codecs.open(FILTERS_FILE, "r", encoding="utf-8") as f:
             for line in f.readlines():
                 if line.startswith("def "):
                     ppos = line.find("(")
@@ -2623,7 +2632,7 @@ class MainFrame(wx.Frame):
         self.panel.addPage(FILTERS_FILE)
 
     def applyFilter(self, evt):
-        with open(FILTERS_FILE, "r") as f:
+        with codecs.open(FILTERS_FILE, "r", encoding="utf-8") as f:
             text = f.read()
         functions = {}
         exec(text, functions)
@@ -2881,7 +2890,7 @@ class MainFrame(wx.Frame):
 
     def setStyle(self, st, fromMenu=False):
         global STYLES
-        with open(os.path.join(ensureNFD(STYLES_PATH), st)) as f:
+        with codecs.open(os.path.join(ensureNFD(STYLES_PATH), st), "r", encoding="utf-8") as f:
             text = f.read()
         spos = text.find("=")
         dictext = text[spos+1:]
@@ -3218,7 +3227,7 @@ class MainFrame(wx.Frame):
 
     def runner(self, event):
         if self.master_document != None:
-            with open(self.master_document, "r") as f:
+            with codecs.open(self.master_document, "r", encoding="utf-8") as f:
                 text = f.read()
         else:
             text = self.panel.editor.GetText()
@@ -3511,7 +3520,7 @@ class MainPanel(wx.Panel):
 
     def addPage(self, file, encoding=None):
         editor = Editor(self.notebook, -1, size=(0, -1), setTitle=self.SetTitle, getTitle=self.GetTitle)
-        label = os.path.split(file)[1].split('.')[0]
+        label = os.path.split(file)[1]
         self.notebook.AddPage(editor, label, True)
         text = ""
         if encoding != None:
@@ -3784,7 +3793,10 @@ class Editor(stc.StyledTextCtrl):
         if ext in ["py", "pyw", "c5"]:
             self.SetLexer(stc.STC_LEX_PYTHON)
             self.SetStyleBits(self.GetStyleBitsNeeded())
-            self.SetKeyWords(0, " ".join(keyword.kwlist) + " None True False print ")
+            if sys.version_info[0] < 3:
+                self.SetKeyWords(0, " ".join(keyword.kwlist) + " None True False print ")
+            else:
+                self.SetKeyWords(0, " ".join(keyword.kwlist) + " None True False ")
             self.SetKeyWords(1, " ".join(PYO_WORDLIST))
             self.StyleSetSpec(stc.STC_P_DEFAULT, buildStyle('default'))
             self.StyleSetSpec(stc.STC_P_COMMENTLINE, buildStyle('comment'))
@@ -4986,7 +4998,7 @@ class OutputLogPanel(wx.Panel):
         self.toolbar.Realize()
         toolbarbox.Add(self.toolbar, 1, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 0)
 
-        tb2 = wx.ToolBar(self, -1, size=(-1,32))
+        tb2 = wx.ToolBar(self, -1)
         if PLATFORM == "darwin":
             tb2.SetToolBitmapSize(tsize)
         tb2.AddSeparator()
@@ -5113,7 +5125,6 @@ class ProjectTree(wx.Panel):
         self.selectedItem = None
         self.edititem = self.editfolder = self.itempath = self.scope = None
 
-        tsize = (24, 24)
         file_add_bmp = catalog['file_add_icon.png'].GetBitmap()
         folder_add_bmp = catalog['folder_add_icon.png'].GetBitmap()
         close_panel_bmp = catalog['close_panel_icon.png'].GetBitmap()
@@ -5122,8 +5133,7 @@ class ProjectTree(wx.Panel):
         self.sizer = wx.BoxSizer(wx.VERTICAL)
 
         toolbarbox = wx.BoxSizer(wx.HORIZONTAL)
-        self.toolbar = wx.ToolBar(self, -1, size=(-1,36))
-        self.toolbar.SetToolBitmapSize(tsize)
+        self.toolbar = wx.ToolBar(self, -1)
 
         if "phoenix" not in wx.version():
             self.toolbar.AddLabelTool(TOOL_ADD_FILE_ID, "Add File",
@@ -5144,8 +5154,7 @@ class ProjectTree(wx.Panel):
         self.toolbar.Realize()
         toolbarbox.Add(self.toolbar, 1, wx.ALIGN_LEFT | wx.EXPAND, 0)
 
-        tb2 = wx.ToolBar(self, -1, size=(-1,36))
-        tb2.SetToolBitmapSize(tsize)
+        tb2 = wx.ToolBar(self, -1)
 
         if "phoenix" not in wx.version():
             tb2.AddLabelTool(15, "Close Panel", close_panel_bmp, shortHelp="Close Panel")
@@ -5168,6 +5177,8 @@ class ProjectTree(wx.Panel):
 
         if PLATFORM == 'darwin':
             pt = 11
+        elif PLATFORM.startswith("linux"):
+            pt = 10
         else:
             pt = 8
         fnt = wx.Font(pt, wx.ROMAN, wx.NORMAL, wx.NORMAL, False, STYLES['face'])
diff --git a/utils/snippets/Utilities/ChooseAudioDev b/utils/snippets/Utilities/ChooseAudioDev
index 07d267c..5b1a020 100644
--- a/utils/snippets/Utilities/ChooseAudioDev
+++ b/utils/snippets/Utilities/ChooseAudioDev
@@ -1 +1 @@
-snippet = {'shortcut': u'Shift-Alt-7', 'value': u'pa_list_devices()\ndevIn = input("Enter your input device number : ")\ndevOut = input("Enter your output device number : ")\ns = Server()\ns.setInputDevice(devIn)\ns.setOutputDevice(devOut)\ns.boot()\n'}
\ No newline at end of file
+snippet = {'shortcut': u'Shift-Alt-7', 'value': u'pa_list_devices()\ndevIn = int(input("Enter your input device number : "))\ndevOut = int(input("Enter your output device number : "))\ns = Server()\ns.setInputDevice(devIn)\ns.setOutputDevice(devOut)\ns.boot()\n'}

-- 
python-pyo packaging



More information about the pkg-multimedia-commits mailing list