چگونه از wam-pack برای ساخت ماژول WebAssembly برای AudioWorkletProcessor استفاده کردم

اگر این مجموعه را دنبال نکرده اید، من در Rust و WebAssembly یک سینتی سایزر می سازم. مزیت استفاده از WebAssembly این است که می توانم از تمام ویژگی های Rust استفاده کنم و در عین حال از مشکلات استفاده از کتابخانه های پیچیده سیستم عامل برای پخش صدا اجتناب کنم.
آخرین راه حل من ایده آل نبود
در آخرین پست، به یک راه حل کارآمد، اما کمتر ایده آل برای وارد کردن WebAssembly به یک رسیدم AudioWorkletProcessor
. حتی اگر در ابتدا نمیتوانستم بفهمم چگونه میتوان یک ماژول WebAssembly را با استفاده از یک Worklet وارد کرد wasm-pack
، روند انجام این کار بدون wasm-pack
به من امکان داد راه حل بالقوه ای را شناسایی کنم که از آن استفاده می کند wasm-pack
.
گام بعدی تلاش برای اجرای آن بود.
یک ایده بهتر
هنگامی که فرآیند وارد کردن یک ماژول WebAssembly را به یک Worklet درک کردم، این کار را با استفاده از آن انجام دادم wasm-pack
ثابت کرد که نسبتاً ساده است. دو مرحله کلی وجود دارد:
- ماژول WebAssembly را با استفاده از
wasm-pack
web
هدف. اینweb
target کد جاوا اسکریپتی را ایجاد می کند که بدون باندلر یا Node کار می کند. - جاوا اسکریپت را در
AudioWorkletProcessor
فایل و آن را با زمینه تطبیق دهیدAudioWorkletGlobalScope
.
توضیح
از آنجا که web
هدف قرار است بدون نیاز به باندلر کار کند، کد JvaScript حاصل را می توان در فایل کپی کرد. AudioWorkletProcessor
فایل و فقط به تغییرات جزئی نیاز دارد. این رویکرد در حال حاضر بهترین گزینه است زیرا AudioWorkletGlobalScope
فاقد ویژگی های مورد نیاز برای وارد کردن ماژول های WebAssembly است.
پیاده سازی راه حل
اولین قدم این است که کد Rust را با استفاده از web
wasm-pack build --target web
پس از کامپایل شدن کد، فایل جاوا اسکریپت با نام “_bg.js” را باز کردم. این فایل حاوی پیوندهای WebAssembly است که توسط wasm-pack
. من همه چیز را در این فایل کپی کردم و در فایل خود جایگذاری کردم AudioWorkletProcessor
کلاس این تنها راه برای وارد کردن پیوندهای جاوا اسکریپت به داخل است AudioWorkletGlobalScope
.
بعد من کپی کردم .wasm
فایل را در مکانی که مرورگر می تواند با استفاده از آن بارگیری کند fetch()
. در بالای فایل Worklet من، کد زیر را داشتم که می دانستم باید تنظیم شود.
// processor.js
let wasm;
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachedUint8Memory0 = null;
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
// wasm-pack generated code ...
export { initSync }
export default init;
class WebSynthProcessor extends AudioWorkletProcessor {
// ...
}
این کد رو با کد زیر جایگزین کردم:
// processor.js
let wasm;
let cachedTextDecoder;
// decode call will be made later
let cachedUint8Memory0 = null;
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
// wasm-pack generated JavaScript
// Notice, I removed the exports.
class WebSynthProcessor extends AudioWorkletProcessor {
// ...
}
یک نمونه از TextDecoder
کلاس باید در دسترس باشد AudioWorkletGlobalScope
، اما محدوده Worklet صوتی اجازه نمی دهد TextDecoder
سازنده کلاس مورد استفاده قرار گیرد. من استفاده کردم AudioWorkletProcessor
‘s MessagePort
برای دادن کارنامه صوتی a TextDecoder
نمونه، مثال.
راه اندازی ماژول WebAssembly
به منظور انتقال ماژول WebAssembly به AudioWorkletProcessor
، می دانستم که باید تماس بگیرم WebAssembly.compile()
در تاپیک اصلی و به پردازنده صوتی من ارسال کنید. برای این کار باید با هر دو تماس بگیرم fetch()
و WebAssembly.compileStreaming()
مثل این:
// index.js
// We will need the worklet node later.
let node = new AudioWorkletNode(context, 'web-synth-proto');
WebAssembly.compileStreaming(fetch('/path/to/library.wasm')).then(module => {
// Pass the module to the AudioWorkletProcessor
});
من در اصل این کار را در یک async
کارکرد تا بتوانم استفاده کنم await
برای دریافت مستقیم مقدار بازگشتی در اینجا، من از یک callback با یک وعده استفاده می کنم زیرا این الگو رایج تر است.
یک بار من یک WebAssembly
نمونه ماژول، من آن را به AudioWorkletProcessor
با استفاده از MessagePort
نمونه ای مانند این:
// index.js
WebAssembly.compileStreaming(fetch('/path/to/library.wasm')).then(module => {
// Now that the module has been created, it can be passed to the processor.
node.port.postMessage({type: 'init-wasm', wasmData: module});
});
استفاده از ماژول در AudioWorkletProcessor
برای استفاده از ماژول WebAssembly، باید از داخل رشته Worklet صوتی نمونه سازی می شد. اولین گام در این فرآیند تنظیم یک شنونده پیام برای این بود MessagePort
نمونه، مثال. من این کار را در سازنده the انجام دادم AudioWorkletProcessor
.
// processor.js
class WebSynthProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.port.onmessage = event => this.onmessage(event.data);
}
onmessage(data) {
// Check to make sure the message we receive is the correct type.
if (data.type === 'init-wasm') {
// Finish instantiating the WebAssembly module
}
}
}
اکنون که وجود دارد onmessage
شنونده برای MessagePort
، Worklet می تواند پیام ارسال شده توسط موضوع اصلی را گوش دهد.
// processor.js
class WebSynthProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.port.onmessage = event => this.onmessage(event.data);
}
onmessage(data) {
// Check to make sure the message we receive is the correct type.
if (data.type === 'init-wasm') {
cachedTextDecoder = data.decoder;
// Load returns a promise
load(data.wasmData, getImports()).then(mod => {
// Once the WebAssembly module has been instantiated, it needs to be finalized
// so that it can be accessed later.
finalizeInit(mod.instance, mod.module);
});
}
}
}
هنگامی که Worklet ماژول WebAssembly را از رشته اصلی دریافت کرد، باید آن را فراخوانی کند load()
تابع کپی شده از خروجی wam-pack به طوری که ماژول WebAssembly می تواند به درستی نمونه سازی شود. در نهایت، موضوع پردازشگر صوتی نیاز به فراخوانی دارد finalizeInit()
برای اطمینان از اینکه می تواند همچنان به کد WebAssembly دسترسی داشته باشد، عمل کند.
اکنون که تمام تنظیمات کامل شده است، رشته صوتی می تواند از آن استفاده کند SineOsc
با فراخوانی توابع جاوا اسکریپت wrapper ایجاد کنید.
در اینجا نحوه تنظیم نوسان ساز است:
// processor.js
class WebSynthProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.port.onmessage = event => this.onmessage(event.data);
}
onmessage(data) {
// Check to make sure the message we receive is the correct type.
if (data.type === 'init-wasm') {
cachedTextDecoder = data.decoder;
// Load returns a promise
load(data.wasmData, getImports()).then(mod => {
// Once the WebAssembly module has been instantiated, it needs to be finalized
// so that it can be accessed later.
finalizeInit(mod.instance, mod.module);
// Create the oscillator instance.
this.osc = SineOsc.new(sampleRate);
});
}
}
}
اکنون که رشته صوتی یک نوسان ساز ایجاد کرده است، می توان آن نوسانگر را برای تولید صدای واقعی فراخوانی کرد.
// processor.js
class WebSynthProcessor extends AudioWorkletProcessor {
// ...
process(inputs, outputs, parameters) {
if (typeof this.osc !== 'undefined' && this.osc !== null) {
// Get the first output channel.
let output = outputs[0];
output.forEach(channel => {
// populate the channel's buffer with samples from the WebAssembly code.
for (let i = 0; i < channel.length; i++) {
// Remember the first argument for the sample method is the pitch we want to
// synthesize and the second argument is the volume.
let sample = this.osc.sample(440, 0.5);
channel[i] = sample;
}
});
} else {
console.log('wasm not instantiated yet');
}
return true;
}
}
بعدش چی؟
در این مرحله، من نحوه سازگاری را نشان دادم wasm-pack
‘s خروجی به طوری که می توان از آن در یک استفاده کرد AudioWorkletProcessor
فایل. گام بعدی شروع ساختن ویژگی های لازم برای یک سینت سایزر قابل استفاده است.
اگر می خواهید در مورد این پروژه بیشتر بخوانید، می توانید من را دنبال کنید یا پست های دیگر من در این مجموعه را بخوانید.
معرفی سریال:
کد منبع این نمونه اولیه:
نمونه اولیه Web Synthesizer 2
این پروژه اثبات مفهومی برای استفاده از Rust برای تولید نمونه برای سینت سایزر پشتیبانی شده از Web Audio API است.
می توانید مقدمه پروژه را در dev.to بخوانید.
وابستگی ها
برای ساخت این پروژه به موارد زیر نیاز دارید:
ساختمان
برای ساخت اجزای Rust این پروژه، به سادگی اجرا کنید
wasm-pack build --target web
برای جزئیات در مورد نحوه ادغام آن در یک صفحه وب به این پست مراجعه کنید.
در حال دویدن
این پروژه را می توان با استفاده از پسوند “Live Server” کد ویژوال استودیو اجرا کرد. به سادگی آن را نصب کنید، و دستورالعمل ها را دنبال کنید تا به صورت زنده پخش شود، پس از اجرای سرور، به دایرکتوری “www” بروید تا صفحه وب را ببینید.
اگر کد ویژوال استودیو ندارید، باید بتوانید باز کنید index.html
در www
دایرکتوری در هر مرورگر وب مدرن اگرچه من این روش را به طور کامل آزمایش نکرده ام، فکر نمی کنم شما اجرا کنید…