برنامه نویسی

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

اگر این مجموعه را دنبال نکرده اید، من در Rust و WebAssembly یک سینتی سایزر می سازم. مزیت استفاده از WebAssembly این است که می توانم از تمام ویژگی های Rust استفاده کنم و در عین حال از مشکلات استفاده از کتابخانه های پیچیده سیستم عامل برای پخش صدا اجتناب کنم.

آخرین راه حل من ایده آل نبود

در آخرین پست، به یک راه حل کارآمد، اما کمتر ایده آل برای وارد کردن WebAssembly به یک رسیدم AudioWorkletProcessor. حتی اگر در ابتدا نمی‌توانستم بفهمم چگونه می‌توان یک ماژول WebAssembly را با استفاده از یک Worklet وارد کرد wasm-pack، روند انجام این کار بدون wasm-pack به من امکان داد راه حل بالقوه ای را شناسایی کنم که از آن استفاده می کند wasm-pack.
گام بعدی تلاش برای اجرای آن بود.

یک ایده بهتر

هنگامی که فرآیند وارد کردن یک ماژول WebAssembly را به یک Worklet درک کردم، این کار را با استفاده از آن انجام دادم wasm-pack ثابت کرد که نسبتاً ساده است. دو مرحله کلی وجود دارد:

  1. ماژول WebAssembly را با استفاده از wasm-pack web هدف. این web target کد جاوا اسکریپتی را ایجاد می کند که بدون باندلر یا Node کار می کند.
  2. جاوا اسکریپت را در 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 دایرکتوری در هر مرورگر وب مدرن اگرچه من این روش را به طور کامل آزمایش نکرده ام، فکر نمی کنم شما اجرا کنید…

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا