[SCM] python-pyo/master: Added a setKeepLast method to TableRead object (will hold last value).

tiago at users.alioth.debian.org tiago at users.alioth.debian.org
Wed May 10 00:22:33 UTC 2017


The following commit has been merged in the master branch:
commit 9a0f3432cc56956edc9cb46b05404668b7b192e1
Author: Tiago Bortoletto Vaz <tiago at debian.org>
Date:   Sun Mar 12 17:42:22 2017 -0400

    Added a setKeepLast method to TableRead object (will hold last value).

diff --git a/ChangeLog b/ChangeLog
index 8ee28a6..6a5832f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2017-03-11 belangeo <belangeo at gmail.com>
+
+    * Added a setKeepLast method to TableRead object (will hold last value).
+
+2017-03-10 belangeo <belangeo at gmail.com>
+
+    * Added a setMode method to Selector object to switch between
+      equal power mode and linear fade.
+
+2017-03-06 belangeo <belangeo at gmail.com>
+
+    * Fixed SfMarkerLooper and SfMarkerShuffler markers not accurate
+      when soundfile sampling rate is not the same as the server's
+      sampling rate.
+
+2017-03-04 belangeo <belangeo at gmail.com>
+
+    * Added a "title" argument to Server.gui() method.
+
+2017-03-03 belangeo <belangeo at gmail.com>
+
+    * MidiDispatcher can send sysex message with sendx() method.
+
+2017-03-03 belangeo <belangeo at gmail.com>
+
+    * Midi input refactoring. Events are now spreaded over the buffer size
+	  according to the event's timestamp.
+
+2017-02-19 belangeo <belangeo at gmail.com>
+
+    * Added examples about multicore audio programming with pyo.
+
 2017-02-13 belangeo <belangeo at gmail.com>
 
     * Final revision for version 0.8.3.
diff --git a/TODO.md b/TODO.md
index c5d2807..6a0fe34 100644
--- a/TODO.md
+++ b/TODO.md
@@ -45,6 +45,8 @@ MIDI
 - Create a MidiLinseg object that act like MidiAdsr but with a breakpoints
   function as the envelope. The sustain point should settable by the user.
 
+- sysex support in MidiListener.
+
 
 GUI
 ---
@@ -58,7 +60,7 @@ GUI
 
 - Keyboard, a virtual MIDI keyboard (adapted from Zyne's one).
 
-- Save item in ctrl() and DataTable graph() windows.
+- Save menu item in ctrl() and DataTable graph() windows.
 
 Tables
 ------
diff --git a/debian/changelog b/debian/changelog
index 0760fae..58547a2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-pyo (0.8.3+git20170311.01-1) unstable; urgency=medium
+
+  * Added a setKeepLast method to TableRead object (will hold last value).
+
+ -- Tiago Bortoletto Vaz <tiago at debian.org>  Sun, 12 Mar 2017 17:36:32 -0400
+
 python-pyo (0.8.3-1) unstable; urgency=medium
 
   * New upstream release.
diff --git a/doc-sphinx/source/index.rst b/doc-sphinx/source/index.rst
index 1053c53..7cb051b 100644
--- a/doc-sphinx/source/index.rst
+++ b/doc-sphinx/source/index.rst
@@ -41,6 +41,7 @@ Examples
    Playing with soundfiles <examples/04-soundfiles/index>
    Amplitude envelopes <examples/05-envelopes/index>
    Filtering <examples/06-filters/index>
+   Multicore audio programming <examples/18-multicore/index>
 
 Much more to come... Stay tuned!
 
diff --git a/examples/18-multicore/01-processes-spawning.py b/examples/18-multicore/01-processes-spawning.py
new file mode 100644
index 0000000..dcd5f9a
--- /dev/null
+++ b/examples/18-multicore/01-processes-spawning.py
@@ -0,0 +1,37 @@
+"""
+01-processes-spawning.py - Simple processes spawning, no synchronization. 
+
+Need at least 4 cores to be really effective.
+
+Usage:
+    python3 -i 01-processes-spawning.py
+
+"""
+import time, random, multiprocessing
+from pyo import *
+
+class Proc(multiprocessing.Process):
+    def __init__(self, pitch):
+        super(Proc, self).__init__()
+        self.daemon = True
+        self.pitch = pitch
+
+    def run(self):
+        self.server = Server(audio="jack")
+        self.server.deactivateMidi()
+        self.server.boot().start()
+
+        # 200 randomized band-limited square wave oscillators.
+        self.amp = Fader(fadein=5, mul=0.01).play()
+        lo, hi = midiToHz((self.pitch - 0.1, self.pitch + 0.1))
+        self.fr = Randi(lo, hi, [random.uniform(.2, .4) for i in range(200)])
+        self.sh = Randi(0.1, 0.9, [random.uniform(.2, .4) for i in range(200)])
+        self.osc = LFO(self.fr, sharp=self.sh, type=2, mul=self.amp).out()
+
+        time.sleep(30) # Play for 30 seconds.
+        self.server.stop()
+
+if __name__ == '__main__':
+    # C major chord (one note per process).
+    p1, p2, p3, p4 = Proc(48), Proc(52), Proc(55), Proc(60)
+    p1.start(); p2.start(); p3.start(); p4.start()
diff --git a/examples/18-multicore/02-sharing-audio.py b/examples/18-multicore/02-sharing-audio.py
new file mode 100644
index 0000000..faef36c
--- /dev/null
+++ b/examples/18-multicore/02-sharing-audio.py
@@ -0,0 +1,48 @@
+"""
+02-sharing-audio.py - Sharing audio signals between processes.
+
+Usage:
+    python3 -i 02-sharing-audio.py
+
+"""
+import time, random, multiprocessing
+from pyo import *
+
+class Proc(multiprocessing.Process):
+    def __init__(self, create):
+        super(Proc, self).__init__()
+        self.daemon = True
+        self.create = create
+
+    def run(self):
+        self.server = Server(audio="jack")
+        self.server.deactivateMidi()
+        self.server.boot().start()
+        bufsize = self.server.getBufferSize()
+
+        nbands = 50
+        names = ["/f%02d" % i for i in range(nbands)]
+
+        if self.create: # 50 bands frequency splitter.
+            freq = [20 * 1.1487 ** i for i in range(nbands)]
+            amp = [pow(10, (i*-1)*0.05) * 8 for i in range(nbands)]
+            self.input = SfPlayer(SNDS_PATH+"/transparent.aif", loop=True)
+            self.filts = IRWinSinc(self.input, freq, freq, 3, 128, amp)
+            self.table = SharedTable(names, create=True, size=bufsize)
+            self.recrd = TableFill(self.filts, self.table)
+        else: # Independent transposition per band.
+            self.table = SharedTable(names, create=False, size=bufsize)
+            self.tscan = TableScan(self.table)
+            transpofac = [random.uniform(0.98,1.02) for i in range(nbands)]
+            self.pvana = PVAnal(self.tscan, size=1024, overlaps=4)
+            self.pvtra = PVTranspose(self.pvana, transpo=transpofac)
+            self.pvsyn = PVSynth(self.pvtra).out()
+
+        time.sleep(30)
+        self.server.stop()
+
+if __name__ == '__main__':
+    analysis = Proc(create=True)
+    synthesis = Proc(create=False)
+    analysis.start()
+    synthesis.start()
diff --git a/examples/18-multicore/03-synchronization.py b/examples/18-multicore/03-synchronization.py
new file mode 100644
index 0000000..0033dd2
--- /dev/null
+++ b/examples/18-multicore/03-synchronization.py
@@ -0,0 +1,74 @@
+"""
+03-synchronization.py - Synchronizing multiple processes.
+
+Usage:
+    python3 -i 03-synchronization.py
+
+"""
+import time, random, multiprocessing
+from pyo import *
+
+RECORD = False
+
+class Main(multiprocessing.Process):
+    def __init__(self):
+        super(Main, self).__init__()
+        self.daemon = True
+
+    def run(self):
+        self.server = Server(audio="jack")
+        self.server.deactivateMidi()
+        self.server.boot().start()
+        bufsize = self.server.getBufferSize()
+        if RECORD:
+            self.server.recstart("synchronization.wav")
+
+        self.tab1 = SharedTable("/audio-1", create=False, size=bufsize)
+        self.tab2 = SharedTable("/audio-2", create=False, size=bufsize)
+        self.out1 = TableScan(self.tab1).out()
+        self.out2 = TableScan(self.tab2).out(1)
+
+        time.sleep(30)
+        self.server.stop()
+
+class Proc(multiprocessing.Process):
+    def __init__(self, voice, conn):
+        super(Proc, self).__init__()
+        self.voice = voice
+        self.connection = conn
+        self.daemon = True
+
+    def run(self):
+        self.server = Server(audio="jack")
+        self.server.deactivateMidi()
+        self.server.boot()
+        bufsize = self.server.getBufferSize()
+
+        name = "/audio-%d" % self.voice
+        self.audiotable = SharedTable(name, create=True, size=bufsize)
+
+        onsets = random.sample([5,6,7,8,9], 2)
+        self.tab = CosTable([(0,0), (32,1), (512,0.5), (4096,0.5), (8191,0)])
+        self.ryt = Euclide(time=.125, taps=16, onsets=onsets, poly=1).play()
+        self.mid = TrigXnoiseMidi(self.ryt, dist=12, mrange=(60, 96))
+        self.frq = Snap(self.mid, choice=[0,2,3,5,7,8,10], scale=1)
+        self.amp = TrigEnv(self.ryt, table=self.tab, dur=self.ryt['dur'], 
+                           mul=self.ryt['amp'])
+        self.sig = SineLoop(freq=self.frq, feedback=0.08, mul=self.amp*0.3)
+        self.fil = TableFill(self.sig, self.audiotable)
+
+        # Wait for an incoming signal before starting the server.
+        while not self.connection.poll():
+            pass
+        self.server.start()
+
+        time.sleep(30)
+        self.server.stop()
+
+if __name__ == '__main__':
+    signal, child = multiprocessing.Pipe()
+    p1, p2 = Proc(1, child), Proc(2, child)
+    main = Main()
+    p1.start(); p2.start(); main.start()
+    time.sleep(.05)
+    signal.send(1)
diff --git a/examples/18-multicore/04-data-control.py b/examples/18-multicore/04-data-control.py
new file mode 100644
index 0000000..e35d884
--- /dev/null
+++ b/examples/18-multicore/04-data-control.py
@@ -0,0 +1,73 @@
+"""
+04-data-control.py - Multicore midi synthesizer.
+
+Need at least 4 cores to be really effective.
+
+Usage:
+    python3 -i 04-data-control.py
+
+"""
+import time, multiprocessing
+from random import uniform
+from pyo import *
+
+VOICES_PER_CORE = 4
+
+class Proc(multiprocessing.Process):
+    def __init__(self, pipe):
+        super(Proc, self).__init__()
+        self.daemon = True
+        self.pipe = pipe
+
+    def run(self):
+        self.server = Server(audio="jack")
+        self.server.deactivateMidi()
+        self.server.boot().start()
+
+        self.mid = Notein(poly=VOICES_PER_CORE, scale=1, first=0, last=127)
+        self.amp = MidiAdsr(self.mid['velocity'], 0.005, .1, .7, 0.5, mul=.1)
+        self.pit = self.mid['pitch'] * [uniform(.99, 1.01) for i in range(40)]
+        self.rc1 = RCOsc(self.pit, sharp=0.8, mul=self.amp).mix(1)
+        self.rc2 = RCOsc(self.pit*0.99, sharp=0.8, mul=self.amp).mix(1)
+        self.mix = Mix([self.rc1, self.rc2], voices=2)
+        self.rev = STRev(Denorm(self.mix), [.1, .9], 2, bal=0.30).out()
+
+        while True:
+            if self.pipe.poll():
+                data = self.pipe.recv()
+                self.server.addMidiEvent(*data)
+            time.sleep(0.001)
+
+        self.server.stop()
+
+if __name__ == '__main__':
+    main1, child1 = multiprocessing.Pipe()
+    main2, child2 = multiprocessing.Pipe()
+    main3, child3 = multiprocessing.Pipe()
+    main4, child4 = multiprocessing.Pipe()
+    mains = [main1, main2, main3, main4]
+    p1, p2, p3, p4 = Proc(child1), Proc(child2), Proc(child3), Proc(child4)
+    p1.start(); p2.start(); p3.start(); p4.start()
+
+    playing = {0: [], 1: [], 2: [], 3: []}
+    currentcore = 0
+    def callback(status, data1, data2):
+        global currentcore
+        if status == 0x80 or status == 0x90 and data2 == 0: 
+            for i in range(4):
+                if data1 in playing[i]:
+                    playing[i].remove(data1)
+                    mains[i].send([status, data1, data2])
+                    break
+        elif status == 0x90:
+            for i in range(4):
+                currentcore = (currentcore + 1) % 4
+                if len(playing[currentcore]) < VOICES_PER_CORE:
+                    playing[currentcore].append(data1)
+                    mains[currentcore].send([status, data1, data2])
+                    break
+
+    s = Server()
+    s.setMidiInputDevice(99) # Open all devices.
+    s.boot().start()
+    raw = RawMidi(callback)
diff --git a/include/md_portmidi.h b/include/md_portmidi.h
index 0248a14..7788d6c 100644
--- a/include/md_portmidi.h
+++ b/include/md_portmidi.h
@@ -41,6 +41,7 @@ void pm_programout(Server *self, int value, int chan, long timestamp);
 void pm_pressout(Server *self, int value, int chan, long timestamp);
 void pm_bendout(Server *self, int value, int chan, long timestamp);
 void pm_sysexout(Server *self, unsigned char *msg, long timestamp);
+long pm_get_current_time();
 
 /* Queries. */
 PyObject * portmidi_count_devices();
diff --git a/include/servermodule.h b/include/servermodule.h
index 4b16f33..6a9c0ad 100644
--- a/include/servermodule.h
+++ b/include/servermodule.h
@@ -80,6 +80,7 @@ typedef struct {
     int midiin_count;
     int midiout_count;
     int midi_count;
+    long midi_time_offset;
     double samplingRate;
     int nchnls;
     int ichnls;
@@ -160,6 +161,8 @@ extern PyObject * Server_removeStream(Server *self, int sid);
 extern MYFLT * Server_getInputBuffer(Server *self);
 extern PyoMidiEvent * Server_getMidiEventBuffer(Server *self);
 extern int Server_getMidiEventCount(Server *self);
+extern long Server_getMidiTimeOffset(Server *self);
+extern unsigned long Server_getElapsedTime(Server *self);
 extern int Server_getCurrentResamplingFactor(Server *self);
 extern int Server_getLastResamplingFactor(Server *self);
 extern int Server_generateSeed(Server *self, int oid);
diff --git a/pyolib/_widgets.py b/pyolib/_widgets.py
index a0b9dad..6f2f348 100644
--- a/pyolib/_widgets.py
+++ b/pyolib/_widgets.py
@@ -387,21 +387,23 @@ def createExprEditorWindow(object, title, wxnoserver=False):
             EXPREDITORWINDOWS.append([object, title])
 
 def createServerGUI(nchnls, start, stop, recstart, recstop, setAmp, started,
-                    locals, shutdown, meter, timer, amp, exit):
+                    locals, shutdown, meter, timer, amp, exit, title):
     "Creates the server's GUI."
     global X, Y, MAX_X, NEXT_Y
+    if title is None:
+        title = "Pyo Server"
     if not PYO_USE_WX:
         createRootWindow()
         win = tkCreateToplevelWindow()
         f = ServerGUI(win, nchnls, start, stop, recstart, recstop, setAmp,
                       started, locals, shutdown, meter, timer, amp)
-        f.master.title("pyo server")
+        f.master.title(title)
         f.focus_set()
     else:
         win = createRootWindow()
         f = ServerGUI(None, nchnls, start, stop, recstart, recstop, setAmp,
                       started, locals, shutdown, meter, timer, amp, exit)
-        f.SetTitle("pyo server")
+        f.SetTitle(title)
         f.SetPosition((30, 30))
         f.Show()
         X, Y = (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) - 50,
diff --git a/pyolib/_wxwidgets.py b/pyolib/_wxwidgets.py
index 68d55c8..e9ecb04 100644
--- a/pyolib/_wxwidgets.py
+++ b/pyolib/_wxwidgets.py
@@ -74,9 +74,6 @@ def GetRoundBitmap( w, h, r ):
     b.SetMaskColour(maskColor)
     return b
 
-def GetRoundShape( w, h, r ):
-    return wx.RegionFromBitmap( GetRoundBitmap(w,h,r) )
-
 class ControlSlider(wx.Panel):
 
     def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0,0), size=(200,16), log=False,
@@ -2793,8 +2790,6 @@ class ServerGUI(wx.Frame):
                 exit=True):
         wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
 
-        self.SetTitle("pyo server")
-
         self.menubar = wx.MenuBar()
         self.menu = wx.Menu()
         self.menu.Append(22999, 'Start/Stop\tCtrl+R', kind=wx.ITEM_NORMAL)
diff --git a/pyolib/listener.py b/pyolib/listener.py
index 3fe6d32..c952aee 100644
--- a/pyolib/listener.py
+++ b/pyolib/listener.py
@@ -113,6 +113,8 @@ class MidiDispatcher(threading.Thread):
 
     Use the `send` method to send midi event to connected devices.
 
+    Use the `sendx` method to send sysex event to connected devices.
+
     :Parent: threading.Thread
 
     :Args:
@@ -184,6 +186,31 @@ class MidiDispatcher(threading.Thread):
         status, data1, data2, timestamp, device, lmax = convertArgsToLists(status, data1, data2, timestamp, device)
         [self._dispatcher.send(wrap(status,i), wrap(data1,i), wrap(data2,i), wrap(timestamp,i), wrap(device,i)) for i in range(lmax)]
 
+    def sendx(self, msg, timestamp=0, device=-1):
+        """
+        Send a MIDI system exclusive message to the selected midi output device.
+
+        Arguments can be list of values to generate multiple events
+        in one call.
+
+        :Args:
+
+            msg: str
+                A valid system exclusive message as a string. The first byte
+                must be 0xf0 and the last one must be 0xf7.
+            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.
+            device: int, optional
+                The index of the device to which the message will
+                be sent. The default (-1) means all devices. See
+                `getDeviceInfos()` to retrieve device indexes.
+
+        """
+        msg, timestamp, device, lmax = convertArgsToLists(msg, timestamp, device)
+        [self._dispatcher.sendx(wrap(msg,i), wrap(timestamp,i), wrap(device,i)) for i in range(lmax)]
+
     def getDeviceInfos(self):
         """
         Returns infos about connected midi devices.
diff --git a/pyolib/midi.py b/pyolib/midi.py
index be9e7c3..9112535 100644
--- a/pyolib/midi.py
+++ b/pyolib/midi.py
@@ -161,17 +161,10 @@ class Midictl(PyoObject):
 
     def setInterpolation(self, x):
         """
-        Activate/Deactivate interpolation. Off by default.
-
-        :Args:
-
-            x: boolean
-                True activates the interpolation, False deactivates it.
+        Deprecated method. If needed, use Port or SigTo to interpolate between values.
 
         """
-        pyoArgsAssert(self, "b", x)
-        x, lmax = convertArgsToLists(x)
-        [obj.setInterpolation(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
+        print("setInterpolation() is deprecated. If needed, use Port or SigTo to interpolate between values.")
 
     def ctrl(self, map_list=None, title=None, wxnoserver=False):
         self._map_list = [SLMap(0, 127, 'lin', 'ctlnumber', self._ctlnumber, res="int", dataOnly=True),
@@ -796,17 +789,10 @@ class Bendin(PyoObject):
 
     def setInterpolation(self, x):
         """
-        Activate/Deactivate interpolation. Off by default.
-
-        :Args:
-
-            x: boolean
-                True activates the interpolation, False deactivates it.
+        Deprecated method. If needed, use Port or SigTo to interpolate between values.
 
         """
-        pyoArgsAssert(self, "b", x)
-        x, lmax = convertArgsToLists(x)
-        [obj.setInterpolation(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
+        print("setInterpolation() is deprecated. If needed, use Port or SigTo to interpolate between values.")
 
     def ctrl(self, map_list=None, title=None, wxnoserver=False):
         self._map_list = [SLMap(0.0, 12.0, 'lin', 'brange', self._brange, dataOnly=True),
@@ -931,17 +917,10 @@ class Touchin(PyoObject):
 
     def setInterpolation(self, x):
         """
-        Activate/Deactivate interpolation. Off by default.
-
-        :Args:
-
-            x: boolean
-                True activates the interpolation, False deactivates it.
+        Deprecated method. If needed, use Port or SigTo to interpolate between values.
 
         """
-        pyoArgsAssert(self, "b", x)
-        x, lmax = convertArgsToLists(x)
-        [obj.setInterpolation(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
+        print("setInterpolation() is deprecated. If needed, use Port or SigTo to interpolate between values.")
 
     def ctrl(self, map_list=None, title=None, wxnoserver=False):
         self._map_list = [SLMap(-1.0, 0.0, 'lin', 'minscale', self._minscale, dataOnly=True),
diff --git a/pyolib/pan.py b/pyolib/pan.py
index 683b4ef..ab68e2d 100644
--- a/pyolib/pan.py
+++ b/pyolib/pan.py
@@ -362,6 +362,7 @@ class Selector(PyoObject):
         PyoObject.__init__(self, mul, add)
         self._inputs = inputs
         self._voice = voice
+        self._mode = 0
         voice, mul, add, self._lmax = convertArgsToLists(voice, mul, add)
         self._length = 1
         for obj in self._inputs:
@@ -419,6 +420,24 @@ class Selector(PyoObject):
         for i, obj in enumerate(self._base_objs):
             obj.setVoice(wrap(x, i // self._length))
 
+    def setMode(self, x):
+        """
+        Change the algorithm used to interpolate between inputs.
+
+        if inputs are phase correlated you should use a linear fade. 
+
+        :Args:
+
+            x: int {0, 1}
+                If 0 (the default) the equal power law is used to
+                interpolate bewtween sources. If 1, linear fade is
+                used instead.
+
+        """
+        pyoArgsAssert(self, "i", x)
+        self._mode = x
+        [obj.setMode(x) for obj in self._base_objs]
+
     def ctrl(self, map_list=None, title=None, wxnoserver=False):
         self._map_list = [SLMap(0, len(self._inputs)-1, "lin", "voice", self._voice), SLMapMul(self._mul)]
         PyoObject.ctrl(self, map_list, title, wxnoserver)
diff --git a/pyolib/server.py b/pyolib/server.py
index 63ea0e5..35891d7 100644
--- a/pyolib/server.py
+++ b/pyolib/server.py
@@ -180,7 +180,7 @@ class Server(object):
         if callable(callback):
             self._server.setCallback(callback)
 
-    def gui(self, locals=None, meter=True, timer=True, exit=True):
+    def gui(self, locals=None, meter=True, timer=True, exit=True, title=None):
         """
         Show the server's user interface.
 
@@ -199,12 +199,15 @@ class Server(object):
                 If True, the python interpreter will exit when the 'Quit' button is pressed,
                 Otherwise, the GUI will be closed leaving the interpreter alive.
                 Defaults to True.
+            title: str, optional
+                Alternate title for the server window. If None (default), generic
+                title, "Pyo Server" is used.
 
         """
         self._gui_frame, win = createServerGUI(self._nchnls, self.start, self.stop,
                                                self.recstart, self.recstop, self.setAmp,
                                                self.getIsStarted(), locals, self.shutdown,
-                                               meter, timer, self._amp, exit)
+                                               meter, timer, self._amp, exit, title)
         if meter:
             self._server.setAmpCallable(self._gui_frame)
         if timer:
@@ -938,12 +941,18 @@ class Server(object):
         """
         Add a MIDI event in the server processing loop.
 
-        This method can be used to  programatically simulate incomming
+        This method can be used to  programmatically simulate incoming
         MIDI events. In an embedded framework (ie. pyo inside puredata,
         openframeworks, etc.), this is useful to control a MIDI-driven
         script from the host program. Arguments can be list of values to
         generate multiple events in one call.
 
+        The MIDI event buffer is emptied at the end of each processing
+        block. So, for events to be processed, addMidiEvent should be
+        called at the beginning of the block. If you use audio objects
+        to generate MIDI events, they should be created before the rest
+        of the processing chain.
+
         :Args:
 
             status: int
diff --git a/pyolib/tableprocess.py b/pyolib/tableprocess.py
index a897006..844ffd1 100644
--- a/pyolib/tableprocess.py
+++ b/pyolib/tableprocess.py
@@ -817,6 +817,7 @@ class TableRead(PyoObject):
         self._freq = freq
         self._loop = loop
         self._interp = interp
+        self._keeplast = 0
         table, freq, loop, interp, mul, add, lmax = convertArgsToLists(table, freq, loop, interp, mul, add)
         self._base_objs = [TableRead_base(wrap(table,i), wrap(freq,i), wrap(loop,i), wrap(interp,i), wrap(mul,i), wrap(add,i)) for i in range(lmax)]
         self._trig_objs = Dummy([TriggerDummy_base(obj) for obj in self._base_objs])
@@ -881,6 +882,22 @@ class TableRead(PyoObject):
         x, lmax = convertArgsToLists(x)
         [obj.setInterp(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
 
+    def setKeepLast(self, x):
+        """
+        If anything but zero, the object will hold the last value when stopped.
+
+        :Args:
+
+            x: bool
+                If 0, the object will be reset to 0 when stopped, otherwise
+                it will hold its last value.
+
+        """
+        pyoArgsAssert(self, "b", x)
+        self._keeplast = x
+        x, lmax = convertArgsToLists(x)
+        [obj.setKeepLast(wrap(x,i)) for i, obj in enumerate(self._base_objs)]
+
     def reset(self):
         """
         Resets current phase to 0.
diff --git a/src/engine/ad_portaudio.c b/src/engine/ad_portaudio.c
index 5e92703..5d3dc89 100644
--- a/src/engine/ad_portaudio.c
+++ b/src/engine/ad_portaudio.c
@@ -264,7 +264,7 @@ Server_pa_deinit(Server *self)
     PaError err;
     PyoPaBackendData *be_data = (PyoPaBackendData *) self->audio_be_data;
 
-    if (Pa_IsStreamActive(be_data->stream) || ! Pa_IsStreamStopped(be_data->stream)) {
+    if (!Pa_IsStreamStopped(be_data->stream)) {
         self->server_started = 0;
         err = Pa_AbortStream(be_data->stream);
         portaudio_assert(err, "Pa_AbortStream");
@@ -286,7 +286,7 @@ Server_pa_start(Server *self)
     PaError err;
     PyoPaBackendData *be_data = (PyoPaBackendData *) self->audio_be_data;
 
-    if (Pa_IsStreamActive(be_data->stream) || ! Pa_IsStreamStopped(be_data->stream)) {
+    if (!Pa_IsStreamStopped(be_data->stream)) {
         err = Pa_AbortStream(be_data->stream);
         portaudio_assert(err, "Pa_AbortStream");
     }
@@ -300,7 +300,7 @@ Server_pa_stop(Server *self)
 {
     PyoPaBackendData *be_data = (PyoPaBackendData *) self->audio_be_data;
 
-    if (Pa_IsStreamActive(be_data->stream) || ! Pa_IsStreamStopped(be_data->stream)) {
+    if (!Pa_IsStreamStopped(be_data->stream)) {
 #ifndef _OSX_
         PaError err = Pa_AbortStream(be_data->stream);
         portaudio_assert(err, "Pa_AbortStream");
diff --git a/src/engine/md_portmidi.c b/src/engine/md_portmidi.c
index 35a9d36..92cae50 100644
--- a/src/engine/md_portmidi.c
+++ b/src/engine/md_portmidi.c
@@ -89,6 +89,7 @@ Server_pm_init(Server *self)
                 const PmDeviceInfo *info = Pm_GetDeviceInfo(self->midi_input);
                 if (info != NULL) {
                     if (info->input) {
+                        Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
                         pmerr = Pm_OpenInput(&be_data->midiin[0], self->midi_input, NULL, 100, NULL, NULL);
                         if (pmerr) {
                             Server_warning(self,
@@ -114,6 +115,7 @@ Server_pm_init(Server *self)
             else if (self->midi_input >= num_devices) {
                 Server_debug(self, "Midi input device : all!\n");
                 self->midiin_count = 0;
+                Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
                 for (i=0; i<num_devices; i++) {
                     const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
                     if (info != NULL) {
@@ -146,7 +148,8 @@ Server_pm_init(Server *self)
                 const PmDeviceInfo *outinfo = Pm_GetDeviceInfo(self->midi_output);
                 if (outinfo != NULL) {
                     if (outinfo->output) {
-                        Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
+                        if (!Pt_Started())
+                            Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
                         pmerr = Pm_OpenOutput(&be_data->midiout[0], self->midi_output, NULL, 100, NULL, NULL, 1);
                         if (pmerr) {
                             Server_warning(self,
@@ -174,7 +177,8 @@ Server_pm_init(Server *self)
             else if (self->midi_output >= num_devices) {
                 Server_debug(self, "Midi output device : all!\n");
                 self->midiout_count = 0;
-                Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
+                if (!Pt_Started())
+                    Pt_Start(1, 0, 0); /* start a timer with millisecond accuracy */
                 for (i=0; i<num_devices; i++) {
                     const PmDeviceInfo *outinfo = Pm_GetDeviceInfo(i);
                     if (outinfo != NULL) {
@@ -193,8 +197,6 @@ Server_pm_init(Server *self)
                     }
                 }
                 if (self->midiout_count == 0) {
-                    if (Pt_Started())
-                        Pt_Stop();
                     self->withPortMidiOut = 0;
                 }
             }
@@ -204,6 +206,8 @@ Server_pm_init(Server *self)
             }
 
             if (self->withPortMidi == 0 && self->withPortMidiOut == 0) {
+                if (Pt_Started())
+                    Pt_Stop();
                 Pm_Terminate();
                 Server_warning(self, "Portmidi closed.\n");
                 ret = -1;
@@ -391,6 +395,15 @@ pm_sysexout(Server *self, unsigned char *msg, long timestamp)
     }
 }
 
+long
+pm_get_current_time()
+{
+    if (Pt_Started())
+        return Pt_Time();
+    else
+        return 0;
+}
+
 /* Query functions. */
 
 PyObject *
diff --git a/src/engine/midilistenermodule.c b/src/engine/midilistenermodule.c
index 6b1a3f1..3c9deac 100644
--- a/src/engine/midilistenermodule.c
+++ b/src/engine/midilistenermodule.c
@@ -77,7 +77,6 @@ void process_midi(PtTimestamp timestamp, void *userData)
     } while (result);
 
     PyGILState_Release(s);
-    Py_XDECREF(tup);
 }
 
 static int
@@ -262,7 +261,7 @@ MidiListener_getDeviceInfos(MidiListener *self) {
     PyObject *lst = PyList_New(0);
     for (i = 0; i < self->midicount; i++) {
         const PmDeviceInfo *info = Pm_GetDeviceInfo(self->ids[i]);
-        str = PyBytes_FromFormat("id: %d, name: %s, interface: %s\n", self->ids[i], info->name, info->interf);
+        str = PyUnicode_FromFormat("id: %d, name: %s, interface: %s\n", self->ids[i], info->name, info->interf);
         PyList_Append(lst, str);
     }
     return lst;
@@ -473,7 +472,8 @@ static PyObject * MidiDispatcher_stop(MidiDispatcher *self) {
 PyObject *
 MidiDispatcher_send(MidiDispatcher *self, PyObject *args)
 {
-    int i, status, data1, data2, device, curtime;
+    int i, status, data1, data2, device;
+    long curtime;
     PmTimestamp timestamp;
     PmEvent buffer[1];
 
@@ -506,13 +506,47 @@ MidiDispatcher_send(MidiDispatcher *self, PyObject *args)
 }
 
 PyObject *
+MidiDispatcher_sendx(MidiDispatcher *self, PyObject *args)
+{
+    unsigned char *msg;
+    int i, size, device;
+    long curtime;
+    PmTimestamp timestamp;
+
+    if (! PyArg_ParseTuple(args, "s#li", &msg, &size, &timestamp, &device))
+        return PyInt_FromLong(-1);
+
+    curtime = Pt_Time();
+    if (device == -1 && self->midicount > 1) {
+        for (i=0; i<self->midicount; i++) {
+            Pm_WriteSysEx(self->midiout[i], curtime + timestamp, msg);
+        }
+    }
+    else if (self->midicount == 1)
+        Pm_WriteSysEx(self->midiout[0], curtime + timestamp, msg);
+    else {
+        for (i=0; i<self->midicount; i++) {
+            if (self->ids[i] == device) {
+                device = i;
+                break;
+            }
+        }
+        if (device < 0 || device >= self->midicount)
+            device = 0;
+        Pm_WriteSysEx(self->midiout[device], curtime + timestamp, msg);
+    }
+
+    Py_RETURN_NONE;
+}
+
+PyObject *
 MidiDispatcher_getDeviceInfos(MidiDispatcher *self) {
     int i;
     PyObject *str;
     PyObject *lst = PyList_New(0);
     for (i = 0; i < self->midicount; i++) {
         const PmDeviceInfo *info = Pm_GetDeviceInfo(self->ids[i]);
-        str = PyBytes_FromFormat("id: %d, name: %s, interface: %s\n", self->ids[i], info->name, info->interf);
+        str = PyUnicode_FromFormat("id: %d, name: %s, interface: %s\n", self->ids[i], info->name, info->interf);
         PyList_Append(lst, str);
     }
     return lst;
@@ -527,6 +561,7 @@ static PyMethodDef MidiDispatcher_methods[] = {
     {"stop", (PyCFunction)MidiDispatcher_stop, METH_NOARGS, "Stops computing."},
     {"getDeviceInfos", (PyCFunction)MidiDispatcher_getDeviceInfos, METH_NOARGS, "Returns a list of device infos."},
     {"send", (PyCFunction)MidiDispatcher_send, METH_VARARGS, "Send a raw midi event."},
+    {"sendx", (PyCFunction)MidiDispatcher_sendx, METH_VARARGS, "Send a sysex midi event."},
     {NULL}  /* Sentinel */
 };
 
diff --git a/src/engine/servermodule.c b/src/engine/servermodule.c
index 82a00f7..7823c76 100644
--- a/src/engine/servermodule.c
+++ b/src/engine/servermodule.c
@@ -73,6 +73,7 @@ void pm_programout(Server *self, int value, int chan, long timestamp) {};
 void pm_pressout(Server *self, int value, int chan, long timestamp) {};
 void pm_bendout(Server *self, int value, int chan, long timestamp) {};
 void pm_sysexout(Server *self, unsigned char *msg, long timestamp) {};
+long pm_get_current_time() { return 0; };
 #endif
 
 /** Array of Server objects. **/
@@ -350,6 +351,9 @@ Server_process_buffers(Server *server)
     */
     PyGILState_STATE s = PyGILState_Ensure();
 
+    if (server->elapsedSamples == 0)
+        server->midi_time_offset = pm_get_current_time();
+
     if (server->CALLBACK != NULL)
         PyObject_Call((PyObject *)server->CALLBACK, PyTuple_New(0), NULL);
 
@@ -583,6 +587,7 @@ Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->midi_input = -1;
     self->midi_output = -1;
     self->midiActive = 1;
+    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->withGUI = 0;
@@ -1696,6 +1701,15 @@ Server_getMidiEventCount(Server *self) {
     return self->midi_count;
 }
 
+long
+Server_getMidiTimeOffset(Server *self) {
+    return self->midi_time_offset;
+}
+
+unsigned long Server_getElapsedTime(Server *self) {
+    return self->elapsedSamples;
+}
+
 static PyObject *
 Server_addMidiEvent(Server *self, PyObject *args)
 {
diff --git a/src/objects/midimodule.c b/src/objects/midimodule.c
index f5d4f98..be090eb 100644
--- a/src/objects/midimodule.c
+++ b/src/objects/midimodule.c
@@ -423,17 +423,31 @@ PyTypeObject CtlScan2Type = {
     CtlScan2_new,                 /* tp_new */
 };
 
+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;
+}
 
 typedef struct {
     pyo_audio_HEAD
     int ctlnumber;
     int channel;
-    int interp;
     MYFLT minscale;
     MYFLT maxscale;
     MYFLT value;
-    MYFLT oldValue;
-    MYFLT sampleToSec;
     int modebuffer[2];
 } Midictl;
 
@@ -484,58 +498,63 @@ Midictl_setProcMode(Midictl *self)
     }
 }
 
-// Take MIDI events and translate them...
-void translateMidi(Midictl *self, PyoMidiEvent *buffer, int count)
+int
+Midictl_translateMidi(Midictl *self, PyoMidiEvent *buffer, int j)
 {
-    int i, ok;
-    for (i=0; i<count; i++) {
-        int status = PyoMidi_MessageStatus(buffer[i].message);    // Temp note event holders
-        int number = PyoMidi_MessageData1(buffer[i].message);
-        int value = PyoMidi_MessageData2(buffer[i].message);
+    int ok, posto = -1;
+    int status = PyoMidi_MessageStatus(buffer[j].message);    // Temp note event holders
+    int number = PyoMidi_MessageData1(buffer[j].message);
+    int value = PyoMidi_MessageData2(buffer[j].message);
 
-        if (self->channel == 0) {
-            if ((status & 0xF0) == 0xB0)
-                ok = 1;
-            else
-                ok = 0;
-        }
-        else {
-            if (status == (0xB0 | (self->channel - 1)))
-                ok = 1;
-            else
-                ok = 0;
-        }
+    if (self->channel == 0) {
+        if ((status & 0xF0) == 0xB0)
+            ok = 1;
+        else
+            ok = 0;
+    }
+    else {
+        if (status == (0xB0 | (self->channel - 1)))
+            ok = 1;
+        else
+            ok = 0;
+    }
 
-        if (ok == 1 && number == self->ctlnumber) {
-            self->value = (value / 127.) * (self->maxscale - self->minscale) + self->minscale;
-            break;
-        }
+    if (ok == 1 && number == self->ctlnumber) {
+        self->value = (value / 127.) * (self->maxscale - self->minscale) + self->minscale;
+        posto = getPosToWrite(buffer[j].timestamp, (Server *)self->server, self->sr, self->bufsize);
     }
-    self->oldValue = self->value;
+
+    return posto;
 }
 
 static void
 Midictl_compute_next_data_frame(Midictl *self)
 {
     PyoMidiEvent *tmp;
-    int i, count;
-    MYFLT step;
+    int i, j, count, posto, oldpos = 0;
+    MYFLT oldval = 0.0;
 
     tmp = Server_getMidiEventBuffer((Server *)self->server);
     count = Server_getMidiEventCount((Server *)self->server);
 
-    if (count > 0)
-        translateMidi((Midictl *)self, tmp, count);
-
-    if (self->interp == 0) {
+    if (count == 0) {
         for (i=0; i<self->bufsize; i++) {
             self->data[i] = self->value;
         }
     }
     else {
-        step = (self->value - self->oldValue) / self->bufsize;
-        for (i=0; i<self->bufsize; i++) {
-            self->data[i] = self->oldValue + step;
+        for (j=0; j<count; j++) {
+            oldval = self->value;
+            posto = Midictl_translateMidi((Midictl *)self, tmp, j);
+            if (posto == -1)
+                continue;
+            for (i=oldpos; i<posto; i++) {
+                self->data[i] = oldval;
+            }
+            oldpos = posto;
+        }
+        for (i=oldpos; i<self->bufsize; i++) {
+            self->data[i] = self->value;
         }
     }
     (*self->muladd_func_ptr)(self);
@@ -573,10 +592,8 @@ Midictl_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     self->channel = 0;
     self->value = 0.;
-    self->oldValue = 0.;
     self->minscale = 0.;
     self->maxscale = 1.;
-    self->interp = 0;
     self->modebuffer[0] = 0;
     self->modebuffer[1] = 0;
 
@@ -586,7 +603,7 @@ Midictl_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     static char *kwlist[] = {"ctlnumber", "minscale", "maxscale", "init", "channel", "mul", "add", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, TYPE_I_FFFIOO, kwlist, &self->ctlnumber, &self->minscale, &self->maxscale, &self->oldValue, &self->channel, &multmp, &addtmp))
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, TYPE_I_FFFIOO, kwlist, &self->ctlnumber, &self->minscale, &self->maxscale, &self->value, &self->channel, &multmp, &addtmp))
         Py_RETURN_NONE;
 
     if (multmp) {
@@ -599,8 +616,6 @@ Midictl_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     PyObject_CallMethod(self->server, "addStream", "O", self->stream);
 
-    self->value = self->oldValue;
-
     (*self->mode_func_ptr)(self);
 
     return (PyObject *)self;
@@ -626,38 +641,14 @@ static PyObject * Midictl_div(Midictl *self, PyObject *arg) { DIV };
 static PyObject * Midictl_inplace_div(Midictl *self, PyObject *arg) { INPLACE_DIV };
 
 static PyObject *
-Midictl_setInterpolation(Midictl *self, PyObject *arg)
-{
-    int tmp;
-
-    ASSERT_ARG_NOT_NULL
-
-    int isNum = PyInt_Check(arg);
-
-    if (isNum == 1) {
-        tmp = PyInt_AsLong(arg);
-        if (tmp == 0)
-            self->interp = 0;
-        else
-            self->interp = 1;
-    }
-
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-
-static PyObject *
 Midictl_setValue(Midictl *self, PyObject *arg)
 {
-    int tmp;
-
     ASSERT_ARG_NOT_NULL
 
     int isNum = PyNumber_Check(arg);
 
     if (isNum == 1) {
-        tmp = PyFloat_AsDouble(arg);
-        self->oldValue = self->value = tmp;
+        self->value = PyFloat_AsDouble(arg);
     }
 
     Py_INCREF(Py_None);
@@ -667,15 +658,12 @@ Midictl_setValue(Midictl *self, PyObject *arg)
 static PyObject *
 Midictl_setMinScale(Midictl *self, PyObject *arg)
 {
-    int tmp;
-
     ASSERT_ARG_NOT_NULL
 
     int isNum = PyNumber_Check(arg);
 
     if (isNum == 1) {
-        tmp = PyFloat_AsDouble(arg);
-        self->minscale = tmp;
+        self->minscale = PyFloat_AsDouble(arg);
     }
 
     Py_INCREF(Py_None);
@@ -685,15 +673,12 @@ Midictl_setMinScale(Midictl *self, PyObject *arg)
 static PyObject *
 Midictl_setMaxScale(Midictl *self, PyObject *arg)
 {
-    int tmp;
-
     ASSERT_ARG_NOT_NULL
 
     int isNum = PyNumber_Check(arg);
 
     if (isNum == 1) {
-        tmp = PyFloat_AsDouble(arg);
-        self->maxscale = tmp;
+        self->maxscale = PyFloat_AsDouble(arg);
     }
 
     Py_INCREF(Py_None);
@@ -751,7 +736,6 @@ static PyMethodDef Midictl_methods[] = {
     {"_getStream", (PyCFunction)Midictl_getStream, METH_NOARGS, "Returns stream object."},
     {"play", (PyCFunction)Midictl_play, METH_VARARGS|METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
     {"stop", (PyCFunction)Midictl_stop, METH_NOARGS, "Stops computing."},
-    {"setInterpolation", (PyCFunction)Midictl_setInterpolation, METH_O, "Activate/Deactivate interpolation."},
     {"setValue", (PyCFunction)Midictl_setValue, METH_O, "Resets audio stream to value in argument."},
     {"setMinScale", (PyCFunction)Midictl_setMinScale, METH_O, "Sets the minimum value of scaling."},
     {"setMaxScale", (PyCFunction)Midictl_setMaxScale, METH_O, "Sets the maximum value of scaling."},
@@ -851,11 +835,8 @@ typedef struct {
     pyo_audio_HEAD
     int channel;
     int scale; /* 0 = midi, 1 = transpo */
-    int interp;
     MYFLT range;
     MYFLT value;
-    MYFLT oldValue;
-    MYFLT sampleToSec;
     int modebuffer[2];
 } Bendin;
 
@@ -906,61 +887,69 @@ Bendin_setProcMode(Bendin *self)
     }
 }
 
-// Take MIDI events and translate them...
-void Bendin_translateMidi(Bendin *self, PyoMidiEvent *buffer, int count)
+int
+Bendin_translateMidi(Bendin *self, PyoMidiEvent *buffer, int j)
 {
-    int i, ok;
+    int ok, posto = -1;
     MYFLT val;
-    for (i=0; i<count; i++) {
-        int status = PyoMidi_MessageStatus(buffer[i].message);    // Temp note event holders
-        int number = PyoMidi_MessageData1(buffer[i].message);
-        int value = PyoMidi_MessageData2(buffer[i].message);
 
-        if (self->channel == 0) {
-            if ((status & 0xF0) == 0xe0)
-                ok = 1;
-            else
-                ok = 0;
-        }
-        else {
-            if (status == (0xe0 | (self->channel - 1)))
-                ok = 1;
-            else
-                ok = 0;
-        }
+    int status = PyoMidi_MessageStatus(buffer[j].message);    // Temp note event holders
+    int number = PyoMidi_MessageData1(buffer[j].message);
+    int value = PyoMidi_MessageData2(buffer[j].message);
 
-        if (ok == 1) {
-            val = (number + (value << 7) - 8192) / 8192.0 * self->range;
-            if (self->scale == 0)
-                self->value = val;
-            else
-                self->value = MYPOW(1.0594630943593, val);
-            break;
-        }
+    if (self->channel == 0) {
+        if ((status & 0xF0) == 0xe0)
+            ok = 1;
+        else
+            ok = 0;
     }
-    self->oldValue = self->value;
+    else {
+        if (status == (0xe0 | (self->channel - 1)))
+            ok = 1;
+        else
+            ok = 0;
+    }
+
+    if (ok == 1) {
+        val = (number + (value << 7) - 8192) / 8192.0 * self->range;
+        if (self->scale == 0)
+            self->value = val;
+        else
+            self->value = MYPOW(1.0594630943593, val);
+        posto = getPosToWrite(buffer[j].timestamp, (Server *)self->server, self->sr, self->bufsize);
+    }
+
+    return posto;
 }
 
 static void
 Bendin_compute_next_data_frame(Bendin *self)
 {
     PyoMidiEvent *tmp;
-    int i, count;
+    int i, j, count, posto, oldpos = 0;
+    MYFLT oldval = 0.0;
 
     tmp = Server_getMidiEventBuffer((Server *)self->server);
     count = Server_getMidiEventCount((Server *)self->server);
-    if (count > 0)
-        Bendin_translateMidi((Bendin *)self, tmp, count);
 
-    if (self->interp == 0) {
+    if (count == 0) {
         for (i=0; i<self->bufsize; i++) {
             self->data[i] = self->value;
         }
     }
     else {
-        MYFLT step = (self->value - self->oldValue) / self->bufsize;
-        for (i=0; i<self->bufsize; i++) {
-            self->data[i] = self->oldValue + step;
+        for (j=0; j<count; j++) {
+            oldval = self->value;
+            posto = Bendin_translateMidi((Bendin *)self, tmp, j);
+            if (posto == -1)
+                continue;
+            for (i=oldpos; i<posto; i++) {
+                self->data[i] = oldval;
+            }
+            oldpos = posto;
+        }
+        for (i=oldpos; i<self->bufsize; i++) {
+            self->data[i] = self->value;
         }
     }
 
@@ -999,9 +988,7 @@ Bendin_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     self->channel = 0;
     self->scale = 0;
-    self->interp = 0;
     self->value = 0.;
-    self->oldValue = 0.;
     self->range = 2.;
     self->modebuffer[0] = 0;
     self->modebuffer[1] = 0;
@@ -1025,10 +1012,8 @@ Bendin_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     PyObject_CallMethod(self->server, "addStream", "O", self->stream);
 
-    if (self->scale == 0)
-        self->oldValue = self->value = 0.;
-    else
-        self->oldValue = self->value = 1.;
+    if (self->scale == 1)
+        self->value = 1.0;
 
     (*self->mode_func_ptr)(self);
 
@@ -1113,27 +1098,6 @@ Bendin_setScale(Bendin *self, PyObject *arg)
     return Py_None;
 }
 
-static PyObject *
-Bendin_setInterpolation(Bendin *self, PyObject *arg)
-{
-    int tmp;
-
-    ASSERT_ARG_NOT_NULL
-
-    int isNum = PyInt_Check(arg);
-
-    if (isNum == 1) {
-        tmp = PyInt_AsLong(arg);
-        if (tmp == 0)
-            self->interp = 0;
-        else
-            self->interp = 1;
-    }
-
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-
 static PyMemberDef Bendin_members[] = {
     {"server", T_OBJECT_EX, offsetof(Bendin, server), 0, "Pyo server."},
     {"stream", T_OBJECT_EX, offsetof(Bendin, stream), 0, "Stream object."},
@@ -1150,7 +1114,6 @@ static PyMethodDef Bendin_methods[] = {
     {"setBrange", (PyCFunction)Bendin_setBrange, METH_O, "Sets the bending bipolar range."},
     {"setScale", (PyCFunction)Bendin_setScale, METH_O, "Sets the output type, midi vs transpo."},
     {"setChannel", (PyCFunction)Bendin_setChannel, METH_O, "Sets the midi channel."},
-    {"setInterpolation", (PyCFunction)Bendin_setInterpolation, METH_O, "Activate/Deactivate interpolation."},
     {"setMul", (PyCFunction)Bendin_setMul, METH_O, "Sets oscillator mul factor."},
     {"setAdd", (PyCFunction)Bendin_setAdd, METH_O, "Sets oscillator add factor."},
     {"setSub", (PyCFunction)Bendin_setSub, METH_O, "Sets inverse add factor."},
@@ -1246,10 +1209,7 @@ typedef struct {
     int channel;
     MYFLT minscale;
     MYFLT maxscale;
-    int interp;
     MYFLT value;
-    MYFLT oldValue;
-    MYFLT sampleToSec;
     int modebuffer[2];
 } Touchin;
 
@@ -1300,56 +1260,62 @@ Touchin_setProcMode(Touchin *self)
     }
 }
 
-// Take MIDI events and translate them...
-void Touchin_translateMidi(Touchin *self, PyoMidiEvent *buffer, int count)
+int
+Touchin_translateMidi(Touchin *self, PyoMidiEvent *buffer, int j)
 {
-    int i, ok;
-    for (i=0; i<count; i++) {
-        int status = PyoMidi_MessageStatus(buffer[i].message);    // Temp note event holders
-        int number = PyoMidi_MessageData1(buffer[i].message);
+    int ok, posto = -1;
+    int status = PyoMidi_MessageStatus(buffer[j].message);    // Temp note event holders
+    int number = PyoMidi_MessageData1(buffer[j].message);
 
-        if (self->channel == 0) {
-            if ((status & 0xF0) == 0xd0)
-                ok = 1;
-            else
-                ok = 0;
-        }
-        else {
-            if (status == (0xd0 | (self->channel - 1)))
-                ok = 1;
-            else
-                ok = 0;
-        }
+    if (self->channel == 0) {
+        if ((status & 0xF0) == 0xd0)
+            ok = 1;
+        else
+            ok = 0;
+    }
+    else {
+        if (status == (0xd0 | (self->channel - 1)))
+            ok = 1;
+        else
+            ok = 0;
+    }
 
-        if (ok == 1) {
-            self->value = (number / 127.) * (self->maxscale - self->minscale) + self->minscale;
-            break;
-        }
+    if (ok == 1) {
+        self->value = (number / 127.) * (self->maxscale - self->minscale) + self->minscale;
+        posto = getPosToWrite(buffer[j].timestamp, (Server *)self->server, self->sr, self->bufsize);
     }
-    self->oldValue = self->value;
+
+    return posto;
 }
 
 static void
 Touchin_compute_next_data_frame(Touchin *self)
 {
     PyoMidiEvent *tmp;
-    int i, count;
+    int i, j, count, posto, oldpos = 0;
+    MYFLT oldval = 0.0;
 
     tmp = Server_getMidiEventBuffer((Server *)self->server);
     count = Server_getMidiEventCount((Server *)self->server);
 
-    if (count > 0)
-        Touchin_translateMidi((Touchin *)self, tmp, count);
-
-    if (self->interp == 0) {
+    if (count == 0) {
         for (i=0; i<self->bufsize; i++) {
             self->data[i] = self->value;
         }
     }
     else {
-        MYFLT step = (self->value - self->oldValue) / self->bufsize;
-        for (i=0; i<self->bufsize; i++) {
-            self->data[i] = self->oldValue + step;
+        for (j=0; j<count; j++) {
+            oldval = self->value;
+            posto = Touchin_translateMidi((Touchin *)self, tmp, j);
+            if (posto == -1)
+                continue;
+            for (i=oldpos; i<posto; i++) {
+                self->data[i] = oldval;
+            }
+            oldpos = posto;
+        }
+        for (i=oldpos; i<self->bufsize; i++) {
+            self->data[i] = self->value;
         }
     }
 
@@ -1388,10 +1354,8 @@ Touchin_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     self->channel = 0;
     self->value = 0.;
-    self->oldValue = 0.;
     self->minscale = 0.;
     self->maxscale = 1.;
-    self->interp = 0;
     self->modebuffer[0] = 0;
     self->modebuffer[1] = 0;
 
@@ -1401,7 +1365,7 @@ Touchin_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     static char *kwlist[] = {"minscale", "maxscale", "init", "channel", "mul", "add", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kwds, TYPE__FFFIOO, kwlist, &self->minscale, &self->maxscale, &self->oldValue, &self->channel, &multmp, &addtmp))
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, TYPE__FFFIOO, kwlist, &self->minscale, &self->maxscale, &self->value, &self->channel, &multmp, &addtmp))
         Py_RETURN_NONE;
 
     if (multmp) {
@@ -1414,8 +1378,6 @@ Touchin_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     PyObject_CallMethod(self->server, "addStream", "O", self->stream);
 
-    self->value = self->oldValue;
-
     (*self->mode_func_ptr)(self);
 
     return (PyObject *)self;
@@ -1443,15 +1405,10 @@ static PyObject * Touchin_inplace_div(Touchin *self, PyObject *arg) { INPLACE_DI
 static PyObject *
 Touchin_setMinScale(Touchin *self, PyObject *arg)
 {
-    int tmp;
-
     ASSERT_ARG_NOT_NULL
 
-    int isNum = PyNumber_Check(arg);
-
-    if (isNum == 1) {
-        tmp = PyFloat_AsDouble(arg);
-            self->minscale = tmp;
+    if (PyNumber_Check(arg) == 1) {
+        self->minscale = PyFloat_AsDouble(arg);
     }
 
     Py_INCREF(Py_None);
@@ -1461,15 +1418,10 @@ Touchin_setMinScale(Touchin *self, PyObject *arg)
 static PyObject *
 Touchin_setMaxScale(Touchin *self, PyObject *arg)
 {
-    int tmp;
-
     ASSERT_ARG_NOT_NULL
 
-    int isNum = PyNumber_Check(arg);
-
-    if (isNum == 1) {
-        tmp = PyFloat_AsDouble(arg);
-        self->maxscale = tmp;
+    if (PyNumber_Check(arg) == 1) {
+        self->maxscale = PyFloat_AsDouble(arg);
     }
 
     Py_INCREF(Py_None);
@@ -1483,9 +1435,7 @@ Touchin_setChannel(Touchin *self, PyObject *arg)
 
     ASSERT_ARG_NOT_NULL
 
-    int isInt = PyInt_Check(arg);
-
-    if (isInt == 1) {
+    if (PyInt_Check(arg) == 1) {
         tmp = PyInt_AsLong(arg);
         if (tmp >= 0 && tmp < 128)
             self->channel = tmp;
@@ -1495,27 +1445,6 @@ Touchin_setChannel(Touchin *self, PyObject *arg)
     return Py_None;
 }
 
-static PyObject *
-Touchin_setInterpolation(Touchin *self, PyObject *arg)
-{
-    int tmp;
-
-    ASSERT_ARG_NOT_NULL
-
-	int isNum = PyInt_Check(arg);
-
-	if (isNum == 1) {
-		tmp = PyInt_AsLong(arg);
-        if (tmp == 0)
-            self->interp = 0;
-        else
-            self->interp = 1;
-	}
-
-	Py_INCREF(Py_None);
-	return Py_None;
-}
-
 static PyMemberDef Touchin_members[] = {
     {"server", T_OBJECT_EX, offsetof(Touchin, server), 0, "Pyo server."},
     {"stream", T_OBJECT_EX, offsetof(Touchin, stream), 0, "Stream object."},
@@ -1532,7 +1461,6 @@ static PyMethodDef Touchin_methods[] = {
     {"setMinScale", (PyCFunction)Touchin_setMinScale, METH_O, "Sets the minimum value of scaling."},
     {"setMaxScale", (PyCFunction)Touchin_setMaxScale, METH_O, "Sets the maximum value of scaling."},
     {"setChannel", (PyCFunction)Touchin_setChannel, METH_O, "Sets the midi channel."},
-	{"setInterpolation", (PyCFunction)Touchin_setInterpolation, METH_O, "Activate/Deactivate interpolation."},
     {"setMul", (PyCFunction)Touchin_setMul, METH_O, "Sets oscillator mul factor."},
     {"setAdd", (PyCFunction)Touchin_setAdd, METH_O, "Sets oscillator add factor."},
     {"setSub", (PyCFunction)Touchin_setSub, METH_O, "Sets inverse add factor."},
@@ -1928,7 +1856,7 @@ PyTypeObject PrograminType = {
 
 typedef struct {
     pyo_audio_HEAD
-    int *notebuf; /* pitch, velocity, ... */
+    int *notebuf; /* pitch, velocity, posToWrite */
     int voices;
     int vcount;
     int scale; /* 0 = midi, 1 = hertz, 2 = transpo */
@@ -1948,7 +1876,7 @@ pitchIsIn(int *buf, int pitch, int len) {
     int i;
     int isIn = 0;
     for (i=0; i<len; i++) {
-        if (buf[i*2] == pitch) {
+        if (buf[i*3] == pitch) {
             isIn = 1;
             break;
         }
@@ -1961,7 +1889,7 @@ int firstEmpty(int *buf, int len) {
     int i;
     int voice = -1;
     for (i=0; i<len; i++) {
-        if (buf[i*2+1] == 0) {
+        if (buf[i*3+1] == 0) {
             voice = i;
             break;
         }
@@ -1974,7 +1902,7 @@ int nextEmptyVoice(int *buf, int voice, int len) {
     int next = -1;
     for (i=1; i<=len; i++) {
         tmp = (i + voice) % len;
-        if (buf[tmp*2+1] == 0) {
+        if (buf[tmp*3+1] == 0) {
             next = tmp;
             break;
         }
@@ -1986,7 +1914,7 @@ int whichVoice(int *buf, int pitch, int len) {
     int i;
     int voice = 0;
     for (i=0; i<len; i++) {
-        if (buf[i*2] == pitch) {
+        if (buf[i*3] == pitch) {
             voice = i;
             break;
         }
@@ -1997,7 +1925,7 @@ int whichVoice(int *buf, int pitch, int len) {
 // Take MIDI events and keep track of notes
 void grabMidiNotes(MidiNote *self, PyoMidiEvent *buffer, int count)
 {
-    int i, ok, voice, kind;
+    int i, ok, voice, kind, samp = 0;
 
     for (i=0; i<count; i++) {
         int status = PyoMidi_MessageStatus(buffer[i].message);    // Temp note event holders
@@ -2018,6 +1946,8 @@ void grabMidiNotes(MidiNote *self, PyoMidiEvent *buffer, int count)
         }
 
         if (ok == 1) {
+            samp = getPosToWrite(buffer[i].timestamp, (Server *)self->server, self->sr, self->bufsize);
+
             if ((status & 0xF0) == 0x80)
                 kind = 0;
             else if ((status & 0xF0) == 0x90 && velocity == 0)
@@ -2031,24 +1961,27 @@ void grabMidiNotes(MidiNote *self, PyoMidiEvent *buffer, int count)
                     voice = nextEmptyVoice(self->notebuf, self->vcount, self->voices);
                     if (voice != -1) {
                         self->vcount = voice;
-                        self->notebuf[voice*2] = pitch;
-                        self->notebuf[voice*2+1] = velocity;
-                        self->trigger_streams[self->bufsize*(self->vcount*2)] = 1.0;
+                        self->notebuf[voice*3] = pitch;
+                        self->notebuf[voice*3+1] = velocity;
+                        self->notebuf[voice*3+2] = samp;
+                        self->trigger_streams[self->bufsize*(self->vcount*2)+samp] = 1.0;
                     }
                 }
                 else {
                     self->vcount = (self->vcount + 1) % self->voices;
-                    self->notebuf[self->vcount*2] = pitch;
-                    self->notebuf[self->vcount*2+1] = velocity;
-                    self->trigger_streams[self->bufsize*(self->vcount*2)] = 1.0;
+                    self->notebuf[self->vcount*3] = pitch;
+                    self->notebuf[self->vcount*3+1] = velocity;
+                    self->notebuf[self->vcount*3+2] = samp;
+                    self->trigger_streams[self->bufsize*(self->vcount*2)+samp] = 1.0;
                 }
             }
             else if (pitchIsIn(self->notebuf, pitch, self->voices) == 1 && kind == 0 && pitch >= self->first && pitch <= self->last) {
                 //PySys_WriteStdout("%i, %i, %i\n", status, pitch, velocity);
                 voice = whichVoice(self->notebuf, pitch, self->voices);
-                self->notebuf[voice*2] = -1;
-                self->notebuf[voice*2+1] = 0.;
-                self->trigger_streams[self->bufsize*(voice*2+1)] = 1.0;
+                self->notebuf[voice*3] = -1;
+                self->notebuf[voice*3+1] = 0;
+                self->notebuf[voice*3+2] = samp;
+                self->trigger_streams[self->bufsize*(voice*2+1)+samp] = 1.0;
             }
         }
     }
@@ -2127,7 +2060,7 @@ MidiNote_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     PyObject_CallMethod(self->server, "addStream", "O", self->stream);
 
-    self->notebuf = (int *)realloc(self->notebuf, self->voices * 2 * sizeof(int));
+    self->notebuf = (int *)realloc(self->notebuf, self->voices * 3 * sizeof(int));
     self->trigger_streams = (MYFLT *)realloc(self->trigger_streams, self->bufsize * self->voices * 2 * sizeof(MYFLT));
 
     for (i=0; i<self->bufsize*self->voices*2; i++) {
@@ -2135,8 +2068,9 @@ MidiNote_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     }
 
     for (i=0; i<self->voices; i++) {
-        self->notebuf[i*2] = -1;
-        self->notebuf[i*2+1] = 0;
+        self->notebuf[i*3] = -1;
+        self->notebuf[i*3+1] = 0;
+        self->notebuf[i*3+2] = 0;
     }
 
     self->centralkey = (self->first + self->last) / 2;
@@ -2146,10 +2080,11 @@ MidiNote_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     return (PyObject *)self;
 }
 
-MYFLT MidiNote_getValue(MidiNote *self, int voice, int which)
+MYFLT
+MidiNote_getValue(MidiNote *self, int voice, int which, int *posto)
 {
     MYFLT val = -1.0;
-    int midival = self->notebuf[voice*2+which];
+    int midival = self->notebuf[voice*3+which];
     if (which == 0 && midival != -1) {
         if (self->scale == 0)
             val = midival;
@@ -2162,6 +2097,9 @@ MYFLT MidiNote_getValue(MidiNote *self, int voice, int which)
         val = (MYFLT)midival;
     else if (which == 1)
         val = (MYFLT)midival / 127.;
+
+    *posto = self->notebuf[voice*3+2];
+
     return val;
 }
 
@@ -2325,6 +2263,8 @@ typedef struct {
     int modebuffer[2];
     int voice;
     int mode; /* 0 = pitch, 1 = velocity */
+    MYFLT lastval;
+    MYFLT lastpitch;
 } Notein;
 
 static void Notein_postprocessing_ii(Notein *self) { POST_PROCESSING_II };
@@ -2377,19 +2317,43 @@ Notein_setProcMode(Notein *self)
 static void
 Notein_compute_next_data_frame(Notein *self)
 {
-    int i;
-    MYFLT tmp = MidiNote_getValue(self->handler, self->voice, self->mode);
+    int i, posto;
+    MYFLT tmp = MidiNote_getValue(self->handler, self->voice, self->mode, &posto);
 
-    if (self->mode == 0 && tmp != -1) {
-        for (i=0; i<self->bufsize; i++) {
-            self->data[i] = tmp;
+    if (self->lastval == tmp) {
+        if (self->mode == 0 && tmp != -1) {
+            for (i=0; i<self->bufsize; i++) {
+                self->data[i] = tmp;
+            }
+        }
+        else if (self->mode == 1) {
+            for (i=0; i<self->bufsize; i++) {
+                self->data[i] = tmp;
+            }
+            (*self->muladd_func_ptr)(self);
         }
     }
-    else if (self->mode == 1) {
-        for (i=0; i<self->bufsize; i++) {
-            self->data[i] = tmp;
+    else { /* There is a new note to compute. */
+        if (self->mode == 0 && tmp != -1) {
+            for (i=0; i<self->bufsize; i++) {
+                if (i < posto)
+                    self->data[i] = self->lastpitch;
+                else
+                    self->data[i] = tmp;
+            }
+        }
+        else if (self->mode == 1) {
+            for (i=0; i<self->bufsize; i++) {
+                if (i < posto)
+                    self->data[i] = self->lastval;
+                else
+                    self->data[i] = tmp;
+            }
+            (*self->muladd_func_ptr)(self);
         }
-        (*self->muladd_func_ptr)(self);
+        self->lastval = tmp;
+        if (tmp != -1)
+            self->lastpitch = tmp;
     }
 }
 
@@ -2427,6 +2391,8 @@ Notein_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
     self->voice = 0;
     self->mode = 0;
+    self->lastval = -1.0;
+    self->lastpitch = 0.0;
     self->modebuffer[0] = 0;
     self->modebuffer[1] = 0;
 
diff --git a/src/objects/oscilmodule.c b/src/objects/oscilmodule.c
index 3041548..8a3ab21 100644
--- a/src/objects/oscilmodule.c
+++ b/src/objects/oscilmodule.c
@@ -5809,6 +5809,8 @@ typedef struct {
     int go;
     int modebuffer[3];
     double pointerPos;
+    MYFLT lastValue;
+    int keepLast;
     MYFLT *trigsBuffer;
     TriggerStream *trig_stream;
     int init;
@@ -5848,10 +5850,14 @@ TableRead_readframes_i(TableRead *self) {
         if (self->go == 1) {
             ipart = (int)self->pointerPos;
             fpart = self->pointerPos - ipart;
-            self->data[i] = (*self->interp_func_ptr)(tablelist, ipart, fpart, size);
+            self->lastValue = self->data[i] = (*self->interp_func_ptr)(tablelist, ipart, fpart, size);
+        }
+        else {
+            if (self->keepLast == 0)
+                self->data[i] = 0.0;
+            else
+                self->data[i] = self->lastValue;
         }
-        else
-            self->data[i] = 0.0;
 
         self->pointerPos += inc;
     }
@@ -5890,10 +5896,14 @@ TableRead_readframes_a(TableRead *self) {
         if (self->go == 1) {
             ipart = (int)self->pointerPos;
             fpart = self->pointerPos - ipart;
-            self->data[i] = (*self->interp_func_ptr)(tablelist, ipart, fpart, size);
+            self->lastValue = self->data[i] = (*self->interp_func_ptr)(tablelist, ipart, fpart, size);
+        }
+        else {
+            if (self->keepLast == 0)
+                self->data[i] = 0.0;
+            else
+                self->data[i] = self->lastValue;
         }
-        else
-            self->data[i] = 0.0;
 
         inc = fr[i] * sizeOnSr;
         self->pointerPos += inc;
@@ -6006,6 +6016,8 @@ TableRead_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->freq = PyFloat_FromDouble(1);
     self->loop = 0;
     self->init = 1;
+    self->keepLast = 0;
+    self->lastValue = 0.0;
 	self->modebuffer[0] = 0;
 	self->modebuffer[1] = 0;
 	self->modebuffer[2] = 0;
@@ -6088,8 +6100,24 @@ static PyObject * TableRead_out(TableRead *self, PyObject *args, PyObject *kwds)
 };
 static PyObject * TableRead_stop(TableRead *self)
 {
+    int i;
     self->go = 0;
-    STOP
+    Stream_setStreamActive(self->stream, 0);
+    Stream_setStreamChnl(self->stream, 0);
+    Stream_setStreamToDac(self->stream, 0);
+    if (self->keepLast == 0) {
+        for (i=0; i<self->bufsize; i++) {
+            self->data[i] = 0;
+        }
+    }
+    else {
+        for (i=0; i<self->bufsize; i++) {
+            self->data[i] = self->lastValue;
+        }
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
 };
 
 static PyObject * TableRead_multiply(TableRead *self, PyObject *arg) { MULTIPLY };
@@ -6183,6 +6211,17 @@ TableRead_setInterp(TableRead *self, PyObject *arg)
 }
 
 static PyObject *
+TableRead_setKeepLast(TableRead *self, PyObject *arg)
+{
+    ASSERT_ARG_NOT_NULL
+
+    self->keepLast = PyInt_AsLong(arg);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
 TableRead_reset(TableRead *self)
 {
     self->pointerPos = 0.0;
@@ -6213,6 +6252,7 @@ static PyMethodDef TableRead_methods[] = {
 {"setFreq", (PyCFunction)TableRead_setFreq, METH_O, "Sets oscillator frequency in cycle per second."},
 {"setLoop", (PyCFunction)TableRead_setLoop, METH_O, "Sets the looping mode."},
 {"setInterp", (PyCFunction)TableRead_setInterp, METH_O, "Sets reader interpolation mode."},
+{"setKeepLast", (PyCFunction)TableRead_setKeepLast, METH_O, "Sets keepLast mode."},
 {"reset", (PyCFunction)TableRead_reset, METH_NOARGS, "Resets pointer position to 0."},
 {"setMul", (PyCFunction)TableRead_setMul, METH_O, "Sets oscillator mul factor."},
 {"setAdd", (PyCFunction)TableRead_setAdd, METH_O, "Sets oscillator add factor."},
diff --git a/src/objects/panmodule.c b/src/objects/panmodule.c
index df741b3..4ace883 100644
--- a/src/objects/panmodule.c
+++ b/src/objects/panmodule.c
@@ -2797,6 +2797,7 @@ typedef struct {
     PyObject *voice;
     Stream *voice_stream;
     int chSize;
+    int mode;
     int modebuffer[3]; // need at least 2 slots for mul & add
 } Selector;
 
@@ -2836,6 +2837,27 @@ Selector_generate_i(Selector *self) {
 }
 
 static void
+Selector_generate_lin_i(Selector *self) {
+    int j1, j, i;
+    MYFLT voice = Selector_clip_voice(self, PyFloat_AS_DOUBLE(self->voice));
+
+    j1 = (int)voice;
+    j = j1 + 1;
+    if (j1 >= (self->chSize-1)) {
+        j1--; j--;
+    }
+
+    MYFLT *st1 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, j1), "_getStream", NULL));
+    MYFLT *st2 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, j), "_getStream", NULL));
+
+    voice = P_clip(voice - j1);
+
+    for (i=0; i<self->bufsize; i++) {
+        self->data[i] = st1[i] * (1.0 - voice) + st2[i] * voice;
+    }
+}
+
+static void
 Selector_generate_a(Selector *self) {
     int old_j1, old_j, j1, j, i;
     MYFLT  voice;
@@ -2870,6 +2892,41 @@ Selector_generate_a(Selector *self) {
     }
 }
 
+static void
+Selector_generate_lin_a(Selector *self) {
+    int old_j1, old_j, j1, j, i;
+    MYFLT  voice;
+    MYFLT *st1, *st2;
+    MYFLT *vc = Stream_getData((Stream *)self->voice_stream);
+
+    old_j1 = 0;
+    old_j = 1;
+    st1 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, old_j1), "_getStream", NULL));
+    st2 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, old_j), "_getStream", NULL));
+
+    for (i=0; i<self->bufsize; i++) {
+        voice = Selector_clip_voice(self, vc[i]);
+
+        j1 = (int)voice;
+        j = j1 + 1;
+        if (j1 >= (self->chSize-1)) {
+            j1--; j--;
+        }
+        if (j1 != old_j1) {
+            st1 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, j1), "_getStream", NULL));
+            old_j1 = j1;
+        }
+        if (j != old_j) {
+            st2 = Stream_getData((Stream *)PyObject_CallMethod((PyObject *)PyList_GET_ITEM(self->inputs, j), "_getStream", NULL));
+            old_j = j;
+        }
+
+        voice = P_clip(voice - j1);
+
+        self->data[i] = st1[i] * (1.0 - voice) + st2[i] * voice;
+    }
+}
+
 static void Selector_postprocessing_ii(Selector *self) { POST_PROCESSING_II };
 static void Selector_postprocessing_ai(Selector *self) { POST_PROCESSING_AI };
 static void Selector_postprocessing_ia(Selector *self) { POST_PROCESSING_IA };
@@ -2889,10 +2946,16 @@ Selector_setProcMode(Selector *self)
 
 	switch (procmode) {
         case 0:
-            self->proc_func_ptr = Selector_generate_i;
+            if (self->mode == 0)
+                self->proc_func_ptr = Selector_generate_i;
+            else
+                self->proc_func_ptr = Selector_generate_lin_i;
             break;
         case 1:
-            self->proc_func_ptr = Selector_generate_a;
+            if (self->mode == 0)
+                self->proc_func_ptr = Selector_generate_a;
+            else
+                self->proc_func_ptr = Selector_generate_lin_a;
             break;
     }
 	switch (muladdmode) {
@@ -2970,6 +3033,7 @@ Selector_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self = (Selector *)type->tp_alloc(type, 0);
 
     self->voice = PyFloat_FromDouble(0.);
+    self->mode = 0;
 	self->modebuffer[0] = 0;
 	self->modebuffer[1] = 0;
 	self->modebuffer[2] = 0;
@@ -3078,6 +3142,21 @@ Selector_setVoice(Selector *self, PyObject *arg)
 	return Py_None;
 }
 
+static PyObject *
+Selector_setMode(Selector *self, PyObject *arg)
+{
+    ASSERT_ARG_NOT_NULL
+
+	if (PyInt_Check(arg) == 1) {
+		self->mode = PyLong_AsLong(arg);
+	}
+
+    (*self->mode_func_ptr)(self);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
 static PyMemberDef Selector_members[] = {
 {"server", T_OBJECT_EX, offsetof(Selector, server), 0, "Pyo server."},
 {"stream", T_OBJECT_EX, offsetof(Selector, stream), 0, "Stream object."},
@@ -3096,6 +3175,7 @@ static PyMethodDef Selector_methods[] = {
 {"stop", (PyCFunction)Selector_stop, METH_NOARGS, "Stops computing."},
 {"setInputs", (PyCFunction)Selector_setInputs, METH_O, "Sets list of input streams."},
 {"setVoice", (PyCFunction)Selector_setVoice, METH_O, "Sets voice position pointer."},
+{"setMode", (PyCFunction)Selector_setMode, METH_O, "Sets interpolation algorithm."},
 {"setMul", (PyCFunction)Selector_setMul, METH_O, "Sets mul factor."},
 {"setAdd", (PyCFunction)Selector_setAdd, METH_O, "Sets add factor."},
 {"setSub", (PyCFunction)Selector_setSub, METH_O, "Sets inverse add factor."},
diff --git a/src/objects/sfplayermodule.c b/src/objects/sfplayermodule.c
index 0dee321..1a14894 100644
--- a/src/objects/sfplayermodule.c
+++ b/src/objects/sfplayermodule.c
@@ -986,8 +986,8 @@ SfMarkerShuffler_chooseNewMark(SfMarkerShuffler *self, int dir)
     if (dir == 1) {
         if (self->startPos == -1) {
             mark = (int)(self->markers_size * RANDOM_UNIFORM);
-            self->startPos = self->markers[mark] * self->srScale;
-            self->endPos = self->markers[mark+1] * self->srScale;
+            self->startPos = self->markers[mark];
+            self->endPos = self->markers[mark+1];
         }
         else {
             self->startPos = self->nextStartPos;
@@ -995,14 +995,14 @@ SfMarkerShuffler_chooseNewMark(SfMarkerShuffler *self, int dir)
         }
 
         mark = (int)(self->markers_size * RANDOM_UNIFORM);
-        self->nextStartPos = self->markers[mark] * self->srScale;
-        self->nextEndPos = self->markers[mark+1] * self->srScale;
+        self->nextStartPos = self->markers[mark];
+        self->nextEndPos = self->markers[mark+1];
     }
     else {
         if (self->startPos == -1) {
             mark = self->markers_size - (int)(self->markers_size * RANDOM_UNIFORM);
-            self->startPos = self->markers[mark] * self->srScale;
-            self->endPos = self->markers[mark-1] * self->srScale;
+            self->startPos = self->markers[mark];
+            self->endPos = self->markers[mark-1];
         }
         else {
             self->startPos = self->nextStartPos;
@@ -1010,8 +1010,8 @@ SfMarkerShuffler_chooseNewMark(SfMarkerShuffler *self, int dir)
         }
 
         mark = self->markers_size - (int)(self->markers_size * RANDOM_UNIFORM);
-        self->nextStartPos = self->markers[mark] * self->srScale;
-        self->nextEndPos = self->markers[mark-1] * self->srScale;
+        self->nextStartPos = self->markers[mark];
+        self->nextEndPos = self->markers[mark-1];
     }
 }
 
@@ -1723,28 +1723,28 @@ SfMarkerLooper_chooseNewMark(SfMarkerLooper *self, int dir)
 
     if (dir == 1) {
         if (self->startPos == -1) {
-            self->startPos = self->markers[mark] * self->srScale;
-            self->endPos = self->markers[mark+1] * self->srScale;
+            self->startPos = self->markers[mark];
+            self->endPos = self->markers[mark+1];
         }
         else {
             self->startPos = self->nextStartPos;
             self->endPos = self->nextEndPos;
         }
-        self->nextStartPos = self->markers[mark] * self->srScale;
-        self->nextEndPos = self->markers[mark+1] * self->srScale;
+        self->nextStartPos = self->markers[mark];
+        self->nextEndPos = self->markers[mark+1];
     }
     else {
         mark = self->markers_size - mark;
         if (self->startPos == -1) {
-            self->startPos = self->markers[mark] * self->srScale;
-            self->endPos = self->markers[mark-1] * self->srScale;
+            self->startPos = self->markers[mark];
+            self->endPos = self->markers[mark-1];
         }
         else {
             self->startPos = self->nextStartPos;
             self->endPos = self->nextEndPos;
         }
-        self->nextStartPos = self->markers[mark] * self->srScale;
-        self->nextEndPos = self->markers[mark-1] * self->srScale;
+        self->nextStartPos = self->markers[mark];
+        self->nextEndPos = self->markers[mark-1];
     }
 }
 

-- 
python-pyo packaging



More information about the pkg-multimedia-commits mailing list