Skip to content

Advanced Examples

By combining pointer classes, you can build powerful, high-level abstractions for complex tasks.

Image Processing Pipeline

This function shows a multi-step image processing pipeline where multiple filters are applied sequentially to the same memory buffer before reading the result.

typescript
async function processImage(imageData: ImageData, wasm: WASMModule) {
    const pixels = Array.from(imageData.data);

    // Create a single WASM array pointer for the entire pipeline
    const pixelPtr = ArrayPointer.from(wasm, "i8", pixels.length, pixels);

    // Apply multiple filters in place
    wasm._blur_filter(pixelPtr.ptr, imageData.width, imageData.height, 3);
    wasm._sharpen_filter(pixelPtr.ptr, imageData.width, imageData.height);
    wasm._color_correction(
        pixelPtr.ptr,
        imageData.width,
        imageData.height,
        1.2
    );

    // Get the final processed data and free the memory
    const processedPixels = pixelPtr.readAndFree();

    // Convert back to ImageData to be drawn on a canvas
    const processedImageData = new ImageData(
        new Uint8ClampedArray(processedPixels),
        imageData.width,
        imageData.height
    );

    return processedImageData;
}

Matrix Class Wrapper

You can create a JavaScript class that encapsulates matrix operations, using wasp-lib internally to manage memory. This provides a clean, object-oriented API to consumers.

typescript
class Matrix {
    private data: ArrayPointer<"double", number>;

    constructor(
        private wasm: WASMModule,
        private rows: number,
        private cols: number,
        initialData?: number[]
    ) {
        this.data = ArrayPointer.from(
            wasm,
            "double",
            rows * cols,
            initialData || new Array(rows * cols).fill(0)
        );
    }

    multiply(other: Matrix): Matrix {
        if (this.cols !== other.rows) {
            throw new Error("Matrix dimensions are incompatible");
        }
        const result = new Matrix(this.wasm, this.rows, other.cols);
        this.wasm._matrix_multiply(
            this.data.ptr,
            other.data.ptr,
            result.data.ptr,
            this.rows,
            this.cols,
            other.cols
        );
        return result;
    }

    toArray(): number[] {
        return [...this.data.read()];
    }

    free(): void {
        this.data.free();
    }
}

Real-time Audio Processing

For real-time applications like audio processing, pointers can be allocated once and reused for each audio buffer, minimizing performance overhead.

typescript
class AudioProcessor {
    private leftChannel: ArrayPointer<"float", number>;
    private rightChannel: ArrayPointer<"float", number>;

    constructor(
        private wasm: WASMModule,
        private bufferSize: number
    ) {
        // Allocate pointers once and reuse them for each processing block
        this.leftChannel = ArrayPointer.alloc(wasm, "float", bufferSize);
        this.rightChannel = ArrayPointer.alloc(wasm, "float", bufferSize);
    }

    processAudio(left: Float32Array, right: Float32Array) {
        // Copy audio samples to WASM memory
        this.leftChannel.write([...left]);
        this.rightChannel.write([...right]);

        // Apply effects in WASM
        this.wasm._apply_reverb(
            this.leftChannel.ptr,
            this.rightChannel.ptr,
            this.bufferSize
        );
        this.wasm._apply_compressor(
            this.leftChannel.ptr,
            this.rightChannel.ptr,
            this.bufferSize
        );

        // Read processed samples back
        const processedLeft = new Float32Array(this.leftChannel.read());
        const processedRight = new Float32Array(this.rightChannel.read());

        return { left: processedLeft, right: processedRight };
    }

    free(): void {
        this.leftChannel.free();
        this.rightChannel.free();
    }
}