Skip to content

Commit

Permalink
LibWeb: Add BaseAudioContext.create_periodic_wave() factory method
Browse files Browse the repository at this point in the history
  • Loading branch information
tcl3 authored and awesomekling committed Jan 3, 2025
1 parent f90b79f commit a6f0450
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Libraries/LibWeb/WebAudio/BaseAudioContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ WebIDL::ExceptionOr<GC::Ref<PannerNode>> BaseAudioContext::create_panner()
return PannerNode::create(realm(), *this);
}

WebIDL::ExceptionOr<GC::Ref<PeriodicWave>> BaseAudioContext::create_periodic_wave(Vector<float> const& real, Vector<float> const& imag, Optional<PeriodicWaveConstraints> const& constraints)
{
PeriodicWaveOptions options;
options.real = real;
options.imag = imag;
if (constraints.has_value())
options.disable_normalization = constraints->disable_normalization;

return PeriodicWave::construct_impl(realm(), *this, options);
}

// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
WebIDL::ExceptionOr<void> BaseAudioContext::verify_audio_options_inside_nominal_range(JS::Realm& realm, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate)
{
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/WebAudio/BaseAudioContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <LibWeb/WebAudio/ChannelMergerNode.h>
#include <LibWeb/WebAudio/ChannelSplitterNode.h>
#include <LibWeb/WebAudio/ConstantSourceNode.h>
#include <LibWeb/WebAudio/PeriodicWave.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::WebAudio {
Expand Down Expand Up @@ -67,6 +68,7 @@ class BaseAudioContext : public DOM::EventTarget {
WebIDL::ExceptionOr<GC::Ref<DynamicsCompressorNode>> create_dynamics_compressor();
WebIDL::ExceptionOr<GC::Ref<GainNode>> create_gain();
WebIDL::ExceptionOr<GC::Ref<PannerNode>> create_panner();
WebIDL::ExceptionOr<GC::Ref<PeriodicWave>> create_periodic_wave(Vector<float> const& real, Vector<float> const& imag, Optional<PeriodicWaveConstraints> const& constraints = {});

GC::Ref<WebIDL::Promise> decode_audio_data(GC::Root<WebIDL::BufferSource>, GC::Ptr<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>);

Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/WebAudio/BaseAudioContext.idl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface BaseAudioContext : EventTarget {
[FIXME] IIRFilterNode createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
OscillatorNode createOscillator();
PannerNode createPanner();
[FIXME] PeriodicWave createPeriodicWave (sequence<float> real, sequence<float> imag, optional PeriodicWaveConstraints constraints = {});
PeriodicWave createPeriodicWave (sequence<float> real, sequence<float> imag, optional PeriodicWaveConstraints constraints = {});
[FIXME] ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0, optional unsigned long numberOfInputChannels = 2, optional unsigned long numberOfOutputChannels = 2);
[FIXME] StereoPannerNode createStereoPanner ();
[FIXME] WaveShaperNode createWaveShaper ();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Harness status: OK

Found 29 tests

28 Pass
1 Fail
Pass # AUDIT TASK RUNNER STARTED.
Pass Executing "create with factory method"
Pass Executing "different length with factory method"
Pass Executing "too small with factory method"
Pass Executing "create with constructor"
Pass Executing "different length with constructor"
Pass Executing "too small with constructor"
Fail Executing "output test"
Pass Audit report
Pass > [create with factory method]
Pass context.createPeriodicWave(new Float32Array(8192), new Float32Array(8192)) did not throw an exception.
Pass < [create with factory method] All assertions passed. (total 1 assertions)
Pass > [different length with factory method]
Pass context.createPeriodicWave(new Float32Array(512), new Float32Array(4)) threw IndexSizeError: "Real and imaginary arrays must have the same length and contain at least 2 elements".
Pass < [different length with factory method] All assertions passed. (total 1 assertions)
Pass > [too small with factory method]
Pass context.createPeriodicWave(new Float32Array(1), new Float32Array(1)) threw IndexSizeError: "Real and imaginary arrays must have the same length and contain at least 2 elements".
Pass < [too small with factory method] All assertions passed. (total 1 assertions)
Pass > [create with constructor]
Pass new PeriodicWave(context, { real : new Float32Array(8192), imag : new Float32Array(8192) }) did not throw an exception.
Pass < [create with constructor] All assertions passed. (total 1 assertions)
Pass > [different length with constructor]
Pass new PeriodicWave(context, { real : new Float32Array(8192), imag : new Float32Array(4) }) threw IndexSizeError: "Real and imaginary arrays must have the same length and contain at least 2 elements".
Pass < [different length with constructor] All assertions passed. (total 1 assertions)
Pass > [too small with constructor]
Pass new PeriodicWave(context, { real : new Float32Array(1), imag : new Float32Array(1) }) threw IndexSizeError: "Real and imaginary arrays must have the same length and contain at least 2 elements".
Pass < [too small with constructor] All assertions passed. (total 1 assertions)
Pass > [output test]
Pass # AUDIT TASK RUNNER FINISHED: 7 tasks ran successfully.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Constructor: PeriodicWave
</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../webaudio/resources/audit-util.js"></script>
<script src="../../../webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// real and imag are used in separate PeriodicWaves to make their peak values
// easy to determine.
const realMax = 99;
var real = new Float32Array(realMax + 1);
real[1] = 2.0; // fundamental
real[realMax] = 3.0;
const realPeak = real[1] + real[realMax];
const realFundamental = 19.0;
var imag = new Float32Array(4);
imag[0] = 6.0; // should be ignored.
imag[3] = 0.5;
const imagPeak = imag[3];
const imagFundamental = 551.0;

const testLength = 8192;
let context = new AudioContext();

let audit = Audit.createTaskRunner();

// Create with the factory method

audit.define('create with factory method', (task, should) => {
should(() => {
context.createPeriodicWave(new Float32Array(testLength), new Float32Array(testLength));
}, 'context.createPeriodicWave(new Float32Array(' + testLength + '), ' +
'new Float32Array(' + testLength + '))').notThrow();
task.done();
});

audit.define('different length with factory method', (task, should) => {
should(() => {
context.createPeriodicWave(new Float32Array(512), new Float32Array(4));
}, 'context.createPeriodicWave(new Float32Array(512), ' +
'new Float32Array(4))').throw(DOMException, "IndexSizeError");
task.done();
});

audit.define('too small with factory method', (task, should) => {
should(() => {
context.createPeriodicWave(new Float32Array(1), new Float32Array(1));
}, 'context.createPeriodicWave(new Float32Array(1), ' +
'new Float32Array(1))').throw(DOMException, "IndexSizeError");
task.done();
});

// Create with the constructor

audit.define('create with constructor', (task, should) => {
should(() => {
new PeriodicWave(context, { real: new Float32Array(testLength), imag: new Float32Array(testLength) });
}, 'new PeriodicWave(context, { real : new Float32Array(' + testLength + '), ' +
'imag : new Float32Array(' + testLength + ') })').notThrow();
task.done();
});

audit.define('different length with constructor', (task, should) => {
should(() => {
new PeriodicWave(context, { real: new Float32Array(testLength), imag: new Float32Array(4) });
}, 'new PeriodicWave(context, { real : new Float32Array(' + testLength + '), ' +
'imag : new Float32Array(4) })').throw(DOMException, "IndexSizeError");
task.done();
});

audit.define('too small with constructor', (task, should) => {
should(() => {
new PeriodicWave(context, { real: new Float32Array(1), imag: new Float32Array(1) });
}, 'new PeriodicWave(context, { real : new Float32Array(1), ' +
'imag : new Float32Array(1) })').throw(DOMException, "IndexSizeError");
task.done();
});

audit.define('output test', (task, should) => {
let context = new OfflineAudioContext(2, testLength, 44100);
// Create the expected output buffer
let expectations = context.createBuffer(2, testLength, context.sampleRate);
for (var i = 0; i < expectations.length; ++i) {

expectations.getChannelData(0)[i] = 1.0 / realPeak *
(real[1] * Math.cos(2 * Math.PI * realFundamental * i /
context.sampleRate) +
real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i /
context.sampleRate));

expectations.getChannelData(1)[i] = 1.0 / imagPeak *
imag[3] * Math.sin(2 * Math.PI * 3 * imagFundamental * i /
context.sampleRate);
}

// Create the real output buffer
let merger = context.createChannelMerger();

let osc1 = context.createOscillator();
let osc2 = context.createOscillator();

osc1.setPeriodicWave(context.createPeriodicWave(
real, new Float32Array(real.length)));
osc2.setPeriodicWave(context.createPeriodicWave(
new Float32Array(imag.length), imag));
osc1.frequency.value = realFundamental;
osc2.frequency.value = imagFundamental;

osc1.start();
osc2.start();

osc1.connect(merger, 0, 0);
osc2.connect(merger, 0, 1);

context.startRendering().then(reality => {
should(reality, 'rendering PeriodicWave').beEqualToArray(expectations);
task.done();
});
});

audit.run();
</script>
</body>
</html>

0 comments on commit a6f0450

Please sign in to comment.