Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Browser build and WebAssembly integration

Copperline compiles to WebAssembly with a canvas and Web Audio frontend. A hosted instance is available at copperline.dev/try.

This chapter covers using the browser build, architecture details, building locally, and embedding the emulator into your own web applications.

Using the hosted web emulator

The web version runs at copperline.dev/try:

Input methods

Save states in the browser

The web build uses the same .clstate file format as the desktop version:

Architecture

The browser implementation consists of the following components:

Building the WebAssembly package locally

Prerequisites

Ensure the wasm32-unknown-unknown Rust target and the matching wasm-bindgen-cli version are installed:

rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.126 --locked

Compilation

cd crates/copperline-web
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --target web --out-dir pkg \
  target/wasm32-unknown-unknown/release/copperline_web.wasm

The compiled JavaScript loader (copperline_web.js) and WebAssembly binary (copperline_web_bg.wasm) are output to the pkg/ directory.

Embedding with the WebEmu API

To embed Copperline in a custom web application:

import init, { WebEmu } from './pkg/copperline_web.js';

const wasm = await init();

// Initialize emulator (Profile, Video standard, Floppy drive count)
const emu = new WebEmu('A1200', 'PAL', 2);

// Load Kickstart ROM and insert disks
emu.load_rom(romUint8Array, extRomUint8Array);
emu.insert_floppy(0, gameDiskBytes, 'game.adf');
emu.insert_floppy_writable(1, saveDiskBytes, 'save.adf');

// Main animation and audio loop
function renderLoop(timestampMs) {
  emu.run(timestampMs, 5); // Step emulator up to current time (max 5 frames)

  const rows = emu.present_rows();
  if (rows > 0) {
    const width = emu.present_width();
    const pixelView = new Uint8ClampedArray(
      wasm.memory.buffer,
      emu.present_ptr(),
      width * rows * 4
    );
    ctx.putImageData(new ImageData(pixelView, width, rows), 0, 0);
  }

  const audioSamples = emu.take_audio(); // Interleaved stereo Float32Array (44.1 kHz)
  if (audioSamples.length > 0) {
    audioWorkletNode.port.postMessage(audioSamples, [audioSamples.buffer]);
  }

  requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);

Key WebEmu API methods

HTML element hooks in try.js

When using the bundled try.js harness, standard UI elements can be connected by ID:

Page configuration file (config.json)

You can provide default settings via a config.json file in the web root:

{
  "machine": "A1200",
  "video": "PAL",
  "df0": "adf/game.adf",
  "autoboot": true,
  "floppy_speed": 400,
  "monitor": "1084",
  "background_run": true
}

Serial port over WebSockets

The browser build can route Amiga serial communication to remote WebSocket servers:

Headless WebAssembly benchmarking

To benchmark WebAssembly performance using Wasmtime:

cargo build --release --target wasm32-wasip1 --bin copperline-bench --features "bench-bin"
wasmtime run --dir . target/wasm32-wasip1/release/copperline-bench.wasm -- --config test.toml --benchmark-until 30