FRFT in the Browser

FRFT in the Browser

WebAssembly ports of the Fractional Fourier Transform with a collection of ready-to-embed interactive browser widgets.

Status: Finished, under maintenance
Source Code Report an Issue

The source code can be found in the /web directory of the repository linked above.

These widgets have been developed by Behzad Haki and Esteban Gutiérrez

Embedding in your pages

Hosting

Adjust the parameters for each widget below, then click Apply to update the live preview and generate a ready-to-paste <iframe> embed snippet with those values baked in as defaults.

FT Spectrum

Shows the magnitude spectrum of the full signal (32768-sample window). Switch between Exact, Hann, and Rect windowing directly inside the embedded widget.

Exact snaps the signal frequency to the nearest DFT bin, then computes the spectrum analytically from the known harmonic series of each waveform. The result is mathematically ideal: spikes land at exact bin positions with absolute silence between them — no windowing artefacts, no spectral leakage. Hann and Rect use the standard FFT path; Hann suppresses leakage at the cost of a wider main lobe, while Rect gives the narrowest peaks but leaks when the frequency falls between bins.


  

Listen to FT Directly

Displays the real part, imaginary part, or magnitude of the Fourier Transform of the selected signal. Hit Play to hear what the FT sounds like: the selected component is played back as audio while a playhead scrubs left-to-right from −fNyq to +fNyq. The x-axis switches from frequency to time during playback, then reverts when playback stops. A Hann window is always applied.

Switch between Re, and Im to explore FT structure: for a real signal, Re[FT] is even-symmetric and Im[FT] is odd-symmetric around DC; |FT| is always even. Try a 440 Hz sine — the imaginary part shows two clean spikes, and the audio is a pair of impulses.


  

FRFT Rotation Diagram

Shows the FRFT output at 16 fractional orders simultaneously — four large panels at the cardinal positions (time, frequency, reversed-time, inverse-FT) and twelve small panels at intermediate α values arranged around a circle. Click any play button to hear the output at that order; an arrow in the centre tracks the active position.


  

FRFT Index Additivity

Demonstrates that FRFT(α₂) ∘ FRFT(α₁) = FRFT(α₁+α₂). The widget shows all four stages — input, intermediate (after α₁), sequential output (after α₂), and direct output (α₁+α₂) — and draws a dashed green equality line between the two outputs once computation completes. Click any panel to hear the audio at that stage.


  

Interactive FRFT

Full controls — let visitors change signal type, α order, block size and overlap directly in the widget. To pre-load your own audio file, add url= pointing to any same-origin path, e.g. url=/assets/audio/my-file.mp3. The widget fetches and decodes it automatically on load, and the file picker is hidden.


  

Non-interactive FRFT

Fixed parameters set at embed time — auto-plays on load, no controls shown to the visitor. To use your own audio file, add url= pointing to any same-origin path, e.g. url=/assets/audio/my-file.mp3. The file is fetched and decoded automatically — no file picker needed.


  

Alpha (α) Sweep

Interactive slider that sweeps the fractional order α — shows how the spectrum rotates from time to frequency domain.


  

Overlap-Add Visualiser

Shows how block-processing artefacts change with different block sizes and overlap factors.


  

Building your own

Resources

The engine is written in C++ and compiled to WebAssembly. All usage examples and widgets are in plain JavaScript — no framework or bundler required.

Three files are included in the ZIP:

File Size Role
wasm/dist/frft.js 45 KB JS glue — loads and wraps the WASM module
wasm/dist/frft.wasm 45 KB Compiled FRFT engine
audio/frft-worker.js 13 KB Web Worker — runs processing off the main thread

The worker keeps heavy computation off the UI thread. Drop the three files into your project and preserve their relative paths — frft-worker.js expects ../wasm/dist/frft.js alongside it.

// 1. Start the worker
const worker = new Worker('audio/frft-worker.js');

// 2. Load the WASM engine
worker.postMessage({
  type: 'init',
  jsUrl:       new URL('wasm/dist/frft.js', location.href).href,
  wasmBaseUrl: new URL('wasm/dist/', location.href).href,
});

// 3. Wait for ready, then process
worker.onmessage = ({ data }) => {
  if (data.type === 'ready') {
    worker.postMessage({
      type: 'process',
      samples:      myFloat32Array,   // mono audio samples
      alpha:        0.5,              // fractional order (0–4)
      bufSize:      16384,            // block size
      overlapFactor: 4,               // overlap (1, 2, 4, 8 …)
      halfSpectrum: false,
      jobId:        1,
    });
  }
  if (data.type === 'result') {
    const output = data.output;       // Float32Array — FRFT of the input
  }
  if (data.type === 'progress') {
    console.log('progress:', data.value); // 0..1
  }
};

// Cancel a running job at any time
worker.postMessage({ type: 'cancel' });

Using the WASM module directly

For offline or non-audio use, load the module directly on the main thread:

import FRFTModule from './wasm/dist/frft.js';

const Module = await FRFTModule();
const proc   = new Module.FRFTProcessor();

proc.prepare(512);

// Write input into WASM memory (zero-copy)
const inR = new Float64Array(Module.HEAPF64.buffer, proc.inputRealPtr(), 512);
inR.set(mySamples);

// Run FRFT
proc.process(512, /* alpha */ 0.5);

// Read result
const outR = new Float64Array(Module.HEAPF64.buffer, proc.outputRealPtr(), 512);

Or use the convenience wrapper (allocates internally):

const result = proc.processArrays(realIn, imagIn, alpha);
// result.real → Float64Array
// result.imag → Float64Array

For the full build guide (recompiling from C++, Emscripten setup, AudioWorklet wiring) see web/README.md in the repo.

Cascading FRFTs

The FRFT engine stores its output in centered form (DC at N/2, equivalent to an fftshift of the mathematical result). This has no visible effect for a single transform, but it matters when you compose two FRFTs — the shift must be accounted for between steps or the additivity property FRFT(α₂)[FRFT(α₁)[x]] = FRFT(α₁+α₂)[x] will not hold.

Use returnComplex: true on the first job to retrieve the raw complex engine output, then rawInput: true on the second job to feed it back in. The worker handles the required shift internally.

let intermediate = null, intermediateImag = null;

worker.onmessage = ({ data }) => {
  if (data.type === 'ready') {
    // Step 1 — FRFT(α₁): get raw complex engine output
    worker.postMessage({
      type: 'process',
      samples: myFloat32Array,
      alpha: alpha1,
      bufSize: 16384, overlapFactor: 4,
      returnComplex: true,   // returns { output, outputImag }
      jobId: 1,
    });
  }

  if (data.type === 'result') {
    if (data.jobId === 1) {
      intermediate     = data.output;      // Float32Array — real part
      intermediateImag = data.outputImag;  // Float32Array — imag part

      // Step 2 — FRFT(α₂) on complex intermediate
      worker.postMessage({
        type: 'process',
        samples:     intermediate,
        samplesImag: intermediateImag,
        alpha: alpha2,
        bufSize: 16384, overlapFactor: 4,
        rawInput: true,   // fftshifts the complex input, no analysis window
        jobId: 2,
      });
    }

    if (data.jobId === 2) {
      // data.output ≡ FRFT(α₁+α₂)[myFloat32Array]
    }
  }
};

Any number of steps can be chained this way. Each intermediate step uses returnComplex: true; only the final step omits it.

Via processArrays directly

processArrays returns its result in centered form. Before passing it as input to the next call, apply an ifftshift (circular shift by N/2):

const proc = new Module.FRFTProcessor();
proc.prepare(N);
const zeros = new Float64Array(N);

// Step 1
const r1 = proc.processArrays(myFloat64Array, zeros, alpha1);

// Undo the output's centered form before the next step
const H = N >> 1;
const r1r = new Float64Array(N), r1i = new Float64Array(N);
for (let i = 0; i < N; i++) {
  r1r[i] = r1.real[(i + H) % N];
  r1i[i] = r1.imag[(i + H) % N];
}

// Step 2
const r2 = proc.processArrays(r1r, r1i, alpha2);
// r2.real ≈ FRFT(α₁+α₂)[myFloat64Array]