/**************************************************************************
 * Copyright 2009-2015 Olivier Belanger                                   *
 *                                                                        *
 * This file is part of pyo, a python module to help digital signal       *
 * processing script creation.                                            *
 *                                                                        *
 * pyo is free software: you can redistribute it and/or modify            *
 * it under the terms of the GNU Lesser General Public License as         *
 * published by the Free Software Foundation, either version 3 of the     *
 * License, or (at your option) any later version.                        *
 *                                                                        *
 * pyo is distributed in the hope that it will be useful,                 *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 * GNU 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/>.   *
 *************************************************************************/

#include <Python.h>
#include "structmember.h"
#include <math.h>
#include "pyomodule.h"
#include "streammodule.h"
#include "servermodule.h"
#include "dummymodule.h"
#include "tablemodule.h"
#include "interpolation.h"

static MYFLT LOOPER_LINEAR_FADE[513] = {0.0, 0.001953125, 0.00390625, 0.005859375, 0.0078125, 0.009765625, 0.01171875, 0.013671875, 0.015625, 0.017578125, 0.01953125, 0.021484375, 0.0234375, 0.025390625, 0.02734375, 0.029296875, 0.03125, 0.033203125, 0.03515625, 0.037109375, 0.0390625, 0.041015625, 0.04296875, 0.044921875, 0.046875, 0.048828125, 0.05078125, 0.052734375, 0.0546875, 0.056640625, 0.05859375, 0.060546875, 0.0625, 0.064453125, 0.06640625, 0.068359375, 0.0703125, 0.072265625, 0.07421875, 0.076171875, 0.078125, 0.080078125, 0.08203125, 0.083984375, 0.0859375, 0.087890625, 0.08984375, 0.091796875, 0.09375, 0.095703125, 0.09765625, 0.099609375, 0.1015625, 0.103515625, 0.10546875, 0.107421875, 0.109375, 0.111328125, 0.11328125, 0.115234375, 0.1171875, 0.119140625, 0.12109375, 0.123046875, 0.125, 0.126953125, 0.12890625, 0.130859375, 0.1328125, 0.134765625, 0.13671875, 0.138671875, 0.140625, 0.142578125, 0.14453125, 0.146484375, 0.1484375, 0.150390625, 0.15234375, 0.154296875, 0.15625, 0.158203125, 0.16015625, 0.162109375, 0.1640625, 0.166015625, 0.16796875, 0.169921875, 0.171875, 0.173828125, 0.17578125, 0.177734375, 0.1796875, 0.181640625, 0.18359375, 0.185546875, 0.1875, 0.189453125, 0.19140625, 0.193359375, 0.1953125, 0.197265625, 0.19921875, 0.201171875, 0.203125, 0.205078125, 0.20703125, 0.208984375, 0.2109375, 0.212890625, 0.21484375, 0.216796875, 0.21875, 0.220703125, 0.22265625, 0.224609375, 0.2265625, 0.228515625, 0.23046875, 0.232421875, 0.234375, 0.236328125, 0.23828125, 0.240234375, 0.2421875, 0.244140625, 0.24609375, 0.248046875, 0.25, 0.251953125, 0.25390625, 0.255859375, 0.2578125, 0.259765625, 0.26171875, 0.263671875, 0.265625, 0.267578125, 0.26953125, 0.271484375, 0.2734375, 0.275390625, 0.27734375, 0.279296875, 0.28125, 0.283203125, 0.28515625, 0.287109375, 0.2890625, 0.291015625, 0.29296875, 0.294921875, 0.296875, 0.298828125, 0.30078125, 0.302734375, 0.3046875, 0.306640625, 0.30859375, 0.310546875, 0.3125, 0.314453125, 0.31640625, 0.318359375, 0.3203125, 0.322265625, 0.32421875, 0.326171875, 0.328125, 0.330078125, 0.33203125, 0.333984375, 0.3359375, 0.337890625, 0.33984375, 0.341796875, 0.34375, 0.345703125, 0.34765625, 0.349609375, 0.3515625, 0.353515625, 0.35546875, 0.357421875, 0.359375, 0.361328125, 0.36328125, 0.365234375, 0.3671875, 0.369140625, 0.37109375, 0.373046875, 0.375, 0.376953125, 0.37890625, 0.380859375, 0.3828125, 0.384765625, 0.38671875, 0.388671875, 0.390625, 0.392578125, 0.39453125, 0.396484375, 0.3984375, 0.400390625, 0.40234375, 0.404296875, 0.40625, 0.408203125, 0.41015625, 0.412109375, 0.4140625, 0.416015625, 0.41796875, 0.419921875, 0.421875, 0.423828125, 0.42578125, 0.427734375, 0.4296875, 0.431640625, 0.43359375, 0.435546875, 0.4375, 0.439453125, 0.44140625, 0.443359375, 0.4453125, 0.447265625, 0.44921875, 0.451171875, 0.453125, 0.455078125, 0.45703125, 0.458984375, 0.4609375, 0.462890625, 0.46484375, 0.466796875, 0.46875, 0.470703125, 0.47265625, 0.474609375, 0.4765625, 0.478515625, 0.48046875, 0.482421875, 0.484375, 0.486328125, 0.48828125, 0.490234375, 0.4921875, 0.494140625, 0.49609375, 0.498046875, 0.5, 0.501953125, 0.50390625, 0.505859375, 0.5078125, 0.509765625, 0.51171875, 0.513671875, 0.515625, 0.517578125, 0.51953125, 0.521484375, 0.5234375, 0.525390625, 0.52734375, 0.529296875, 0.53125, 0.533203125, 0.53515625, 0.537109375, 0.5390625, 0.541015625, 0.54296875, 0.544921875, 0.546875, 0.548828125, 0.55078125, 0.552734375, 0.5546875, 0.556640625, 0.55859375, 0.560546875, 0.5625, 0.564453125, 0.56640625, 0.568359375, 0.5703125, 0.572265625, 0.57421875, 0.576171875, 0.578125, 0.580078125, 0.58203125, 0.583984375, 0.5859375, 0.587890625, 0.58984375, 0.591796875, 0.59375, 0.595703125, 0.59765625, 0.599609375, 0.6015625, 0.603515625, 0.60546875, 0.607421875, 0.609375, 0.611328125, 0.61328125, 0.615234375, 0.6171875, 0.619140625, 0.62109375, 0.623046875, 0.625, 0.626953125, 0.62890625, 0.630859375, 0.6328125, 0.634765625, 0.63671875, 0.638671875, 0.640625, 0.642578125, 0.64453125, 0.646484375, 0.6484375, 0.650390625, 0.65234375, 0.654296875, 0.65625, 0.658203125, 0.66015625, 0.662109375, 0.6640625, 0.666015625, 0.66796875, 0.669921875, 0.671875, 0.673828125, 0.67578125, 0.677734375, 0.6796875, 0.681640625, 0.68359375, 0.685546875, 0.6875, 0.689453125, 0.69140625, 0.693359375, 0.6953125, 0.697265625, 0.69921875, 0.701171875, 0.703125, 0.705078125, 0.70703125, 0.708984375, 0.7109375, 0.712890625, 0.71484375, 0.716796875, 0.71875, 0.720703125, 0.72265625, 0.724609375, 0.7265625, 0.728515625, 0.73046875, 0.732421875, 0.734375, 0.736328125, 0.73828125, 0.740234375, 0.7421875, 0.744140625, 0.74609375, 0.748046875, 0.75, 0.751953125, 0.75390625, 0.755859375, 0.7578125, 0.759765625, 0.76171875, 0.763671875, 0.765625, 0.767578125, 0.76953125, 0.771484375, 0.7734375, 0.775390625, 0.77734375, 0.779296875, 0.78125, 0.783203125, 0.78515625, 0.787109375, 0.7890625, 0.791015625, 0.79296875, 0.794921875, 0.796875, 0.798828125, 0.80078125, 0.802734375, 0.8046875, 0.806640625, 0.80859375, 0.810546875, 0.8125, 0.814453125, 0.81640625, 0.818359375, 0.8203125, 0.822265625, 0.82421875, 0.826171875, 0.828125, 0.830078125, 0.83203125, 0.833984375, 0.8359375, 0.837890625, 0.83984375, 0.841796875, 0.84375, 0.845703125, 0.84765625, 0.849609375, 0.8515625, 0.853515625, 0.85546875, 0.857421875, 0.859375, 0.861328125, 0.86328125, 0.865234375, 0.8671875, 0.869140625, 0.87109375, 0.873046875, 0.875, 0.876953125, 0.87890625, 0.880859375, 0.8828125, 0.884765625, 0.88671875, 0.888671875, 0.890625, 0.892578125, 0.89453125, 0.896484375, 0.8984375, 0.900390625, 0.90234375, 0.904296875, 0.90625, 0.908203125, 0.91015625, 0.912109375, 0.9140625, 0.916015625, 0.91796875, 0.919921875, 0.921875, 0.923828125, 0.92578125, 0.927734375, 0.9296875, 0.931640625, 0.93359375, 0.935546875, 0.9375, 0.939453125, 0.94140625, 0.943359375, 0.9453125, 0.947265625, 0.94921875, 0.951171875, 0.953125, 0.955078125, 0.95703125, 0.958984375, 0.9609375, 0.962890625, 0.96484375, 0.966796875, 0.96875, 0.970703125, 0.97265625, 0.974609375, 0.9765625, 0.978515625, 0.98046875, 0.982421875, 0.984375, 0.986328125, 0.98828125, 0.990234375, 0.9921875, 0.994140625, 0.99609375, 0.998046875, 1.0};
static MYFLT LOOPER_POWER_FADE[513] = {0.0, 0.0030679567629659761, 0.0061358846491544753, 0.0092037547820598194, 0.012271538285719925, 0.0153392062849881, 0.01840672990580482, 0.021474080275469508, 0.024541228522912288, 0.02760814577896574, 0.030674803176636626, 0.03374117185137758, 0.036807222941358832, 0.039872927587739811, 0.04293825693494082, 0.046003182130914623, 0.049067674327418015, 0.052131704680283324, 0.055195244349689941, 0.058258264500435752, 0.061320736302208578, 0.064382630929857465, 0.067443919563664051, 0.070504573389613856, 0.073564563599667426, 0.076623861392031492, 0.079682437971430126, 0.082740264549375692, 0.085797312344439894, 0.0888535525825246, 0.091908956497132724, 0.094963495329638992, 0.098017140329560604, 0.10106986275482782, 0.10412163387205459, 0.10717242495680884, 0.11022220729388306, 0.11327095217756435, 0.11631863091190475, 0.11936521481099135, 0.1224106751992162, 0.12545498341154623, 0.12849811079379317, 0.13154002870288312, 0.13458070850712617, 0.13762012158648604, 0.14065823933284921, 0.14369503315029447, 0.14673047445536175, 0.14976453467732151, 0.15279718525844344, 0.15582839765426523, 0.15885814333386145, 0.16188639378011183, 0.16491312048996992, 0.16793829497473117, 0.17096188876030122, 0.17398387338746382, 0.17700422041214875, 0.18002290140569951, 0.18303988795514095, 0.18605515166344663, 0.18906866414980619, 0.19208039704989244, 0.19509032201612825, 0.19809841071795356, 0.2011046348420919, 0.20410896609281687, 0.20711137619221856, 0.21011183688046961, 0.21311031991609136, 0.21610679707621952, 0.2191012401568698, 0.22209362097320351, 0.22508391135979283, 0.22807208317088573, 0.23105810828067111, 0.23404195858354343, 0.2370236059943672, 0.2400030224487415, 0.24298017990326387, 0.24595505033579459, 0.24892760574572015, 0.25189781815421697, 0.25486565960451457, 0.25783110216215899, 0.26079411791527551, 0.26375467897483135, 0.26671275747489837, 0.26966832557291509, 0.27262135544994898, 0.27557181931095814, 0.27851968938505306, 0.28146493792575794, 0.28440753721127188, 0.28734745954472951, 0.29028467725446233, 0.29321916269425863, 0.29615088824362379, 0.29907982630804048, 0.30200594931922808, 0.30492922973540237, 0.30784964004153487, 0.31076715274961147, 0.31368174039889152, 0.31659337555616585, 0.31950203081601569, 0.32240767880106985, 0.32531029216226293, 0.3282098435790925, 0.33110630575987643, 0.33399965144200938, 0.33688985339222005, 0.33977688440682685, 0.34266071731199438, 0.34554132496398909, 0.34841868024943456, 0.35129275608556709, 0.35416352542049034, 0.35703096123342998, 0.35989503653498811, 0.36275572436739723, 0.36561299780477385, 0.36846682995337232, 0.37131719395183754, 0.37416406297145793, 0.37700741021641826, 0.37984720892405116, 0.38268343236508978, 0.38551605384391885, 0.38834504669882625, 0.39117038430225387, 0.3939920400610481, 0.39680998741671031, 0.39962419984564679, 0.40243465085941843, 0.40524131400498986, 0.40804416286497869, 0.41084317105790391, 0.41363831223843456, 0.41642956009763715, 0.41921688836322391, 0.42200027079979968, 0.42477968120910881, 0.42755509343028208, 0.43032648134008261, 0.43309381885315196, 0.43585707992225547, 0.43861623853852766, 0.44137126873171667, 0.4441221445704292, 0.44686884016237416, 0.44961132965460654, 0.45234958723377089, 0.45508358712634384, 0.45781330359887723, 0.46053871095824001, 0.46325978355186015, 0.46597649576796618, 0.46868882203582796, 0.47139673682599764, 0.47410021465054997, 0.47679923006332209, 0.47949375766015301, 0.48218377207912272, 0.48486924800079106, 0.487550160148436, 0.49022648328829116, 0.49289819222978404, 0.49556526182577254, 0.49822766697278187, 0.50088538261124071, 0.50353838372571758, 0.50618664534515523, 0.50883014254310699, 0.5114688504379703, 0.51410274419322166, 0.51673179901764987, 0.51935599016558964, 0.52197529293715439, 0.52458968267846895, 0.52719913478190139, 0.52980362468629461, 0.5324031278771979, 0.53499761988709715, 0.53758707629564539, 0.54017147272989285, 0.54275078486451589, 0.54532498842204646, 0.54789405917310019, 0.55045797293660481, 0.55301670558002747, 0.55557023301960218, 0.5581185312205561, 0.56066157619733603, 0.56319934401383409, 0.56573181078361312, 0.56825895267013149, 0.57078074588696726, 0.5732971666980422, 0.57580819141784534, 0.57831379641165559, 0.58081395809576453, 0.58330865293769829, 0.58579785745643886, 0.58828154822264522, 0.59075970185887416, 0.5932322950397998, 0.59569930449243336, 0.59816070699634238, 0.60061647938386897, 0.60306659854034816, 0.60551104140432555, 0.60794978496777363, 0.61038280627630948, 0.61281008242940971, 0.61523159058062682, 0.61764730793780387, 0.6200572117632891, 0.62246127937414997, 0.62485948814238634, 0.62725181549514408, 0.62963823891492698, 0.63201873593980906, 0.63439328416364549, 0.6367618612362842, 0.63912444486377573, 0.64148101280858316, 0.64383154288979139, 0.64617601298331628, 0.64851440102211244, 0.65084668499638099, 0.65317284295377676, 0.65549285299961535, 0.65780669329707864, 0.66011434206742048, 0.66241577759017178, 0.66471097820334479, 0.66699992230363747, 0.66928258834663601, 0.67155895484701833, 0.67382900037875604, 0.67609270357531592, 0.67835004312986147, 0.68060099779545302, 0.68284554638524808, 0.68508366777270036, 0.68731534089175905, 0.68954054473706683, 0.69175925836415775, 0.69397146088965389, 0.69617713149146299, 0.69837624940897292, 0.70056879394324834, 0.7027547444572253, 0.70493408037590488, 0.70710678118654746, 0.70927282643886558, 0.71143219574521632, 0.71358486878079352, 0.71573082528381859, 0.7178700450557316, 0.72000250796138165, 0.72212819392921523, 0.72424708295146689, 0.7263591550843459, 0.7284643904482252, 0.73056276922782759, 0.7326542716724127, 0.73473887809596339, 0.73681656887736979, 0.73888732446061511, 0.74095112535495899, 0.74300795213512161, 0.74505778544146595, 0.74710060598018013, 0.74913639452345926, 0.75116513190968637, 0.75318679904361241, 0.75520137689653644, 0.75720884650648446, 0.75920918897838796, 0.76120238548426178, 0.76318841726338127, 0.76516726562245885, 0.76713891193582029, 0.76910333764557959, 0.77106052426181371, 0.77301045336273688, 0.77495310659487382, 0.77688846567323244, 0.77881651238147587, 0.78073722857209438, 0.78265059616657562, 0.78455659715557524, 0.78645521359908577, 0.78834642762660623, 0.79023022143731003, 0.79210657730021228, 0.79397547755433706, 0.79583690460888346, 0.79769084094339104, 0.79953726910790501, 0.80137617172314013, 0.80320753148064483, 0.80503133114296355, 0.80684755354379922, 0.80865618158817498, 0.81045719825259477, 0.81225058658520388, 0.8140363297059483, 0.81581441080673378, 0.81758481315158371, 0.8193475200767969, 0.82110251499110465, 0.82284978137582632, 0.82458930278502529, 0.82632106284566342, 0.82804504525775569, 0.82976123379452305, 0.83146961230254512, 0.83317016470191319, 0.83486287498638001, 0.83654772722351189, 0.83822470555483797, 0.83989379419599941, 0.84155497743689833, 0.84320823964184544, 0.84485356524970701, 0.84649093877405202, 0.84812034480329712, 0.84974176800085244, 0.8513551931052652, 0.85296060493036363, 0.85455798836540053, 0.85614732837519436, 0.85772861000027212, 0.85930181835700825, 0.8608669386377672, 0.8624239561110405, 0.8639728561215867, 0.86551362409056898, 0.86704624551569265, 0.8685707059713409, 0.87008699110871135, 0.87159508665595098, 0.87309497841828998, 0.87458665227817611, 0.87607009419540649, 0.87754529020726124, 0.87901222642863341, 0.88047088905216075, 0.88192126434835494, 0.88336333866573158, 0.88479709843093779, 0.88622253014888064, 0.88763962040285393, 0.88904835585466446, 0.89044872324475788, 0.89184070939234272, 0.89322430119551532, 0.89459948563138258, 0.89596624975618511, 0.89732458070541832, 0.89867446569395382, 0.90001589201616028, 0.90134884704602203, 0.90267331823725883, 0.90398929312344334, 0.90529675931811882, 0.90659570451491533, 0.90788611648766615, 0.90916798309052238, 0.91044129225806714, 0.91170603200542988, 0.9129621904283981, 0.91420975570353069, 0.91544871608826783, 0.9166790599210427, 0.91790077562139039, 0.91911385169005777, 0.92031827670911048, 0.9215140393420419, 0.92270112833387852, 0.92387953251128674, 0.92504924078267758, 0.92621024213831138, 0.92736252565040111, 0.92850608047321548, 0.92964089584318121, 0.93076696107898371, 0.93188426558166815, 0.93299279883473885, 0.93409255040425887, 0.9351835099389475, 0.93626566717027826, 0.93733901191257496, 0.93840353406310806, 0.93945922360218992, 0.9405060705932683, 0.94154406518302081, 0.94257319760144687, 0.94359345816196039, 0.94460483726148026, 0.94560732538052128, 0.94660091308328353, 0.94758559101774109, 0.94856134991573027, 0.94952818059303667, 0.9504860739494817, 0.95143502096900834, 0.95237501271976588, 0.95330604035419375, 0.95422809510910567, 0.95514116830577067, 0.95604525134999641, 0.95694033573220894, 0.95782641302753291, 0.9587034748958716, 0.95957151308198452, 0.96043051941556579, 0.96128048581132064, 0.96212140426904158, 0.96295326687368388, 0.96377606579543984, 0.96458979328981265, 0.9653944416976894, 0.96619000344541262, 0.96697647104485207, 0.96775383709347551, 0.96852209427441727, 0.96928123535654853, 0.97003125319454397, 0.97077214072895035, 0.97150389098625178, 0.97222649707893627, 0.97293995220556007, 0.97364424965081187, 0.97433938278557586, 0.97502534506699412, 0.97570213003852857, 0.97636973133002114, 0.97702814265775439, 0.97767735782450993, 0.97831737071962765, 0.9789481753190622, 0.97956976568544052, 0.98018213596811732, 0.98078528040323043, 0.98137919331375456, 0.98196386910955524, 0.98253930228744124, 0.98310548743121629, 0.98366241921173025, 0.98421009238692903, 0.98474850180190421, 0.98527764238894122, 0.98579750916756737, 0.98630809724459867, 0.98680940181418542, 0.98730141815785843, 0.98778414164457218, 0.98825756773074946, 0.98872169196032378, 0.98917650996478101, 0.98962201746320078, 0.99005821026229712, 0.99048508425645698, 0.99090263542778001, 0.99131085984611544, 0.99170975366909953, 0.9920993131421918, 0.99247953459870997, 0.9928504144598651, 0.9932119492347945, 0.9935641355205953, 0.99390697000235606, 0.9942404494531879, 0.99456457073425542, 0.99487933079480562, 0.99518472667219682, 0.99548075549192694, 0.99576741446765982, 0.99604470090125197, 0.996312612182778, 0.99657114579055484, 0.99682029929116567, 0.99706007033948296, 0.99729045667869021, 0.99751145614030345, 0.99772306664419164, 0.997925286198596, 0.99811811290014918, 0.99830154493389289, 0.99847558057329477, 0.99864021818026527, 0.99879545620517241, 0.99894129318685687, 0.99907772775264536, 0.99920475861836389, 0.99932238458834954, 0.99943060455546173, 0.99952941750109314, 0.99961882249517864, 0.99969881869620425, 0.99976940535121528, 0.9998305817958234, 0.99988234745421256, 0.9999247018391445, 0.9999576445519639, 0.99998117528260111, 0.99999529380957619, 1.0};
static MYFLT LOOPER_SIGMOID_FADE[513] = {0.0, 9.4123586994454556e-06, 3.7649080427748505e-05, 8.4709102088298405e-05, 0.00015059065189787502, 0.00023529124945342872, 0.00033880770582522812, 0.00046113612367731927, 0.0006022718974137975, 0.00076220971335261289, 0.00094094354992541041, 0.0011384666779041819, 0.0013547716606548965, 0.0015898503544171105, 0.0018436939086109994, 0.0021162927661700914, 0.0024076366639015356, 0.0027177146328722368, 0.0030465149988219697, 0.0033940253826026945, 0.0037602327006450165, 0.0041451231654502374, 0.0045486822861099951, 0.0049708948688514387, 0.0054117450176094928, 0.0058712161346252678, 0.0063492909210707826, 0.0068459513777006653, 0.0073611788055293337, 0.0078949538065354319, 0.008447256284391802, 0.0090180654452223785, 0.0096073597983847847, 0.010215117157279741, 0.010841314640186173, 0.011485928671122803, 0.012148934980735715, 0.012830308607212071, 0.013530023897219912, 0.014248054506874053, 0.014984373402728013, 0.015738952862791311, 0.016511764477573909, 0.017302779151155301, 0.018111967102280024, 0.01893929786547921, 0.019784740292217051, 0.0206482625520642, 0.021529832133895532, 0.022429415847114664, 0.023346979822903013, 0.024282489515495831, 0.025235909703481607, 0.026207204491129399, 0.02719633730973936, 0.028203270919019807, 0.029227967408489597, 0.030270388198904985, 0.03133049404371252, 0.032408245030526195, 0.033503600582630522, 0.034616519460508088, 0.035746959763392205, 0.036894878930844255, 0.038060233744356631, 0.039242980328979049, 0.040443074154971115, 0.041660470039478648, 0.042895122148234655, 0.044146983997285061, 0.045416008454738754, 0.046702147742542333, 0.048005353438278275, 0.049325576476988986, 0.050662767153022981, 0.052016875121907391, 0.053387849402242338, 0.054775638377621005, 0.056180189798573033, 0.05760145078453105, 0.059039367825822475, 0.060493886785683182, 0.061964952902296699, 0.06345251079085501, 0.06495650444564427, 0.066476877242153565, 0.068013571939206596, 0.069566530681116401, 0.071135694999863941, 0.072721005817299733, 0.074322403447367402, 0.075939827598351384, 0.077573217375146386, 0.079222511281550778, 0.080887647222581016, 0.082568562506809939, 0.084265193848727271, 0.085977477371122046, 0.087705348607487355, 0.08944874250444762, 0.091207593424208144, 0.092981835147025738, 0.094771400873702616, 0.096576223228100277, 0.098396234259677529, 0.10023136544604749, 0.10208154769555822, 0.10394671134989369, 0.10582678618669683, 0.10772170142221238, 0.1096313857139527, 0.11155576716338378, 0.11349477331863145, 0.11544833117721015, 0.11741636718877047, 0.11939880725786906, 0.12139557674675777, 0.12340660047819374, 0.1254318027382702, 0.12747110727926697, 0.1295244373225205, 0.13159171556131499, 0.13367286416379359, 0.13576780477588735, 0.1378764585242665, 0.13999874601930906, 0.14213458735809065, 0.14428390212739184, 0.14644660940672616, 0.14862262777138718, 0.15081187529551349, 0.153014269555173, 0.15522972763146642, 0.15745816611364977, 0.15969950110227338, 0.16195364821234198, 0.16422052257649067, 0.16650003884818121, 0.16879211120491411, 0.17109665335146057, 0.17341357852311146, 0.17574279948894383, 0.17808422855510425, 0.18043777756811202, 0.18280335791817703, 0.18518088054253651, 0.18757025592880677, 0.18997139411835529, 0.19238420470968631, 0.19480859686184526, 0.19724447929783717, 0.19969176030806535, 0.20215034775378338, 0.20462014907056286, 0.20710107127178046, 0.20959302095211751, 0.21209590429107739, 0.21460962705651632, 0.21713409460819322, 0.21966921190133171, 0.22221488349019891, 0.22477101353169748, 0.2273375057889766, 0.22991426363505363, 0.23250119005645137, 0.23509818765685253, 0.23770515866076569, 0.24032200491720523, 0.24294862790338906, 0.24558492872844634, 0.24823080813714093, 0.25088616651360907, 0.25355090388510787, 0.25622491992578178, 0.25890811396043867, 0.26160038496833893, 0.26430163158700104, 0.26701175211601708, 0.26973064452088003, 0.27245820643682805, 0.27519433517269654, 0.27793892771478512, 0.2806918807307362, 0.28345309057342394, 0.28622245328485874, 0.28899986460010024, 0.2917852199511814, 0.29457841447104793, 0.29737934299750524, 0.30018790007717666, 0.30300397996947592, 0.30582747665058668, 0.30865828381745486, 0.31149629489179087, 0.31434140302408115, 0.31719350109761285, 0.320052481732506, 0.32291823728975477, 0.32579065987528255, 0.32866964134400301, 0.33155507330389, 0.33444684712006173, 0.33734485391886837, 0.34024898459199188, 0.3431591298005543, 0.34607517997923243, 0.34899702534038579, 0.35192455587818816, 0.35485766137276881, 0.35779623139436395, 0.36074015530747361, 0.36368932227502559, 0.36664362126255073, 0.36960294104236202, 0.37256717019774244, 0.37553619712713993, 0.37850991004836793, 0.38148819700281617, 0.38447094585966451, 0.38745804432010356, 0.39044937992156492, 0.39344484004195446, 0.39644431190389073, 0.39944768257895397, 0.40245483899193568, 0.40546566792509658, 0.40848005602242954, 0.41149788979392549, 0.41451905561984914, 0.41754343975501512, 0.42057092833306925, 0.42360140737077812, 0.42663476277231926, 0.42967088033357542, 0.43270964574643683, 0.43575094460310321, 0.43879466240039156, 0.44184068454404762, 0.44488889635305834, 0.44793918306397246, 0.45099142983521978, 0.45404552175143359, 0.45710134382777989, 0.46015878101428509, 0.46321771820016633, 0.46627804021816788, 0.46933963184889549, 0.47240237782515471, 0.47546616283629101, 0.47853087153252949, 0.48159638852932035, 0.48466259841168174, 0.4877293857385438, 0.49079663504709742, 0.49386423085714021, 0.49693205767542281, 0.49999999999999989, 0.50306794232457708, 0.50613576914285963, 0.50920336495290242, 0.51227061426145604, 0.51533740158831809, 0.51840361147067948, 0.5214691284674704, 0.52453383716370883, 0.52759762217484507, 0.53066036815110429, 0.5337219597818319, 0.53678228179983345, 0.53984121898571469, 0.54289865617221988, 0.54595447824856624, 0.54900857016478, 0.55206081693602738, 0.55511110364694149, 0.55815931545595221, 0.56120533759960822, 0.56424905539689663, 0.567290354253563, 0.57032911966642441, 0.57336523722768051, 0.57639859262922166, 0.57942907166693058, 0.58245656024498471, 0.58548094438015064, 0.58850211020607435, 0.59151994397757024, 0.59453433207490325, 0.5975451610080641, 0.60055231742104587, 0.60355568809610904, 0.60655515995804532, 0.60955062007843486, 0.61254195567989622, 0.61552905414033532, 0.61851180299718367, 0.62149008995163191, 0.62446380287285996, 0.62743282980225745, 0.63039705895763776, 0.6333563787374491, 0.63631067772497429, 0.63925984469252617, 0.64220376860563588, 0.64514233862723103, 0.64807544412181162, 0.6510029746596141, 0.65392482002076735, 0.65684087019944559, 0.65975101540800796, 0.66265514608113152, 0.6655531528799381, 0.66844492669610978, 0.67133035865599688, 0.67420934012471723, 0.67708176271024501, 0.67994751826749389, 0.68280649890238698, 0.68565859697591869, 0.68850370510820902, 0.69134171618254503, 0.69417252334941315, 0.69699602003052397, 0.69981209992282323, 0.70262065700249465, 0.70542158552895196, 0.70821478004881844, 0.71100013539989959, 0.71377754671514104, 0.71654690942657595, 0.71930811926926363, 0.72206107228521477, 0.72480566482730335, 0.72754179356317183, 0.7302693554791202, 0.73298824788398276, 0.73569836841299874, 0.73839961503166096, 0.74109188603956111, 0.743775080074218, 0.74644909611489196, 0.74911383348639071, 0.75176919186285884, 0.75441507127155349, 0.75705137209661078, 0.75967799508279499, 0.76229484133923409, 0.76490181234314725, 0.76749880994354847, 0.77008573636494626, 0.77266249421102318, 0.77522898646830229, 0.77778511650980087, 0.78033078809866807, 0.78286590539180656, 0.78539037294348346, 0.78790409570892272, 0.79040697904788193, 0.79289892872821932, 0.79537985092943697, 0.7978496522462164, 0.80030823969193454, 0.80275552070216261, 0.80519140313815452, 0.80761579529031347, 0.8100286058816446, 0.81242974407119306, 0.81481911945746366, 0.81719664208182241, 0.81956222243188781, 0.82191577144489558, 0.824257200511056, 0.82658642147688832, 0.82890334664853926, 0.83120788879508578, 0.83349996115181879, 0.83577947742350922, 0.83804635178765785, 0.84030049889772662, 0.84254183388634996, 0.84477027236853341, 0.84698573044482683, 0.84918812470448612, 0.85137737222861265, 0.85355339059327373, 0.85571609787260805, 0.85786541264190941, 0.86000125398069083, 0.86212354147573333, 0.86423219522411276, 0.86632713583620613, 0.8684082844386849, 0.87047556267747939, 0.87252889272073275, 0.87456819726172963, 0.87659339952180615, 0.87860442325324217, 0.880601192742131, 0.88258363281122942, 0.88455166882278968, 0.88650522668136855, 0.888444232836616, 0.89036861428604719, 0.89227829857778751, 0.89417321381330295, 0.89605328865010625, 0.89791845230444167, 0.8997686345539524, 0.90160376574032253, 0.90342377677189956, 0.90522859912629738, 0.90701816485297426, 0.90879240657579163, 0.91055125749555232, 0.91229465139251253, 0.91402252262887773, 0.91573480615127267, 0.91743143749319001, 0.91911235277741887, 0.92077748871844922, 0.92242678262485356, 0.92406017240164862, 0.92567759655263271, 0.92727899418269999, 0.92886430500013595, 0.9304334693188836, 0.93198642806079324, 0.93352312275784632, 0.93504349555435562, 0.93654748920914499, 0.93803504709770336, 0.93950611321431676, 0.94096063217417747, 0.94239854921546895, 0.9438198102014268, 0.94522436162237888, 0.94661215059775761, 0.94798312487809244, 0.94933723284697691, 0.9506744235230109, 0.9519946465617215, 0.95329785225745778, 0.95458399154526119, 0.95585301600271488, 0.95710487785176546, 0.95833952996052119, 0.95955692584502883, 0.9607570196710209, 0.96193976625564326, 0.96310512106915569, 0.96425304023660774, 0.96538348053949186, 0.96649639941736942, 0.96759175496947369, 0.96866950595628742, 0.96972961180109496, 0.97077203259151024, 0.97179672908098014, 0.97280366269026053, 0.97379279550887043, 0.97476409029651834, 0.97571751048450417, 0.97665302017709688, 0.97757058415288545, 0.97847016786610441, 0.97935173744793569, 0.98021525970778289, 0.98106070213452068, 0.98188803289771998, 0.9826972208488447, 0.98348823552242592, 0.98426104713720863, 0.98501562659727204, 0.98575194549312584, 0.98646997610278009, 0.98716969139278787, 0.98785106501926423, 0.98851407132887714, 0.98915868535981377, 0.98978488284272026, 0.99039264020161522, 0.99098193455477757, 0.9915527437156082, 0.99210504619346451, 0.99263882119447056, 0.99315404862229939, 0.99365070907892916, 0.99412878386537473, 0.99458825498239056, 0.99502910513114851, 0.99545131771388995, 0.99585487683454976, 0.99623976729935493, 0.99660597461739719, 0.99695348500117809, 0.99728228536712771, 0.99759236333609846, 0.99788370723382991, 0.99815630609138895, 0.99841014964558283, 0.99864522833934499, 0.99886153332209582, 0.99905905645007453, 0.99923779028664739, 0.9993977281025862, 0.99953886387632274, 0.99966119229417472, 0.99976470875054657, 0.99984940934810207, 0.99991529089791165, 0.99996235091957231, 0.99999058764130055, 1.0};

typedef struct
{
    pyo_audio_HEAD
    PyObject *table;
    PyObject *env;
    PyObject *pitch;
    Stream *pitch_stream;
    PyObject *pos;
    Stream *pos_stream;
    PyObject *dur;
    Stream *dur_stream;
    int ngrains;
    MYFLT basedur;
    MYFLT pointerPos;
    MYFLT *startPos;
    MYFLT *gsize;
    MYFLT *gphase;
    MYFLT *lastppos;
    MYFLT srScale;
    int modebuffer[5];
} Granulator;

static void
Granulator_transform_iii(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT pit = PyFloat_AS_DOUBLE(self->pitch);
    MYFLT pos = PyFloat_AS_DOUBLE(self->pos);
    MYFLT dur = PyFloat_AS_DOUBLE(self->dur);

    inc = pit * (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos;
                self->gsize[j] = dur * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_aii(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos, frtosamps;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *pit = Stream_getData((Stream *)self->pitch_stream);
    MYFLT pos = PyFloat_AS_DOUBLE(self->pos);
    MYFLT dur = PyFloat_AS_DOUBLE(self->dur);

    frtosamps = (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        inc = pit[i] * frtosamps;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos;
                self->gsize[j] = dur * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_iai(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT pit = PyFloat_AS_DOUBLE(self->pitch);
    MYFLT *pos = Stream_getData((Stream *)self->pos_stream);
    MYFLT dur = PyFloat_AS_DOUBLE(self->dur);

    inc = pit * (1.0 / self->basedur) / self->sr;

    MYFLT gsize = dur * self->sr * self->srScale;

    for (j = 0; j < self->ngrains; j++)
    {
        self->gsize[j] = gsize;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos[i];
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_aai(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos, frtosamps;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *pit = Stream_getData((Stream *)self->pitch_stream);
    MYFLT *pos = Stream_getData((Stream *)self->pos_stream);
    MYFLT dur = PyFloat_AS_DOUBLE(self->dur);

    frtosamps = (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        inc = pit[i] * frtosamps;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos[i];
                self->gsize[j] = dur * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_iia(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT pit = PyFloat_AS_DOUBLE(self->pitch);
    MYFLT pos = PyFloat_AS_DOUBLE(self->pos);
    MYFLT *dur = Stream_getData((Stream *)self->dur_stream);

    inc = pit * (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos;
                self->gsize[j] = dur[i] * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_aia(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos, frtosamps;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *pit = Stream_getData((Stream *)self->pitch_stream);
    MYFLT pos = PyFloat_AS_DOUBLE(self->pos);
    MYFLT *dur = Stream_getData((Stream *)self->dur_stream);

    frtosamps = (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        inc = pit[i] * frtosamps;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos;
                self->gsize[j] = dur[i] * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_iaa(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT pit = PyFloat_AS_DOUBLE(self->pitch);
    MYFLT *pos = Stream_getData((Stream *)self->pos_stream);
    MYFLT *dur = Stream_getData((Stream *)self->dur_stream);

    inc = pit * (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos[i];
                self->gsize[j] = dur[i] * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void
Granulator_transform_aaa(Granulator *self)
{
    MYFLT val, x, x1, inc, index, fpart, amp, ppos, frtosamps;
    int i, j;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *pit = Stream_getData((Stream *)self->pitch_stream);
    MYFLT *pos = Stream_getData((Stream *)self->pos_stream);
    MYFLT *dur = Stream_getData((Stream *)self->dur_stream);

    frtosamps = (1.0 / self->basedur) / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        inc = pit[i] * frtosamps;
        self->pointerPos += inc;

        for (j = 0; j < self->ngrains; j++)
        {
            ppos = self->pointerPos + self->gphase[j];

            if (ppos >= 1.0)
            {
                ppos -= 1.0;
            }

            // compute envelope
            index = ppos * envsize;
            ipart = (T_SIZE_T)index;
            fpart = index - ipart;
            x = envlist[ipart];
            x1 = envlist[ipart + 1];
            amp = x + (x1 - x) * fpart;

            if (ppos < self->lastppos[j])
            {
                self->startPos[j] = pos[i];
                self->gsize[j] = dur[i] * self->sr * self->srScale;
            }

            self->lastppos[j] = ppos;

            // compute sampling
            index = ppos * self->gsize[j] + self->startPos[j];

            if (index >= 0 && index < size)
            {
                ipart = (T_SIZE_T)index;
                fpart = index - ipart;
                x = tablelist[ipart];
                x1 = tablelist[ipart + 1];
                val = x + (x1 - x) * fpart;
            }
            else
                val = 0.0;

            self->data[i] += (val * amp);
        }

        if (self->pointerPos < 0)
            self->pointerPos += 1.0;
        else if (self->pointerPos >= 1)
            self->pointerPos -= 1.0;
    }
}

static void Granulator_postprocessing_ii(Granulator *self) { POST_PROCESSING_II };
static void Granulator_postprocessing_ai(Granulator *self) { POST_PROCESSING_AI };
static void Granulator_postprocessing_ia(Granulator *self) { POST_PROCESSING_IA };
static void Granulator_postprocessing_aa(Granulator *self) { POST_PROCESSING_AA };
static void Granulator_postprocessing_ireva(Granulator *self) { POST_PROCESSING_IREVA };
static void Granulator_postprocessing_areva(Granulator *self) { POST_PROCESSING_AREVA };
static void Granulator_postprocessing_revai(Granulator *self) { POST_PROCESSING_REVAI };
static void Granulator_postprocessing_revaa(Granulator *self) { POST_PROCESSING_REVAA };
static void Granulator_postprocessing_revareva(Granulator *self) { POST_PROCESSING_REVAREVA };

static void
Granulator_setProcMode(Granulator *self)
{
    int procmode, muladdmode;
    procmode = self->modebuffer[2] + self->modebuffer[3] * 10 + self->modebuffer[4] * 100;
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (procmode)
    {
        case 0:
            self->proc_func_ptr = Granulator_transform_iii;
            break;

        case 1:
            self->proc_func_ptr = Granulator_transform_aii;
            break;

        case 10:
            self->proc_func_ptr = Granulator_transform_iai;
            break;

        case 11:
            self->proc_func_ptr = Granulator_transform_aai;
            break;

        case 100:
            self->proc_func_ptr = Granulator_transform_iia;
            break;

        case 101:
            self->proc_func_ptr = Granulator_transform_aia;
            break;

        case 110:
            self->proc_func_ptr = Granulator_transform_iaa;
            break;

        case 111:
            self->proc_func_ptr = Granulator_transform_aaa;
            break;
    }

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = Granulator_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = Granulator_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = Granulator_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = Granulator_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = Granulator_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = Granulator_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = Granulator_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = Granulator_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = Granulator_postprocessing_revareva;
            break;
    }
}

static void
Granulator_compute_next_data_frame(Granulator *self)
{
    (*self->proc_func_ptr)(self);
    (*self->muladd_func_ptr)(self);
}

static int
Granulator_traverse(Granulator *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->pitch);
    Py_VISIT(self->pos);
    Py_VISIT(self->dur);
    return 0;
}

static int
Granulator_clear(Granulator *self)
{
    pyo_CLEAR
    Py_CLEAR(self->pitch);
    Py_CLEAR(self->pos);
    Py_CLEAR(self->dur);
    return 0;
}

static void
Granulator_dealloc(Granulator* self)
{
    pyo_DEALLOC
    PyMem_RawFree(self->startPos);
    PyMem_RawFree(self->gphase);
    PyMem_RawFree(self->gsize);
    PyMem_RawFree(self->lastppos);
    Granulator_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Granulator_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    MYFLT phase;
    PyObject *tabletmp, *envtmp, *pitchtmp = NULL, *postmp = NULL, *durtmp = NULL, *multmp = NULL, *addtmp = NULL;
    Granulator *self;
    self = (Granulator *)type->tp_alloc(type, 0);

    self->pitch = PyFloat_FromDouble(1);
    self->pos = PyFloat_FromDouble(0.0);
    self->dur = PyFloat_FromDouble(0.1);
    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;
    self->modebuffer[3] = 0;
    self->modebuffer[4] = 0;

    INIT_OBJECT_COMMON
    Stream_setFunctionPtr(self->stream, Granulator_compute_next_data_frame);
    self->mode_func_ptr = Granulator_setProcMode;

    static char *kwlist[] = {"table", "env", "pitch", "pos", "dur", "grains", "basedur", "mul", "add", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, TYPE_OO_OOOIFOO, kwlist, &tabletmp, &envtmp, &pitchtmp, &postmp, &durtmp, &self->ngrains, &self->basedur, &multmp, &addtmp))
        Py_RETURN_NONE;

    if ( PyObject_HasAttrString((PyObject *)tabletmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"table\" argument of Granulator must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");
    self->srScale = TableStream_getSamplingRate((TableStream *)self->table) / self->sr;

    if ( PyObject_HasAttrString((PyObject *)envtmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"env\" argument of Granulator must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->env = PyObject_CallMethod((PyObject *)envtmp, "getTableStream", "");

    if (pitchtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPitch", "O", pitchtmp);
    }

    if (postmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPos", "O", postmp);
    }

    if (durtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDur", "O", durtmp);
    }

    if (multmp)
    {
        PyObject_CallMethod((PyObject *)self, "setMul", "O", multmp);
    }

    if (addtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setAdd", "O", addtmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    self->startPos = (MYFLT *)PyMem_RawRealloc(self->startPos, self->ngrains * sizeof(MYFLT));
    self->gsize = (MYFLT *)PyMem_RawRealloc(self->gsize, self->ngrains * sizeof(MYFLT));
    self->gphase = (MYFLT *)PyMem_RawRealloc(self->gphase, self->ngrains * sizeof(MYFLT));
    self->lastppos = (MYFLT *)PyMem_RawRealloc(self->lastppos, self->ngrains * sizeof(MYFLT));

    Server_generateSeed((Server *)self->server, GRANULATOR_ID);

    for (i = 0; i < self->ngrains; i++)
    {
        phase = ((MYFLT)i / self->ngrains) * (1.0 + ((RANDOM_UNIFORM * 2.0 - 1.0) * 0.01));

        if (phase < 0.0)
            phase = 0.0;
        else if (phase >= 1.0)
            phase -= 1.0;

        self->gphase[i] = phase;
        self->startPos[i] = self->gsize[i] = 0.0;
        self->lastppos[i] = 1.0;
    }

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * Granulator_getServer(Granulator* self) { GET_SERVER };
static PyObject * Granulator_getStream(Granulator* self) { GET_STREAM };
static PyObject * Granulator_setMul(Granulator *self, PyObject *arg) { SET_MUL };
static PyObject * Granulator_setAdd(Granulator *self, PyObject *arg) { SET_ADD };
static PyObject * Granulator_setSub(Granulator *self, PyObject *arg) { SET_SUB };
static PyObject * Granulator_setDiv(Granulator *self, PyObject *arg) { SET_DIV };

static PyObject * Granulator_play(Granulator *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * Granulator_out(Granulator *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * Granulator_stop(Granulator *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * Granulator_multiply(Granulator *self, PyObject *arg) { MULTIPLY };
static PyObject * Granulator_inplace_multiply(Granulator *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * Granulator_add(Granulator *self, PyObject *arg) { ADD };
static PyObject * Granulator_inplace_add(Granulator *self, PyObject *arg) { INPLACE_ADD };
static PyObject * Granulator_sub(Granulator *self, PyObject *arg) { SUB };
static PyObject * Granulator_inplace_sub(Granulator *self, PyObject *arg) { INPLACE_SUB };
static PyObject * Granulator_div(Granulator *self, PyObject *arg) { DIV };
static PyObject * Granulator_inplace_div(Granulator *self, PyObject *arg) { INPLACE_DIV };

static PyObject * Granulator_setPitch(Granulator *self, PyObject *arg) { SET_PARAM(self->pitch, self->pitch_stream, 2); }
static PyObject * Granulator_setPos(Granulator *self, PyObject *arg) { SET_PARAM(self->pos, self->pos_stream, 3); }
static PyObject * Granulator_setDur(Granulator *self, PyObject *arg) { SET_PARAM(self->dur, self->dur_stream, 4); }

static PyObject *
Granulator_getTable(Granulator* self)
{
    Py_INCREF(self->table);
    return self->table;
};

static PyObject *
Granulator_setTable(Granulator *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->table);
    self->table = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyObject *
Granulator_getEnv(Granulator* self)
{
    Py_INCREF(self->env);
    return self->env;
};

static PyObject *
Granulator_setEnv(Granulator *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->env);
    self->env = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyObject *
Granulator_setBaseDur(Granulator *self, PyObject *arg)
{
    if (arg != NULL)
        self->basedur = PyFloat_AsDouble(arg);

    Py_RETURN_NONE;
}

static PyObject *
Granulator_setGrains(Granulator *self, PyObject *arg)
{
    int i;
    MYFLT phase;

    if (PyLong_Check(arg))
    {
        self->ngrains = PyLong_AsLong(arg);
        self->startPos = (MYFLT *)PyMem_RawRealloc(self->startPos, self->ngrains * sizeof(MYFLT));
        self->gsize = (MYFLT *)PyMem_RawRealloc(self->gsize, self->ngrains * sizeof(MYFLT));
        self->gphase = (MYFLT *)PyMem_RawRealloc(self->gphase, self->ngrains * sizeof(MYFLT));
        self->lastppos = (MYFLT *)PyMem_RawRealloc(self->lastppos, self->ngrains * sizeof(MYFLT));

        for (i = 0; i < self->ngrains; i++)
        {
            phase = ((MYFLT)i / self->ngrains) * (1.0 + ((RANDOM_UNIFORM * 2.0 - 1.0) * 0.01));

            if (phase < 0.0)
                phase = 0.0;
            else if (phase >= 1.0)
                phase -= 1.0;

            self->gphase[i] = phase;
            self->startPos[i] = self->gsize[i] = 0.0;
            self->lastppos[i] = 1.0;
        }
    }

    Py_RETURN_NONE;
}

static PyMemberDef Granulator_members[] =
{
    {"server", T_OBJECT_EX, offsetof(Granulator, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(Granulator, stream), 0, "Stream object."},
    {"table", T_OBJECT_EX, offsetof(Granulator, table), 0, "Sound table."},
    {"env", T_OBJECT_EX, offsetof(Granulator, env), 0, "Envelope table."},
    {"pitch", T_OBJECT_EX, offsetof(Granulator, pitch), 0, "Speed of the reading pointer."},
    {"pos", T_OBJECT_EX, offsetof(Granulator, pos), 0, "Position in the sound table."},
    {"dur", T_OBJECT_EX, offsetof(Granulator, dur), 0, "Duration of each grains."},
    {"mul", T_OBJECT_EX, offsetof(Granulator, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(Granulator, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef Granulator_methods[] =
{
    {"getTable", (PyCFunction)Granulator_getTable, METH_NOARGS, "Returns sound table object."},
    {"setTable", (PyCFunction)Granulator_setTable, METH_O, "Sets sound table."},
    {"getEnv", (PyCFunction)Granulator_getEnv, METH_NOARGS, "Returns envelope table object."},
    {"setEnv", (PyCFunction)Granulator_setEnv, METH_O, "Sets envelope table."},
    {"getServer", (PyCFunction)Granulator_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)Granulator_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)Granulator_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)Granulator_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)Granulator_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setPitch", (PyCFunction)Granulator_setPitch, METH_O, "Sets global pitch factor."},
    {"setPos", (PyCFunction)Granulator_setPos, METH_O, "Sets position in the sound table."},
    {"setDur", (PyCFunction)Granulator_setDur, METH_O, "Sets the grain duration."},
    {"setBaseDur", (PyCFunction)Granulator_setBaseDur, METH_O, "Sets the grain base duration."},
    {"setGrains", (PyCFunction)Granulator_setGrains, METH_O, "Sets the number of grains."},
    {"setMul", (PyCFunction)Granulator_setMul, METH_O, "Sets granulator mul factor."},
    {"setAdd", (PyCFunction)Granulator_setAdd, METH_O, "Sets granulator add factor."},
    {"setSub", (PyCFunction)Granulator_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)Granulator_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods Granulator_as_number =
{
    (binaryfunc)Granulator_add,                      /*nb_add*/
    (binaryfunc)Granulator_sub,                 /*nb_subtract*/
    (binaryfunc)Granulator_multiply,                 /*nb_multiply*/
    0,                /*nb_remainder*/
    0,                   /*nb_divmod*/
    0,                   /*nb_power*/
    0,                  /*nb_neg*/
    0,                /*nb_pos*/
    0,                  /*(unaryfunc)array_abs,*/
    0,                    /*nb_nonzero*/
    0,                    /*nb_invert*/
    0,               /*nb_lshift*/
    0,              /*nb_rshift*/
    0,              /*nb_and*/
    0,              /*nb_xor*/
    0,               /*nb_or*/
    0,                       /*nb_int*/
    0,                      /*nb_long*/
    0,                     /*nb_float*/
    (binaryfunc)Granulator_inplace_add,              /*inplace_add*/
    (binaryfunc)Granulator_inplace_sub,         /*inplace_subtract*/
    (binaryfunc)Granulator_inplace_multiply,         /*inplace_multiply*/
    0,        /*inplace_remainder*/
    0,           /*inplace_power*/
    0,       /*inplace_lshift*/
    0,      /*inplace_rshift*/
    0,      /*inplace_and*/
    0,      /*inplace_xor*/
    0,       /*inplace_or*/
    0,             /*nb_floor_divide*/
    (binaryfunc)Granulator_div,                       /*nb_true_divide*/
    0,     /*nb_inplace_floor_divide*/
    (binaryfunc)Granulator_inplace_div,                       /*nb_inplace_true_divide*/
    0,                     /* nb_index */
};

PyTypeObject GranulatorType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.Granulator_base",         /*tp_name*/
    sizeof(Granulator),         /*tp_basicpitch*/
    0,                         /*tp_itempitch*/
    (destructor)Granulator_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &Granulator_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
    "Granulator objects. Accumulation of multiples grains of sound.",           /* tp_doc */
    (traverseproc)Granulator_traverse,   /* tp_traverse */
    (inquiry)Granulator_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Granulator_methods,             /* tp_methods */
    Granulator_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    Granulator_new,                 /* tp_new */
};

typedef struct
{
    pyo_audio_HEAD
    PyObject *table;
    PyObject *pitch;
    Stream *pitch_stream;
    PyObject *start;
    Stream *start_stream;
    PyObject *dur;
    Stream *dur_stream;
    PyObject *xfade;
    Stream *xfade_stream;
    MYFLT *trigsBuffer;
    TriggerStream *trig_stream;
    MYFLT *time_buffer;
    int xfadeshape;
    int startfromloop;
    int init;
    int mode[2]; /* 0 = no loop, 1 = forward, 2 = backward, 3 = back-and-forth */
    int tmpmode;
    int direction[2];
    double pointerPos[2];
    double loopDuration;
    int current;
    int active[2];
    long loopstart[2];
    long loopend[2];
    long crossfadedur[2];
    MYFLT crossfadescaling[2];
    long minfadepoint[2];
    long maxfadepoint[2];
    MYFLT *fader;
    int interp; /* 0 = default to 2, 1 = nointerp, 2 = linear, 3 = cos, 4 = cubic */
    MYFLT (*interp_func_ptr)(MYFLT *, T_SIZE_T, MYFLT, T_SIZE_T);
    int modebuffer[6];
    int autosmooth;
    int appendfade;
    int fadeinseconds;
    MYFLT lastpitch;
    // sample memories
    MYFLT y1;
    MYFLT y2;
    // variables
    MYFLT c1;
} Looper;

static void
Looper_endloop(Looper *self)
{
    int which = self->current;

    switch (self->mode[which])
    {
        case 0:
            self->maxfadepoint[which] = self->pointerPos[which];
            self->loopend[which] = self->maxfadepoint[which] + self->crossfadedur[which];
            break;

        case 1:
            self->maxfadepoint[which] = self->pointerPos[which];
            self->loopend[which] = self->maxfadepoint[which] + self->crossfadedur[which];
            break;

        case 2:
            self->maxfadepoint[which] = self->pointerPos[which];
            self->loopend[which] = self->maxfadepoint[which] - self->crossfadedur[which];
            break;

        case 3:
            if (self->direction[which] == 0)
            {
                self->maxfadepoint[which] = self->pointerPos[which];
                self->loopend[which] = self->maxfadepoint[which] + self->crossfadedur[which];
            }
            else
            {
                self->maxfadepoint[which] = self->pointerPos[which];
                self->loopend[which] = self->maxfadepoint[which] - self->crossfadedur[which];
            }

            break;
    }
}

static void
Looper_reset(Looper *self, int x, int which, int init)
{
    MYFLT start, dur, xfade;

    T_SIZE_T size = TableStream_getSize((TableStream *)self->table) - 1;
    double tableSr = TableStream_getSamplingRate((TableStream *)self->table);

    if (self->modebuffer[3] == 0)
        start = PyFloat_AS_DOUBLE(self->start);
    else
        start = Stream_getData((Stream *)self->start_stream)[x];

    if (self->modebuffer[4] == 0)
        dur = PyFloat_AS_DOUBLE(self->dur);
    else
        dur = Stream_getData((Stream *)self->dur_stream)[x];

    if (self->modebuffer[5] == 0)
        xfade = PyFloat_AS_DOUBLE(self->xfade);
    else
        xfade = Stream_getData((Stream *)self->xfade_stream)[x];

    if (start < 0.0)
        start = 0.0;
    else if (start > (size / tableSr))
        start = (MYFLT)(size / tableSr);

    if (dur < 0.001)
        dur = 0.001;

    if (self->fadeinseconds == 1)
        xfade = xfade / dur * 100.0;

    if (xfade < 0.0)
        xfade = 0.0;
    else if (xfade > 50.0)
        xfade = 50.0;

    if (self->xfadeshape == 0)
        self->fader = LOOPER_LINEAR_FADE;
    else if (self->xfadeshape == 1)
        self->fader = LOOPER_POWER_FADE;
    else if (self->xfadeshape == 2)
        self->fader = LOOPER_SIGMOID_FADE;
    else
        self->fader = LOOPER_LINEAR_FADE;

    self->current = which;

    if (self->tmpmode != self->mode[which])
        self->mode[which] = self->tmpmode;

    if (init == 0)
        self->trigsBuffer[x] = 1.0;

    switch (self->mode[which])
    {
        case 0:
            self->loopstart[which] = 0;
            self->loopend[which] = (long)size;
            self->crossfadedur[which] = (long)((self->loopend[which] - self->loopstart[which]) * xfade * 0.01);

            if (self->crossfadedur[which] < 1)
                self->crossfadedur[which] = 1;

            self->crossfadescaling[which] = 1.0 / self->crossfadedur[which] * 512.0;

            if (self->appendfade == 1)
                self->loopend[which] += self->crossfadedur[which];

            if (init == 1 && self->startfromloop == 0)
            {
                self->minfadepoint[which] = self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which] = 0.0;
            }
            else
            {
                self->minfadepoint[which] = self->loopstart[which] + self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which];
            }

            self->loopDuration = self->maxfadepoint[which] - self->loopstart[which];
            break;

        case 1:
            self->loopstart[which] = (long)(start * tableSr);
            self->loopend[which] = (long)((start + dur) * tableSr);
            self->crossfadedur[which] = (long)((self->loopend[which] - self->loopstart[which]) * xfade * 0.01);

            if (self->crossfadedur[which] < 1)
                self->crossfadedur[which] = 1;

            self->crossfadescaling[which] = 1.0 / self->crossfadedur[which] * 512.0;

            if (self->appendfade == 1)
                self->loopend[which] += self->crossfadedur[which];

            if (init == 1 && self->startfromloop == 0)
            {
                self->minfadepoint[which] = self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which] = 0.0;
            }
            else
            {
                self->minfadepoint[which] = self->loopstart[which] + self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which];
            }

            self->loopDuration = self->maxfadepoint[which] - self->loopstart[which];
            break;

        case 2:
            self->loopstart[which] = (long)((start + dur) * tableSr);
            self->loopend[which] = (long)(start * tableSr);
            self->crossfadedur[which] = (long)((self->loopstart[which] - self->loopend[which]) * xfade * 0.01);

            if (self->crossfadedur[which] < 1)
                self->crossfadedur[which] = 1;

            self->crossfadescaling[which] = 1.0 / self->crossfadedur[which] * 512.0;

            if (self->appendfade == 1)
                self->loopend[which] -= self->crossfadedur[which];

            if (init == 1 && self->startfromloop == 0)
            {
                self->minfadepoint[which] = size - self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] + self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which] = (MYFLT)size;
            }
            else
            {
                self->minfadepoint[which] = self->loopstart[which] - self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] + self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which];
            }

            self->loopDuration = self->loopstart[which] - self->maxfadepoint[which];
            break;

        case 3:
            if (self->direction[1 - which] == 0 && init == 0)
            {
                self->direction[which] = 1;
                self->loopstart[which] = (long)((start + dur) * tableSr);
                self->loopend[which] = (long)(start * tableSr);
                self->crossfadedur[which] = (long)((self->loopstart[which] - self->loopend[which]) * xfade * 0.01);

                if (self->crossfadedur[which] < 1)
                    self->crossfadedur[which] = 1;

                self->crossfadescaling[which] = 1.0 / self->crossfadedur[which] * 512.0;

                if (self->appendfade == 1)
                    self->loopend[which] -= self->crossfadedur[which];

                self->minfadepoint[which] = self->loopstart[which] - self->crossfadedur[which];
                self->maxfadepoint[which] = self->loopend[which] + self->crossfadedur[which];
                self->pointerPos[which] = self->loopstart[which];
                self->loopDuration = self->loopstart[which] - self->maxfadepoint[which];
            }
            else
            {
                self->direction[which] = 0;
                self->loopstart[which] = (long)(start * tableSr);
                self->loopend[which] = (long)((start + dur) * tableSr);
                self->crossfadedur[which] = (long)((self->loopend[which] - self->loopstart[which]) * xfade * 0.01);

                if (self->crossfadedur[which] < 1)
                    self->crossfadedur[which] = 1;

                self->crossfadescaling[which] = 1.0 / self->crossfadedur[which] * 512.0;

                if (self->appendfade == 1)
                    self->loopend[which] += self->crossfadedur[which];

                if (init == 1 && self->startfromloop == 0)
                {
                    self->minfadepoint[which] = self->crossfadedur[which];
                    self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                    self->pointerPos[which] = self->loopstart[which] = 0.0;
                }
                else
                {
                    self->minfadepoint[which] = self->loopstart[which] + self->crossfadedur[which];
                    self->maxfadepoint[which] = self->loopend[which] - self->crossfadedur[which];
                    self->pointerPos[which] = self->loopstart[which];
                }

                self->loopDuration = self->maxfadepoint[which] - self->loopstart[which];
            }

            break;
    }

    self->active[which] = 1;
}

static MYFLT
Looper_get_amp(Looper *self, MYFLT pos)
{
    int ipart = (int)pos;
    return self->fader[ipart] + (self->fader[ipart + 1] - self->fader[ipart]) * (pos - ipart);
}

static void
Looper_transform_i(Looper *self)
{
    MYFLT fpart, amp, fr;
    double pit;
    int i, j, k;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);
    double tableSr = TableStream_getSamplingRate((TableStream *)self->table);

    MYFLT pitval = PyFloat_AS_DOUBLE(self->pitch);

    if (pitval < 0.0)
        pitval = 0.0;

    pit = pitval * tableSr / self->sr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->trigsBuffer[i] = 0.0;
    }

    if (self->active[0] == 0 && self->active[1] == 0)
    {
        Looper_reset(self, 0, 0, 1);
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;

        for (j = 0; j < 2; j++)
        {
            if (self->active[j] == 1)
            {
                switch (self->mode[j])
                {
                    case 0:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] < self->minfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j])
                            {
                                fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] += pit;

                        if (self->pointerPos[j] < 0.0)
                        {
                            self->pointerPos[j] = self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] <= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] >= self->loopend[j])
                        {
                            self->active[j] = 0;

                            for (k = 0; k < self->bufsize; k++)
                            {
                                self->time_buffer[k] = 0.0;
                            }

                            PyObject_CallMethod((PyObject *)self, "stop", NULL);
                        }
                        else
                        {
                            self->time_buffer[i] = 1.0;
                        }

                        break;

                    case 1:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] < self->minfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j])
                            {
                                fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] += pit;

                        if (self->pointerPos[j] < 0.0)
                        {
                            self->pointerPos[j] = self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] <= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] > self->maxfadepoint[j] && self->active[1 - j] == 0)
                            Looper_reset(self, i, 1 - j, 0);

                        if (self->pointerPos[j] >= self->loopend[j])
                            self->active[j] = 0;

                        break;

                    case 2:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] > self->minfadepoint[j])
                            {
                                fpart = (self->loopstart[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] < self->maxfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopend[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] -= pit;

                        if (self->pointerPos[j] >= size)
                        {
                            self->pointerPos[j] = size - 1;
                            self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] >= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->loopstart[j] - self->pointerPos[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] < self->maxfadepoint[j] && self->active[1 - j] == 0)
                            Looper_reset(self, i, 1 - j, 0);

                        if (self->pointerPos[j] <= self->loopend[j])
                            self->active[j] = 0;

                        break;

                    case 3:
                        if (self->direction[j] == 0)
                        {
                            if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                            {
                                if (self->pointerPos[j] < self->minfadepoint[j])
                                {
                                    fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else if (self->pointerPos[j] > self->maxfadepoint[j])
                                {
                                    fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else
                                    amp = 1.0;

                                ipart = (T_SIZE_T)self->pointerPos[j];
                                fpart = self->pointerPos[j] - ipart;
                                self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                            }

                            self->pointerPos[j] += pit;

                            if (self->pointerPos[j] < 0.0)
                            {
                                self->pointerPos[j] = self->time_buffer[i] = 0.0;
                            }
                            else if (self->pointerPos[j] <= self->maxfadepoint[j])
                            {
                                self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j] && self->active[1 - j] == 0)
                                Looper_reset(self, i, 1 - j, 0);

                            if (self->pointerPos[j] >= self->loopend[j])
                                self->active[j] = 0;
                        }
                        else
                        {
                            if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                            {
                                if (self->pointerPos[j] > self->minfadepoint[j])
                                {
                                    fpart = (self->loopstart[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else if (self->pointerPos[j] < self->maxfadepoint[j])
                                {
                                    fpart = (self->pointerPos[j] - self->loopend[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else
                                    amp = 1.0;

                                ipart = (T_SIZE_T)self->pointerPos[j];
                                fpart = self->pointerPos[j] - ipart;
                                self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                            }

                            self->pointerPos[j] -= pit;

                            if (self->pointerPos[j] >= size)
                            {
                                self->pointerPos[j] = size - 1;
                                self->time_buffer[i] = 0.0;
                            }
                            else if (self->pointerPos[j] >= self->maxfadepoint[j])
                            {
                                self->time_buffer[i] = (self->loopstart[j] - self->pointerPos[j]) / self->loopDuration;
                            }
                            else if (self->pointerPos[j] < self->maxfadepoint[j] && self->active[1 - j] == 0)
                                Looper_reset(self, i, 1 - j, 0);

                            if (self->pointerPos[j] <= self->loopend[j])
                                self->active[j] = 0;
                        }

                        break;
                } // end of switch
            }
        }
    }

    /* Automatic smoothering of low transposition */
    if (self->autosmooth == 1 && pitval < 1.0)
    {
        if (self->lastpitch != pitval)
        {
            self->lastpitch = pitval;

            if (pitval < 0.001)
                pitval = 0.001;

            fr = pitval * self->sr * 0.45;
            self->c1 = MYEXP(-TWOPI * fr / self->sr);
        }

        for (i = 0; i < self->bufsize; i++)
        {
            self->y1 = self->data[i] + (self->y1 - self->data[i]) * self->c1;
            self->y2 = self->y1 + (self->y2 - self->y1) * self->c1;
            self->data[i] = self->y2;
        }
    }
}

static void
Looper_transform_a(Looper *self)
{
    MYFLT fpart, amp, fr, pitval;
    double pit, srFactor;
    int i, j, k;
    T_SIZE_T ipart;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);
    double tableSr = TableStream_getSamplingRate((TableStream *)self->table);

    MYFLT *pitch = Stream_getData((Stream *)self->pitch_stream);

    srFactor = tableSr / self->sr;

    if (self->active[0] == 0 && self->active[1] == 0)
    {
        Looper_reset(self, 0, 0, 1);
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->trigsBuffer[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;
        pitval = pitch[i];

        if (pitval < 0.0)
            pitval = 0.0;

        pit = pitval * srFactor;

        for (j = 0; j < 2; j++)
        {
            if (self->active[j] == 1)
            {
                switch (self->mode[j])
                {
                    case 0:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] < self->minfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j])
                            {
                                fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] += pit;

                        if (self->pointerPos[j] < 0.0)
                        {
                            self->pointerPos[j] = self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] <= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] >= self->loopend[j])
                        {
                            self->active[j] = 0;

                            for (k = 0; k < self->bufsize; k++)
                            {
                                self->time_buffer[k] = 0.0;
                            }

                            PyObject_CallMethod((PyObject *)self, "stop", NULL);
                        }

                        break;

                    case 1:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] < self->minfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j])
                            {
                                fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] += pit;

                        if (self->pointerPos[j] < 0.0)
                        {
                            self->pointerPos[j] = self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] <= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] > self->maxfadepoint[j] && self->active[1 - j] == 0)
                            Looper_reset(self, i, 1 - j, 0);

                        if (self->pointerPos[j] >= self->loopend[j])
                            self->active[j] = 0;

                        break;

                    case 2:
                        if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                        {
                            if (self->pointerPos[j] > self->minfadepoint[j])
                            {
                                fpart = (self->loopstart[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else if (self->pointerPos[j] < self->maxfadepoint[j])
                            {
                                fpart = (self->pointerPos[j] - self->loopend[j]) * self->crossfadescaling[j];
                                amp = Looper_get_amp(self, fpart);
                            }
                            else
                                amp = 1.0;

                            ipart = (T_SIZE_T)self->pointerPos[j];
                            fpart = self->pointerPos[j] - ipart;
                            self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                        }

                        self->pointerPos[j] -= pit;

                        if (self->pointerPos[j] >= size)
                        {
                            self->pointerPos[j] = size - 1;
                            self->time_buffer[i] = 0.0;
                        }
                        else if (self->pointerPos[j] >= self->maxfadepoint[j])
                        {
                            self->time_buffer[i] = (self->loopstart[j] - self->pointerPos[j]) / self->loopDuration;
                        }
                        else if (self->pointerPos[j] < self->maxfadepoint[j] && self->active[1 - j] == 0)
                            Looper_reset(self, i, 1 - j, 0);

                        if (self->pointerPos[j] <= self->loopend[j])
                            self->active[j] = 0;

                        break;

                    case 3:
                        if (self->direction[j] == 0)
                        {
                            if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                            {
                                if (self->pointerPos[j] < self->minfadepoint[j])
                                {
                                    fpart = (self->pointerPos[j] - self->loopstart[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else if (self->pointerPos[j] > self->maxfadepoint[j])
                                {
                                    fpart = (self->loopend[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else
                                    amp = 1.0;

                                ipart = (T_SIZE_T)self->pointerPos[j];
                                fpart = self->pointerPos[j] - ipart;
                                self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                            }

                            self->pointerPos[j] += pit;

                            if (self->pointerPos[j] < 0.0)
                            {
                                self->pointerPos[j] = self->time_buffer[i] = 0.0;
                            }
                            else if (self->pointerPos[j] <= self->maxfadepoint[j])
                            {
                                self->time_buffer[i] = (self->pointerPos[j] - self->loopstart[j]) / self->loopDuration;
                            }
                            else if (self->pointerPos[j] > self->maxfadepoint[j] && self->active[1 - j] == 0)
                                Looper_reset(self, i, 1 - j, 0);

                            if (self->pointerPos[j] >= self->loopend[j])
                                self->active[j] = 0;
                        }
                        else
                        {
                            if (self->pointerPos[j] >= 0 && self->pointerPos[j] < size)
                            {
                                if (self->pointerPos[j] > self->minfadepoint[j])
                                {
                                    fpart = (self->loopstart[j] - self->pointerPos[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else if (self->pointerPos[j] < self->maxfadepoint[j])
                                {
                                    fpart = (self->pointerPos[j] - self->loopend[j]) * self->crossfadescaling[j];
                                    amp = Looper_get_amp(self, fpart);
                                }
                                else
                                    amp = 1.0;

                                ipart = (T_SIZE_T)self->pointerPos[j];
                                fpart = self->pointerPos[j] - ipart;
                                self->data[i] += (*self->interp_func_ptr)(tablelist, ipart, fpart, size) * amp;
                            }

                            self->pointerPos[j] -= pit;

                            if (self->pointerPos[j] >= size)
                            {
                                self->pointerPos[j] = size - 1;
                                self->time_buffer[i] = 0.0;
                            }
                            else if (self->pointerPos[j] >= self->maxfadepoint[j])
                            {
                                self->time_buffer[i] = (self->loopstart[j] - self->pointerPos[j]) / self->loopDuration;
                            }
                            else if (self->pointerPos[j] < self->maxfadepoint[j] && self->active[1 - j] == 0)
                                Looper_reset(self, i, 1 - j, 0);

                            if (self->pointerPos[j] <= self->loopend[j])
                                self->active[j] = 0;
                        }

                        break;
                } // end of switch
            }
        }
    }

    /* Automatic smoothering of low transposition */
    if (self->autosmooth == 1)
    {
        for (i = 0; i < self->bufsize; i++)
        {
            pitval = pitch[i];

            if (pitval < 0.001)
                pitval = 0.001;

            if (pitval < 1.0)
            {
                if (self->lastpitch != pitval)
                {
                    self->lastpitch = pitval;
                    fr = pitval * self->sr * 0.45;
                    self->c1 = MYEXP(-TWOPI * fr / self->sr);
                }

                self->y1 = self->data[i] + (self->y1 - self->data[i]) * self->c1;
                self->y2 = self->y1 + (self->y2 - self->y1) * self->c1;
                self->data[i] = self->y2;
            }
        }
    }
}

static void Looper_postprocessing_ii(Looper *self) { POST_PROCESSING_II };
static void Looper_postprocessing_ai(Looper *self) { POST_PROCESSING_AI };
static void Looper_postprocessing_ia(Looper *self) { POST_PROCESSING_IA };
static void Looper_postprocessing_aa(Looper *self) { POST_PROCESSING_AA };
static void Looper_postprocessing_ireva(Looper *self) { POST_PROCESSING_IREVA };
static void Looper_postprocessing_areva(Looper *self) { POST_PROCESSING_AREVA };
static void Looper_postprocessing_revai(Looper *self) { POST_PROCESSING_REVAI };
static void Looper_postprocessing_revaa(Looper *self) { POST_PROCESSING_REVAA };
static void Looper_postprocessing_revareva(Looper *self) { POST_PROCESSING_REVAREVA };

static void
Looper_setProcMode(Looper *self)
{
    int procmode, muladdmode;
    procmode = self->modebuffer[2];
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (procmode)
    {
        case 0:
            self->proc_func_ptr = Looper_transform_i;
            break;

        case 1:
            self->proc_func_ptr = Looper_transform_a;
            break;
    }

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = Looper_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = Looper_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = Looper_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = Looper_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = Looper_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = Looper_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = Looper_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = Looper_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = Looper_postprocessing_revareva;
            break;
    }
}

static void
Looper_compute_next_data_frame(Looper *self)
{
    (*self->proc_func_ptr)(self);
    (*self->muladd_func_ptr)(self);
}

static MYFLT *
Looper_getTimeBuffer(Looper *self)
{
    return self->time_buffer;
}

static int
Looper_traverse(Looper *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->pitch);
    Py_VISIT(self->start);
    Py_VISIT(self->dur);
    Py_VISIT(self->xfade);
    return 0;
}

static int
Looper_clear(Looper *self)
{
    pyo_CLEAR
    Py_CLEAR(self->pitch);
    Py_CLEAR(self->start);
    Py_CLEAR(self->dur);
    Py_CLEAR(self->xfade);
    return 0;
}

static void
Looper_dealloc(Looper* self)
{
    pyo_DEALLOC
    PyMem_RawFree(self->trigsBuffer);
    PyMem_RawFree(self->time_buffer);
    Looper_clear(self);
    Py_TYPE(self->trig_stream)->tp_free((PyObject*)self->trig_stream);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Looper_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *tabletmp, *pitchtmp = NULL, *starttmp = NULL, *durtmp = NULL, *xfadetmp = NULL, *multmp = NULL, *addtmp = NULL;
    Looper *self;
    self = (Looper *)type->tp_alloc(type, 0);

    self->pitch = PyFloat_FromDouble(1.0);
    self->start = PyFloat_FromDouble(0.0);
    self->dur = PyFloat_FromDouble(1.0);
    self->xfade = PyFloat_FromDouble(20.0);
    self->lastpitch = -1.0;
    self->autosmooth = 0;
    self->y1 = self->y2 = self->c1 = 0.0;
    self->xfadeshape = 0;
    self->startfromloop = 0;
    self->interp = 2;
    self->init = 1;
    self->appendfade = self->fadeinseconds = 0;
    self->mode[0] = self->mode[1] = self->tmpmode = 1;
    self->direction[0] = self->direction[1] = 0;
    self->pointerPos[0] = self->pointerPos[1] = 0.0;
    self->loopDuration = 0.0;
    self->active[0] = self->active[1] = self->current = 0;
    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;
    self->modebuffer[2] = 0;
    self->modebuffer[3] = 0;
    self->modebuffer[4] = 0;

    INIT_OBJECT_COMMON
    Stream_setFunctionPtr(self->stream, Looper_compute_next_data_frame);
    self->mode_func_ptr = Looper_setProcMode;

    static char *kwlist[] = {"table", "pitch", "start", "dur", "xfade", "mode", "xfadeshape", "startfromloop", "interp", "autosmooth", "mul", "add", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOOiiiiiOO", kwlist, &tabletmp, &pitchtmp, &starttmp, &durtmp, &xfadetmp, &self->tmpmode, &self->xfadeshape, &self->startfromloop, &self->interp, &self->autosmooth, &multmp, &addtmp))
        Py_RETURN_NONE;

    if ( PyObject_HasAttrString((PyObject *)tabletmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"table\" argument of Looper must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");

    if (pitchtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPitch", "O", pitchtmp);
    }

    if (starttmp)
    {
        PyObject_CallMethod((PyObject *)self, "setStart", "O", starttmp);
    }

    if (durtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDur", "O", durtmp);
    }

    if (xfadetmp)
    {
        PyObject_CallMethod((PyObject *)self, "setXfade", "O", xfadetmp);
    }

    if (multmp)
    {
        PyObject_CallMethod((PyObject *)self, "setMul", "O", multmp);
    }

    if (addtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setAdd", "O", addtmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    (*self->mode_func_ptr)(self);

    self->trigsBuffer = (MYFLT *)PyMem_RawRealloc(self->trigsBuffer, self->bufsize * sizeof(MYFLT));
    self->time_buffer = (MYFLT *)PyMem_RawRealloc(self->time_buffer, self->bufsize * sizeof(MYFLT));

    for (i = 0; i < self->bufsize; i++)
    {
        self->trigsBuffer[i] = self->time_buffer[i] = 0.0;
    }

    MAKE_NEW_TRIGGER_STREAM(self->trig_stream, &TriggerStreamType, NULL);
    TriggerStream_setData(self->trig_stream, self->trigsBuffer);

    if (self->tmpmode >= 0 && self->tmpmode < 4)
        self->mode[0] = self->mode[1] = self->tmpmode;
    else
        self->mode[0] = self->mode[1] = self->tmpmode = 1;

    SET_INTERP_POINTER

    return (PyObject *)self;
}

static PyObject * Looper_getServer(Looper* self) { GET_SERVER };
static PyObject * Looper_getStream(Looper* self) { GET_STREAM };
static PyObject * Looper_getTriggerStream(Looper* self) { GET_TRIGGER_STREAM };
static PyObject * Looper_setMul(Looper *self, PyObject *arg) { SET_MUL };
static PyObject * Looper_setAdd(Looper *self, PyObject *arg) { SET_ADD };
static PyObject * Looper_setSub(Looper *self, PyObject *arg) { SET_SUB };
static PyObject * Looper_setDiv(Looper *self, PyObject *arg) { SET_DIV };

static PyObject * Looper_play(Looper *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * Looper_out(Looper *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * Looper_stop(Looper *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * Looper_multiply(Looper *self, PyObject *arg) { MULTIPLY };
static PyObject * Looper_inplace_multiply(Looper *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * Looper_add(Looper *self, PyObject *arg) { ADD };
static PyObject * Looper_inplace_add(Looper *self, PyObject *arg) { INPLACE_ADD };
static PyObject * Looper_sub(Looper *self, PyObject *arg) { SUB };
static PyObject * Looper_inplace_sub(Looper *self, PyObject *arg) { INPLACE_SUB };
static PyObject * Looper_div(Looper *self, PyObject *arg) { DIV };
static PyObject * Looper_inplace_div(Looper *self, PyObject *arg) { INPLACE_DIV };

static PyObject * Looper_setPitch(Looper *self, PyObject *arg) { SET_PARAM(self->pitch, self->pitch_stream, 2); }
static PyObject * Looper_setStart(Looper *self, PyObject *arg) { SET_PARAM(self->start, self->start_stream, 3); }
static PyObject * Looper_setDur(Looper *self, PyObject *arg) { SET_PARAM(self->dur, self->dur_stream, 4); }
static PyObject * Looper_setXfade(Looper *self, PyObject *arg)  { SET_PARAM(self->xfade, self->xfade_stream, 5); }

static PyObject *
Looper_getTable(Looper* self)
{
    Py_INCREF(self->table);
    return self->table;
};

static PyObject *
Looper_setTable(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->table);
    self->table = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyObject *
Looper_setStartFromLoop(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        self->startfromloop = PyLong_AsLong(arg);
    }

    Py_RETURN_NONE;
}

static PyObject *
Looper_setXfadeShape(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        self->xfadeshape = PyLong_AsLong(arg);
    }

    Py_RETURN_NONE;
}

static PyObject *
Looper_setMode(Looper *self, PyObject *arg)
{
    int tmp;

    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        tmp = PyLong_AsLong(arg);

        if (tmp >= 0 && tmp < 4)
            self->tmpmode = tmp;
    }

    Py_RETURN_NONE;
}

static PyObject *
Looper_setInterp(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyNumber_Check(arg))
    {
        self->interp = PyLong_AsLong(PyNumber_Long(arg));
    }

    SET_INTERP_POINTER

    Py_RETURN_NONE;
}

static PyObject *
Looper_setAutoSmooth(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        self->autosmooth = PyLong_AsLong(arg);
    }

    Py_RETURN_NONE;
}

static PyObject *
Looper_on_reset(Looper *self)
{
    self->pointerPos[0] = self->pointerPos[1] = 0.0;
    self->active[0] = self->active[1] = 0;
    Py_RETURN_NONE;
};

static PyObject *
Looper_loopnow(Looper *self)
{
    Looper_endloop(self);
    Py_RETURN_NONE;
};

static PyObject *
Looper_appendFadeTime(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        self->appendfade = PyLong_AsLong(arg) == 0 ? 0 : 1;
    }

    Py_RETURN_NONE;
}

static PyObject *
Looper_fadeInSeconds(Looper *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    if (PyLong_Check(arg))
    {
        self->fadeinseconds = PyLong_AsLong(arg) == 0 ? 0 : 1;
    }

    Py_RETURN_NONE;
}

static PyMemberDef Looper_members[] =
{
    {"server", T_OBJECT_EX, offsetof(Looper, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(Looper, stream), 0, "Stream object."},
    {"trig_stream", T_OBJECT_EX, offsetof(Looper, trig_stream), 0, "Trigger Stream object."},
    {"table", T_OBJECT_EX, offsetof(Looper, table), 0, "Sound table."},
    {"pitch", T_OBJECT_EX, offsetof(Looper, pitch), 0, "Speed of the reading pointer."},
    {"start", T_OBJECT_EX, offsetof(Looper, start), 0, "Position in the sound table."},
    {"dur", T_OBJECT_EX, offsetof(Looper, dur), 0, "Duration of each grains."},
    {"xfade", T_OBJECT_EX, offsetof(Looper, xfade), 0, "Crossfade duration in percent."},
    {"mul", T_OBJECT_EX, offsetof(Looper, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(Looper, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef Looper_methods[] =
{
    {"getTable", (PyCFunction)Looper_getTable, METH_NOARGS, "Returns sound table object."},
    {"setTable", (PyCFunction)Looper_setTable, METH_O, "Sets sound table."},
    {"getServer", (PyCFunction)Looper_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)Looper_getStream, METH_NOARGS, "Returns stream object."},
    {"_getTriggerStream", (PyCFunction)Looper_getTriggerStream, METH_NOARGS, "Returns trigger stream object."},
    {"play", (PyCFunction)Looper_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)Looper_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)Looper_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setPitch", (PyCFunction)Looper_setPitch, METH_O, "Sets global pitch factor."},
    {"setStart", (PyCFunction)Looper_setStart, METH_O, "Sets position in the sound table."},
    {"setDur", (PyCFunction)Looper_setDur, METH_O, "Sets the grain duration."},
    {"setXfade", (PyCFunction)Looper_setXfade, METH_O, "Sets crossfade length in percent."},
    {"setXfadeShape", (PyCFunction)Looper_setXfadeShape, METH_O, "Sets crossfade shape."},
    {"setMode", (PyCFunction)Looper_setMode, METH_O, "Sets looping mode (0 = no loop, 1 = forward, 2 = backward, 3 = back-and-forth)."},
    {"setStartFromLoop", (PyCFunction)Looper_setStartFromLoop, METH_O, "Sets init pointer position."},
    {"setInterp", (PyCFunction)Looper_setInterp, METH_O, "Sets oscillator interpolation mode."},
    {"setAutoSmooth", (PyCFunction)Looper_setAutoSmooth, METH_O, "Activate lowpass filter for transposition below 1."},
    {"appendFadeTime", (PyCFunction)Looper_appendFadeTime, METH_O, "If positive, fade time is added to loop duration."},
    {"fadeInSeconds", (PyCFunction)Looper_fadeInSeconds, METH_O, "If positive, fade time is set in seconds instead of percentage."},
    {"reset", (PyCFunction)Looper_on_reset, METH_NOARGS, "Resets internal counters."},
    {"loopnow", (PyCFunction)Looper_loopnow, METH_NOARGS, "Satrts a new loop immediately."},
    {"setMul", (PyCFunction)Looper_setMul, METH_O, "Sets granulator mul factor."},
    {"setAdd", (PyCFunction)Looper_setAdd, METH_O, "Sets granulator add factor."},
    {"setSub", (PyCFunction)Looper_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)Looper_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods Looper_as_number =
{
    (binaryfunc)Looper_add,                      /*nb_add*/
    (binaryfunc)Looper_sub,                 /*nb_subtract*/
    (binaryfunc)Looper_multiply,                 /*nb_multiply*/
    0,                /*nb_remainder*/
    0,                   /*nb_divmod*/
    0,                   /*nb_power*/
    0,                  /*nb_neg*/
    0,                /*nb_pos*/
    0,                  /*(unaryfunc)array_abs,*/
    0,                    /*nb_nonzero*/
    0,                    /*nb_invert*/
    0,               /*nb_lshift*/
    0,              /*nb_rshift*/
    0,              /*nb_and*/
    0,              /*nb_xor*/
    0,               /*nb_or*/
    0,                       /*nb_int*/
    0,                      /*nb_long*/
    0,                     /*nb_float*/
    (binaryfunc)Looper_inplace_add,              /*inplace_add*/
    (binaryfunc)Looper_inplace_sub,         /*inplace_subtract*/
    (binaryfunc)Looper_inplace_multiply,         /*inplace_multiply*/
    0,        /*inplace_remainder*/
    0,           /*inplace_power*/
    0,       /*inplace_lshift*/
    0,      /*inplace_rshift*/
    0,      /*inplace_and*/
    0,      /*inplace_xor*/
    0,       /*inplace_or*/
    0,             /*nb_floor_divide*/
    (binaryfunc)Looper_div,                       /*nb_true_divide*/
    0,     /*nb_inplace_floor_divide*/
    (binaryfunc)Looper_inplace_div,                       /*nb_inplace_true_divide*/
    0,                     /* nb_index */
};

PyTypeObject LooperType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.Looper_base",         /*tp_name*/
    sizeof(Looper),         /*tp_basicpitch*/
    0,                         /*tp_itempitch*/
    (destructor)Looper_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &Looper_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
    "Looper objects. Sound looper with crossfade.",           /* tp_doc */
    (traverseproc)Looper_traverse,   /* tp_traverse */
    (inquiry)Looper_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Looper_methods,             /* tp_methods */
    Looper_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    Looper_new,                 /* tp_new */
};

typedef struct
{
    pyo_audio_HEAD
    Looper *mainPlayer;
    int modebuffer[2];
} LooperTimeStream;

static void LooperTimeStream_postprocessing_ii(LooperTimeStream *self) { POST_PROCESSING_II };
static void LooperTimeStream_postprocessing_ai(LooperTimeStream *self) { POST_PROCESSING_AI };
static void LooperTimeStream_postprocessing_ia(LooperTimeStream *self) { POST_PROCESSING_IA };
static void LooperTimeStream_postprocessing_aa(LooperTimeStream *self) { POST_PROCESSING_AA };
static void LooperTimeStream_postprocessing_ireva(LooperTimeStream *self) { POST_PROCESSING_IREVA };
static void LooperTimeStream_postprocessing_areva(LooperTimeStream *self) { POST_PROCESSING_AREVA };
static void LooperTimeStream_postprocessing_revai(LooperTimeStream *self) { POST_PROCESSING_REVAI };
static void LooperTimeStream_postprocessing_revaa(LooperTimeStream *self) { POST_PROCESSING_REVAA };
static void LooperTimeStream_postprocessing_revareva(LooperTimeStream *self) { POST_PROCESSING_REVAREVA };

static void
LooperTimeStream_setProcMode(LooperTimeStream *self)
{
    int muladdmode;
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = LooperTimeStream_postprocessing_revareva;
            break;
    }
}

static void
LooperTimeStream_compute_next_data_frame(LooperTimeStream *self)
{
    int i;
    MYFLT *tmp;
    tmp = Looper_getTimeBuffer((Looper *)self->mainPlayer);

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = tmp[i];
    }

    (*self->muladd_func_ptr)(self);
}

static int
LooperTimeStream_traverse(LooperTimeStream *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->mainPlayer);
    return 0;
}

static int
LooperTimeStream_clear(LooperTimeStream *self)
{
    pyo_CLEAR
    Py_CLEAR(self->mainPlayer);
    return 0;
}

static void
LooperTimeStream_dealloc(LooperTimeStream* self)
{
    pyo_DEALLOC
    LooperTimeStream_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
LooperTimeStream_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *maintmp = NULL;
    LooperTimeStream *self;
    self = (LooperTimeStream *)type->tp_alloc(type, 0);

    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;

    INIT_OBJECT_COMMON
    Stream_setFunctionPtr(self->stream, LooperTimeStream_compute_next_data_frame);
    self->mode_func_ptr = LooperTimeStream_setProcMode;

    static char *kwlist[] = {"mainPlayer", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &maintmp))
        Py_RETURN_NONE;

    self->mainPlayer = (Looper *)maintmp;
    Py_INCREF(self->mainPlayer);

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * LooperTimeStream_getServer(LooperTimeStream* self) { GET_SERVER };
static PyObject * LooperTimeStream_getStream(LooperTimeStream* self) { GET_STREAM };
static PyObject * LooperTimeStream_setMul(LooperTimeStream *self, PyObject *arg) { SET_MUL };
static PyObject * LooperTimeStream_setAdd(LooperTimeStream *self, PyObject *arg) { SET_ADD };
static PyObject * LooperTimeStream_setSub(LooperTimeStream *self, PyObject *arg) { SET_SUB };
static PyObject * LooperTimeStream_setDiv(LooperTimeStream *self, PyObject *arg) { SET_DIV };

static PyObject * LooperTimeStream_play(LooperTimeStream *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * LooperTimeStream_out(LooperTimeStream *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * LooperTimeStream_stop(LooperTimeStream *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * LooperTimeStream_multiply(LooperTimeStream *self, PyObject *arg) { MULTIPLY };
static PyObject * LooperTimeStream_inplace_multiply(LooperTimeStream *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * LooperTimeStream_add(LooperTimeStream *self, PyObject *arg) { ADD };
static PyObject * LooperTimeStream_inplace_add(LooperTimeStream *self, PyObject *arg) { INPLACE_ADD };
static PyObject * LooperTimeStream_sub(LooperTimeStream *self, PyObject *arg) { SUB };
static PyObject * LooperTimeStream_inplace_sub(LooperTimeStream *self, PyObject *arg) { INPLACE_SUB };
static PyObject * LooperTimeStream_div(LooperTimeStream *self, PyObject *arg) { DIV };
static PyObject * LooperTimeStream_inplace_div(LooperTimeStream *self, PyObject *arg) { INPLACE_DIV };

static PyMemberDef LooperTimeStream_members[] =
{
    {"server", T_OBJECT_EX, offsetof(LooperTimeStream, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(LooperTimeStream, stream), 0, "Stream object."},
    {"mul", T_OBJECT_EX, offsetof(LooperTimeStream, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(LooperTimeStream, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef LooperTimeStream_methods[] =
{
    {"getServer", (PyCFunction)LooperTimeStream_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)LooperTimeStream_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)LooperTimeStream_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)LooperTimeStream_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)LooperTimeStream_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setMul", (PyCFunction)LooperTimeStream_setMul, METH_O, "Sets oscillator mul factor."},
    {"setAdd", (PyCFunction)LooperTimeStream_setAdd, METH_O, "Sets oscillator add factor."},
    {"setSub", (PyCFunction)LooperTimeStream_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)LooperTimeStream_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods LooperTimeStream_as_number =
{
    (binaryfunc)LooperTimeStream_add,                         /*nb_add*/
    (binaryfunc)LooperTimeStream_sub,                         /*nb_subtract*/
    (binaryfunc)LooperTimeStream_multiply,                    /*nb_multiply*/
    0,                                              /*nb_remainder*/
    0,                                              /*nb_divmod*/
    0,                                              /*nb_power*/
    0,                                              /*nb_neg*/
    0,                                              /*nb_pos*/
    0,                                              /*(unaryfunc)array_abs,*/
    0,                                              /*nb_nonzero*/
    0,                                              /*nb_invert*/
    0,                                              /*nb_lshift*/
    0,                                              /*nb_rshift*/
    0,                                              /*nb_and*/
    0,                                              /*nb_xor*/
    0,                                              /*nb_or*/
    0,                                              /*nb_int*/
    0,                                              /*nb_long*/
    0,                                              /*nb_float*/
    (binaryfunc)LooperTimeStream_inplace_add,                 /*inplace_add*/
    (binaryfunc)LooperTimeStream_inplace_sub,                 /*inplace_subtract*/
    (binaryfunc)LooperTimeStream_inplace_multiply,            /*inplace_multiply*/
    0,                                              /*inplace_remainder*/
    0,                                              /*inplace_power*/
    0,                                              /*inplace_lshift*/
    0,                                              /*inplace_rshift*/
    0,                                              /*inplace_and*/
    0,                                              /*inplace_xor*/
    0,                                              /*inplace_or*/
    0,                                              /*nb_floor_divide*/
    (binaryfunc)LooperTimeStream_div,                       /*nb_true_divide*/
    0,                                              /*nb_inplace_floor_divide*/
    (binaryfunc)LooperTimeStream_inplace_div,                       /*nb_inplace_true_divide*/
    0,                                              /* nb_index */
};

PyTypeObject LooperTimeStreamType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.LooperTimeStream_base",         /*tp_name*/
    sizeof(LooperTimeStream),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)LooperTimeStream_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &LooperTimeStream_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,  /*tp_flags*/
    "LooperTimeStream objects. Returns the current recording time, in samples, of a Looper object.",           /* tp_doc */
    (traverseproc)LooperTimeStream_traverse,   /* tp_traverse */
    (inquiry)LooperTimeStream_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    LooperTimeStream_methods,             /* tp_methods */
    LooperTimeStream_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    LooperTimeStream_new,                 /* tp_new */
};

static const MYFLT Granule_MAX_GRAINS = 4096;
typedef struct
{
    pyo_audio_HEAD
    PyObject *table;
    PyObject *env;
    PyObject *dens;
    Stream *dens_stream;
    PyObject *pitch;
    Stream *pitch_stream;
    PyObject *pos;
    Stream *pos_stream;
    PyObject *dur;
    Stream *dur_stream;
    MYFLT *gpos;
    MYFLT *glen;
    MYFLT *inc;
    MYFLT *phase;
    int *flags;
    int num;
    int sync;
    double timer;
    MYFLT oneOnSr;
    MYFLT srOnRandMax;
    int modebuffer[6];
} Granule;

static void
Granule_transform_i(Granule *self)
{
    MYFLT dens, inc, index, amp, phase;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    dens = PyFloat_AS_DOUBLE(self->dens);

    if (dens < 0.0)
        dens = 0.0;

    inc = dens * self->oneOnSr;

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;

        if (self->sync == 1)
        {
            /* synchronous */
            self->timer += inc;

            if (self->timer >= 1.0)
            {
                self->timer -= 1.0;
                flag = 1;
            }
        }
        else
        {
            /* asynchronous */
            if ((pyorand() * self->srOnRandMax) < dens)
                flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < Granule_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[3] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[4] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[5] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                self->data[i] += (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
Granule_transform_a(Granule *self)
{
    MYFLT index, amp, phase;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *density = Stream_getData((Stream *)self->dens_stream);

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = 0.0;

        if (self->sync == 1)
        {
            /* synchronous */
            self->timer += density[i] * self->oneOnSr;

            if (self->timer >= 1.0)
            {
                self->timer -= 1.0;
                flag = 1;
            }
        }
        else
        {
            /* asynchronous */
            if ((pyorand() * self->srOnRandMax) < density[i])
                flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < Granule_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[3] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[4] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[5] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                // compute envelope
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                // compute sampling
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                self->data[i] += (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void Granule_postprocessing_ii(Granule *self) { POST_PROCESSING_II };
static void Granule_postprocessing_ai(Granule *self) { POST_PROCESSING_AI };
static void Granule_postprocessing_ia(Granule *self) { POST_PROCESSING_IA };
static void Granule_postprocessing_aa(Granule *self) { POST_PROCESSING_AA };
static void Granule_postprocessing_ireva(Granule *self) { POST_PROCESSING_IREVA };
static void Granule_postprocessing_areva(Granule *self) { POST_PROCESSING_AREVA };
static void Granule_postprocessing_revai(Granule *self) { POST_PROCESSING_REVAI };
static void Granule_postprocessing_revaa(Granule *self) { POST_PROCESSING_REVAA };
static void Granule_postprocessing_revareva(Granule *self) { POST_PROCESSING_REVAREVA };

static void
Granule_setProcMode(Granule *self)
{
    int procmode, muladdmode;
    procmode = self->modebuffer[2];
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (procmode)
    {
        case 0:
            self->proc_func_ptr = Granule_transform_i;
            break;

        case 1:
            self->proc_func_ptr = Granule_transform_a;
            break;
    }

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = Granule_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = Granule_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = Granule_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = Granule_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = Granule_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = Granule_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = Granule_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = Granule_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = Granule_postprocessing_revareva;
            break;
    }
}

static void
Granule_compute_next_data_frame(Granule *self)
{
    (*self->proc_func_ptr)(self);
    (*self->muladd_func_ptr)(self);
}

static int
Granule_traverse(Granule *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->dens);
    Py_VISIT(self->pitch);
    Py_VISIT(self->pos);
    Py_VISIT(self->dur);
    return 0;
}

static int
Granule_clear(Granule *self)
{
    pyo_CLEAR
    Py_CLEAR(self->dens);
    Py_CLEAR(self->pitch);
    Py_CLEAR(self->pos);
    Py_CLEAR(self->dur);
    return 0;
}

static void
Granule_dealloc(Granule* self)
{
    pyo_DEALLOC
    PyMem_RawFree(self->gpos);
    PyMem_RawFree(self->glen);
    PyMem_RawFree(self->inc);
    PyMem_RawFree(self->flags);
    PyMem_RawFree(self->phase);
    Granule_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Granule_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *tabletmp, *envtmp, *denstmp = NULL, *pitchtmp = NULL, *postmp = NULL, *durtmp = NULL, *multmp = NULL, *addtmp = NULL;
    Granule *self;
    self = (Granule *)type->tp_alloc(type, 0);

    self->dens = PyFloat_FromDouble(50);
    self->pitch = PyFloat_FromDouble(1);
    self->pos = PyFloat_FromDouble(0.0);
    self->dur = PyFloat_FromDouble(0.1);
    self->timer = 1.0;
    self->num = 0;
    self->sync = 1;
    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;
    self->modebuffer[2] = 0;
    self->modebuffer[3] = 0;
    self->modebuffer[4] = 0;
    self->modebuffer[5] = 0;

    INIT_OBJECT_COMMON

    self->oneOnSr = 1.0 / self->sr;
    self->srOnRandMax = self->sr / (MYFLT)PYO_RAND_MAX;

    Stream_setFunctionPtr(self->stream, Granule_compute_next_data_frame);
    self->mode_func_ptr = Granule_setProcMode;

    static char *kwlist[] = {"table", "env", "dens", "pitch", "pos", "dur", "mul", "add", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OOOOOO", kwlist, &tabletmp, &envtmp, &denstmp, &pitchtmp, &postmp, &durtmp, &multmp, &addtmp))
        Py_RETURN_NONE;

    if ( PyObject_HasAttrString((PyObject *)tabletmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"table\" argument of Granule must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");

    if ( PyObject_HasAttrString((PyObject *)envtmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"env\" argument of Granule must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->env = PyObject_CallMethod((PyObject *)envtmp, "getTableStream", "");

    if (denstmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDens", "O", denstmp);
    }

    if (pitchtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPitch", "O", pitchtmp);
    }

    if (postmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPos", "O", postmp);
    }

    if (durtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDur", "O", durtmp);
    }

    if (multmp)
    {
        PyObject_CallMethod((PyObject *)self, "setMul", "O", multmp);
    }

    if (addtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setAdd", "O", addtmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    self->gpos = (MYFLT *)PyMem_RawRealloc(self->gpos, Granule_MAX_GRAINS * sizeof(MYFLT));
    self->glen = (MYFLT *)PyMem_RawRealloc(self->glen, Granule_MAX_GRAINS * sizeof(MYFLT));
    self->inc = (MYFLT *)PyMem_RawRealloc(self->inc, Granule_MAX_GRAINS * sizeof(MYFLT));
    self->phase = (MYFLT *)PyMem_RawRealloc(self->phase, Granule_MAX_GRAINS * sizeof(MYFLT));
    self->flags = (int *)PyMem_RawRealloc(self->flags, Granule_MAX_GRAINS * sizeof(int));

    for (i = 0; i < Granule_MAX_GRAINS; i++)
    {
        self->gpos[i] = self->glen[i] = self->inc[i] = self->phase[i] = 0.0;
        self->flags[i] = 0;
    }

    Server_generateSeed((Server *)self->server, GRANULE_ID);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * Granule_getServer(Granule* self) { GET_SERVER };
static PyObject * Granule_getStream(Granule* self) { GET_STREAM };
static PyObject * Granule_setMul(Granule *self, PyObject *arg) { SET_MUL };
static PyObject * Granule_setAdd(Granule *self, PyObject *arg) { SET_ADD };
static PyObject * Granule_setSub(Granule *self, PyObject *arg) { SET_SUB };
static PyObject * Granule_setDiv(Granule *self, PyObject *arg) { SET_DIV };

static PyObject * Granule_play(Granule *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * Granule_out(Granule *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * Granule_stop(Granule *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * Granule_multiply(Granule *self, PyObject *arg) { MULTIPLY };
static PyObject * Granule_inplace_multiply(Granule *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * Granule_add(Granule *self, PyObject *arg) { ADD };
static PyObject * Granule_inplace_add(Granule *self, PyObject *arg) { INPLACE_ADD };
static PyObject * Granule_sub(Granule *self, PyObject *arg) { SUB };
static PyObject * Granule_inplace_sub(Granule *self, PyObject *arg) { INPLACE_SUB };
static PyObject * Granule_div(Granule *self, PyObject *arg) { DIV };
static PyObject * Granule_inplace_div(Granule *self, PyObject *arg) { INPLACE_DIV };

static PyObject * Granule_setDens(Granule *self, PyObject *arg) { SET_PARAM(self->dens, self->dens_stream, 2); }
static PyObject * Granule_setPitch(Granule *self, PyObject *arg) { SET_PARAM(self->pitch, self->pitch_stream, 3); }
static PyObject * Granule_setPos(Granule *self, PyObject *arg) { SET_PARAM(self->pos, self->pos_stream, 4); }
static PyObject * Granule_setDur(Granule *self, PyObject *arg) { SET_PARAM(self->dur, self->dur_stream, 5); }

static PyObject *
Granule_getTable(Granule* self)
{
    Py_INCREF(self->table);
    return self->table;
};

static PyObject *
Granule_setTable(Granule *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->table);
    self->table = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyObject *
Granule_getEnv(Granule* self)
{
    Py_INCREF(self->env);
    return self->env;
};

static PyObject *
Granule_setEnv(Granule *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->env);
    self->env = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyObject *
Granule_setSync(Granule *self, PyObject *arg)
{
    if (PyLong_Check(arg))
    {
        self->sync = PyLong_AsLong(arg);

        if (self->sync <= 0)
            self->sync = 0;
        else
            self->sync = 1;
    }

    Py_RETURN_NONE;
}

static PyMemberDef Granule_members[] =
{
    {"server", T_OBJECT_EX, offsetof(Granule, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(Granule, stream), 0, "Stream object."},
    {"table", T_OBJECT_EX, offsetof(Granule, table), 0, "Sound table."},
    {"env", T_OBJECT_EX, offsetof(Granule, env), 0, "Envelope table."},
    {"dens", T_OBJECT_EX, offsetof(Granule, dens), 0, "Density of grains per second."},
    {"pitch", T_OBJECT_EX, offsetof(Granule, pitch), 0, "Speed of the reading pointer."},
    {"pos", T_OBJECT_EX, offsetof(Granule, pos), 0, "Position in the sound table."},
    {"dur", T_OBJECT_EX, offsetof(Granule, dur), 0, "Duration of each grains."},
    {"mul", T_OBJECT_EX, offsetof(Granule, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(Granule, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef Granule_methods[] =
{
    {"getTable", (PyCFunction)Granule_getTable, METH_NOARGS, "Returns sound table object."},
    {"setTable", (PyCFunction)Granule_setTable, METH_O, "Sets sound table."},
    {"getEnv", (PyCFunction)Granule_getEnv, METH_NOARGS, "Returns envelope table object."},
    {"setEnv", (PyCFunction)Granule_setEnv, METH_O, "Sets envelope table."},
    {"getServer", (PyCFunction)Granule_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)Granule_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)Granule_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)Granule_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)Granule_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setDens", (PyCFunction)Granule_setDens, METH_O, "Sets the density of grains per second."},
    {"setPitch", (PyCFunction)Granule_setPitch, METH_O, "Sets global pitch factor."},
    {"setPos", (PyCFunction)Granule_setPos, METH_O, "Sets position in the sound table."},
    {"setDur", (PyCFunction)Granule_setDur, METH_O, "Sets the grain duration."},
    {"setSync", (PyCFunction)Granule_setSync, METH_O, "Sets the granulator mode: synchronous or asynchronous."},
    {"setMul", (PyCFunction)Granule_setMul, METH_O, "Sets Granule mul factor."},
    {"setAdd", (PyCFunction)Granule_setAdd, METH_O, "Sets Granule add factor."},
    {"setSub", (PyCFunction)Granule_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)Granule_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods Granule_as_number =
{
    (binaryfunc)Granule_add,                      /*nb_add*/
    (binaryfunc)Granule_sub,                 /*nb_subtract*/
    (binaryfunc)Granule_multiply,                 /*nb_multiply*/
    0,                /*nb_remainder*/
    0,                   /*nb_divmod*/
    0,                   /*nb_power*/
    0,                  /*nb_neg*/
    0,                /*nb_pos*/
    0,                  /*(unaryfunc)array_abs,*/
    0,                    /*nb_nonzero*/
    0,                    /*nb_invert*/
    0,               /*nb_lshift*/
    0,              /*nb_rshift*/
    0,              /*nb_and*/
    0,              /*nb_xor*/
    0,               /*nb_or*/
    0,                       /*nb_int*/
    0,                      /*nb_long*/
    0,                     /*nb_float*/
    (binaryfunc)Granule_inplace_add,              /*inplace_add*/
    (binaryfunc)Granule_inplace_sub,         /*inplace_subtract*/
    (binaryfunc)Granule_inplace_multiply,         /*inplace_multiply*/
    0,        /*inplace_remainder*/
    0,           /*inplace_power*/
    0,       /*inplace_lshift*/
    0,      /*inplace_rshift*/
    0,      /*inplace_and*/
    0,      /*inplace_xor*/
    0,       /*inplace_or*/
    0,             /*nb_floor_divide*/
    (binaryfunc)Granule_div,                       /*nb_true_divide*/
    0,     /*nb_inplace_floor_divide*/
    (binaryfunc)Granule_inplace_div,                       /*nb_inplace_true_divide*/
    0,                     /* nb_index */
};

PyTypeObject GranuleType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.Granule_base",         /*tp_name*/
    sizeof(Granule),         /*tp_basicpitch*/
    0,                         /*tp_itempitch*/
    (destructor)Granule_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &Granule_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
    "Granule objects. Accumulation of multiples grains of sound.",           /* tp_doc */
    (traverseproc)Granule_traverse,   /* tp_traverse */
    (inquiry)Granule_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Granule_methods,             /* tp_methods */
    Granule_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    Granule_new,                 /* tp_new */
};

static const MYFLT MAINPARTICLE_MAX_GRAINS = 4096;
typedef struct
{
    pyo_audio_HEAD
    PyObject *table;
    PyObject *env;
    PyObject *dens;
    Stream *dens_stream;
    PyObject *pitch;
    Stream *pitch_stream;
    PyObject *pos;
    Stream *pos_stream;
    PyObject *dur;
    Stream *dur_stream;
    PyObject *dev;
    Stream *dev_stream;
    PyObject *pan;
    Stream *pan_stream;
    MYFLT *gpos;
    MYFLT *glen;
    MYFLT *inc;
    MYFLT *phase;
    MYFLT *amp1;
    MYFLT *amp2;
    int *flags;
    int *k1;
    int *k2;
    int num;
    int chnls;
    double timer;
    double devFactor;
    double srScale;
    MYFLT oneOnSr;
    MYFLT srOnRandMax;
    MYFLT *buffer_streams;
    int modebuffer[6];
} MainParticle;

static void
MainParticle_transform_mono_i(MainParticle *self)
{
    MYFLT dens, inc, index, amp, phase, val;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    dens = PyFloat_AS_DOUBLE(self->dens);

    if (dens < 0.0)
        dens = 0.0;

    inc = dens * self->oneOnSr * self->devFactor;

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->timer += inc;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;
                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                self->buffer_streams[i] += val;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle_transform_mono_a(MainParticle *self)
{
    MYFLT dens, index, amp, phase, val;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *density = Stream_getData((Stream *)self->dens_stream);

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        if (density[i] < 0.0)
            dens = 0.0;
        else
            dens = density[i];

        self->timer += dens * self->oneOnSr * self->devFactor;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;
                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                self->buffer_streams[i] += val;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle_transform_i(MainParticle *self)
{
    MYFLT dens, inc, index, amp, phase, val, min = 0;
    int i, j, l, l1, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, pan = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    dens = PyFloat_AS_DOUBLE(self->dens);

    if (dens < 0.0)
        dens = 0.0;

    inc = dens * self->oneOnSr * self->devFactor;

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->timer += inc;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[5] == 0)
                        pan = PyFloat_AS_DOUBLE(self->pan);
                    else
                        pan = Stream_getData((Stream *)self->pan_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (pan < 0.0)
                        pan = 0.0;
                    else if (pan > 1.0)
                        pan = 1.0;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (self->chnls == 2)
                    {
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                    }
                    else
                    {
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                        min = 0;
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;

                        for (l = self->chnls; l > 0; l--)
                        {
                            l1 = l - 1;
                            min = l1 / (MYFLT)self->chnls;

                            if (pan > min)
                            {
                                self->k1[j] = l1 * self->bufsize;

                                if (l == self->chnls)
                                    self->k2[j] = 0;
                                else
                                    self->k2[j] = l * self->bufsize;

                                break;
                            }
                        }
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                self->buffer_streams[i + self->k1[j]] += val * self->amp1[j];
                self->buffer_streams[i + self->k2[j]] += val * self->amp2[j];
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle_transform_a(MainParticle *self)
{
    MYFLT dens, index, amp, phase, val, min = 0;
    int i, j, l, l1, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, pan = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *density = Stream_getData((Stream *)self->dens_stream);

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        if (density[i] < 0.0)
            dens = 0.0;
        else
            dens = density[i];

        self->timer += dens * self->oneOnSr * self->devFactor;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[5] == 0)
                        pan = PyFloat_AS_DOUBLE(self->pan);
                    else
                        pan = Stream_getData((Stream *)self->pan_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (pan < 0.0)
                        pan = 0.0;
                    else if (pan > 1.0)
                        pan = 1.0;

                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (self->chnls == 2)
                    {
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                    }
                    else
                    {
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                        min = 0;
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;

                        for (l = self->chnls; l > 0; l--)
                        {
                            l1 = l - 1;
                            min = l1 / (MYFLT)self->chnls;

                            if (pan > min)
                            {
                                self->k1[j] = l1 * self->bufsize;

                                if (l == self->chnls)
                                    self->k2[j] = 0;
                                else
                                    self->k2[j] = l * self->bufsize;

                                break;
                            }
                        }
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart)) * amp;
                self->buffer_streams[i + self->k1[j]] += val * self->amp1[j];
                self->buffer_streams[i + self->k2[j]] += val * self->amp2[j];
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle_setProcMode(MainParticle *self)
{
    int procmode = self->modebuffer[0];

    switch (procmode)
    {
        case 0:
            if (self->chnls == 1)
                self->proc_func_ptr = MainParticle_transform_mono_i;
            else
                self->proc_func_ptr = MainParticle_transform_i;

            break;

        case 1:
            if (self->chnls == 1)
                self->proc_func_ptr = MainParticle_transform_mono_a;
            else
                self->proc_func_ptr = MainParticle_transform_a;

            break;
    }
}

static void
MainParticle_compute_next_data_frame(MainParticle *self)
{
    (*self->proc_func_ptr)(self);
}

static int
MainParticle_traverse(MainParticle *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->dens);
    Py_VISIT(self->pitch);
    Py_VISIT(self->pos);
    Py_VISIT(self->dur);
    Py_VISIT(self->dev);
    Py_VISIT(self->pan);
    return 0;
}

static int
MainParticle_clear(MainParticle *self)
{
    pyo_CLEAR
    Py_CLEAR(self->dens);
    Py_CLEAR(self->pitch);
    Py_CLEAR(self->pos);
    Py_CLEAR(self->dur);
    Py_CLEAR(self->dev);
    Py_CLEAR(self->pan);
    return 0;
}

static void
MainParticle_dealloc(MainParticle* self)
{
    pyo_DEALLOC
    PyMem_RawFree(self->gpos);
    PyMem_RawFree(self->glen);
    PyMem_RawFree(self->inc);
    PyMem_RawFree(self->flags);
    PyMem_RawFree(self->k1);
    PyMem_RawFree(self->k2);
    PyMem_RawFree(self->phase);
    PyMem_RawFree(self->amp1);
    PyMem_RawFree(self->amp2);
    PyMem_RawFree(self->buffer_streams);
    MainParticle_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

MYFLT *
MainParticle_getSamplesBuffer(MainParticle *self)
{
    return (MYFLT *)self->buffer_streams;
}

static PyObject *
MainParticle_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *tabletmp, *envtmp, *denstmp = NULL, *pitchtmp = NULL, *postmp = NULL, *durtmp = NULL, *devtmp = NULL, *pantmp = NULL;
    MainParticle *self;
    self = (MainParticle *)type->tp_alloc(type, 0);

    self->dens = PyFloat_FromDouble(50);
    self->pitch = PyFloat_FromDouble(1);
    self->pos = PyFloat_FromDouble(0.0);
    self->dur = PyFloat_FromDouble(0.1);
    self->dev = PyFloat_FromDouble(0);
    self->pan = PyFloat_FromDouble(0.5);
    self->timer = self->devFactor = 1.0;
    self->srScale = 1.0;
    self->num = 0;
    self->chnls = 1;
    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;
    self->modebuffer[2] = 0;
    self->modebuffer[3] = 0;
    self->modebuffer[4] = 0;
    self->modebuffer[5] = 0;

    INIT_OBJECT_COMMON

    self->oneOnSr = 1.0 / self->sr;
    self->srOnRandMax = self->sr / (MYFLT)PYO_RAND_MAX;

    Stream_setFunctionPtr(self->stream, MainParticle_compute_next_data_frame);
    self->mode_func_ptr = MainParticle_setProcMode;

    static char *kwlist[] = {"table", "env", "dens", "pitch", "pos", "dur", "dev", "pan", "chnls", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OOOOOOi", kwlist, &tabletmp, &envtmp, &denstmp, &pitchtmp, &postmp, &durtmp, &devtmp, &pantmp, &self->chnls))
        Py_RETURN_NONE;

    if ( PyObject_HasAttrString((PyObject *)tabletmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"table\" argument of MainParticle must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");
    self->srScale = TableStream_getSamplingRate((TableStream *)self->table) / self->sr;

    if ( PyObject_HasAttrString((PyObject *)envtmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"env\" argument of MainParticle must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->env = PyObject_CallMethod((PyObject *)envtmp, "getTableStream", "");

    if (denstmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDens", "O", denstmp);
    }

    if (pitchtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPitch", "O", pitchtmp);
    }

    if (postmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPos", "O", postmp);
    }

    if (durtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDur", "O", durtmp);
    }

    if (devtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDev", "O", devtmp);
    }

    if (pantmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPan", "O", pantmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    if (self->chnls < 1)
        self->chnls = 1;

    self->gpos = (MYFLT *)PyMem_RawRealloc(self->gpos, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->glen = (MYFLT *)PyMem_RawRealloc(self->glen, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->inc = (MYFLT *)PyMem_RawRealloc(self->inc, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->phase = (MYFLT *)PyMem_RawRealloc(self->phase, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->amp1 = (MYFLT *)PyMem_RawRealloc(self->amp1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->amp2 = (MYFLT *)PyMem_RawRealloc(self->amp2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->flags = (int *)PyMem_RawRealloc(self->flags, MAINPARTICLE_MAX_GRAINS * sizeof(int));
    self->k1 = (int *)PyMem_RawRealloc(self->k1, MAINPARTICLE_MAX_GRAINS * sizeof(int));
    self->k2 = (int *)PyMem_RawRealloc(self->k2, MAINPARTICLE_MAX_GRAINS * sizeof(int));

    for (i = 0; i < MAINPARTICLE_MAX_GRAINS; i++)
    {
        self->gpos[i] = self->glen[i] = self->inc[i] = self->phase[i] = self->amp1[i] = self->amp2[i] = 0.0;
        self->flags[i] = self->k1[i] = self->k2[i] = 0;
    }

    self->buffer_streams = (MYFLT *)PyMem_RawRealloc(self->buffer_streams, self->bufsize * self->chnls * sizeof(MYFLT));

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    Server_generateSeed((Server *)self->server, MAINPARTICLE_ID);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * MainParticle_getServer(MainParticle* self) { GET_SERVER };
static PyObject * MainParticle_getStream(MainParticle* self) { GET_STREAM };

static PyObject * MainParticle_play(MainParticle *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * MainParticle_stop(MainParticle *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * MainParticle_setDens(MainParticle *self, PyObject *arg) { SET_PARAM(self->dens, self->dens_stream, 0); }
static PyObject * MainParticle_setPitch(MainParticle *self, PyObject *arg) { SET_PARAM(self->pitch, self->pitch_stream, 1); }
static PyObject * MainParticle_setPos(MainParticle *self, PyObject *arg) { SET_PARAM(self->pos, self->pos_stream, 2); }
static PyObject * MainParticle_setDur(MainParticle *self, PyObject *arg) { SET_PARAM(self->dur, self->dur_stream, 3); }
static PyObject * MainParticle_setDev(MainParticle *self, PyObject *arg) { SET_PARAM(self->dev, self->dev_stream, 4); }
static PyObject * MainParticle_setPan(MainParticle *self, PyObject *arg) { SET_PARAM(self->pan, self->pan_stream, 5); }

static PyObject *
MainParticle_getTable(MainParticle* self)
{
    Py_INCREF(self->table);
    return self->table;
};

static PyObject *
MainParticle_setTable(MainParticle *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->table);
    self->table = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");
    self->srScale = TableStream_getSamplingRate((TableStream *)self->table) / self->sr;

    Py_RETURN_NONE;
}

static PyObject *
MainParticle_getEnv(MainParticle* self)
{
    Py_INCREF(self->env);
    return self->env;
};

static PyObject *
MainParticle_setEnv(MainParticle *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->env);
    self->env = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyMemberDef MainParticle_members[] =
{
    {"server", T_OBJECT_EX, offsetof(MainParticle, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(MainParticle, stream), 0, "Stream object."},
    {"table", T_OBJECT_EX, offsetof(MainParticle, table), 0, "Sound table."},
    {"env", T_OBJECT_EX, offsetof(MainParticle, env), 0, "Envelope table."},
    {"dens", T_OBJECT_EX, offsetof(MainParticle, dens), 0, "Density of grains per second."},
    {"pitch", T_OBJECT_EX, offsetof(MainParticle, pitch), 0, "Speed of the reading pointer."},
    {"pos", T_OBJECT_EX, offsetof(MainParticle, pos), 0, "Position in the sound table."},
    {"dur", T_OBJECT_EX, offsetof(MainParticle, dur), 0, "Duration of each grains."},
    {"dev", T_OBJECT_EX, offsetof(MainParticle, dev), 0, "Grain start point deviation factor."},
    {"pan", T_OBJECT_EX, offsetof(MainParticle, pan), 0, "Grain panning factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef MainParticle_methods[] =
{
    {"getTable", (PyCFunction)MainParticle_getTable, METH_NOARGS, "Returns sound table object."},
    {"setTable", (PyCFunction)MainParticle_setTable, METH_O, "Sets sound table."},
    {"getEnv", (PyCFunction)MainParticle_getEnv, METH_NOARGS, "Returns envelope table object."},
    {"setEnv", (PyCFunction)MainParticle_setEnv, METH_O, "Sets envelope table."},
    {"getServer", (PyCFunction)MainParticle_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)MainParticle_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)MainParticle_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"stop", (PyCFunction)MainParticle_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setDens", (PyCFunction)MainParticle_setDens, METH_O, "Sets the density of grains per second."},
    {"setPitch", (PyCFunction)MainParticle_setPitch, METH_O, "Sets global pitch factor."},
    {"setPos", (PyCFunction)MainParticle_setPos, METH_O, "Sets position in the sound table."},
    {"setDur", (PyCFunction)MainParticle_setDur, METH_O, "Sets the grain duration."},
    {"setDev", (PyCFunction)MainParticle_setDev, METH_O, "Sets grain start point deviation factor."},
    {"setPan", (PyCFunction)MainParticle_setPan, METH_O, "Sets grain panning factor."},
    {NULL}  /* Sentinel */
};

PyTypeObject MainParticleType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.MainParticle_base",         /*tp_name*/
    sizeof(MainParticle),         /*tp_basicpitch*/
    0,                         /*tp_itempitch*/
    (destructor)MainParticle_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    0,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
    "MainParticle objects. Accumulation of multiples grains of sound.",           /* tp_doc */
    (traverseproc)MainParticle_traverse,   /* tp_traverse */
    (inquiry)MainParticle_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    MainParticle_methods,             /* tp_methods */
    MainParticle_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    MainParticle_new,                 /* tp_new */
};

typedef struct
{
    pyo_audio_HEAD
    MainParticle *mainSplitter;
    int modebuffer[2];
    int chnl; // panning order
} Particle;

static void Particle_postprocessing_ii(Particle *self) { POST_PROCESSING_II };
static void Particle_postprocessing_ai(Particle *self) { POST_PROCESSING_AI };
static void Particle_postprocessing_ia(Particle *self) { POST_PROCESSING_IA };
static void Particle_postprocessing_aa(Particle *self) { POST_PROCESSING_AA };
static void Particle_postprocessing_ireva(Particle *self) { POST_PROCESSING_IREVA };
static void Particle_postprocessing_areva(Particle *self) { POST_PROCESSING_AREVA };
static void Particle_postprocessing_revai(Particle *self) { POST_PROCESSING_REVAI };
static void Particle_postprocessing_revaa(Particle *self) { POST_PROCESSING_REVAA };
static void Particle_postprocessing_revareva(Particle *self) { POST_PROCESSING_REVAREVA };

static void
Particle_setProcMode(Particle *self)
{
    int muladdmode;
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = Particle_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = Particle_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = Particle_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = Particle_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = Particle_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = Particle_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = Particle_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = Particle_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = Particle_postprocessing_revareva;
            break;
    }
}

static void
Particle_compute_next_data_frame(Particle *self)
{
    int i;
    MYFLT *tmp;
    int offset = self->chnl * self->bufsize;
    tmp = MainParticle_getSamplesBuffer((MainParticle *)self->mainSplitter);

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = tmp[i + offset];
    }

    (*self->muladd_func_ptr)(self);
}

static int
Particle_traverse(Particle *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->mainSplitter);
    return 0;
}

static int
Particle_clear(Particle *self)
{
    pyo_CLEAR
    Py_CLEAR(self->mainSplitter);
    return 0;
}

static void
Particle_dealloc(Particle* self)
{
    pyo_DEALLOC
    Particle_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Particle_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *maintmp = NULL, *multmp = NULL, *addtmp = NULL;
    Particle *self;
    self = (Particle *)type->tp_alloc(type, 0);

    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;

    INIT_OBJECT_COMMON
    Stream_setFunctionPtr(self->stream, Particle_compute_next_data_frame);
    self->mode_func_ptr = Particle_setProcMode;

    static char *kwlist[] = {"mainSplitter", "chnl", "mul", "add", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi|OO", kwlist, &maintmp, &self->chnl, &multmp, &addtmp))
        Py_RETURN_NONE;

    self->mainSplitter = (MainParticle *)maintmp;
    Py_INCREF(self->mainSplitter);

    if (multmp)
    {
        PyObject_CallMethod((PyObject *)self, "setMul", "O", multmp);
    }

    if (addtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setAdd", "O", addtmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * Particle_getServer(Particle* self) { GET_SERVER };
static PyObject * Particle_getStream(Particle* self) { GET_STREAM };
static PyObject * Particle_setMul(Particle *self, PyObject *arg) { SET_MUL };
static PyObject * Particle_setAdd(Particle *self, PyObject *arg) { SET_ADD };
static PyObject * Particle_setSub(Particle *self, PyObject *arg) { SET_SUB };
static PyObject * Particle_setDiv(Particle *self, PyObject *arg) { SET_DIV };

static PyObject * Particle_play(Particle *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * Particle_out(Particle *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * Particle_stop(Particle *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * Particle_multiply(Particle *self, PyObject *arg) { MULTIPLY };
static PyObject * Particle_inplace_multiply(Particle *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * Particle_add(Particle *self, PyObject *arg) { ADD };
static PyObject * Particle_inplace_add(Particle *self, PyObject *arg) { INPLACE_ADD };
static PyObject * Particle_sub(Particle *self, PyObject *arg) { SUB };
static PyObject * Particle_inplace_sub(Particle *self, PyObject *arg) { INPLACE_SUB };
static PyObject * Particle_div(Particle *self, PyObject *arg) { DIV };
static PyObject * Particle_inplace_div(Particle *self, PyObject *arg) { INPLACE_DIV };

static PyMemberDef Particle_members[] =
{
    {"server", T_OBJECT_EX, offsetof(Particle, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(Particle, stream), 0, "Stream object."},
    {"mul", T_OBJECT_EX, offsetof(Particle, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(Particle, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef Particle_methods[] =
{
    {"getServer", (PyCFunction)Particle_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)Particle_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)Particle_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)Particle_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)Particle_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setMul", (PyCFunction)Particle_setMul, METH_O, "Sets Particle mul factor."},
    {"setAdd", (PyCFunction)Particle_setAdd, METH_O, "Sets Particle add factor."},
    {"setSub", (PyCFunction)Particle_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)Particle_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods Particle_as_number =
{
    (binaryfunc)Particle_add,                      /*nb_add*/
    (binaryfunc)Particle_sub,                 /*nb_subtract*/
    (binaryfunc)Particle_multiply,                 /*nb_multiply*/
    0,                /*nb_remainder*/
    0,                   /*nb_divmod*/
    0,                   /*nb_power*/
    0,                  /*nb_neg*/
    0,                /*nb_pos*/
    0,                  /*(unaryfunc)array_abs,*/
    0,                    /*nb_nonzero*/
    0,                    /*nb_invert*/
    0,               /*nb_lshift*/
    0,              /*nb_rshift*/
    0,              /*nb_and*/
    0,              /*nb_xor*/
    0,               /*nb_or*/
    0,                       /*nb_int*/
    0,                      /*nb_long*/
    0,                     /*nb_float*/
    (binaryfunc)Particle_inplace_add,              /*inplace_add*/
    (binaryfunc)Particle_inplace_sub,         /*inplace_subtract*/
    (binaryfunc)Particle_inplace_multiply,         /*inplace_multiply*/
    0,        /*inplace_remainder*/
    0,           /*inplace_power*/
    0,       /*inplace_lshift*/
    0,      /*inplace_rshift*/
    0,      /*inplace_and*/
    0,      /*inplace_xor*/
    0,       /*inplace_or*/
    0,             /*nb_floor_divide*/
    (binaryfunc)Particle_div,                       /*nb_true_divide*/
    0,     /*nb_inplace_floor_divide*/
    (binaryfunc)Particle_inplace_div,                       /*nb_inplace_true_divide*/
    0,                     /* nb_index */
};

PyTypeObject ParticleType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.Particle_base",         /*tp_name*/
    sizeof(Particle),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)Particle_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &Particle_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,  /*tp_flags*/
    "Particle objects. Reads one band from a MainParticle object.",           /* tp_doc */
    (traverseproc)Particle_traverse,   /* tp_traverse */
    (inquiry)Particle_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Particle_methods,             /* tp_methods */
    Particle_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    Particle_new,                 /* tp_new */
};

typedef struct
{
    pyo_audio_HEAD
    PyObject *table;
    PyObject *env;
    PyObject *dens;
    Stream *dens_stream;
    PyObject *pitch;
    Stream *pitch_stream;
    PyObject *pos;
    Stream *pos_stream;
    PyObject *dur;
    Stream *dur_stream;
    PyObject *dev;
    Stream *dev_stream;
    PyObject *pan;
    Stream *pan_stream;

    PyObject *filterfreq;
    Stream *filterfreq_stream;
    PyObject *filterq;
    Stream *filterq_stream;
    PyObject *filtertype;
    Stream *filtertype_stream;
    // filters last values
    MYFLT *last_freq;
    MYFLT *last_q;
    MYFLT *last_type;
    // sample memories
    MYFLT *x1;
    MYFLT *x2;
    MYFLT *y1;
    MYFLT *y2;
    // variables
    MYFLT *c;
    MYFLT *w0;
    MYFLT *alpha;
    MYFLT *qcomp;
    // coefficients
    MYFLT *b0;
    MYFLT *b1;
    MYFLT *b2;
    MYFLT *a0;
    MYFLT *a1;
    MYFLT *a2;

    MYFLT *gpos;
    MYFLT *glen;
    MYFLT *inc;
    MYFLT *phase;
    MYFLT *amp1;
    MYFLT *amp2;
    int *flags;
    int *k1;
    int *k2;
    int num;
    int chnls;
    double timer;
    double devFactor;
    double srScale;
    MYFLT oneOnSr;
    MYFLT nyquist;
    MYFLT twopiOverSr;
    MYFLT srOnRandMax;
    MYFLT *buffer_streams;
    int modebuffer[9];
} MainParticle2;

static void
MainParticle2_compute_coeffs_lp(MainParticle2 *self, int which)
{
    self->b0[which] = self->b2[which] = (1 - self->c[which]) / 2;
    self->b1[which] = 1 - self->c[which];
    self->a0[which] = 1.0 / (1 + self->alpha[which]);
    self->a1[which] = -2 * self->c[which];
    self->a2[which] = 1 - self->alpha[which];
}

static void
MainParticle2_compute_coeffs_hp(MainParticle2 *self, int which)
{
    self->b0[which] = self->b2[which] = (1 + self->c[which]) / 2;
    self->b1[which] = -(1 + self->c[which]);
    self->a0[which] = 1.0 / (1 + self->alpha[which]);
    self->a1[which] = -2 * self->c[which];
    self->a2[which] = 1 - self->alpha[which];
}

static void
MainParticle2_compute_coeffs_bp(MainParticle2 *self, int which)
{
    self->b0[which] = self->alpha[which];
    self->b1[which] = 0;
    self->b2[which] = -self->alpha[which];
    self->a0[which] = 1.0 / (1 + self->alpha[which]);
    self->a1[which] = -2 * self->c[which];
    self->a2[which] = 1 - self->alpha[which];
}

static void
MainParticle2_compute_coeffs_bs(MainParticle2 *self, int which)
{
    self->b0[which] = self->b2[which] = 1;
    self->b1[which] = self->a1[which] = -2 * self->c[which];
    self->a0[which] = 1.0 / (1 + self->alpha[which]);
    self->a2[which] = 1 - self->alpha[which];
}

static void
MainParticle2_compute_coeffs_ap(MainParticle2 *self, int which)
{
    self->b0[which] = self->a2[which] = 1 - self->alpha[which];
    self->b1[which] = self->a1[which] = -2 * self->c[which];
    self->b2[which] = 1 + self->alpha[which];
    self->a0[which] = 1.0 / (1 + self->alpha[which]);
}

static void
MainParticle2_compute_variables(MainParticle2 *self, MYFLT freq, MYFLT q, int which)
{
    self->w0[which] = freq * self->twopiOverSr;
    self->c[which] = MYCOS(self->w0[which]);
    self->alpha[which] = MYSIN(self->w0[which]) / (2 * q);
}

static void
MainParticle2_transform_mono_i(MainParticle2 *self)
{
    MYFLT dens, inc, index, amp, phase, val, fval;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, filterfreq = 0, filterq = 0, filtertype = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    dens = PyFloat_AS_DOUBLE(self->dens);

    if (dens < 0.0)
        dens = 0.0;

    inc = dens * self->oneOnSr * self->devFactor;

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->timer += inc;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[6] == 0)
                        filterfreq = PyFloat_AS_DOUBLE(self->filterfreq);
                    else
                        filterfreq = Stream_getData((Stream *)self->filterfreq_stream)[i];

                    if (self->modebuffer[7] == 0)
                        filterq = PyFloat_AS_DOUBLE(self->filterq);
                    else
                        filterq = Stream_getData((Stream *)self->filterq_stream)[i];

                    if (self->modebuffer[8] == 0)
                        filtertype = PyFloat_AS_DOUBLE(self->filtertype);
                    else
                        filtertype = Stream_getData((Stream *)self->filtertype_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (filterfreq < 1.0)
                        filterfreq = 1.0;
                    else if (filterfreq > self->nyquist)
                        filterfreq = self->nyquist;

                    if (filterq < 0.1)
                        filterq = 0.1;

                    if (filtertype < 0)
                        filtertype = 0;
                    else if (filtertype > 4)
                        filtertype = 4;

                    filtertype = MYROUND(filtertype);
                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (filtertype != self->last_type[j] || filterfreq != self->last_freq[j] || filterq != self->last_q[j])
                    {
                        self->last_freq[j] = filterfreq;
                        self->last_q[j] = filterq;
                        self->last_type[j] = filtertype;
                        MainParticle2_compute_variables(self, filterfreq, filterq, j);
                        self->qcomp[j] = 1.0;
                        self->b0[j] = self->b1[j] = self->b2[j] = self->a0[j] = self->a1[j] = self->a2[j] = 0.0;

                        if (filtertype == 0)
                            MainParticle2_compute_coeffs_lp(self, j);
                        else if (filtertype == 1)
                            MainParticle2_compute_coeffs_hp(self, j);
                        else if (filtertype == 2)
                        {
                            MainParticle2_compute_coeffs_bp(self, j);
                            filterq = filterq < 10.0 ? filterq : 10.0;
                            self->qcomp[j] = MYPOW(10, (filterq - 1) * 0.1);
                        }
                        else if (filtertype == 3)
                            MainParticle2_compute_coeffs_bs(self, j);
                        else if (filtertype == 4)
                            MainParticle2_compute_coeffs_ap(self, j);
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart));
                /* filtering */
                fval = ( (self->b0[j] * val) + (self->b1[j] * self->x1[j]) + (self->b2[j] * self->x2[j]) - (self->a1[j] * self->y1[j]) - (self->a2[j] * self->y2[j]) ) * self->a0[j];
                self->y2[j] = self->y1[j];
                self->y1[j] = fval;
                self->x2[j] = self->x1[j];
                self->x1[j] = val;
                fval *= self->qcomp[j];
                /* Add to output streams */
                fval *= amp;
                self->buffer_streams[i] += fval;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle2_transform_mono_a(MainParticle2 *self)
{
    MYFLT dens, index, amp, phase, val, fval;
    int i, j, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, filterfreq = 0, filterq = 0, filtertype = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *density = Stream_getData((Stream *)self->dens_stream);

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        if (density[i] < 0.0)
            dens = 0.0;
        else
            dens = density[i];

        self->timer += dens * self->oneOnSr * self->devFactor;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[6] == 0)
                        filterfreq = PyFloat_AS_DOUBLE(self->filterfreq);
                    else
                        filterfreq = Stream_getData((Stream *)self->filterfreq_stream)[i];

                    if (self->modebuffer[7] == 0)
                        filterq = PyFloat_AS_DOUBLE(self->filterq);
                    else
                        filterq = Stream_getData((Stream *)self->filterq_stream)[i];

                    if (self->modebuffer[8] == 0)
                        filtertype = PyFloat_AS_DOUBLE(self->filtertype);
                    else
                        filtertype = Stream_getData((Stream *)self->filtertype_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (filterfreq < 1.0)
                        filterfreq = 1.0;
                    else if (filterfreq > self->nyquist)
                        filterfreq = self->nyquist;

                    if (filterq < 0.1)
                        filterq = 0.1;

                    if (filtertype < 0)
                        filtertype = 0;
                    else if (filtertype > 4)
                        filtertype = 4;

                    filtertype = MYROUND(filtertype);
                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (filtertype != self->last_type[j] || filterfreq != self->last_freq[j] || filterq != self->last_q[j])
                    {
                        self->last_freq[j] = filterfreq;
                        self->last_q[j] = filterq;
                        self->last_type[j] = filtertype;
                        MainParticle2_compute_variables(self, filterfreq, filterq, j);
                        self->qcomp[j] = 1.0;
                        self->b0[j] = self->b1[j] = self->b2[j] = self->a0[j] = self->a1[j] = self->a2[j] = 0.0;

                        if (filtertype == 0)
                            MainParticle2_compute_coeffs_lp(self, j);
                        else if (filtertype == 1)
                            MainParticle2_compute_coeffs_hp(self, j);
                        else if (filtertype == 2)
                        {
                            MainParticle2_compute_coeffs_bp(self, j);
                            filterq = filterq < 10.0 ? filterq : 10.0;
                            self->qcomp[j] = MYPOW(10, (filterq - 1) * 0.1);
                        }
                        else if (filtertype == 3)
                            MainParticle2_compute_coeffs_bs(self, j);
                        else if (filtertype == 4)
                            MainParticle2_compute_coeffs_ap(self, j);
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart));
                /* filtering */
                fval = ( (self->b0[j] * val) + (self->b1[j] * self->x1[j]) + (self->b2[j] * self->x2[j]) - (self->a1[j] * self->y1[j]) - (self->a2[j] * self->y2[j]) ) * self->a0[j];
                self->y2[j] = self->y1[j];
                self->y1[j] = fval;
                self->x2[j] = self->x1[j];
                self->x1[j] = val;
                fval *= self->qcomp[j];
                /* Add to output streams */
                fval *= amp;
                self->buffer_streams[i] += fval;
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle2_transform_i(MainParticle2 *self)
{
    MYFLT dens, inc, index, amp, phase, val, fval, min = 0;
    int i, j, l, l1, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, pan = 0, filterfreq = 0, filterq = 0, filtertype = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    dens = PyFloat_AS_DOUBLE(self->dens);

    if (dens < 0.0)
        dens = 0.0;

    inc = dens * self->oneOnSr * self->devFactor;

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        self->timer += inc;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[5] == 0)
                        pan = PyFloat_AS_DOUBLE(self->pan);
                    else
                        pan = Stream_getData((Stream *)self->pan_stream)[i];

                    if (self->modebuffer[6] == 0)
                        filterfreq = PyFloat_AS_DOUBLE(self->filterfreq);
                    else
                        filterfreq = Stream_getData((Stream *)self->filterfreq_stream)[i];

                    if (self->modebuffer[7] == 0)
                        filterq = PyFloat_AS_DOUBLE(self->filterq);
                    else
                        filterq = Stream_getData((Stream *)self->filterq_stream)[i];

                    if (self->modebuffer[8] == 0)
                        filtertype = PyFloat_AS_DOUBLE(self->filtertype);
                    else
                        filtertype = Stream_getData((Stream *)self->filtertype_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (pan < 0.0)
                        pan = 0.0;
                    else if (pan > 1.0)
                        pan = 1.0;

                    if (filterfreq < 1.0)
                        filterfreq = 1.0;
                    else if (filterfreq > self->nyquist)
                        filterfreq = self->nyquist;

                    if (filterq < 0.1)
                        filterq = 0.1;

                    if (filtertype < 0)
                        filtertype = 0;
                    else if (filtertype > 4)
                        filtertype = 4;

                    filtertype = MYROUND(filtertype);
                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (self->chnls == 2)
                    {
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                    }
                    else
                    {
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                        min = 0;
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;

                        for (l = self->chnls; l > 0; l--)
                        {
                            l1 = l - 1;
                            min = l1 / (MYFLT)self->chnls;

                            if (pan > min)
                            {
                                self->k1[j] = l1 * self->bufsize;

                                if (l == self->chnls)
                                    self->k2[j] = 0;
                                else
                                    self->k2[j] = l * self->bufsize;

                                break;
                            }
                        }
                    }

                    if (filtertype != self->last_type[j] || filterfreq != self->last_freq[j] || filterq != self->last_q[j])
                    {
                        self->last_freq[j] = filterfreq;
                        self->last_q[j] = filterq;
                        self->last_type[j] = filtertype;
                        MainParticle2_compute_variables(self, filterfreq, filterq, j);
                        self->qcomp[j] = 1.0;
                        self->b0[j] = self->b1[j] = self->b2[j] = self->a0[j] = self->a1[j] = self->a2[j] = 0.0;

                        if (filtertype == 0)
                            MainParticle2_compute_coeffs_lp(self, j);
                        else if (filtertype == 1)
                            MainParticle2_compute_coeffs_hp(self, j);
                        else if (filtertype == 2)
                        {
                            MainParticle2_compute_coeffs_bp(self, j);
                            filterq = filterq < 10.0 ? filterq : 10.0;
                            self->qcomp[j] = MYPOW(10, (filterq - 1) * 0.1);
                        }
                        else if (filtertype == 3)
                            MainParticle2_compute_coeffs_bs(self, j);
                        else if (filtertype == 4)
                            MainParticle2_compute_coeffs_ap(self, j);
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart));
                /* filtering */
                fval = ( (self->b0[j] * val) + (self->b1[j] * self->x1[j]) + (self->b2[j] * self->x2[j]) - (self->a1[j] * self->y1[j]) - (self->a2[j] * self->y2[j]) ) * self->a0[j];
                self->y2[j] = self->y1[j];
                self->y1[j] = fval;
                self->x2[j] = self->x1[j];
                self->x1[j] = val;
                fval *= self->qcomp[j];
                /* Add to output streams */
                fval *= amp;
                self->buffer_streams[i + self->k1[j]] += fval * self->amp1[j];
                self->buffer_streams[i + self->k2[j]] += fval * self->amp2[j];
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle2_transform_a(MainParticle2 *self)
{
    MYFLT dens, index, amp, phase, val, fval, min = 0;
    int i, j, l, l1, flag = 0;
    T_SIZE_T ipart;
    MYFLT pit = 0, pos = 0, dur = 0, dev = 0, pan = 0, filterfreq = 0, filterq = 0, filtertype = 0;

    MYFLT *tablelist = TableStream_getData((TableStream *)self->table);
    T_SIZE_T size = TableStream_getSize((TableStream *)self->table);

    MYFLT *envlist = TableStream_getData((TableStream *)self->env);
    T_SIZE_T envsize = TableStream_getSize((TableStream *)self->env);

    MYFLT *density = Stream_getData((Stream *)self->dens_stream);

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    for (i = 0; i < self->bufsize; i++)
    {
        if (density[i] < 0.0)
            dens = 0.0;
        else
            dens = density[i];

        self->timer += dens * self->oneOnSr * self->devFactor;

        if (self->timer >= 1.0)
        {
            self->timer -= 1.0;
            flag = 1;
        }

        /* need to start a new grain */
        if (flag)
        {
            for (j = 0; j < MAINPARTICLE_MAX_GRAINS; j++)
            {
                if (self->flags[j] == 0)
                {
                    self->flags[j] = 1;

                    if (j >= self->num)
                        self->num = j + 1;

                    if (self->modebuffer[1] == 0)
                        pit = PyFloat_AS_DOUBLE(self->pitch);
                    else
                        pit = Stream_getData((Stream *)self->pitch_stream)[i];

                    if (self->modebuffer[2] == 0)
                        pos = PyFloat_AS_DOUBLE(self->pos);
                    else
                        pos = Stream_getData((Stream *)self->pos_stream)[i];

                    if (self->modebuffer[3] == 0)
                        dur = PyFloat_AS_DOUBLE(self->dur);
                    else
                        dur = Stream_getData((Stream *)self->dur_stream)[i];

                    if (self->modebuffer[4] == 0)
                        dev = PyFloat_AS_DOUBLE(self->dev);
                    else
                        dev = Stream_getData((Stream *)self->dev_stream)[i];

                    if (self->modebuffer[5] == 0)
                        pan = PyFloat_AS_DOUBLE(self->pan);
                    else
                        pan = Stream_getData((Stream *)self->pan_stream)[i];

                    if (self->modebuffer[6] == 0)
                        filterfreq = PyFloat_AS_DOUBLE(self->filterfreq);
                    else
                        filterfreq = Stream_getData((Stream *)self->filterfreq_stream)[i];

                    if (self->modebuffer[7] == 0)
                        filterq = PyFloat_AS_DOUBLE(self->filterq);
                    else
                        filterq = Stream_getData((Stream *)self->filterq_stream)[i];

                    if (self->modebuffer[8] == 0)
                        filtertype = PyFloat_AS_DOUBLE(self->filtertype);
                    else
                        filtertype = Stream_getData((Stream *)self->filtertype_stream)[i];

                    if (pit < 0.0)
                        pit = -pit;

                    if (pos < 0.0)
                        pos = 0.0;
                    else if (pos >= size)
                        pos = (MYFLT)size;

                    if (dur < 0.0001)
                        dur = 0.0001;

                    if (dev < 0.0)
                        dev = 0.0;
                    else if (dev > 1.0)
                        dev = 1.0;

                    if (pan < 0.0)
                        pan = 0.0;
                    else if (pan > 1.0)
                        pan = 1.0;

                    if (filterfreq < 1.0)
                        filterfreq = 1.0;
                    else if (filterfreq > self->nyquist)
                        filterfreq = self->nyquist;

                    if (filterq < 0.1)
                        filterq = 0.1;

                    if (filtertype < 0)
                        filtertype = 0;
                    else if (filtertype > 4)
                        filtertype = 4;

                    filtertype = MYROUND(filtertype);
                    self->gpos[j] = pos;
                    self->glen[j] = dur * self->sr * pit * self->srScale;

                    if ((pos + self->glen[j]) >= size || (pos + self->glen[j]) < 0)
                        self->flags[j] = 0;

                    self->phase[j] = 0.0;
                    self->inc[j] = 1.0 / (dur * self->sr);
                    self->devFactor = (RANDOM_UNIFORM * 2.0 - 1.0) * dev + 1.0;

                    if (self->chnls == 2)
                    {
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                    }
                    else
                    {
                        self->amp1[j] = MYSQRT(1.0 - pan);
                        self->amp2[j] = MYSQRT(pan);
                        min = 0;
                        self->k1[j] = 0;
                        self->k2[j] = self->bufsize;

                        for (l = self->chnls; l > 0; l--)
                        {
                            l1 = l - 1;
                            min = l1 / (MYFLT)self->chnls;

                            if (pan > min)
                            {
                                self->k1[j] = l1 * self->bufsize;

                                if (l == self->chnls)
                                    self->k2[j] = 0;
                                else
                                    self->k2[j] = l * self->bufsize;

                                break;
                            }
                        }
                    }

                    if (filtertype != self->last_type[j] || filterfreq != self->last_freq[j] || filterq != self->last_q[j])
                    {
                        self->last_freq[j] = filterfreq;
                        self->last_q[j] = filterq;
                        self->last_type[j] = filtertype;
                        MainParticle2_compute_variables(self, filterfreq, filterq, j);
                        self->qcomp[j] = 1.0;
                        self->b0[j] = self->b1[j] = self->b2[j] = self->a0[j] = self->a1[j] = self->a2[j] = 0.0;

                        if (filtertype == 0)
                            MainParticle2_compute_coeffs_lp(self, j);
                        else if (filtertype == 1)
                            MainParticle2_compute_coeffs_hp(self, j);
                        else if (filtertype == 2)
                        {
                            MainParticle2_compute_coeffs_bp(self, j);
                            filterq = filterq < 10.0 ? filterq : 10.0;
                            self->qcomp[j] = MYPOW(10, (filterq - 1) * 0.1);
                        }
                        else if (filtertype == 3)
                            MainParticle2_compute_coeffs_bs(self, j);
                        else if (filtertype == 4)
                            MainParticle2_compute_coeffs_ap(self, j);
                    }

                    break;
                }
            }
        }

        /* compute active grains */
        for (j = 0; j < self->num; j++)
        {
            if (self->flags[j])
            {
                phase = self->phase[j];
                /* compute envelope */
                index = phase * envsize;
                ipart = (T_SIZE_T)index;
                amp = envlist[ipart] + (envlist[ipart + 1] - envlist[ipart]) * (index - ipart);
                /* compute sampling */
                index = phase * self->glen[j] + self->gpos[j];
                ipart = (T_SIZE_T)index;
                val = (tablelist[ipart] + (tablelist[ipart + 1] - tablelist[ipart]) * (index - ipart));
                /* filtering */
                fval = ( (self->b0[j] * val) + (self->b1[j] * self->x1[j]) + (self->b2[j] * self->x2[j]) - (self->a1[j] * self->y1[j]) - (self->a2[j] * self->y2[j]) ) * self->a0[j];
                self->y2[j] = self->y1[j];
                self->y1[j] = fval;
                self->x2[j] = self->x1[j];
                self->x1[j] = val;
                fval *= self->qcomp[j];
                /* Add to output streams */
                fval *= amp;
                self->buffer_streams[i + self->k1[j]] += fval * self->amp1[j];
                self->buffer_streams[i + self->k2[j]] += fval * self->amp2[j];
                phase += self->inc[j];

                if (phase >= 1.0)
                    self->flags[j] = 0;
                else
                    self->phase[j] = phase;
            }
        }

        flag = 0;
    }
}

static void
MainParticle2_setProcMode(MainParticle2 *self)
{
    int procmode = self->modebuffer[0];

    switch (procmode)
    {
        case 0:
            if (self->chnls == 1)
                self->proc_func_ptr = MainParticle2_transform_mono_i;
            else
                self->proc_func_ptr = MainParticle2_transform_i;

            break;

        case 1:
            if (self->chnls == 1)
                self->proc_func_ptr = MainParticle2_transform_mono_a;
            else
                self->proc_func_ptr = MainParticle2_transform_a;

            break;
    }
}

static void
MainParticle2_compute_next_data_frame(MainParticle2 *self)
{
    (*self->proc_func_ptr)(self);
}

static int
MainParticle2_traverse(MainParticle2 *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->dens);
    Py_VISIT(self->pitch);
    Py_VISIT(self->pos);
    Py_VISIT(self->dur);
    Py_VISIT(self->dev);
    Py_VISIT(self->pan);
    Py_VISIT(self->filterfreq);
    Py_VISIT(self->filterq);
    Py_VISIT(self->filtertype);
    return 0;
}

static int
MainParticle2_clear(MainParticle2 *self)
{
    pyo_CLEAR
    Py_CLEAR(self->dens);
    Py_CLEAR(self->pitch);
    Py_CLEAR(self->pos);
    Py_CLEAR(self->dur);
    Py_CLEAR(self->dev);
    Py_CLEAR(self->pan);
    Py_CLEAR(self->filterfreq);
    Py_CLEAR(self->filterq);
    Py_CLEAR(self->filtertype);
    return 0;
}

static void
MainParticle2_dealloc(MainParticle2* self)
{
    pyo_DEALLOC
    PyMem_RawFree(self->gpos);
    PyMem_RawFree(self->glen);
    PyMem_RawFree(self->inc);
    PyMem_RawFree(self->flags);
    PyMem_RawFree(self->k1);
    PyMem_RawFree(self->k2);
    PyMem_RawFree(self->phase);
    PyMem_RawFree(self->amp1);
    PyMem_RawFree(self->amp2);
    PyMem_RawFree(self->last_freq);
    PyMem_RawFree(self->last_q);
    PyMem_RawFree(self->last_type);
    PyMem_RawFree(self->x1);
    PyMem_RawFree(self->x2);
    PyMem_RawFree(self->y1);
    PyMem_RawFree(self->y2);
    PyMem_RawFree(self->c);
    PyMem_RawFree(self->w0);
    PyMem_RawFree(self->alpha);
    PyMem_RawFree(self->qcomp);
    PyMem_RawFree(self->b0);
    PyMem_RawFree(self->b1);
    PyMem_RawFree(self->b2);
    PyMem_RawFree(self->a0);
    PyMem_RawFree(self->a1);
    PyMem_RawFree(self->a2);
    PyMem_RawFree(self->buffer_streams);
    MainParticle2_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

MYFLT *
MainParticle2_getSamplesBuffer(MainParticle2 *self)
{
    return (MYFLT *)self->buffer_streams;
}

static PyObject *
MainParticle2_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *tabletmp, *envtmp, *denstmp = NULL, *pitchtmp = NULL, *postmp = NULL, *durtmp = NULL;
    PyObject *devtmp = NULL, *pantmp = NULL, *filterfreqtmp = NULL, *filterqtmp = NULL, *filtertypetmp = NULL;
    MainParticle2 *self;
    self = (MainParticle2 *)type->tp_alloc(type, 0);

    self->dens = PyFloat_FromDouble(50);
    self->pitch = PyFloat_FromDouble(1);
    self->pos = PyFloat_FromDouble(0.0);
    self->dur = PyFloat_FromDouble(0.1);
    self->dev = PyFloat_FromDouble(0);
    self->pan = PyFloat_FromDouble(0.5);
    self->filterq = PyFloat_FromDouble(0.7);
    self->filtertype = PyFloat_FromDouble(0);
    self->timer = self->devFactor = 1.0;
    self->srScale = 1.0;
    self->num = 0;
    self->chnls = 1;
    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;
    self->modebuffer[2] = 0;
    self->modebuffer[3] = 0;
    self->modebuffer[4] = 0;
    self->modebuffer[5] = 0;
    self->modebuffer[6] = 0;
    self->modebuffer[7] = 0;
    self->modebuffer[8] = 0;

    INIT_OBJECT_COMMON

    self->oneOnSr = 1.0 / self->sr;
    self->srOnRandMax = self->sr / (MYFLT)PYO_RAND_MAX;
    self->nyquist = (MYFLT)self->sr * 0.49;
    self->twopiOverSr = TWOPI / (MYFLT)self->sr;

    self->filterfreq = PyFloat_FromDouble(self->nyquist);

    Stream_setFunctionPtr(self->stream, MainParticle2_compute_next_data_frame);
    self->mode_func_ptr = MainParticle2_setProcMode;

    static char *kwlist[] = {"table", "env", "dens", "pitch", "pos", "dur", "dev", "pan", "filterfreq", "filterq", "filtertype", "chnls", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OOOOOOOOOi", kwlist, &tabletmp, &envtmp, &denstmp, &pitchtmp, &postmp, &durtmp, &devtmp, &pantmp, &filterfreqtmp, &filterqtmp, &filtertypetmp, &self->chnls))
        Py_RETURN_NONE;

    if ( PyObject_HasAttrString((PyObject *)tabletmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"table\" argument of MainParticle2 must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->table = PyObject_CallMethod((PyObject *)tabletmp, "getTableStream", "");
    self->srScale = TableStream_getSamplingRate((TableStream *)self->table) / self->sr;

    if ( PyObject_HasAttrString((PyObject *)envtmp, "getTableStream") == 0 )
    {
        PyErr_SetString(PyExc_TypeError, "\"env\" argument of MainParticle2 must be a PyoTableObject.\n");
        Py_RETURN_NONE;
    }

    self->env = PyObject_CallMethod((PyObject *)envtmp, "getTableStream", "");

    if (denstmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDens", "O", denstmp);
    }

    if (pitchtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPitch", "O", pitchtmp);
    }

    if (postmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPos", "O", postmp);
    }

    if (durtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDur", "O", durtmp);
    }

    if (devtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setDev", "O", devtmp);
    }

    if (pantmp)
    {
        PyObject_CallMethod((PyObject *)self, "setPan", "O", pantmp);
    }

    if (filterfreqtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setFilterfreq", "O", filterfreqtmp);
    }

    if (filterqtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setFilterq", "O", filterqtmp);
    }

    if (filtertypetmp)
    {
        PyObject_CallMethod((PyObject *)self, "setFiltertype", "O", filtertypetmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    if (self->chnls < 1)
        self->chnls = 1;

    self->gpos = (MYFLT *)PyMem_RawRealloc(self->gpos, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->glen = (MYFLT *)PyMem_RawRealloc(self->glen, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->inc = (MYFLT *)PyMem_RawRealloc(self->inc, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->phase = (MYFLT *)PyMem_RawRealloc(self->phase, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->amp1 = (MYFLT *)PyMem_RawRealloc(self->amp1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->amp2 = (MYFLT *)PyMem_RawRealloc(self->amp2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->flags = (int *)PyMem_RawRealloc(self->flags, MAINPARTICLE_MAX_GRAINS * sizeof(int));
    self->k1 = (int *)PyMem_RawRealloc(self->k1, MAINPARTICLE_MAX_GRAINS * sizeof(int));
    self->k2 = (int *)PyMem_RawRealloc(self->k2, MAINPARTICLE_MAX_GRAINS * sizeof(int));
    self->last_freq = (MYFLT *)PyMem_RawRealloc(self->last_freq, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->last_q = (MYFLT *)PyMem_RawRealloc(self->last_q, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->last_type = (MYFLT *)PyMem_RawRealloc(self->last_type, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->x1 = (MYFLT *)PyMem_RawRealloc(self->x1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->x2 = (MYFLT *)PyMem_RawRealloc(self->x2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->y1 = (MYFLT *)PyMem_RawRealloc(self->y1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->y2 = (MYFLT *)PyMem_RawRealloc(self->y2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->c = (MYFLT *)PyMem_RawRealloc(self->c, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->w0 = (MYFLT *)PyMem_RawRealloc(self->w0, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->alpha = (MYFLT *)PyMem_RawRealloc(self->alpha, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->qcomp = (MYFLT *)PyMem_RawRealloc(self->qcomp, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->b0 = (MYFLT *)PyMem_RawRealloc(self->b0, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->b1 = (MYFLT *)PyMem_RawRealloc(self->b1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->b2 = (MYFLT *)PyMem_RawRealloc(self->b2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->a0 = (MYFLT *)PyMem_RawRealloc(self->a0, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->a1 = (MYFLT *)PyMem_RawRealloc(self->a1, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));
    self->a2 = (MYFLT *)PyMem_RawRealloc(self->a2, MAINPARTICLE_MAX_GRAINS * sizeof(MYFLT));

    for (i = 0; i < MAINPARTICLE_MAX_GRAINS; i++)
    {
        self->gpos[i] = self->glen[i] = self->inc[i] = self->phase[i] = self->amp1[i] = self->amp2[i] = 0.0;
        self->last_freq[i] = self->last_q[i] = self->x1[i] = self->x2[i] = self->y1[i] = self->y2[i] = 0.0;
        self->last_type[i] = -1.0;
        self->qcomp[i] = 1.0;
        self->c[i] = self->w0[i] = self->alpha[i] = self->b0[i] = self->b1[i] = self->b2[i] = self->a0[i] = self->a1[i] = self->a2[i] = 0.0;
        self->flags[i] = self->k1[i] = self->k2[i] = 0;
    }

    self->buffer_streams = (MYFLT *)PyMem_RawRealloc(self->buffer_streams, self->bufsize * self->chnls * sizeof(MYFLT));

    for (i = 0; i < self->bufsize * self->chnls; i++)
    {
        self->buffer_streams[i] = 0.0;
    }

    Server_generateSeed((Server *)self->server, MAINPARTICLE_ID);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * MainParticle2_getServer(MainParticle2* self) { GET_SERVER };
static PyObject * MainParticle2_getStream(MainParticle2* self) { GET_STREAM };

static PyObject * MainParticle2_play(MainParticle2 *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * MainParticle2_stop(MainParticle2 *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * MainParticle2_setDens(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->dens, self->dens_stream, 0); }
static PyObject * MainParticle2_setPitch(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->pitch, self->pitch_stream, 1); }
static PyObject * MainParticle2_setPos(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->pos, self->pos_stream, 2); }
static PyObject * MainParticle2_setDur(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->dur, self->dur_stream, 3); }
static PyObject * MainParticle2_setDev(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->dev, self->dev_stream, 4); }
static PyObject * MainParticle2_setPan(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->pan, self->pan_stream, 5); }
static PyObject * MainParticle2_setFilterfreq(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->filterfreq, self->filterfreq_stream, 6); }
static PyObject * MainParticle2_setFilterq(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->filterq, self->filterq_stream, 7); }
static PyObject * MainParticle2_setFiltertype(MainParticle2 *self, PyObject *arg) { SET_PARAM(self->filtertype, self->filtertype_stream, 8); }

static PyObject *
MainParticle2_getTable(MainParticle2* self)
{
    Py_INCREF(self->table);
    return self->table;
};

static PyObject *
MainParticle2_setTable(MainParticle2 *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->table);
    self->table = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");
    self->srScale = TableStream_getSamplingRate((TableStream *)self->table) / self->sr;

    Py_RETURN_NONE;
}

static PyObject *
MainParticle2_getEnv(MainParticle2* self)
{
    Py_INCREF(self->env);
    return self->env;
};

static PyObject *
MainParticle2_setEnv(MainParticle2 *self, PyObject *arg)
{
    ASSERT_ARG_NOT_NULL

    Py_DECREF(self->env);
    self->env = PyObject_CallMethod((PyObject *)arg, "getTableStream", "");

    Py_RETURN_NONE;
}

static PyMemberDef MainParticle2_members[] =
{
    {"server", T_OBJECT_EX, offsetof(MainParticle2, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(MainParticle2, stream), 0, "Stream object."},
    {"table", T_OBJECT_EX, offsetof(MainParticle2, table), 0, "Sound table."},
    {"env", T_OBJECT_EX, offsetof(MainParticle2, env), 0, "Envelope table."},
    {"dens", T_OBJECT_EX, offsetof(MainParticle2, dens), 0, "Density of grains per second."},
    {"pitch", T_OBJECT_EX, offsetof(MainParticle2, pitch), 0, "Speed of the reading pointer."},
    {"pos", T_OBJECT_EX, offsetof(MainParticle2, pos), 0, "Position in the sound table."},
    {"dur", T_OBJECT_EX, offsetof(MainParticle2, dur), 0, "Duration of each grains."},
    {"dev", T_OBJECT_EX, offsetof(MainParticle2, dev), 0, "Grain start point deviation factor."},
    {"pan", T_OBJECT_EX, offsetof(MainParticle2, pan), 0, "Grain panning factor."},
    {"filterfreq", T_OBJECT_EX, offsetof(MainParticle2, filterfreq), 0, "Grain filter center/cutoff frequency."},
    {"filterq", T_OBJECT_EX, offsetof(MainParticle2, filterq), 0, "Grain filter Q."},
    {"filtertype", T_OBJECT_EX, offsetof(MainParticle2, filtertype), 0, "Grain filter type."},
    {NULL}  /* Sentinel */
};

static PyMethodDef MainParticle2_methods[] =
{
    {"getTable", (PyCFunction)MainParticle2_getTable, METH_NOARGS, "Returns sound table object."},
    {"setTable", (PyCFunction)MainParticle2_setTable, METH_O, "Sets sound table."},
    {"getEnv", (PyCFunction)MainParticle2_getEnv, METH_NOARGS, "Returns envelope table object."},
    {"setEnv", (PyCFunction)MainParticle2_setEnv, METH_O, "Sets envelope table."},
    {"getServer", (PyCFunction)MainParticle2_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)MainParticle2_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)MainParticle2_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"stop", (PyCFunction)MainParticle2_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setDens", (PyCFunction)MainParticle2_setDens, METH_O, "Sets the density of grains per second."},
    {"setPitch", (PyCFunction)MainParticle2_setPitch, METH_O, "Sets global pitch factor."},
    {"setPos", (PyCFunction)MainParticle2_setPos, METH_O, "Sets position in the sound table."},
    {"setDur", (PyCFunction)MainParticle2_setDur, METH_O, "Sets the grain duration."},
    {"setDev", (PyCFunction)MainParticle2_setDev, METH_O, "Sets grain start point deviation factor."},
    {"setPan", (PyCFunction)MainParticle2_setPan, METH_O, "Sets grain panning factor."},
    {"setFilterfreq", (PyCFunction)MainParticle2_setFilterfreq, METH_O, "Sets grain filter center/cutoff frequency."},
    {"setFilterq", (PyCFunction)MainParticle2_setFilterq, METH_O, "Sets grain filter Q."},
    {"setFiltertype", (PyCFunction)MainParticle2_setFiltertype, METH_O, "Sets grain filter type."},
    {NULL}  /* Sentinel */
};

PyTypeObject MainParticle2Type =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.MainParticle2_base",         /*tp_name*/
    sizeof(MainParticle2),         /*tp_basicpitch*/
    0,                         /*tp_itempitch*/
    (destructor)MainParticle2_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    0,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
    "MainParticle2 objects. Accumulation of multiples bandpass filtered grains of sound.",           /* tp_doc */
    (traverseproc)MainParticle2_traverse,   /* tp_traverse */
    (inquiry)MainParticle2_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    MainParticle2_methods,             /* tp_methods */
    MainParticle2_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    MainParticle2_new,                 /* tp_new */
};

typedef struct
{
    pyo_audio_HEAD
    MainParticle2 *mainSplitter;
    int modebuffer[2];
    int chnl; // panning order
} Particle2;

static void Particle2_postprocessing_ii(Particle2 *self) { POST_PROCESSING_II };
static void Particle2_postprocessing_ai(Particle2 *self) { POST_PROCESSING_AI };
static void Particle2_postprocessing_ia(Particle2 *self) { POST_PROCESSING_IA };
static void Particle2_postprocessing_aa(Particle2 *self) { POST_PROCESSING_AA };
static void Particle2_postprocessing_ireva(Particle2 *self) { POST_PROCESSING_IREVA };
static void Particle2_postprocessing_areva(Particle2 *self) { POST_PROCESSING_AREVA };
static void Particle2_postprocessing_revai(Particle2 *self) { POST_PROCESSING_REVAI };
static void Particle2_postprocessing_revaa(Particle2 *self) { POST_PROCESSING_REVAA };
static void Particle2_postprocessing_revareva(Particle2 *self) { POST_PROCESSING_REVAREVA };

static void
Particle2_setProcMode(Particle2 *self)
{
    int muladdmode;
    muladdmode = self->modebuffer[0] + self->modebuffer[1] * 10;

    switch (muladdmode)
    {
        case 0:
            self->muladd_func_ptr = Particle2_postprocessing_ii;
            break;

        case 1:
            self->muladd_func_ptr = Particle2_postprocessing_ai;
            break;

        case 2:
            self->muladd_func_ptr = Particle2_postprocessing_revai;
            break;

        case 10:
            self->muladd_func_ptr = Particle2_postprocessing_ia;
            break;

        case 11:
            self->muladd_func_ptr = Particle2_postprocessing_aa;
            break;

        case 12:
            self->muladd_func_ptr = Particle2_postprocessing_revaa;
            break;

        case 20:
            self->muladd_func_ptr = Particle2_postprocessing_ireva;
            break;

        case 21:
            self->muladd_func_ptr = Particle2_postprocessing_areva;
            break;

        case 22:
            self->muladd_func_ptr = Particle2_postprocessing_revareva;
            break;
    }
}

static void
Particle2_compute_next_data_frame(Particle2 *self)
{
    int i;
    MYFLT *tmp;
    int offset = self->chnl * self->bufsize;
    tmp = MainParticle2_getSamplesBuffer((MainParticle2 *)self->mainSplitter);

    for (i = 0; i < self->bufsize; i++)
    {
        self->data[i] = tmp[i + offset];
    }

    (*self->muladd_func_ptr)(self);
}

static int
Particle2_traverse(Particle2 *self, visitproc visit, void *arg)
{
    pyo_VISIT
    Py_VISIT(self->mainSplitter);
    return 0;
}

static int
Particle2_clear(Particle2 *self)
{
    pyo_CLEAR
    Py_CLEAR(self->mainSplitter);
    return 0;
}

static void
Particle2_dealloc(Particle2* self)
{
    pyo_DEALLOC
    Particle2_clear(self);
    Py_TYPE(self->stream)->tp_free((PyObject*)self->stream);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Particle2_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    int i;
    PyObject *maintmp = NULL, *multmp = NULL, *addtmp = NULL;
    Particle2 *self;
    self = (Particle2 *)type->tp_alloc(type, 0);

    self->modebuffer[0] = 0;
    self->modebuffer[1] = 0;

    INIT_OBJECT_COMMON
    Stream_setFunctionPtr(self->stream, Particle2_compute_next_data_frame);
    self->mode_func_ptr = Particle2_setProcMode;

    static char *kwlist[] = {"mainSplitter", "chnl", "mul", "add", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi|OO", kwlist, &maintmp, &self->chnl, &multmp, &addtmp))
        Py_RETURN_NONE;

    self->mainSplitter = (MainParticle2 *)maintmp;
    Py_INCREF(self->mainSplitter);

    if (multmp)
    {
        PyObject_CallMethod((PyObject *)self, "setMul", "O", multmp);
    }

    if (addtmp)
    {
        PyObject_CallMethod((PyObject *)self, "setAdd", "O", addtmp);
    }

    PyObject_CallMethod(self->server, "addStream", "O", self->stream);

    (*self->mode_func_ptr)(self);

    return (PyObject *)self;
}

static PyObject * Particle2_getServer(Particle2* self) { GET_SERVER };
static PyObject * Particle2_getStream(Particle2* self) { GET_STREAM };
static PyObject * Particle2_setMul(Particle2 *self, PyObject *arg) { SET_MUL };
static PyObject * Particle2_setAdd(Particle2 *self, PyObject *arg) { SET_ADD };
static PyObject * Particle2_setSub(Particle2 *self, PyObject *arg) { SET_SUB };
static PyObject * Particle2_setDiv(Particle2 *self, PyObject *arg) { SET_DIV };

static PyObject * Particle2_play(Particle2 *self, PyObject *args, PyObject *kwds) { PLAY };
static PyObject * Particle2_out(Particle2 *self, PyObject *args, PyObject *kwds) { OUT };
static PyObject * Particle2_stop(Particle2 *self, PyObject *args, PyObject *kwds) { STOP };

static PyObject * Particle2_multiply(Particle2 *self, PyObject *arg) { MULTIPLY };
static PyObject * Particle2_inplace_multiply(Particle2 *self, PyObject *arg) { INPLACE_MULTIPLY };
static PyObject * Particle2_add(Particle2 *self, PyObject *arg) { ADD };
static PyObject * Particle2_inplace_add(Particle2 *self, PyObject *arg) { INPLACE_ADD };
static PyObject * Particle2_sub(Particle2 *self, PyObject *arg) { SUB };
static PyObject * Particle2_inplace_sub(Particle2 *self, PyObject *arg) { INPLACE_SUB };
static PyObject * Particle2_div(Particle2 *self, PyObject *arg) { DIV };
static PyObject * Particle2_inplace_div(Particle2 *self, PyObject *arg) { INPLACE_DIV };

static PyMemberDef Particle2_members[] =
{
    {"server", T_OBJECT_EX, offsetof(Particle2, server), 0, "Pyo server."},
    {"stream", T_OBJECT_EX, offsetof(Particle2, stream), 0, "Stream object."},
    {"mul", T_OBJECT_EX, offsetof(Particle2, mul), 0, "Mul factor."},
    {"add", T_OBJECT_EX, offsetof(Particle2, add), 0, "Add factor."},
    {NULL}  /* Sentinel */
};

static PyMethodDef Particle2_methods[] =
{
    {"getServer", (PyCFunction)Particle2_getServer, METH_NOARGS, "Returns server object."},
    {"_getStream", (PyCFunction)Particle2_getStream, METH_NOARGS, "Returns stream object."},
    {"play", (PyCFunction)Particle2_play, METH_VARARGS | METH_KEYWORDS, "Starts computing without sending sound to soundcard."},
    {"out", (PyCFunction)Particle2_out, METH_VARARGS | METH_KEYWORDS, "Starts computing and sends sound to soundcard channel speficied by argument."},
    {"stop", (PyCFunction)Particle2_stop, METH_VARARGS | METH_KEYWORDS, "Stops computing."},
    {"setMul", (PyCFunction)Particle2_setMul, METH_O, "Sets Particle2 mul factor."},
    {"setAdd", (PyCFunction)Particle2_setAdd, METH_O, "Sets Particle2 add factor."},
    {"setSub", (PyCFunction)Particle2_setSub, METH_O, "Sets inverse add factor."},
    {"setDiv", (PyCFunction)Particle2_setDiv, METH_O, "Sets inverse mul factor."},
    {NULL}  /* Sentinel */
};

static PyNumberMethods Particle2_as_number =
{
    (binaryfunc)Particle2_add,                      /*nb_add*/
    (binaryfunc)Particle2_sub,                 /*nb_subtract*/
    (binaryfunc)Particle2_multiply,                 /*nb_multiply*/
    0,                /*nb_remainder*/
    0,                   /*nb_divmod*/
    0,                   /*nb_power*/
    0,                  /*nb_neg*/
    0,                /*nb_pos*/
    0,                  /*(unaryfunc)array_abs,*/
    0,                    /*nb_nonzero*/
    0,                    /*nb_invert*/
    0,               /*nb_lshift*/
    0,              /*nb_rshift*/
    0,              /*nb_and*/
    0,              /*nb_xor*/
    0,               /*nb_or*/
    0,                       /*nb_int*/
    0,                      /*nb_long*/
    0,                     /*nb_float*/
    (binaryfunc)Particle2_inplace_add,              /*inplace_add*/
    (binaryfunc)Particle2_inplace_sub,         /*inplace_subtract*/
    (binaryfunc)Particle2_inplace_multiply,         /*inplace_multiply*/
    0,        /*inplace_remainder*/
    0,           /*inplace_power*/
    0,       /*inplace_lshift*/
    0,      /*inplace_rshift*/
    0,      /*inplace_and*/
    0,      /*inplace_xor*/
    0,       /*inplace_or*/
    0,             /*nb_floor_divide*/
    (binaryfunc)Particle2_div,                       /*nb_true_divide*/
    0,     /*nb_inplace_floor_divide*/
    (binaryfunc)Particle2_inplace_div,                       /*nb_inplace_true_divide*/
    0,                     /* nb_index */
};

PyTypeObject Particle2Type =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_pyo.Particle2_base",         /*tp_name*/
    sizeof(Particle2),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)Particle2_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_as_async (tp_compare in Python 2)*/
    0,                         /*tp_repr*/
    &Particle2_as_number,             /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,  /*tp_flags*/
    "Particle2 objects. Reads one band from a MainParticle2 object.",           /* tp_doc */
    (traverseproc)Particle2_traverse,   /* tp_traverse */
    (inquiry)Particle2_clear,           /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Particle2_methods,             /* tp_methods */
    Particle2_members,             /* tp_members */
    0,                      /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,      /* tp_init */
    0,                         /* tp_alloc */
    Particle2_new,                 /* tp_new */
};