# als-fft v3.2.0

See [change-log.md](change-log.md) for changes.

## Features

- **Short-Time Fourier Transform (STFT) Support**  
  Enabling time-frequency analysis of signals with configurable overlap and windowing.

- **Forward FFT:**  
  Compute the FFT of an input signal (array of real numbers or complex pairs).

- **Inverse FFT (IFFT):**  
  Reconstruct the time-domain signal from its frequency-domain representation.

- **Segmented FFT Processing:**  
  Automatically splits the input signal into fixed-size segments (with zero-padding when necessary) and computes the FFT for each segment.

- **Spectral Restoration:**  
  Restores the full spectrum from a half-spectrum using mirror symmetry.

- **Signal Restoration:**  
  Recombines FFT segments back into a continuous time-domain signal via the `samples` getter.

- **Cutting (Segment Removal) Functionality:**  
  Remove unwanted FFT segments by:
  - Directly specifying segment indices.
  - Specifying a range of segments.
  - Specifying a time interval (in milliseconds).

- **Flexible API:**  
  Use static methods for direct processing or instantiate the FFT class for segmented operations and dynamic editing.

- **Works in Node.js and Browsers:**  
  Available as a module for Node.js (via npm) and as a bundled script for browser environments.

---

## Installation

### Node.js

Install via npm:
```bash
npm install als-fft
```

Then require it in your project:
```js
const FFT = require("als-fft");
```

or 

```js
import FFT from 'als-fft';
```

### Browser

Include the bundled script (e.g., `fft.js`) in your HTML:
```html
<script src="node_modules/als-fft/fft.js"></script>
```
This exposes the `FFT` class as a global variable.

---

## API Reference

### Class: `FFT`

The `FFT` class provides both static and instance methods for FFT-based operations, signal restoration, and segment cutting.

#### Static Methods & Utilities

- **`FFT.fft(data)`**  
  Computes the forward Fast Fourier Transform of the input data.
  - **Parameters:**  
    - `data`: An array of length `N` (a power of 2) containing real numbers or complex pairs `[re, im]`.
  - **Returns:**  
    An array of complex pairs `[re, im]` representing the frequency-domain data.
  - **Example:**
    ```js
    const signal = [1, 0, 1, 0, 2, 0, 2, 0];
    const spectrum = FFT.fft(signal);
    console.log(spectrum);
    ```

- **`FFT.ifft(data)`**  
  Computes the inverse FFT (IFFT) of the input frequency-domain data.
  - **Parameters:**  
    - `data`: An array of complex pairs `[re, im]`.
  - **Returns:**  
    An array of real numbers representing the reconstructed time-domain signal.
  - **Example:**
    ```js
    const recovered = FFT.ifft(spectrum);
    console.log(recovered);
    ```

- **`FFT.restoreSymmetry(halfSpectrum)`**  
  Re-creates the full Hermitian spectrum from a half-spectrum that was
  produced when the FFT was run with `splited: true`.

  - **Parameters**  
    - `halfSpectrum` — array of complex pairs `[re, im]` of length `N / 2`.

  - **Returns**  
    An array of length `N` (complex pairs) where the negative-frequency
    part is rebuilt by mirror symmetry.

  - **Example**
    ```js
    const half = fftInstance.timePoints[0];      // stored half-spectrum
    const full = FFT.restoreSymmetry(half);
    ```


#### Instance Methods

**Constructor:**
```js
new FFT(samples, options)
```
- **Parameters:**
  - `samples`: An array (or Float32Array) of real-valued samples.
  - `options`: An object with the following properties:
    - `segmentSize` (number, required): The length of each FFT segment (must be a power of 2).
    - `splited` (boolean, optional, default `false`): Store FFT segments as half-spectra if `true`.
    - `sampleRate` (number, required for time-based cutting): The sample rate (in Hz) of the input signal.
    - `hopSizeFactor` (number, optional, default `1`): Fraction of `segmentSize` defining the step size between segments (e.g., `0.25` for 75% overlap).
    - `hannWindow` (boolean, optional, default `false`): Apply a Hann window to each segment if `true`.
    - `zeroPadding` (boolean, default **false**)  
      If **true**, each segment is zero-padded up to `targetSize` before
      the FFT.  Increases spectral resolution without changing the
      analysed window length.
    - `targetSize` (number, optional)  
      Absolute FFT size to pad to (must be a power of two and  
      `≥ segmentSize`).  Ignored unless `zeroPadding` is **true**.
    - `normalize` (boolean, default **false**)  
      When enabled, values returned in `.$timePoints` are divided by the
      effective FFT size (`segmentSize` or `targetSize`).  Useful for
      comparing spectra obtained with different padding factors.

- **`getComputedTimePoints()`**  
  Calculates and caches magnitude values `√(re² + im²)` for each frequency bin in every segment.
  - If the instance was created with `normalize: true` the magnitudes are additionally divided by the effective FFT size (`segmentSize` or `targetSize`), so spectra obtained with different padding factors remain comparable.
  - **Example:**
    ```js
    const fftInstance = new FFT(signal, { segmentSize: 1024, sampleRate: 44100 });
    fftInstance.getComputedTimePoints();
    ```

- **`get samples`**  
  A getter that restores and returns the time-domain signal from the current FFT segments (after any cuts).
  - **Example:**
    ```js
    const restoredSignal = fftInstance.samples;
    console.log(restoredSignal);
    ```
- **`freqStep`**
  freqStep property - frequency step for each time point

#### Cutting Methods

The FFT class provides cutting (segment removal) functionality via a helper instance exposed by the `cut` getter.

- **`fftInstance.cut.timepoints(arr)`**  
  Removes FFT segments whose indices are included in the provided array.
  - **Example:**
    ```js
    fftInstance.cut.timepoints([2, 3, 7]);
    ```

- **`fftInstance.cut.timepointsRange(from, to)`**  
  Removes FFT segments over a continuous index range.  
  *Note:* This method uses an internal `range()` helper to generate the indices.
  - **Example:**
    ```js
    fftInstance.cut.timepointsRange(5, 10);
    ```

- **`fftInstance.cut.byTime(from, to)`**  
  Removes FFT segments corresponding to a time interval specified in milliseconds.  
  The start and end times are converted to segment indices using the segment duration computed from `segmentSize` and `sampleRate`.
  - **Example:**
    ```js
    fftInstance.cut.byTime(1000, 2000); // Remove segments from 1s to 2s of the signal
    ```

For convenience, the FFT class also provides shortcut instance methods:

- **`cutTimepoints(arr)`** – equivalent to `fftInstance.cut.timepoints(arr)`.
- **`cutTimepointsRange(from, to)`** – equivalent to `fftInstance.cut.timepointsRange(from, to)`.
- **`cutByTime(from, to)`** – equivalent to `fftInstance.cut.byTime(from, to)`.

---

## Short-Time Fourier Transform (STFT) Support

Starting with version 3.2.0, `als-fft` supports Short-Time Fourier Transform (STFT), enabling time-frequency analysis of signals with configurable overlap and windowing. This is useful for applications like speech analysis, where frequency content changes over time.

### New Options
The `FFT` constructor now accepts additional options to enable STFT:

- **`hopSizeFactor` (number, optional, default `1`)**:  
  Defines the step size between segments as a fraction of `segmentSize`. A value less than 1 introduces overlap (e.g., `0.25` means 75% overlap, step size = `segmentSize * 0.25`).
- **`hannWindow` (boolean, optional, default `false`)**:  
  When `true`, applies a Hann window to each segment before computing the FFT, reducing spectral leakage.

### STFT Matrix
- **`get computeSTFTMatrix`**  
  A getter that returns a 2D array representing the STFT of the signal:
  - **Rows**: Frequency bins (half-spectrum if `splited = true`, full otherwise).
  - **Columns**: Time frames (number depends on `hopSizeFactor`).
  - **Values**: Amplitude magnitudes computed as `√(re² + im²)` for each frequency-time point.
  - **Example:**
    ```js
    const samples = new Float32Array(1024).fill(1);
    const fft = new FFT(samples, {
      segmentSize: 256,
      hopSizeFactor: 0.25,
      hannWindow: true,
      sampleRate: 44100,
      splited: true
    });
    const stftMatrix = fft.computeSTFTMatrix;
    console.log(stftMatrix); // [128 frequencies] × [13 time frames]
    ```

### Enhanced Signal Restoration
When using `hopSizeFactor < 1` and `hannWindow = true`, the `samples` getter restores the signal using overlap-add normalization to account for overlapping segments, ensuring accurate reconstruction.

### Example Usage

#### Basic STFT Analysis
```js
const FFT = require('als-fft');

// Example signal: 1 second of 100 Hz sine wave at 44100 Hz
const samples = new Float32Array(44100).map((_, i) => Math.sin(2 * Math.PI * 100 * i / 44100));
const fft = new FFT(samples, {
  segmentSize: 1024,
  hopSizeFactor: 0.25, // 256-point step, 75% overlap
  hannWindow: true,
  sampleRate: 44100,
  splited: true
});

// Get STFT matrix
const stftMatrix = fft.computeSTFTMatrix;
console.log('STFT Matrix:', stftMatrix);

// Restore signal
const restored = fft.samples;
console.log('Restored Signal Length:', restored.length);
```

#### Cutting with STFT
```js
const fft = new FFT(samples, {
  segmentSize: 1024,
  hopSizeFactor: 0.25,
  hannWindow: true,
  sampleRate: 44100
});

// Remove segments from 0.5s to 1s
fft.cut.byTime(500, 1000);

// Get updated STFT matrix
const updatedStftMatrix = fft.computeSTFTMatrix;
console.log('Updated STFT Matrix:', updatedStftMatrix);
```

---

## Zero-Padding & Normalisation  *(v3.4.0+)*

Zero-padding allows you to keep a short analysis window  
(e.g. 32 ms) while obtaining a denser frequency grid.

```js
const fft = new FFT(samples, {
  segmentSize : 512,     // 32 ms at 16 kHz
  zeroPadding : true,
  targetSize  : 2048,    // four-fold finer bin spacing
  normalize   : true     // keep amplitudes comparable
});
```

## Helper Class: `CutTimePoints`

This internal helper class is instantiated via the `cut` getter of an FFT instance. It provides methods to generate index ranges and remove FFT segments:

- **`range(start, end, step = 1)`**  
  Returns an array of numbers from `start` (inclusive) to `end` (exclusive) with the specified step.

- **`timepoints(arr)`**  
  Removes FFT segments at indices specified in the array `arr` and updates the computed time points.

- **`timepointsRange(from, to)`**  
  Convenience method that removes FFT segments over a continuous index range.

- **`byTime(from, to)`**  
  Converts the given time interval (in milliseconds) to segment indices and removes the corresponding FFT segments.

---

## Example Usage

### Node.js Example

```js
const FFT = require('als-fft');

// Example signal: 0.5 seconds of 100 Hz sine wave at 44100 Hz
const samples = new Float32Array(22050).map((_, i) => Math.sin(2 * Math.PI * 100 * i / 44100));
const fftInstance = new FFT(samples, {
  segmentSize: 1024,
  hopSizeFactor: 0.25,
  hannWindow: true,
  sampleRate: 44100
});

// Get STFT matrix
const stftMatrix = fftInstance.computeSTFTMatrix;
console.log("STFT Matrix:", stftMatrix);
```

### Browser Example

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>als-fft v3.2.0 Demo</title>
  <script src="fft.js"></script>
</head>
<body>
  <input type="file" id="fileInput" accept="audio/wav">
  <script>
    document.getElementById('fileInput').addEventListener('change', async (e) => {
      const file = e.target.files[0];
      if (!file) return;
      
      const arrayBuffer = await file.arrayBuffer();
      const samples = new Float32Array(arrayBuffer); // Simplified, requires WAV parsing
      
      const fftInstance = new FFT(samples, {
        segmentSize: 1024,
        hopSizeFactor: 0.25,
        hannWindow: true,
        sampleRate: 44100
      });
      
      // Get STFT matrix
      const stftMatrix = fftInstance.computeSTFTMatrix;
      console.log("STFT Matrix:", stftMatrix);
    });
  </script>
</body>
</html>
```
