A Rust library for generating Aegisub-like spectrogram images from audio data, optimized for WebAssembly.
This library's processing logic and visual output are a direct port of the spectrogram implementation found in Aegisub.
(The timeruler is not generated by this library)
The following functions are exposed to JavaScript via wasm-bindgen:
#[wasm_bindgen]
pub fn generate_spectrogram_image(
audio_data: &[f32],
sample_rate: u32,
fft_size: usize,
hop_length: usize,
img_width: usize,
img_height: usize,
gain: f32,
) -> Result<Vec<u8>, JsValue>
// If using the 'parallel' feature:
#[wasm_bindgen]
pub fn init_thread_pool(num_threads: usize) -> js_sys::PromiseJavaScript Example (with parallel support)
import init, {
generate_spectrogram_image,
initThreadPool,
} from "./pkg/wasm_spectrogram.js";
async function run() {
await init();
// This step is only needed if you compiled with the 'parallel' feature.
await initThreadPool(navigator.hardwareConcurrency);
// --- Your audio loading logic ---
// const audioData = getYourF32AudioData();
// const sampleRate = 48000;
// --------------------------------
const fftSize = 1024;
const hopLength = 128;
const imgWidth = 800;
const imgHeight = 600;
const gain = 9.0; // Adjust gain as needed
try {
const pixelData = generate_spectrogram_image(
audioData,
sampleRate,
fftSize,
hopLength,
imgWidth,
imgHeight,
gain,
);
// 4. `pixelData` is a Uint8Array of [R, G, B, A, R, G, B, A, ...]
// You can now render this to a <canvas>
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
canvas.width = imgWidth;
canvas.height = imgHeight;
const imgData = new ImageData(
new Uint8ClampedArray(pixelData.buffer),
imgWidth,
imgHeight,
);
// The image is rendered from bottom-up, but canvas putImageData
// works top-down, so it will appear correctly oriented.
ctx.putImageData(imgData, 0, 0);
} catch (e) {
console.error("Error generating spectrogram:", e);
}
}
run();You can also use this library in a native Rust application by enabling the native_api module.
use wasm_spectrogram::native_api::generate_spectrogram_image_native;
// --- Your audio loading logic ---
// let audio_data: Vec<f32> = ...;
// let sample_rate = 48000;
// --------------------------------
let pixels = generate_spectrogram_image_native(
&audio_data,
sample_rate,
1024,
128,
800,
600,
9.0
).unwrap();
// `pixels` is a Vec<u8> of RGBA data
// You can now save this with a crate like `image`By default, native processing is single-threaded. If you want to enable multi-threading via Rayon, add the parallel feature to your Cargo.toml:
wasm-spectrogram = { version = "0.1", features = ["parallel"] }-
Install the Toolchain: The required toolchain is specified in
rust-toolchain.toml(nightly-2025-11-04). You can change it to latest version if you like.rustup toolchain install nightly-2025-11-04 rustup target add wasm32-unknown-unknown --toolchain nightly-2025-11-04
-
Install
wasm-pack:cargo install wasm-pack
-
Build (Serial - No Threads): This builds the standard, single-threaded Wasm module. We use a Python build script to handle cross-platform environments automatically.
python build.py
-
Build (Parallel - With Threads): To enable multi-threading, simply pass the
--parallelflag to the script. This will automatically inject the necessaryRUSTFLAGS(for Atomics and Shared Memory) and compile the wasm.python build.py --parallel
⚠️ Important: To use the multi-threaded build, your web server must set the following headers to enable Cross-Origin Isolation. This is required forSharedArrayBuffer(which powers Wasm threads) to work.Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp
