← All tasks
javascriptcodex/javascript-t1 #44Not a task: repair changed code

FFT Spectrum Analyzer (javascript, written by Codex)

envgap__codex__javascript-t1-44

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

Named export fft not found - fft-js is CJS module
Not a benchmark task.
  • Its repair changed source code, so it is not an environment task.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/javascript-t1 #44 · read the task the agent was given
Codex wrote this javascript project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: FFT Spectrum Analyzer

Write a program that performs Fast Fourier Transform (FFT) analysis on time-domain signal data, identifying dominant frequencies, computing power spectral density, and supporting windowing functions.

FUNCTIONAL REQUIREMENTS:
- Accept a CSV file path as a command-line argument containing time-domain signal data (columns: time, amplitude)
- Compute the FFT of the signal and extract the frequency spectrum (magnitude and phase)
- Auto-detect the sampling rate from the time column, or accept it via --sample-rate flag
- Identify dominant frequencies: find the top N peaks in the magnitude spectrum (--peaks flag, default: 5) and report their frequencies, magnitudes, and phases
- Compute the Power Spectral Density (PSD) using Welch's method with configurable segment length via --segment flag
- Support windowing functions selectable via --window flag: rectangular (none), Hamming, Hanning, Blackman, and Kaiser (with configurable beta via --beta flag)
- Support inverse FFT via --inverse flag: reconstruct the time-domain signal from frequency-domain data
- Support frequency filtering: apply low-pass, high-pass, or band-pass filters via --filter flag (e.g., --filter low:1000 for 1kHz low-pass) and output the filtered signal
- Export the frequency spectrum data as CSV via --export flag
- Print analysis summary to console: sampling rate, number of samples, frequency resolution, dominant frequencies with magnitudes, and total signal power
- Save the full analysis as JSON with --output flag (default: fft_analysis.json)
- If no input is given, generate a sample signal composed of three sine waves at known frequencies (100Hz, 250Hz, 800Hz) with added white noise, sampled at 4000Hz for 1 second, analyze it, and show that the FFT correctly identifies the three component frequencies
- Handle errors: non-uniform sampling, insufficient data points, and signals with DC offset

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

4 files, exactly as written, before any repair.

package-lock.json
{
  "name": "fft-spectrum-analyzer",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "fft-spectrum-analyzer",
      "version": "1.0.0",
      "license": "MIT",
      "dependencies": {
        "fft-js": "0.0.12"
      },
      "engines": {
        "node": ">=20.0.0"
      }
    },
    "node_modules/bit-twiddle": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz",
      "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==",
      "license": "MIT"
    },
    "node_modules/commander": {
      "version": "2.7.1",
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.7.1.tgz",
      "integrity": "sha512-5qK/Wsc2fnRCiizV1JlHavWrSGAXQI7AusK423F8zJLwIGq8lmtO5GmO8PVMrtDUJMwTXOFBzSN6OCRD8CEMWw==",
      "license": "MIT",
      "dependencies": {
        "graceful-readlink": ">= 1.0.0"
      },
      "engines": {
        "node": ">= 0.6.x"
      }
    },
    "node_modules/fft-js": {
      "version": "0.0.12",
      "resolved": "https://registry.npmjs.org/fft-js/-/fft-js-0.0.12.tgz",
      "integrity": "sha512-nLOa0/SYYnN2NPcLrI81UNSPxyg3q0sGiltfe9G1okg0nxs5CqAwtmaqPQdGcOryeGURaCoQx8Y4AUkhGTh7IQ==",
      "license": "MIT",
      "dependencies": {
        "bit-twiddle": "~1.0.2",
        "commander": "~2.7.1"
      },
      "engines": {
        "node": ">=0.12.0"
      }
    },
    "node_modules/graceful-readlink": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
      "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==",
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "fft-spectrum-analyzer",
  "version": "1.0.0",
  "description": "FFT and PSD analyzer",
  "type": "module",
  "main": "src/index.js",
  "scripts": { "start": "node src/index.js" },
  "engines": { "node": ">=20.0.0" },
  "dependencies": {
    "fft-js": "0.0.12"
  },
  "license": "MIT"
}
README.md
# FFT Spectrum Analyzer (JavaScript)

## Requirements
- Ubuntu 22.04
- Node.js 20+

## Install
```bash
npm install
```

## Run
```bash
node src/index.js signal.csv --peaks 5 --window hamming
node src/index.js signal.csv --sample-rate 4000 --segment 512 --filter low:1000 --export spectrum.csv
node src/index.js signal.csv --inverse --output fft_analysis.json
```

If no input is given, a synthetic signal is generated (100Hz, 250Hz, 800Hz + noise).
src/index.js
import fs from "node:fs";
import path from "node:path";
import { fft, ifft } from "fft-js";

function parseArgs(argv) {
  const cfg = {
    file: null,
    sampleRate: null,
    peaks: 5,
    segment: 256,
    window: "rectangular",
    beta: 8,
    inverse: false,
    filter: null,
    export: null,
    output: "fft_analysis.json"
  };
  const pos = [];
  for (let i = 0; i < argv.length; i += 1) {
    const a = argv[i];
    if (!a.startsWith("--")) { pos.push(a); continue; }
    if (a === "--sample-rate") cfg.sampleRate = Number(argv[++i]);
    else if (a === "--peaks") cfg.peaks = Number.parseInt(argv[++i], 10);
    else if (a === "--segment") cfg.segment = Number.parseInt(argv[++i], 10);
    else if (a === "--window") cfg.window = argv[++i];
    else if (a === "--beta") cfg.beta = Number(argv[++i]);
    else if (a === "--inverse") cfg.inverse = true;
    else if (a === "--filter") cfg.filter = argv[++i];
    else if (a === "--export") cfg.export = argv[++i];
    else if (a === "--output") cfg.output = argv[++i];
    else throw new Error(`Unknown option: ${a}`);
  }
  if (pos.length > 0) cfg.file = pos[0];
  return cfg;
}

function parseCsv(file) {
  const lines = fs.readFileSync(file, "utf8").trim().split(/\r?\n/);
  const rows = lines.slice(1).map((l) => l.split(",").map((x) => Number(x.trim())));
  return { t: rows.map((r) => r[0]), x: rows.map((r) => r[1]) };
}

function generateSample(file) {
  const sr = 4000;
  const N = 4000;
  const lines = ["time,amplitude"];
  for (let i = 0; i < N; i += 1) {
    const t = i / sr;
    const x =
      1.2 * Math.sin(2 * Math.PI * 100 * t) +
      0.8 * Math.sin(2 * Math.PI * 250 * t + 0.7) +
      0.5 * Math.sin(2 * Math.PI * 800 * t + 1.2) +
      (Math.random() - 0.5) * 0.35;
    lines.push(`${t.toFixed(6)},${x.toFixed(8)}`);
  }
  fs.writeFileSync(file, `${lines.join("\n")}\n`, "utf8");
}

function autoSampleRate(t) {
  if (t.length < 2) throw new Error("Insufficient data points");
  const diffs = [];
  for (let i = 1; i < t.length; i += 1) diffs.push(t[i] - t[i - 1]);
  const avg = diffs.reduce((s, d) => s + d, 0) / diffs.length;
  const varErr = diffs.reduce((s, d) => s + (d - avg) ** 2, 0) / diffs.length;
  if (Math.sqrt(varErr) > avg * 0.05) throw new Error("Non-uniform sampling detected");
  return 1 / avg;
}

function windowValue(name, n, N, beta = 8) {
  if (name === "rectangular") return 1;
  if (name === "hamming") return 0.54 - 0.46 * Math.cos((2 * Math.PI * n) / (N - 1));
  if (name === "hanning") return 0.5 * (1 - Math.cos((2 * Math.PI * n) / (N - 1)));
  if (name === "blackman") return 0.42 - 0.5 * Math.cos((2 * Math.PI * n) / (N - 1)) + 0.08 * Math.cos((4 * Math.PI * n) / (N - 1));
  if (name === "kaiser") {
    const i0 = (x) => {
      let s = 1; let term = 1;
      for (let k = 1; k < 25; k += 1) { term *= (x * x) / (4 * k * k); s += term; }
      return s;
    };
    const alpha = (N - 1) / 2;
    const r = (n - alpha) / alpha;
    return i0(beta * Math.sqrt(1 - r * r)) / i0(beta);
  }
  return 1;
}

function applyWindow(x, name, beta) {
  return x.map((v, i) => v * windowValue(name, i, x.length, beta));
}

function toSpectrum(Xf, sr) {
  const N = Xf.length;
  const half = Math.floor(N / 2);
  const freq = [];
  const mag = [];
  const phase = [];
  for (let k = 0; k <= half; k += 1) {
    const re = Xf[k][0];
    const im = Xf[k][1];
    freq.push((k * sr) / N);
    mag.push(Math.sqrt(re * re + im * im));
    phase.push(Math.atan2(im, re));
  }
  return { freq, mag, phase };
}

function findPeaks(spec, n) {
  const peaks = [];
  for (let i = 1; i < spec.mag.length - 1; i += 1) {
    if (spec.mag[i] > spec.mag[i - 1] && spec.mag[i] > spec.mag[i + 1]) peaks.push(i);
  }
  peaks.sort((a, b) => spec.mag[b] - spec.mag[a]);
  return peaks.slice(0, n).map((i) => ({ frequency: spec.freq[i], magnitude: spec.mag[i], phase: spec.phase[i] }));
}

function welchPsd(x, sr, segLen) {
  const step = Math.floor(segLen / 2);
  const psd = [];
  let count = 0;
  for (let start = 0; start + segLen <= x.length; start += step) {
    const seg = x.slice(start, start + segLen);
    const Xf = fft(seg);
    for (let k = 0; k <= segLen / 2; k += 1) {
      const re = Xf[k][0];
      const im = Xf[k][1];
      const p = (re * re + im * im) / (segLen * sr);
      if (!psd[k]) psd[k] = 0;
      psd[k] += p;
    }
    count += 1;
  }
  for (let i = 0; i < psd.length; i += 1) psd[i] /= Math.max(1, count);
  const freq = Array.from({ length: psd.length }, (_, k) => (k * sr) / segLen);
  return { freq, psd };
}

function applyFilter(Xf, sr, filterSpec) {
  if (!filterSpec) return Xf;
  const N = Xf.length;
  const half = Math.floor(N / 2);
  const out = Xf.map((c) => [...c]);

  const keep = (f) => {
    if (filterSpec.startsWith("low:")) return f <= Number(filterSpec.split(":")[1]);
    if (filterSpec.startsWith("high:")) return f >= Number(filterSpec.split(":")[1]);
    if (filterSpec.startsWith("band:")) {
      const [a, b] = filterSpec.split(":")[1].split("-").map(Number);
      return f >= a && f <= b;
    }
    return true;
  };

  for (let k = 0; k <= half; k += 1) {
    const f = (k * sr) / N;
    if (!keep(f)) {
      out[k] = [0, 0];
      if (k !== 0 && k !== half) out[N - k] = [0, 0];
    }
  }
  return out;
}

function main() {
  try {
    const cfg = parseArgs(process.argv.slice(2));
    if (!cfg.file) {
      cfg.file = path.resolve("sample_signal.csv");
      generateSample(cfg.file);
    }

    const { t, x: raw } = parseCsv(cfg.file);
    if (raw.length < 8) throw new Error("Insufficient data points");

    const sr = cfg.sampleRate ?? autoSampleRate(t);
    const x = applyWindow(raw, cfg.window, cfg.beta);

    const Xf = fft(x);
    const spectrum = toSpectrum(Xf, sr);
    const peaks = findPeaks(spectrum, cfg.peaks);
    const psd = welchPsd(x, sr, cfg.segment);

    let reconstructed = null;
    if (cfg.inverse) reconstructed = ifft(Xf).map((c) => c[0]);

    let filteredSignal = null;
    if (cfg.filter) {
      const filteredF = applyFilter(Xf, sr, cfg.filter);
      filteredSignal = ifft(filteredF).map((c) => c[0]);
    }

    if (cfg.export) {
      const lines = ["frequency,magnitude,phase"];
      for (let i = 0; i < spectrum.freq.length; i += 1) lines.push(`${spectrum.freq[i]},${spectrum.mag[i]},${spectrum.phase[i]}`);
      fs.writeFileSync(cfg.export, `${lines.join("\n")}\n`, "utf8");
    }

    const totalPower = raw.reduce((s, v) => s + v * v, 0) / raw.length;
    const report = {
      config: cfg,
      samplingRate: sr,
      samples: raw.length,
      frequencyResolution: sr / raw.length,
      dominantFrequencies: peaks,
      totalSignalPower: totalPower,
      spectrum,
      psd,
      reconstructed,
      filteredSignal
    };

    console.log(`Sampling rate: ${sr.toFixed(3)} Hz`);
    console.log(`Samples: ${raw.length}`);
    console.log(`Resolution: ${(sr / raw.length).toFixed(6)} Hz`);
    console.log("Top peaks:");
    peaks.forEach((p, i) => console.log(`${i + 1}. f=${p.frequency.toFixed(3)}Hz mag=${p.magnitude.toFixed(5)} phase=${p.phase.toFixed(4)}`));

    fs.writeFileSync(cfg.output, JSON.stringify(report, null, 2), "utf8");
  } catch (err) {
    console.error(`Error: ${err.message}`);
    process.exit(1);
  }
}

main();