+
+
diff --git a/ecgsyn-rs/site/src/index.ts b/ecgsyn-rs/site/src/index.ts
new file mode 100644
index 0000000..fe5b600
--- /dev/null
+++ b/ecgsyn-rs/site/src/index.ts
@@ -0,0 +1,18 @@
+import * as ecg from "ecgsyn";
+import "./styles.css";
+
+//ecg.greet("WebAssembly with Penis!");
+let tp = new ecg.TimeParameters();
+
+let rrprocess = new ecg.RrProcess(tp);
+let rrp = new ecg.RrParameters();
+
+let series = rrprocess.generate_series(rrp);
+
+let params = new ecg.Parameters(-0.4, 1.2, 0.0);
+params.push_extremum(new ecg.Extremum(0.0, 30, 0.1, 0.0));
+
+let buffer = new ecg.Buffer();
+ecg.ecgsyn(params, series, buffer);
+(window as any).ecg = ecg;
+(window as any).buffer = buffer;
diff --git a/ecgsyn-rs/site/src/styles.css b/ecgsyn-rs/site/src/styles.css
new file mode 100644
index 0000000..6eb36fd
--- /dev/null
+++ b/ecgsyn-rs/site/src/styles.css
@@ -0,0 +1,68 @@
+@import "picnic/picnic.css";
+
+.wlabel {
+ text-align: center;
+ margin-bottom: 0.2em;
+}
+
+.control-panel {
+ max-width: 600px;
+ margin: 20px auto;
+ padding: 20px;
+}
+
+.wave-selector {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 15px;
+ background: #f8f8f8;
+ border-radius: 4px;
+}
+
+.wave-selector label {
+ margin: 0;
+}
+
+.wave-buttons {
+ margin-left: auto;
+ display: flex;
+ gap: 10px;
+}
+
+.parameter-group {
+ margin: 0 0;
+ padding: 15px;
+ background: #f8f8f8;
+ border-radius: 4px;
+}
+
+.slider-group {
+ margin: 10px 0;
+}
+
+.slider-container {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin: 5px 0;
+}
+
+.slider-container label {
+ width: 80px;
+}
+
+.slider-container input[type="range"] {
+ flex-grow: 1;
+}
+
+.slider-container .value {
+ width: 8em;
+ text-align: right;
+}
+
+button.small {
+ padding: 0.3em 0.7em;
+ font-size: 0.9em;
+}
diff --git a/ecgsyn-rs/site/tsconfig.json b/ecgsyn-rs/site/tsconfig.json
new file mode 100644
index 0000000..c2de9f6
--- /dev/null
+++ b/ecgsyn-rs/site/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES5",
+ "module": "ES6",
+ "jsx": "react",
+ "moduleResolution": "Node",
+ "outDir": "./dist/",
+ "strict": true,
+ "sourceMap": true,
+ "declaration": true,
+ "declarationDir": "./dist/types/",
+ "noImplicitAny": true,
+ "esModuleInterop": true,
+ "downlevelIteration": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "isolatedDeclarations": true
+ },
+ "include": ["src"],
+ "exclude": ["src/**/*.test.ts"]
+}
diff --git a/ecgsyn-rs/site/webpack.config.js b/ecgsyn-rs/site/webpack.config.js
new file mode 100644
index 0000000..7e2206e
--- /dev/null
+++ b/ecgsyn-rs/site/webpack.config.js
@@ -0,0 +1,34 @@
+const CopyPlugin = require("copy-webpack-plugin");
+const path = require("path");
+
+module.exports = {
+ entry: "./src/index.ts",
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: ["style-loader", "css-loader"],
+ },
+ {
+ test: /\.tsx?$/,
+ use: "ts-loader",
+ exclude: /node_modules/,
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".tsx", ".ts", ".js"],
+ },
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "bundle.js",
+ },
+ experiments: {
+ asyncWebAssembly: true,
+ },
+ plugins: [
+ new CopyPlugin({
+ patterns: [{ from: "src/index.html" }],
+ }),
+ ],
+};
diff --git a/ecgsyn-rs/src/lib.rs b/ecgsyn-rs/src/lib.rs
new file mode 100644
index 0000000..e3576e3
--- /dev/null
+++ b/ecgsyn-rs/src/lib.rs
@@ -0,0 +1,558 @@
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
+
+use itertools::Itertools;
+use itertools::MinMaxResult::MinMax;
+use js_sys::{Float64Array, WebAssembly};
+use nalgebra::{Complex, Vector3};
+use num_traits::float::Float;
+use ode_solvers::Dopri5;
+use rand_xoshiro::{
+ rand_core::{RngCore, SeedableRng},
+ Xoshiro256Plus,
+};
+use realfft::{ComplexToReal, RealFftPlanner};
+use std::{f64::consts::TAU, iter::Sum, sync::Arc};
+use wasm_bindgen::prelude::*;
+trait RoundTies {
+ fn round_ties_even(self) -> Self;
+}
+
+impl RoundTies for f32 {
+ #[inline(always)]
+ fn round_ties_even(self) -> Self {
+ f32::round_ties_even(self)
+ }
+}
+
+impl RoundTies for f64 {
+ #[inline(always)]
+ fn round_ties_even(self) -> Self {
+ f64::round_ties_even(self)
+ }
+}
+
+fn ieee_rem