WebAssembly (ali “Wasm”), ki ga podpirajo vsi sodobni brskalniki, spreminja način, kako razvijamo uporabniške izkušnje za splet. Je preprosta binarna izvedljiva oblika, ki knjižnicam ali celo celotnim programom, ki so bili napisani v drugih programskih jezikih, omogoča zagon v spletnem brskalniku.
Razvijalci pogosto iščejo načine za bolj produktivnost, na primer:
Za front-end razvijalce WebAssembly ponuja vse tri in odgovori na iskanje uporabniškega vmesnika spletne aplikacije, ki resnično tekmuje z domačo mobilno ali namizno izkušnjo. Omogoča celo uporabo knjižnic, napisanih v jezikih, ki niso JavaScript, kot sta C ++ ali Go!
V tej vadnici Wasm / Rust bomo ustvarili preprosto aplikacijo za zaznavanje tona, kot je kitarski uglaševalec. Uporabil bo vgrajene zvočne zmožnosti brskalnika in teče s hitrostjo 60 sličic na sekundo (FPS) - tudi v mobilnih napravah. Vam ni treba razumeti spletnega zvočnega API-ja ali ga celo poznati Rja slediti tej vadnici; vendar se pričakuje udobje z JavaScriptom.
Opomba: Na žalost od tega pisanja tehnika, uporabljena v tem članku, specifična za API spletnega zvoka, v Firefoxu še ne deluje. Zato zaenkrat za to vadnico priporočamo Chrome, Chromium ali Edge, kljub sicer odlični podpori API-jev Wasm in Web Audio v Firefoxu.
AudioWorklet
API brskalnika za visoko zmogljivo obdelavo zvoka v brskalnikuOpomba: Če vas bolj kot »zakaj« v tem članku zanima »kako«, vas prosimo, da skočite naravnost v to vadnico .
Obstaja več razlogov, zakaj bi bilo smiselno uporabljati WebAssembly:
Priljubljenost WebAssembly bo zagotovo še naprej rasla; vendar ni primeren za ves spletni razvoj:
Medtem ko se mnogi programski jeziki prevajajo v Wasm, sem za ta primer izbral Rust. Rust je Mozilla ustvarila leta 2010 in ima vedno večjo priljubljenost. Rja zaseda prvo mesto za 'najbolj priljubljeni jezik' v raziskavi razvijalcev iz leta 2020 Stack Overflow. Toda razlogi za uporabo Rusta z WebAssembly presegajo zgolj trend:
Številne prednosti Rusta prihajajo tudi s strmo učno krivuljo, zato je izbira pravega programskega jezika odvisna od različnih dejavnikov, na primer od sestave ekipe, ki bo razvijala in vzdrževala kodo.
Ker programiramo v WebAssembly z Rustom, kako bi lahko uporabili Rust, da bi pridobili prednosti glede zmogljivosti, ki so nas sploh pripeljale do Wasma? Da se aplikacija s hitro posodabljajočim uporabniškim vmesnikom počuti 'gladko' za uporabnike, mora biti sposobna osveževati zaslon tako redno kot strojna oprema zaslona. To je običajno 60 sličic na sekundo, zato mora biti naša aplikacija sposobna prerisati svoj uporabniški vmesnik v približno 16,7 ms (1000 ms / 60 FPS).
Naša aplikacija zazna in prikaže trenutno višino tona v realnem času, kar pomeni, da bi morali kombinirani izračun zaznavanja in risanje ostati znotraj 16,7 ms na okvir. V naslednjem razdelku bomo izkoristili podporo brskalnika za analizo zvoka v drugi niti medtem glavna nit opravlja svoje delo. To je velika zmaga za uspešnost, od takrat računanja in risanja vsak na voljo 16,7 ms.
V tej aplikaciji bomo za zaznavanje tona uporabili visoko zmogljiv zvočni modul WebAssembly. Poleg tega bomo zagotovili, da se izračun ne bo izvajal v glavni niti.
Zakaj ne moremo stvari poenostaviti in zaznavanja smole opraviti na glavni niti?
Delovni program Web Audio omogoča, da aplikacije še naprej dosegajo gladkih 60 sličic na sekundo, ker obdelava zvoka ne more zadržati glavne niti. Če je obdelava zvoka prepočasna in zaostaja, se pojavijo drugi učinki, na primer zaostajanje zvoka. Vendar bo UX ostal odziven za uporabnika.
Ta vadnica predpostavlja, da imate nameščen Node.js in npx
. Če nimate npx
že lahko uporabite npm
(ki je priložen Node.js), da ga namestite:
npm install -g npx
Za to vadnico Wasm / Rust bomo uporabili React.
V terminalu bomo izvajali naslednje ukaze:
npx create-react-app wasm-audio-app cd wasm-audio-app
Ta uporablja npx
za izvedbo create-react-app
ukaz (vsebovan v ustreznem paketu, ki ga vzdržuje Facebook) za ustvarjanje nove aplikacije React v imeniku wasm-audio-app
create-react-app
je CLI za generiranje reaktnih enojnih aplikacij (SPA). Z Reactom je neverjetno enostavno začeti nov projekt. Vendar pa izhodni projekt vključuje kodo vzorca, ki jo bo treba zamenjati.
Prvič, čeprav toplo priporočam enotno testiranje aplikacije med razvojem, testiranje presega obseg te vadnice. Torej bomo nadaljevali in izbrisali src/App.test.js
in src/setupTests.js
.
V naši aplikaciji bo pet glavnih komponent JavaScript:
public/wasm-audio/wasm-audio.js
vsebuje vezave JavaScript na modul Wasm, ki zagotavlja algoritem za zaznavanje tona.public/PitchProcessor.js
se zgodi obdelava zvoka. Deluje v niti upodabljanja spletnega zvoka in bo porabil Wasm API.src/PitchNode.js
vsebuje izvedbo vozlišča Web Audio, ki je povezano z grafom Web Audio in deluje v glavni niti.src/setupAudio.js
uporablja API-je spletnega brskalnika za dostop do razpoložljive naprave za snemanje zvoka.src/App.js
in src/App.css
obsegajo uporabniški vmesnik aplikacije.
Poglobimo se v bistvo naše aplikacije in določimo kodo Rust za naš modul Wasm. Nato bomo kodirali različne dele JavaScript-a, povezanega s spletnim zvokom, in končali z uporabniškim vmesnikom.
Naša koda Rust bo izračunala glasbeno višino iz vrste zvočnih vzorcev.
Lahko sledite ta navodila za izgradnjo verige Rust za razvoj.
wasm-pack
vam omogoča izdelavo, preizkušanje in objavljanje komponent WebAssembly, ki jih ustvari Rust. Če še niste, namestite wasm-pack .
cargo-generate
pomaga pri zagonu novega projekta Rust z izkoriščanjem že obstoječega repozitorija Git kot predloge. To bomo uporabili za zagon preprostega analizatorja zvoka v Rustu, do katerega lahko dostopate s pomočjo brskalnika WebAssembly.
Uporaba cargo
orodje, ki ste ga dobili z verigo Rust, lahko namestite cargo-generate
:
cargo install cargo-generate
Ko je namestitev (ki lahko traja nekaj minut) končana, smo pripravljeni ustvariti svoj projekt Rust.
V korenski mapi naše aplikacije bomo klonirali predlogo projekta:
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
Ko boste pozvani k novemu imenu projekta, bomo vnesli wasm-audio
.
V wasm-audio
imenik, bo zdaj Cargo.toml
datoteka z naslednjo vsebino:
[package] name = 'wasm-audio' version = '0.1.0' authors = ['Your Name < [email protected] '] edition = '2018' [lib] crate-type = ['cdylib', 'rlib'] [features] default = ['console_error_panic_hook'] [dependencies] wasm-bindgen = '0.2.63' ...
Cargo.toml
se uporablja za definiranje paketa Rust (ki ga Rust imenuje 'zaboj'), ki služi podobno funkcijo za aplikacije Rust, ki package.json
ne za aplikacije JavaScript.
The [package]
Poglavje opredeljuje metapodatke, ki se uporabljajo pri objavi paketa uradniku registra paketov Rust.
The [lib]
poglavje opisuje izhodno obliko iz postopka prevajanja Rust. Tu “cdylib” Rustu naroči, naj ustvari “dinamično sistemsko knjižnico”, ki jo je mogoče naložiti iz drugega jezika (v našem primeru JavaScript), vključno z “rlib” Rustu pa doda statično knjižnico z metapodatki o izdelani knjižnici. Ta drugi specifikator za naše namene ni potreben - pomaga pri razvoju nadaljnjih modulov Rust, ki ta zaboj porabijo kot odvisnost - vendar je varno zapustiti.
V [features]
prosimo Rust, da vključi neobvezno funkcijo console_error_panic_hook
zagotoviti funkcionalnost, ki pretvori mehanizem neobdelanih napak Rusta (imenovan panic
) v napake, ki se pojavijo v orodjih za razvijanje programov za razvijanje.
Končno, [dependencies]
našteva vse zaboje, od katerih je ta odvisen. Edina odvisnost, ki je na voljo takoj, je wasm-bindgen
, ki omogoča samodejno generiranje vezi JavaScript v naš modul Wasm.
Namen te aplikacije je, da lahko v realnem času zazna glas glasbenika ali višino instrumenta. Da se to izvede čim prej, je modul WebAssembly zadolžen za izračun višine tona. Za enojno zaznavanje tona bomo uporabili metodo višine tona 'McLeod', ki je implementirana v obstoječi Rust pitch-detection
knjižnica.
Podobno kot upravitelj paketov Node.js (npm), tudi Rust vključuje lastnega upravitelja paketov, imenovanega Cargo. To omogoča enostavno namestitev paketov, ki so bili objavljeni v registru zabojev Rust.
Če želite dodati odvisnost, uredite Cargo.toml
in dodajte vrstico za pitch-detection
v razdelek odvisnosti:
[dependencies] wasm-bindgen = '0.2.63' pitch-detection = '0.1'
To naloži Cargou, da prenese in namesti pitch-detection
odvisnost v naslednjem cargo build
ali, ker ciljamo na WebAssembly, bo to izvedeno v naslednjem wasm-pack
Najprej bomo dodali datoteko, ki opredeljuje uporabno pripomoček, o namenu katerega bomo razpravljali kasneje:
Ustvari wasm-audio/src/utils.rs
in prilepite vsebino te datoteke vanj.
Ustvarjeno kodo bomo zamenjali v wasm-audio/lib.rs
z naslednjo kodo, ki izvaja zaznavanje tona prek algoritma hitre Fourierjeve transformacije (FFT):
use pitch_detection::{McLeodDetector, PitchDetector}; use wasm_bindgen::prelude::*; mod utils; #[wasm_bindgen] pub struct WasmPitchDetector { sample_rate: usize, fft_size: usize, detector: McLeodDetector, } #[wasm_bindgen] impl WasmPitchDetector { pub fn new(sample_rate: usize, fft_size: usize) -> WasmPitchDetector { utils::set_panic_hook(); let fft_pad = fft_size / 2; WasmPitchDetector { sample_rate, fft_size, detector: McLeodDetector::::new(fft_size, fft_pad), } } pub fn detect_pitch(&mut self, audio_samples: Vec) -> f32 { if audio_samples.len() pitch.frequency, None => 0.0, } } }
To podrobneje preučimo:
#[wasm_bindgen]
wasm_bindgen
je makro Rust, ki pomaga pri izvedbi vezave med JavaScriptom in Rustom. Ko je ta makro preveden v WebAssembly, prevajalniku naroči, naj ustvari vezavo JavaScript na razred. Zgornja koda Rust bo prevedena v vezi JavaScript, ki so preprosto tanki ovoji za klice v modul Wasm in iz njega. Lahka plast abstrakcije v kombinaciji z neposrednim skupnim pomnilnikom med JavaScriptom pomaga Wasmu zagotoviti odlično zmogljivost.
#[wasm_bindgen] pub struct WasmPitchDetector { sample_rate: usize, fft_size: usize, detector: McLeodDetector, } #[wasm_bindgen] impl WasmPitchDetector { ... }
Rust nima koncepta razredov. Precej, podatki predmeta je opisano z struct
in njegovo vedenje do impl
s ali trait
s.
Zakaj bi funkcijo zaznavanja smole izpostavili prek predmeta in ne navadne funkcije? Na ta način inicializiramo samo podatkovne strukture, ki jih uporablja notranji McLeodDetector enkrat , med ustvarjanjem WasmPitchDetector
. To ohranja detect_pitch
hitro delujejo tako, da se med delovanjem izognejo dragemu dodeljevanju pomnilnika.
pub fn new(sample_rate: usize, fft_size: usize) -> WasmPitchDetector { utils::set_panic_hook(); let fft_pad = fft_size / 2; WasmPitchDetector { sample_rate, fft_size, detector: McLeodDetector::::new(fft_size, fft_pad), } }
Ko aplikacija Rust naleti na napako, ki je ne more zlahka obnoviti, je zelo pogosto, da pokličete panic!
makro. To Rustu naroči, naj prijavi napako in takoj prekine aplikacijo. Uporaba panike je lahko koristna zlasti za zgodnji razvoj, preden se vzpostavi strategija za obvladovanje napak, saj vam omogoča hitro ujemanje napačnih predpostavk.
Klicanje utils::set_panic_hook()
enkrat med namestitvijo bo zagotovil, da se v orodjih za razvoj brskalnika pojavijo panična sporočila.
Nato določimo fft_pad
, količino ničelnih oblazinjenj, uporabljenih za vsak analizni FFT. Oblazinjenje v kombinaciji s funkcijo okna, ki jo uporablja algoritem, pomaga 'zgladiti' rezultate, ko se analiza premika po dohodnih vzorčenih zvočnih podatkih. Uporaba blazinice s polovico dolžine FFT dobro deluje pri mnogih instrumentih.
Končno Rust samodejno vrne rezultat zadnjega stavka, zato WasmPitchDetector
stavek struct je vrnjena vrednost new()
.
Preostali del impl WasmPitchDetector
Koda rje določa API za odkrivanje parcel:
pub fn detect_pitch(&mut self, audio_samples: Vec) -> f32 { ... }
Tako je videti definicija funkcije člana v Rustu. Javni član detect_pitch
se doda WasmPitchDetector
. Njegov prvi argument je spremenljiv sklic (&mut
) na instanciran objekt iste vrste, ki vsebuje struct
in impl
polja - vendar se to samodejno prenese med klicanjem, kot bomo videli spodaj.
Poleg tega naša članska funkcija sprejme poljubno veliko paleto 32-bitnih števil s plavajočo vejico in vrne eno številko. Tukaj bo to dobljeni korak, izračunan za te vzorce (v Hz).
if audio_samples.len() Zgornja koda zazna, ali je bilo funkciji na voljo dovolj vzorcev za izvedbo veljavne analize tona. V nasprotnem primeru Rust panic!
Pokliče se makro, ki povzroči takojšen izhod iz Wasma in sporočilo o napaki, natisnjeno na konzolo orodij za brskalnik.
let optional_pitch = self.detector.get_pitch( &audio_samples, self.sample_rate, POWER_THRESHOLD, CLARITY_THRESHOLD, );
To pokliče v knjižnico drugih proizvajalcev, da izračuna višino tona iz najnovejših zvočnih vzorcev. POWER_THRESHOLD
in CLARITY_THRESHOLD
lahko prilagodite tako, da prilagodite občutljivost algoritma.
Končamo z implicitnim vrnitvijo vrednosti s plavajočo vejico prek match
ključna beseda, ki deluje podobno kot switch
izjava v drugih jezikih. Some()
in None
primerno ravnajmo s primeri, ne da bi naleteli na izjemo ničelnega kazalca.
Izdelava aplikacij WebAssembly
Pri razvijanju aplikacij Rust je običajen postopek gradnje priklic gradnje z uporabo cargo build
. Vendar ustvarjamo modul Wasm, zato bomo uporabili wasm-pack
, ki zagotavlja preprostejšo sintakso pri ciljanju na Wasm. (Omogoča tudi objavo nastalih vezi JavaScript v registru npm, vendar to ni v okviru te vadnice.)
wasm-pack
podpira različne cilje gradnje. Ker bomo modul porabili neposredno iz delovne knjižice Web Audio, bomo ciljali na web
možnost. Drugi cilji vključujejo gradnjo za paket, kot je spletni paket, ali za porabo iz Node.js. To bomo izvedli iz wasm-audio/
podimenik:
wasm-pack build --target web
Če je uspešen, se v ./pkg
ustvari modul npm.
To je modul JavaScript s svojim lastnim samodejno ustvarjenim package.json
. To lahko po želji objavite v registru npm. Da bodo stvari za zdaj preproste, lahko to kopiramo in prilepimo pkg
pod našo mapo public/wasm-audio
:
cp -R ./wasm-audio/pkg ./public/wasm-audio
S tem smo ustvarili modul Rust Wasm, pripravljen za uporabo v spletni aplikaciji ali natančneje v PitchProcessor
2. Naš PitchProcessor
Razred (na podlagi izvornega AudioWorkletProcessor
)
Za to aplikacijo bomo uporabili standard za obdelavo zvoka, ki je nedavno pridobil široko združljivost z brskalniki. Natančneje, uporabili bomo API za spletni zvok in drage izračune izvedli po meri AudioWorkletProcessor
. Nato bomo ustvarili ustrezne po meri AudioWorkletNode
razred (ki ga bomo imenovali PitchNode
) kot most nazaj do glavne niti.
Ustvari novo datoteko public/PitchProcessor.js
in prilepite vanjo naslednjo kodo:
import init, { WasmPitchDetector } from './wasm-audio/wasm_audio.js'; class PitchProcessor extends AudioWorkletProcessor { constructor() { super(); // Initialized to an array holding a buffer of samples for analysis later - // once we know how many samples need to be stored. Meanwhile, an empty // array is used, so that early calls to process() with empty channels // do not break initialization. this.samples = []; this.totalSamples = 0; // Listen to events from the PitchNode running on the main thread. this.port.onmessage = (event) => this.onmessage(event.data); this.detector = null; } onmessage(event) { if (event.type === 'send-wasm-module') { // PitchNode has sent us a message containing the Wasm library to load into // our context as well as information about the audio device used for // recording. init(WebAssembly.compile(event.wasmBytes)).then(() => { this.port.postMessage({ type: 'wasm-module-loaded' }); }); } else if (event.type === 'init-detector') { const { sampleRate, numAudioSamplesPerAnalysis } = event; // Store this because we use it later to detect when we have enough recorded // audio samples for our first analysis. this.numAudioSamplesPerAnalysis = numAudioSamplesPerAnalysis; this.detector = WasmPitchDetector.new(sampleRate, numAudioSamplesPerAnalysis); // Holds a buffer of audio sample values that we'll send to the Wasm module // for analysis at regular intervals. this.samples = new Array(numAudioSamplesPerAnalysis).fill(0); this.totalSamples = 0; } }; process(inputs, outputs) { // inputs contains incoming audio samples for further processing. outputs // contains the audio samples resulting from any processing performed by us. // Here, we are performing analysis only to detect pitches so do not modify // outputs. // inputs holds one or more 'channels' of samples. For example, a microphone // that records 'in stereo' would provide two channels. For this simple app, // we use assume either 'mono' input or the 'left' channel if microphone is // stereo. const inputChannels = inputs[0]; // inputSamples holds an array of new samples to process. const inputSamples = inputChannels[0]; // In the AudioWorklet spec, process() is called whenever exactly 128 new // audio samples have arrived. We simplify the logic for filling up the // buffer by making an assumption that the analysis size is 128 samples or // larger and is a power of 2. if (this.totalSamples The PitchProcessor
je spremljevalec PitchNode
vendar teče v ločeni niti, tako da je mogoče izračunavanje zvočne obdelave izvesti brez blokiranja dela na glavni niti.
Predvsem je PitchProcessor
:
- Obravnava
'send-wasm-module'
dogodek poslan iz PitchNode
z zbiranjem in nalaganjem modula Wasm v delovno knjižico. Ko konča, omogoči PitchNode
vedeti tako, da pošljete 'wasm-module-loaded'
dogodek. Ta pristop povratnega klica je potreben, ker je vsa komunikacija med PitchNode
in PitchProcessor
prečka mejo niti in je ni mogoče izvajati sinhrono. - Odzove se tudi na
'init-detector'
dogodek iz PitchNode
s konfiguriranjem WasmPitchDetector
. - Obdela zvočne vzorce, prejete iz zvočnega grafa brskalnika, prenese izračun zaznavanja smole na modul Wasm in nato pošlje morebitno zaznano smolo nazaj na
PitchNode
(ki pošlje smolo skupaj s plastjo React preko onPitchDetectedCallback
). - Registrira se sam pod določenim, edinstvenim imenom. Na ta način brskalnik ve - preko osnovnega razreda
PitchNode
, izvorni AudioWorkletNode
- kako narediti primer za naš PitchProcessor
pozneje, ko PitchNode
je zgrajena. Glej setupAudio.js
.
Naslednji diagram prikazuje tok dogodkov med PitchNode
in PitchProcessor
:

Sporočila dogodka med izvajanjem.
3. Dodajte kodo Web Audio Worklet
PitchNode.js
ponuja vmesnik za obdelavo zvoka po meri z zaznavanjem tona. The PitchNode
object je mehanizem, pri katerem smole zaznamo z uporabo modula WebAssembly, ki deluje v AudioWorklet
thread bo prišel do glavne niti in React za upodabljanje.
V src/PitchNode.js
bomo podrazvrstili vgrajeno AudioWorkletNode
spletnega zvočnega API-ja:
export default class PitchNode extends AudioWorkletNode { /** * Initialize the Audio processor by sending the fetched WebAssembly module to * the processor worklet. * * @param {ArrayBuffer} wasmBytes Sequence of bytes representing the entire * WASM module that will handle pitch detection. * @param {number} numAudioSamplesPerAnalysis Number of audio samples used * for each analysis. Must be a power of 2. */ init(wasmBytes, onPitchDetectedCallback, numAudioSamplesPerAnalysis) { this.onPitchDetectedCallback = onPitchDetectedCallback; this.numAudioSamplesPerAnalysis = numAudioSamplesPerAnalysis; // Listen to messages sent from the audio processor. this.port.onmessage = (event) => this.onmessage(event.data); this.port.postMessage({ type: 'send-wasm-module', wasmBytes, }); } // Handle an uncaught exception thrown in the PitchProcessor. onprocessorerror(err) { console.log( `An error from AudioWorkletProcessor.process() occurred: ${err}` ); }; onmessage(event) { if (event.type === 'wasm-module-loaded') { // The Wasm module was successfully sent to the PitchProcessor running on the // AudioWorklet thread and compiled. This is our cue to configure the pitch // detector. this.port.postMessage({ type: 'init-detector', sampleRate: this.context.sampleRate, numAudioSamplesPerAnalysis: this.numAudioSamplesPerAnalysis }); } else if (event.type === 'pitch') { // A pitch was detected. Invoke our callback which will result in the UI updating. this.onPitchDetectedCallback(event.pitch); } } }
Ključne naloge, ki jih izvaja PitchNode
so:
- Pošljite modul WebAssembly kot zaporedje surovih bajtov - tistih, ki so bili posredovani iz
setupAudio.js
- v PitchProcessor
, ki se izvaja na AudioWorklet
nit. Tako je PitchProcessor
naloži modul Wasm za zaznavanje tona. - Obravnavajte dogodek, ki ga je poslal
PitchProcessor
ko uspešno prevede Wasm, in mu pošlje še en dogodek, ki mu posreduje informacije o konfiguraciji zaznavanja smole. - Obravnavajte zaznane parcele, ko prispejo iz
PitchProcessor
in jih posredujte v funkcijo UI setLatestPitch()
preko onPitchDetectedCallback()
.
Opomba: Ta koda predmeta deluje na glavni niti, zato se izogibajte nadaljnji obdelavi zaznanih parcel, če je to drago in povzroči upadanje hitrosti sličic.
4. Dodajte kodo za nastavitev spletnega zvoka
Da lahko spletna aplikacija dostopa in obdeluje vnos v živo iz mikrofona odjemalskega stroja, mora:
- Pridobite uporabnikovo dovoljenje za brskalnik za dostop do katerega koli povezanega mikrofona
- Dostopite do izhoda mikrofona kot predmet zvočnega toka
- Priložite kodo za obdelavo vzorcev dohodnega zvočnega toka in ustvarjanje zaporedja zaznanih tonov
V src/setupAudio.js
bomo to storili in asinhrono naložili modul Wasm, da bomo lahko z njim inicializirali naš PitchNode, preden bomo pritrdili naše PitchNode:
import PitchNode from './PitchNode'; async function getWebAudioMediaStream() { if (!window.navigator.mediaDevices) { throw new Error( 'This browser does not support web audio or it is not enabled.' ); } try { const result = await window.navigator.mediaDevices.getUserMedia({ audio: true, video: false, }); return result; } catch (e) { switch (e.name) { case 'NotAllowedError': throw new Error( 'A recording device was found but has been disallowed for this application. Enable the device in the browser settings.' ); case 'NotFoundError': throw new Error( 'No recording device was found. Please attach a microphone and click Retry.' ); default: throw e; } } } export async function setupAudio(onPitchDetectedCallback) { // Get the browser audio. Awaits user 'allowing' it for the current tab. const mediaStream = await getWebAudioMediaStream(); const context = new window.AudioContext(); const audioSource = context.createMediaStreamSource(mediaStream); let node; try { // Fetch the WebAssembly module that performs pitch detection. const response = await window.fetch('wasm-audio/wasm_audio_bg.wasm'); const wasmBytes = await response.arrayBuffer(); // Add our audio processor worklet to the context. const processorUrl = 'PitchProcessor.js'; try { await context.audioWorklet.addModule(processorUrl); } catch (e) { throw new Error( `Failed to load audio analyzer worklet at url: ${processorUrl}. Further info: ${e.message}` ); } // Create the AudioWorkletNode which enables the main JavaScript thread to // communicate with the audio processor (which runs in a Worklet). node = new PitchNode(context, 'PitchProcessor'); // numAudioSamplesPerAnalysis specifies the number of consecutive audio samples that // the pitch detection algorithm calculates for each unit of work. Larger values tend // to produce slightly more accurate results but are more expensive to compute and // can lead to notes being missed in faster passages i.e. where the music note is // changing rapidly. 1024 is usually a good balance between efficiency and accuracy // for music analysis. const numAudioSamplesPerAnalysis = 1024; // Send the Wasm module to the audio node which in turn passes it to the // processor running in the Worklet thread. Also, pass any configuration // parameters for the Wasm detection algorithm. node.init(wasmBytes, onPitchDetectedCallback, numAudioSamplesPerAnalysis); // Connect the audio source (microphone output) to our analysis node. audioSource.connect(node); // Connect our analysis node to the output. Required even though we do not // output any audio. Allows further downstream audio processing or output to // occur. node.connect(context.destination); } catch (err) { throw new Error( `Failed to load audio analyzer WASM module. Further info: ${err.message}` ); } return { context, node }; }
To predvideva, da je na voljo modul WebAssembly, ki se lahko naloži na public/wasm-audio
, kar smo dosegli v prejšnjem razdelku Rust.
5. Določite uporabniški vmesnik aplikacije
Določimo osnovni uporabniški vmesnik za detektor tona. Vsebino src/App.js
bomo zamenjali z naslednjo kodo:
import React from 'react'; import './App.css'; import { setupAudio } from './setupAudio'; function PitchReadout({ running, latestPitch }) { return ( {latestPitch ? `Latest pitch: ${latestPitch.toFixed(1)} Hz` : running ? 'Listening...' : 'Paused'} ); } function AudioRecorderControl() { // Ensure the latest state of the audio module is reflected in the UI // by defining some variables (and a setter function for updating them) // that are managed by React, passing their initial values to useState. // 1. audio is the object returned from the initial audio setup that // will be used to start/stop the audio based on user input. While // this is initialized once in our simple application, it is good // practice to let React know about any state that _could_ change // again. const [audio, setAudio] = React.useState(undefined); // 2. running holds whether the application is currently recording and // processing audio and is used to provide button text (Start vs Stop). const [running, setRunning] = React.useState(false); // 3. latestPitch holds the latest detected pitch to be displayed in // the UI. const [latestPitch, setLatestPitch] = React.useState(undefined); // Initial state. Initialize the web audio once a user gesture on the page // has been registered. if (!audio) { return ( { setAudio(await setupAudio(setLatestPitch)); setRunning(true); }} > Start listening ); } // Audio already initialized. Suspend / resume based on its current state. const { context } = audio; return ( { if (running) { await context.suspend(); setRunning(context.state === 'running'); } else { await context.resume(); setRunning(context.state === 'running'); } }} disabled={context.state !== 'running' && context.state !== 'suspended'} > {running ? 'Pause' : 'Resume'} ); } function App() { return ( Wasm Audio Tutorial ); } export default App;
In zamenjali bomo App.css
z nekaj osnovnimi slogi:
.App { display: flex; flex-direction: column; align-items: center; text-align: center; background-color: #282c34; min-height: 100vh; color: white; justify-content: center; } .App-header { font-size: 1.5rem; margin: 10%; } .App-content { margin-top: 15vh; height: 85vh; } .Pitch-readout { margin-top: 5vh; font-size: 3rem; } button { background-color: rgb(26, 115, 232); border: none; outline: none; color: white; margin: 1em; padding: 10px 14px; border-radius: 4px; width: 190px; text-transform: capitalize; cursor: pointer; font-size: 1.5rem; } button:hover { background-color: rgb(45, 125, 252); }
S tem bi morali biti pripravljeni zagnati našo aplikacijo, vendar je treba najprej rešiti pasti.
Spletna vadnica / Rust Vadnica: Tako blizu!
Zdaj, ko zaženemo yarn
in yarn start
, preklopite na brskalnik in poskusite snemati zvok (z uporabo Chroma ali Chromiuma z odprtimi orodji za razvijalce), smo naleteli na nekaj napak:

Wasm zahteve imajo široko podporo - le še ne v specifikaciji Worklet.
Prva napaka, TextDecoder is not defined
, se pojavi, ko brskalnik poskuša zagnati vsebino wasm_audio.js
. To pa povzroči neuspešno nalaganje ovoja JavaScript Wasm, kar povzroči drugo napako, ki jo vidimo v konzoli.
Osnovni vzrok težave je, da moduli, ki jih je ustvaril generator paketov Wasm iz Rusta, domnevajo, da TextDecoder
(in TextEncoder
) bo zagotovil brskalnik. Ta predpostavka velja za sodobne brskalnike, ko se modul Wasm izvaja iz glavne niti ali celo delovne niti. Za delovne zvezke (kot je kontekst AudioWorklet
, ki je potreben v tej vadnici), TextDecoder
in TextEncoder
še niso del specifikacije in zato niso na voljo.
TextDecoder
je potreben generatorju kod Rust Wasm za pretvorbo iz ploščate, pakirane predstavitve Rust v skupnem pomnilniku v format niza, ki ga uporablja JavaScript. Povedano drugače, da bi videli nize, ki jih je ustvaril generator kode Wasm, TextEncoder
in TextDecoder
je treba določiti .
Ta težava je simptom relativne novosti WebAssembly. Ker se podpora za brskalnik izboljšuje tako, da podpira običajne vzorce WebAssembly, bodo te težave verjetno izginile.
Za zdaj se lahko rešimo tako, da določimo polifil za TextDecoder
.
Ustvari novo datoteko public/TextEncoder.js
in ga uvozite iz public/PitchProcessor.js
:
import './TextEncoder.js';
Prepričajte se, da je to import
stavek pride pred wasm_audio
uvoz.
Na koncu prilepite to izvedbo v TextEncoder.js
(z dovoljenjem @Yaffle na GitHub).
Vprašanje za Firefox
Kot smo že omenili, način, kako v naši aplikaciji kombiniramo Wasm z delovnimi zvezki Web Audio, v Firefoxu ne bo deloval. Tudi z zgornjim podstavkom bo klik na gumb »Začni poslušati« povzročil naslednje:
Unhandled Rejection (Error): Failed to load audio analyzer WASM module. Further info: Failed to load audio analyzer worklet at url: PitchProcessor.js. Further info: The operation was aborted.
To je zato, ker Firefox še ne podpira uvoz modulov iz AudioWorklets
- za nas je to PitchProcessor.js
teče v AudioWorklet
nit.
Izpolnjena vloga
Ko končamo, preprosto znova naložimo stran. Aplikacija se mora naložiti brez napak. Kliknite »Začni poslušati« in dovolite brskalniku dostop do vašega mikrofona. Videli boste zelo osnovni detektor tona, napisan v JavaScript z uporabo Wasma:

Zaznavanje smole v realnem času.
Programiranje v WebAssembly z Rust: Spletna zvočna rešitev v realnem času
V tej vadnici smo iz nič zgradili spletno aplikacijo, ki izvaja računsko drago obdelavo zvoka s pomočjo WebAssembly. WebAssembly nam je omogočil, da smo izkoristili skoraj domače zmogljivosti Rusta za izvedbo zaznavanja smole. Nadalje bi to delo lahko izvedli na drugi niti, ki bi glavni niti JavaScript omogočila, da se osredotoči na upodabljanje, ki podpira svilnato gladke hitrosti sličic tudi na mobilnih napravah.
Wasm / Rust in spletni zvočni posnetki
- Sodobni brskalniki omogočajo zmogljivo zajemanje in obdelavo zvoka (in videa) v spletnih aplikacijah.
- Rust je odlično orodje za Wasm , ki ga priporoča kot jezik izbire za projekte, ki vključujejo WebAssembly.
- Računalniško intenzivno delo je mogoče v brskalniku učinkovito izvajati z Wasmom.
Kljub številnim prednostim spletnega zbiranja je treba paziti nekaj pasti Wasm:
- Orodja za Wasm znotraj delovnih plošč se še vedno razvijajo. Na primer, morali smo implementirati lastne različice funkcij TextEncoder in TextDecoder, ki so potrebne za posredovanje nizov med JavaScriptom in Wasmom, ker so manjkali v
AudioWorklet
kontekstu. To in uvoz vezav Javascripta za našo podporo Wasmu iz AudioWorklet
še ni na voljo v Firefoxu. - Čeprav je bila aplikacija, ki smo jo razvili, zelo preprosta, je bila zgradba modula WebAssembly in nalaganje iz
AudioWorklet
zahteval pomembno nastavitev. Uvajanje Wasma v projekte resnično povečuje zapletenost orodja, kar je pomembno upoštevati.
Za vaše udobje ta GitHub repo vsebuje končni, zaključeni projekt. Če se ukvarjate tudi z zalednim razvojem, vas bo morda zanimala tudi uporaba Rusta prek WebAssembly znotraj Node.js .
Nadaljnje branje na spletnem dnevniku ApeeScape Engineering:
- Web Audio API: Zakaj sestavljati, ko lahko kodirate?
- WebVR 3. del: Sprostitev potenciala WebAssembly in AssemblyScript
Razumevanje osnov
Je WebAssembly jezik?
WebAssembly je programski jezik - vendar ne tisti, ki naj bi ga ljudje neposredno napisali. Namesto tega je iz drugih jezikov višje stopnje preveden v kompaktno, binarno obliko bajtnih kod za učinkovit prenos prek spleta in izvajanje v današnjih brskalnikih.
Za kaj je koristen WebAssembly?
WebAssembly omogoča brezhibno izvajanje programske opreme, napisane v drugih jezikih, kot je JavaScript, v brskalniku. To spletnim razvijalcem omogoča, da izkoristijo edinstvene prednosti določenega jezika ali ponovno uporabijo obstoječe knjižnice zaradi udobja in razširjenosti spleta.
Zakaj je WebAssembly hiter?
Programi WebAssembly se hitreje prenesejo v brskalnik kot JavaScript, ker uporabljajo kompaktno binarno predstavitev. Visokozmogljivi jeziki, kot je Rust, se na splošno prelevijo v hitro delujočo bajtkodo Wasm.
V čem je zapisano WebAssembly?
Programi WebAssembly uporabljajo kompaktno binarno predstavitev bajt kod, ki omogoča hitrejši prenos prek spleta kot JavaScript. Te bajtode ni namenjeno temu, da bi jo ljudje napisali neposredno, temveč se ustvari pri sestavljanju kode, napisane v jeziku višje ravni, kot sta C / C ++ ali Rust.
Za kaj se uporablja programski jezik Rust?
Rust ima robusten pomnilniški model, dobro hkratno podporo in majhen izkoristek, zaradi česar je primeren za programsko opremo na sistemski ravni, kot so operacijski sistemi, gonilniki naprav in vdelani programi. Je tudi zmogljiva možnost WebAssembly za spletne aplikacije z zahtevnimi grafičnimi zahtevami ali zahtevami za obdelavo podatkov.
Zakaj je Rust tako hiter?
Programi Rust so hitri, ker se njihova koda prevaja na optimizirana navodila na ravni stroja, Rust pa ne uporablja zbiranja smeti, programerji pa imajo popoln nadzor nad porabo pomnilnika. To ima za posledico dosledno in predvidljivo delovanje.