#[cfg(test)]
mod tests;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct Capabilities {
simd: bool,
threads: bool,
cross_origin_isolated: bool,
webgpu: bool,
memory_mb: u32,
hardware_concurrency: u32,
}
#[wasm_bindgen]
impl Capabilities {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_values(
simd: bool,
threads: bool,
cross_origin_isolated: bool,
hardware_concurrency: u32,
) -> Self {
Self {
simd,
threads,
cross_origin_isolated,
webgpu: false,
memory_mb: 0,
hardware_concurrency,
}
}
#[wasm_bindgen(getter)]
pub fn simd(&self) -> bool {
self.simd
}
#[wasm_bindgen(getter)]
pub fn threads(&self) -> bool {
self.threads
}
#[wasm_bindgen(getter, js_name = crossOriginIsolated)]
pub fn cross_origin_isolated(&self) -> bool {
self.cross_origin_isolated
}
#[wasm_bindgen(getter)]
pub fn webgpu(&self) -> bool {
self.webgpu
}
#[wasm_bindgen(getter, js_name = memoryMb)]
pub fn memory_mb(&self) -> u32 {
self.memory_mb
}
#[wasm_bindgen(getter, js_name = hardwareConcurrency)]
pub fn hardware_concurrency(&self) -> u32 {
self.hardware_concurrency
}
#[wasm_bindgen(setter)]
pub fn set_simd(&mut self, value: bool) {
self.simd = value;
}
#[wasm_bindgen(setter)]
pub fn set_threads(&mut self, value: bool) {
self.threads = value;
}
#[wasm_bindgen(setter, js_name = setCrossOriginIsolated)]
pub fn set_cross_origin_isolated(&mut self, value: bool) {
self.cross_origin_isolated = value;
}
#[wasm_bindgen(setter)]
pub fn set_webgpu(&mut self, value: bool) {
self.webgpu = value;
}
#[wasm_bindgen(setter, js_name = setMemoryMb)]
pub fn set_memory_mb(&mut self, value: u32) {
self.memory_mb = value;
}
#[wasm_bindgen(setter, js_name = setHardwareConcurrency)]
pub fn set_hardware_concurrency(&mut self, value: u32) {
self.hardware_concurrency = value;
}
#[wasm_bindgen(js_name = getBinaryName)]
pub fn get_binary_name(&self) -> String {
if self.simd && self.threads && self.cross_origin_isolated {
"whisper-apr-simd-threaded.wasm".to_string()
} else if self.simd {
"whisper-apr-simd-sequential.wasm".to_string()
} else {
"whisper-apr-scalar.wasm".to_string()
}
}
#[wasm_bindgen(js_name = optimalThreadCount)]
pub fn optimal_thread_count(&self) -> u32 {
if !self.threads {
return 1;
}
let hw = self.hardware_concurrency;
if hw <= 1 {
return 1;
}
let available = hw.saturating_sub(1);
available.clamp(1, 8)
}
#[wasm_bindgen(js_name = canRunModel)]
#[allow(clippy::match_same_arms)]
pub fn can_run_model(&self, model_type: &str) -> bool {
let required_mb = match model_type {
"tiny" | "tiny.en" => 200,
"base" | "base.en" => 400,
"small" | "small.en" => 900,
"medium" | "medium.en" => 2500,
"large" | "large-v2" | "large-v3" => 4000,
"large-v3-turbo" => 2500,
_ => 200, };
if self.memory_mb == 0 {
return true;
}
self.memory_mb >= required_mb
}
#[wasm_bindgen(js_name = performanceTier)]
pub fn performance_tier(&self) -> u8 {
match (self.simd, self.threads) {
(true, true) => 3,
(true, false) => 2,
(false, true) => 1,
(false, false) => 0,
}
}
#[wasm_bindgen(js_name = description)]
pub fn description(&self) -> String {
let mut parts = Vec::new();
if self.simd {
parts.push("SIMD");
}
if self.threads {
parts.push("Threads");
}
if self.cross_origin_isolated {
parts.push("CrossOriginIsolated");
}
if self.webgpu {
parts.push("WebGPU");
}
if parts.is_empty() {
"Scalar (no acceleration)".to_string()
} else {
parts.join(" + ")
}
}
#[wasm_bindgen(js_name = executionMode)]
pub fn execution_mode(&self) -> ExecutionMode {
ExecutionMode::from(self)
}
#[wasm_bindgen(js_name = rtfMultiplier)]
pub fn rtf_multiplier(&self) -> f32 {
self.execution_mode().rtf_multiplier()
}
#[wasm_bindgen(js_name = executionModeName)]
pub fn execution_mode_name(&self) -> String {
self.execution_mode().name()
}
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionMode {
SimdThreaded,
SimdSequential,
Scalar,
}
impl From<&Capabilities> for ExecutionMode {
fn from(caps: &Capabilities) -> Self {
if caps.simd && caps.threads && caps.cross_origin_isolated {
Self::SimdThreaded
} else if caps.simd {
Self::SimdSequential
} else {
Self::Scalar
}
}
}
impl ExecutionMode {
#[must_use]
pub fn rtf_multiplier(self) -> f32 {
match self {
Self::SimdThreaded => 1.0,
Self::SimdSequential => 1.5,
Self::Scalar => 4.0,
}
}
#[must_use]
pub fn name(self) -> String {
match self {
Self::SimdThreaded => "High Performance (SIMD + Threads)".to_string(),
Self::SimdSequential => "Compatibility (SIMD Sequential)".to_string(),
Self::Scalar => "Fallback (Scalar)".to_string(),
}
}
}