diff --git a/applications/lofar2/model/pfb_os/multirate_mixer.ipynb b/applications/lofar2/model/pfb_os/multirate_mixer.ipynb
index ebf514732ccbac8ac08ede5b35eae09a17b12e7d..1b47f011fe91aa3387415041cf64eb72cac04caf 100644
--- a/applications/lofar2/model/pfb_os/multirate_mixer.ipynb
+++ b/applications/lofar2/model/pfb_os/multirate_mixer.ipynb
@@ -20,7 +20,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 389,
+   "execution_count": 1,
    "id": "8043fa7b",
    "metadata": {},
    "outputs": [],
@@ -33,19 +33,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 390,
+   "execution_count": 2,
    "id": "fc530dbc",
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The autoreload extension is already loaded. To reload it, use:\n",
-      "  %reload_ext autoreload\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "# Auto reload module when it is changed\n",
     "%load_ext autoreload\n",
@@ -79,7 +70,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 391,
+   "execution_count": 3,
    "id": "5b37a1dc",
    "metadata": {},
    "outputs": [
@@ -148,7 +139,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 392,
+   "execution_count": 4,
    "id": "e5680c7b",
    "metadata": {},
    "outputs": [],
@@ -160,7 +151,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 393,
+   "execution_count": 5,
    "id": "74ca764f",
    "metadata": {},
    "outputs": [
@@ -189,7 +180,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 394,
+   "execution_count": 6,
    "id": "786af296",
    "metadata": {},
    "outputs": [],
@@ -220,7 +211,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 395,
+   "execution_count": 7,
    "id": "1bb76ada",
    "metadata": {},
    "outputs": [
@@ -252,7 +243,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 396,
+   "execution_count": 8,
    "id": "6ebc94aa",
    "metadata": {},
    "outputs": [
@@ -283,7 +274,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 397,
+   "execution_count": 9,
    "id": "3abeee86",
    "metadata": {},
    "outputs": [
@@ -339,7 +330,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 398,
+   "execution_count": 10,
    "id": "372445f4",
    "metadata": {},
    "outputs": [
@@ -371,7 +362,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 399,
+   "execution_count": 11,
    "id": "e74340e4",
    "metadata": {},
    "outputs": [],
@@ -390,7 +381,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 400,
+   "execution_count": 12,
    "id": "bd2add56",
    "metadata": {},
    "outputs": [],
@@ -415,17 +406,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 401,
+   "execution_count": 13,
    "id": "7106ad3f",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "[<matplotlib.lines.Line2D at 0x7fd6133cae80>]"
+       "[<matplotlib.lines.Line2D at 0x7f14108ec160>]"
       ]
      },
-     "execution_count": 401,
+     "execution_count": 13,
      "metadata": {},
      "output_type": "execute_result"
     },
@@ -465,7 +456,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 402,
+   "execution_count": 14,
    "id": "da53b25e",
    "metadata": {},
    "outputs": [],
@@ -477,17 +468,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 403,
+   "execution_count": 15,
    "id": "9acf0ec2",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "<matplotlib.legend.Legend at 0x7fd613deacd0>"
+       "<matplotlib.legend.Legend at 0x7f1410acb760>"
       ]
      },
-     "execution_count": 403,
+     "execution_count": 15,
      "metadata": {},
      "output_type": "execute_result"
     },
@@ -533,7 +524,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 404,
+   "execution_count": 16,
    "id": "b8036250",
    "metadata": {},
    "outputs": [
@@ -578,7 +569,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 405,
+   "execution_count": 17,
    "id": "0663df66",
    "metadata": {},
    "outputs": [],
@@ -594,7 +585,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 406,
+   "execution_count": 18,
    "id": "3a039428",
    "metadata": {},
    "outputs": [
@@ -638,7 +629,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 407,
+   "execution_count": 19,
    "id": "327236c2",
    "metadata": {},
    "outputs": [
@@ -675,7 +666,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 408,
+   "execution_count": 20,
    "id": "aefa8615",
    "metadata": {},
    "outputs": [
@@ -688,6 +679,7 @@
       "  . Nx       = 373\n",
       "  . Nblocks  = 32\n",
       "  . len(yc)  = 32\n",
+      "  . Ros      = 1.3333333333333333\n",
       "  . Ndown    = 12\n",
       "  . Ndft     = 16\n",
       "  . k        = 1\n",
@@ -698,10 +690,10 @@
     {
      "data": {
       "text/plain": [
-       "[<matplotlib.lines.Line2D at 0x7fd613329700>]"
+       "[<matplotlib.lines.Line2D at 0x7f140afb4d60>]"
       ]
      },
-     "execution_count": 408,
+     "execution_count": 20,
      "metadata": {},
      "output_type": "execute_result"
     },
@@ -738,7 +730,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 409,
+   "execution_count": 21,
    "id": "f814c9b9",
    "metadata": {},
    "outputs": [
@@ -780,7 +772,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 410,
+   "execution_count": 22,
    "id": "dd7a9503",
    "metadata": {},
    "outputs": [],
@@ -817,7 +809,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 411,
+   "execution_count": 23,
    "id": "3cf6aa74",
    "metadata": {},
    "outputs": [],
@@ -828,7 +820,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 412,
+   "execution_count": 24,
    "id": "03da0b30",
    "metadata": {},
    "outputs": [
@@ -849,7 +841,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 413,
+   "execution_count": 25,
    "id": "a5c60be5",
    "metadata": {},
    "outputs": [
@@ -904,7 +896,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 414,
+   "execution_count": 26,
    "id": "7049249e",
    "metadata": {},
    "outputs": [
@@ -938,7 +930,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 415,
+   "execution_count": 27,
    "id": "64cc34f3",
    "metadata": {},
    "outputs": [],
@@ -954,7 +946,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 416,
+   "execution_count": 28,
    "id": "71a91beb",
    "metadata": {},
    "outputs": [
@@ -1003,7 +995,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 417,
+   "execution_count": 29,
    "id": "a3aae48a",
    "metadata": {},
    "outputs": [
@@ -1040,7 +1032,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 418,
+   "execution_count": 30,
    "id": "3c2c8ec5",
    "metadata": {},
    "outputs": [
@@ -1067,7 +1059,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "a9e76fc1",
+   "id": "ee9daf9f",
    "metadata": {},
    "source": [
     "# 5 Compare with DFT filterbank"
@@ -1075,7 +1067,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "a6bce8dc",
+   "id": "d8bb436d",
    "metadata": {},
    "source": [
     "# 5.1 Analysis"
@@ -1083,8 +1075,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 419,
-   "id": "45850a76",
+   "execution_count": 31,
+   "id": "b0f997d3",
    "metadata": {},
    "outputs": [
     {
@@ -1092,17 +1084,19 @@
      "output_type": "stream",
      "text": [
       "> analysis_dft_filterbank():\n",
-      "  . len(x)   = 384\n",
-      "  . Nx       = 373\n",
-      "  . Nblocks  = 32\n",
-      "  . Ndown    = 12\n",
-      "  . Ndft     = 16\n",
+      "  . len(x)     = 384\n",
+      "  . Nx         = 373\n",
+      "  . Nblocks    = 32\n",
+      "  . Ros        = 1.3333333333333333\n",
+      "  . Ndown      = 12\n",
+      "  . Ndft       = 16\n",
+      "  . commutator = cw\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "Yc = analysis_dft_filterbank(xData, Ndown, Ndft, hPrototype, verbosity=1)\n",
+    "Yc = analysis_dft_filterbank(xData, Ndown, Ndft, hPrototype, commutator='cw', verbosity=1)\n",
     "yDownBin = Yc[kLo]\n",
     "\n",
     "result = np.all(np.isclose(yDown, yDownBin))\n",
@@ -1117,7 +1111,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "772350cd",
+   "id": "d3f49b8e",
    "metadata": {},
    "source": [
     "# 5.2 Synthesis"
@@ -1126,7 +1120,7 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "9a027306",
+   "id": "47d5bf5b",
    "metadata": {},
    "outputs": [],
    "source": []
diff --git a/applications/lofar2/model/rtdsp/multirate.py b/applications/lofar2/model/rtdsp/multirate.py
index 2388ea34388ea18bae5fa7ba092005c438042650..7c3d14862d2a630fe637f76fcaf960d5166a9ad9 100644
--- a/applications/lofar2/model/rtdsp/multirate.py
+++ b/applications/lofar2/model/rtdsp/multirate.py
@@ -28,10 +28,10 @@
 #
 # References:
 # [1] dsp_study_erko.txt
-# [2] http://localhost:8888/notebooks/pfb_os/up_downsampling.ipynb
+# [2] pfb_os/up_downsampling.ipynb, pfb_os/multirate_mixer.ipynb
 #
 # Books:
-# . HARRIS 6 title Figure
+# . HARRIS 6
 # . LYONS
 # . CROCHIERE
 
@@ -54,6 +54,17 @@ def ones(shape, cmplx=False):
     return zeros(shape, cmplx) + 1
 
 
+def fold(x):
+    """Circular reverse sequence around index 0.
+
+    x[n] --> flip(x) --> x[N - n - 1]
+    x[n] --> fold(x) -> x[-n] = x[N - n] = roll(flip(x), 1)
+
+    The index is modulo N, so x[N - 0] = x[N] = x[0].
+    """
+    return np.roll(np.flip(x), 1)
+
+
 def down(x, D, phase=0):
     """Downsample x[n] by factor D, xD[m] = x[m D], m = n // D
 
@@ -248,7 +259,7 @@ class PolyPhaseFirFilterStructure:
             and <= Nphases for downsampling.
         . flipped: False then inData order is inData[n] with newest sample last
             and still needs to be flipped. If True then the inData is already
-            flipped in time, so with newest sample first.
+            flipped in time, so with newest sample at inData[0].
         Return:
         . pfsData: block of polyphase FIR filtered output data for Nphases, with
             pfsData[p] and p = 0:Nphases-1 from top to bottom.
@@ -308,7 +319,7 @@ def polyphase_data_for_downsampling_whole_x(x, Ndown, Nzeros):
     """
     Lx = len(x)
     Nxp = (Nzeros + Lx) // Ndown  # Number of samples per polyphase
-    Nx = 1 + Ndown * (Nxp - 1)  # Used number of samples from x
+    Nx = Ndown * Nxp - Nzeros  # Used number of samples from x
 
     # Load x into polyX with Ndown rows = polyphases
     # . prepend x with Nzeros zeros
@@ -817,6 +828,8 @@ def non_maximal_downsample_bpf(x, Ndown, k, Ndft, coefs, verbosity=1):
     . yc: Downsampled and downconverted output signal yc[m], m = n // D for
           bin k. Complex baseband signal.
     """
+    Ros = Ndft / Ndown
+
     # Prepend x with Ndown - 1 zeros, and represent x in Nblocks of Ndown
     # samples
     Nzeros = Ndown - 1
@@ -849,6 +862,7 @@ def non_maximal_downsample_bpf(x, Ndown, k, Ndft, coefs, verbosity=1):
         print('  . Nx       =', str(Nx))
         print('  . Nblocks  =', str(Nblocks))
         print('  . len(yc)  =', str(len(yc)))  # = Nblocks
+        print('  . Ros      =', str(Ros))
         print('  . Ndown    =', str(Ndown))
         print('  . Ndft     =', str(Ndft))
         print('  . k        =', str(k))
@@ -937,6 +951,7 @@ def non_maximal_upsample_bpf(xBase, Nup, k, Ndft, coefs, verbosity=1):
         print('  . len(xBase) =', str(len(xBase)))
         print('  . Nblocks    =', str(Nblocks))
         print('  . len(yc)    =', str(len(yc)))  # = Nblocks
+        print('  . Ros        =', str(Ros))
         print('  . Nup        =', str(Nup))
         print('  . Ndft       =', str(Ndft))
         print('  . k          =', str(k))
@@ -944,7 +959,7 @@ def non_maximal_upsample_bpf(xBase, Nup, k, Ndft, coefs, verbosity=1):
     return yc
 
 
-def analysis_dft_filterbank(x, Ndown, Ndft, coefs, verbosity=1):
+def analysis_dft_filterbank(x, Ndown, Ndft, coefs, commutator, verbosity=1):
     """DFT filterbank with Ros = Ndft / Ndown.
 
     Implements WOLA structure for DFT filterbank [CROCHIERE 7.2.5]. Key steps:
@@ -967,23 +982,29 @@ def analysis_dft_filterbank(x, Ndown, Ndft, coefs, verbosity=1):
       exp(-j 2pi k m M / K) of the DFT output is equivalent to a circular time
       shift by l = (m M) % K samples of the DFT input. Circular time shift DFT
       theorem:
-
         x[n] <= DFT => X(k), then
         x[(n - l) % K] <= DFT => X(k) exp(-j 2pi k l / K)
+    - For counter clockwise commutator use Ndft * IDFT(pfsData).
+      For clockwise commutator use DFT(fold(pfsData)), because
+      DFT(x) = Ndft * IDFT(fold(x)).
 
     Input:
     . x: Input signal x[n]
     . Ndown: downsample factor and input block size
     . Ndft: DFT size, number of polyphases in PFS FIR filter
     . coefs: prototype LPF FIR filter coefficients for anti aliasing BPF
+    . commutator: 'ccw' to use IDFT or 'cw' to use DFT
     - verbosity: when > 0 print() status, else no print()
     Return:
     . Yc: Downsampled and downconverted output signal Yc[k, m], m = n // M for
           all Ndft bins k. Complex baseband signals.
     """
+    Ros = Ndft / Ndown
+
     # Prepend x with Ndown - 1 zeros, and represent x in Nblocks of Ndown
     # samples
     Nzeros = Ndown - 1
+    # Nzeros = 0
     xBlocks, Nx, Nblocks = polyphase_data_for_downsampling_whole_x(x, Ndown, Nzeros)
 
     # Prepare output
@@ -998,20 +1019,31 @@ def analysis_dft_filterbank(x, Ndown, Ndft, coefs, verbosity=1):
         inData = xBlocks[:, b]
         pfsData = pfs.filter_block(inData, flipped=True)
 
+        # Default with 'ccw' keep the pfsData to apply IDFT
+        if commutator == 'cw':
+            # For 'cw' fold (= circular flip) the pfsData to apply the DFT
+            pfsData = fold(pfsData)
+
         # Apply time (m) and bin (k) dependend phase shift W_K^(kmM) by circular
         # time shift of DFT input
         tShift = (b * Ndown) % Ndft
-        pfsShifted = np.roll(pfsData, -tShift)
 
-        # IDFT
-        Yc[:, b] = Ndft * np.fft.ifft(pfsShifted)
+        # For 'ccw' apply IDFT, for 'cw' apply DFT
+        if commutator == 'cw':  # DFT
+            pfsShifted = np.roll(pfsData, tShift)
+            Yc[:, b] = np.fft.fft(pfsShifted)
+        else:  # 'ccw', IDFT
+            pfsShifted = np.roll(pfsData, -tShift)
+            Yc[:, b] = np.fft.ifft(pfsShifted) * Ndft
 
     if verbosity:
         print('> analysis_dft_filterbank():')
-        print('  . len(x)   =', str(len(x)))
-        print('  . Nx       =', str(Nx))
-        print('  . Nblocks  =', str(Nblocks))
-        print('  . Ndown    =', str(Ndown))
-        print('  . Ndft     =', str(Ndft))
+        print('  . len(x)     =', str(len(x)))
+        print('  . Nx         =', str(Nx))
+        print('  . Nblocks    =', str(Nblocks))
+        print('  . Ros        =', str(Ros))
+        print('  . Ndown      =', str(Ndown))
+        print('  . Ndft       =', str(Ndft))
+        print('  . commutator =', commutator)
         print('')
     return Yc