Skip to content

Commit

Permalink
Wire up safety limiter to destination node
Browse files Browse the repository at this point in the history
 * Migrate destination node to proper full-featured `ForeignNode`
 * Finish up `SafetyLimiterAWP` with full-featured wasm loading, io handling, etc.
 * Setup dynamic dis/connect for safety limiter enabled and disabled cases
  • Loading branch information
Ameobea committed Dec 13, 2024
1 parent b9c8bbb commit 13fab1e
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 101 deletions.
53 changes: 27 additions & 26 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ tokio = { version = "1.24", features = ["macros", "rt-multi-thread"] }
scrypt = "0.11.0"
base64 = "0.22"

rust-s3 = { version = "0.34", features = [] }
rust-s3 = { version = "0.35", features = [] }
aws-region = { version = "0.25.0", features = ["serde"] }
urlencoding = "2.1"

Expand Down
3 changes: 3 additions & 0 deletions engine/safety_limiter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ fn process(envelope: &mut f32, sample: f32) -> f32 {
dsp::clamp(-4., 4., sample)
}

#[no_mangle]
pub extern "C" fn safety_limiter_get_io_buffer_ptr() -> *mut f32 { io_buf().as_mut_ptr() }

#[no_mangle]
pub extern "C" fn safety_limiter_process() {
let state = state();
Expand Down
74 changes: 74 additions & 0 deletions public/SafetyLimiterAWP.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
class SafetyLimiterAWP extends AudioWorkletProcessor {
constructor() {
super();

this.isShutdown = false;
this.wasmInstance = null;
this.ioBufPtr = 0;
this.wasmMemoryBuffer = null;

this.port.onmessage = evt => this.handleMessage(evt.data);
}

handleMessage(data) {
switch (data.type) {
case 'setWasmBytes': {
this.initWasm(data.wasmBytes);
break;
}
default: {
console.error('Unhandled message type in safety limiter AWP: ', evt.data.type);
}
}
}

/**
* @param {ArrayBuffer} wasmBytes
*/
async initWasm(wasmBytes) {
const compiledModule = await WebAssembly.compile(wasmBytes);
this.wasmInstance = await WebAssembly.instantiate(compiledModule, { env: {} });
this.ioBufPtr = this.wasmInstance.exports.safety_limiter_get_io_buffer_ptr();
this.wasmMemoryBuffer = new Float32Array(this.wasmInstance.exports.memory.buffer);
}

getWasmMemoryBuffer() {
if (this.wasmMemoryBuffer?.buffer !== this.wasmInstance.exports.memory.buffer) {
this.wasmMemoryBuffer = new Float32Array(this.wasmInstance.exports.memory.buffer);
}
return this.wasmMemoryBuffer;
}

process(inputs, outputs, _params) {
if (!this.ioBufPtr || this.isShutdown) {
return false;
}

const input = inputs[0]?.[0];
const output = outputs[0]?.[0];
if (!input || !output) {
return true;
}

const wasmMemory = this.getWasmMemoryBuffer();
const sampleCount = input.length;
let ioBuf = wasmMemory.subarray(
this.ioBufPtr / Float32Array.BYTES_PER_ELEMENT,
this.ioBufPtr / Float32Array.BYTES_PER_ELEMENT + sampleCount
);
ioBuf.set(input);

this.wasmInstance.exports.safety_limiter_process();

if (ioBuf.length === 0) {
ioBuf = this.getWasmMemoryBuffer().subarray(
this.ioBufPtr / Float32Array.BYTES_PER_ELEMENT,
this.ioBufPtr / Float32Array.BYTES_PER_ELEMENT + sampleCount
);
}

return true;
}
}

registerProcessor('safety-limiter-awp', SafetyLimiterAWP);
35 changes: 0 additions & 35 deletions src/globalLimiter/GlobalLimiterAWP.js

This file was deleted.

26 changes: 1 addition & 25 deletions src/graphEditor/nodes/CustomAudio/CustomAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { LGAudioConnectables } from '../AudioConnectablesNode';
import { FMSynthFxNode } from './FMSynthFx/FMSynthFxNode';
import { SubgraphPortalNode } from 'src/graphEditor/nodes/CustomAudio/Subgraph/SubgraphPortalNode';
import { BPMNode } from 'src/graphEditor/nodes/CustomAudio/BPM/BPMNode';
import { DestinationNodeSmallViewShim } from 'src/graphEditor/nodes/CustomAudio/Destination/DestinationNodeSmallView';
import { CustomDestinationNode } from 'src/graphEditor/nodes/CustomAudio/Destination/CustomDestinationNode';

const ctx = new AudioContext();

Expand Down Expand Up @@ -336,30 +336,6 @@ const CustomBiquadFilterNode = enhanceAudioNode({
),
});

export class CustomAudioDestinationNode extends GainNode {
constructor(ctx: AudioContext) {
super(ctx);
return (ctx as any).globalVolume as GainNode;
}
}

const CustomDestinationNode = enhanceAudioNode({
AudioNodeClass: CustomAudioDestinationNode,
nodeType: 'customAudio/destination',
name: 'Destination',
buildConnectables: (foreignNode: ForeignNode<GainNode> & { node: GainNode }) => ({
inputs: Map<string, ConnectableInput>().set('input', {
node: foreignNode.node,
type: 'customAudio',
}),
outputs: Map<string, ConnectableOutput>(),
node: foreignNode,
}),
SmallViewRenderer: DestinationNodeSmallViewShim,
getOverridableParams: () => [],
paramKeys: [],
});

const NativeCompressorNode = enhanceAudioNode({
AudioNodeClass: DynamicsCompressorNode,
nodeType: 'customAudio/nativeCompressor',
Expand Down
Loading

0 comments on commit 13fab1e

Please sign in to comment.