Lab
The spectrum viewer opens NMR, infrared, Raman, UV-Vis and mass spectra for analytical and QC labs, contract research and teaching: JCAMP-DX files in every compressed form, compound files of several spectra, and columns of numbers from an instrument's export or a spreadsheet. Each kind is drawn the way it is read - ppm or hertz, wavenumbers or micrometres, nanometres or electronvolts, transmittance or absorbance - with NMR and infrared running from high to low, overlaid on one scale or stacked one above another, and zoomed by dragging, with the buttons or from the keyboard.
Peaks are labelled with their positions and widths; integrals carry their running curves and are read against a reference, for NMR a number of protons. Baseline correction, Savitzky-Golay smoothing and normalisation apply to what is drawn and to everything measured from it. Search a library of your own spectra - none come with the viewer - take one spectrum from another, and take the work away as a PDF report, an SVG chart, CSV or JCAMP-DX.
Try it with your own spectra in confidence: JCAMP-DX and CSV files are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
Checking a batch by proton NMR
Wrenfield Analytical, a made-up contract lab, checks a batch of ethyl 4-methoxybenzoate that a made-up client has made, by proton NMR in deuterochloroform at 400 MHz. The chemistry is real; the lab, the client, the batch and the spectrum are invented, and this page writes the spectrometer's JCAMP-DX file itself. Open it, read it in ppm, label the peaks, integrate against the methoxy group's three protons, look closer at one region, take the baseline off and smooth it, and send the client the report.
// Node 20 or later, from the Gold package: check each night's proton spectra for ethyl acetate, and write their reports.
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
import { readSpectrumFile, shownSpectrum, findPeaks, integrate, writeSpectrumPdf } from './dist/node/bmx-spectrum-viewer.mjs';
const processing = { baseline: { method: 'als', stiffness: 8 }, smooth: { window: 7, order: 2 } };
for (const name of readdirSync('nmr').filter(f => f.endsWith('.jdx'))) {
const [spectrum] = readSpectrumFile(readFileSync(`nmr/${name}`, 'latin1'), { name }).spectra;
const { x, y } = shownSpectrum(spectrum, 'ppm', 'recorded', processing);
const proton = integrate(x, y, 3.81, 3.91).area / 3; // the methoxy singlet is 3 H
const acetate = integrate(x, y, 2.02, 2.08).area / 3 / proton; // mol of ethyl acetate per mol
const lines = findPeaks(x, y, { threshold: 0.03, from: 0.5, to: 8.5 }).length;
console.log(name, `${lines} lines`, `ethyl acetate ${acetate.toFixed(3)} mol/mol`, acetate > 0.02 ? 'DRY AGAIN' : 'pass');
writeFileSync(name.replace(/\.jdx$/, '.pdf'), await writeSpectrumPdf({
spectra: [spectrum], unit: 'ppm', processing, peakThreshold: 0.03,
integrals: [{ spectrum: spectrum.id, from: 3.81, to: 3.91, reference: 3 }, { spectrum: spectrum.id, from: 2.02, to: 2.08 }],
}, { title: `${spectrum.title}, proton` }));
}
Incoming solvents, against the lab's own library
The same made-up lab records an infrared spectrum of every drum of solvent it takes in. This morning's three lots come off the spectrometer as one CSV file, in percent transmittance, and each is searched against the lab's reference library: eight spectra of the solvents it buys, invented and made by this page - none come with the viewer. See the lots as absorbance, overlaid and stacked, search them, take the best hit from a lot that does not pass to see what else is in it, and keep the record as CSV, JCAMP-DX and an SVG chart.