Skip to content

Commit 116757a

Browse files
author
Thomas Dodds
committed
Adding new pitch bend methods for the AdvancedAudioPlayer class, bumping to version v2.6.7
1 parent 9d2b1bd commit 116757a

File tree

15 files changed

+1145
-9
lines changed

15 files changed

+1145
-9
lines changed

dist/Superpowered.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable */
2+
// @ts-nocheck
23

34
class SuperpoweredGlue {
45

5-
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.6/dist/superpowered-npm.wasm"
6+
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.7/dist/superpowered-npm.wasm"
67

78
niceSize(bytes) {
89
if (bytes == 0) return '0 byte'; else if (bytes == 1) return '1 byte';
@@ -748,7 +749,7 @@ class SuperpoweredWebAudio {
748749
}.bind(node);
749750
});
750751
} else {
751-
import(/* webpackIgnore: true */ url).then((processorModule) => {
752+
import(/* webpackIgnore: true */ /* viteIgnore: true */ url).then((processorModule) => {
752753
const node = this.audioContext.createScriptProcessor(1024, 2, 2);
753754
node.trackLoaderID = this.Superpowered.registerTrackLoader(node);
754755
node.samplerate = this.audioContext.sampleRate;

dist/superpowered-npm.wasm

274 Bytes
Binary file not shown.

dist/superpowered.wasm

276 Bytes
Binary file not shown.

examples/example_effects/Superpowered.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable */
2+
// @ts-nocheck
23

34
class SuperpoweredGlue {
45

5-
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.6/dist/superpowered-npm.wasm"
6+
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.7/dist/superpowered-npm.wasm"
67

78
niceSize(bytes) {
89
if (bytes == 0) return '0 byte'; else if (bytes == 1) return '1 byte';
@@ -748,7 +749,7 @@ class SuperpoweredWebAudio {
748749
}.bind(node);
749750
});
750751
} else {
751-
import(/* webpackIgnore: true */ url).then((processorModule) => {
752+
import(/* webpackIgnore: true */ /* viteIgnore: true */ url).then((processorModule) => {
752753
const node = this.audioContext.createScriptProcessor(1024, 2, 2);
753754
node.trackLoaderID = this.Superpowered.registerTrackLoader(node);
754755
node.samplerate = this.audioContext.sampleRate;

examples/example_guitardistortion/Superpowered.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable */
2+
// @ts-nocheck
23

34
class SuperpoweredGlue {
45

5-
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.6/dist/superpowered-npm.wasm"
6+
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.7/dist/superpowered-npm.wasm"
67

78
niceSize(bytes) {
89
if (bytes == 0) return '0 byte'; else if (bytes == 1) return '1 byte';
@@ -748,7 +749,7 @@ class SuperpoweredWebAudio {
748749
}.bind(node);
749750
});
750751
} else {
751-
import(/* webpackIgnore: true */ url).then((processorModule) => {
752+
import(/* webpackIgnore: true */ /* viteIgnore: true */ url).then((processorModule) => {
752753
const node = this.audioContext.createScriptProcessor(1024, 2, 2);
753754
node.trackLoaderID = this.Superpowered.registerTrackLoader(node);
754755
node.samplerate = this.audioContext.sampleRate;

examples/example_pitchbend/Superpowered.js

+869
Large diffs are not rendered by default.

examples/example_pitchbend/index.html

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Superpowered WebAssembly Audio Player Time Stretching and Pitch Shifting Example</title>
5+
<script type="module" src="./polyfill_worklet_import.js"></script>
6+
</head>
7+
<body>
8+
<div id="content">Initializing...</div>
9+
<script src="main.js" type="module"></script>
10+
</body>
11+
</html>

examples/example_pitchbend/main.js

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import "./Superpowered.js";
2+
3+
var webaudioManager = null; // The SuperpoweredWebAudio helper class managing Web Audio for us.
4+
var Superpowered = null; // A Superpowered instance.
5+
var audioNode = null; // This example uses one audio node only.
6+
var content = null; // The <div> displaying everything.
7+
var pitchShift = 0; // The current pitch shift value.
8+
var currentPath = null;
9+
var pbPerc = null;
10+
11+
function changePitchShift(e) {
12+
// limiting the new pitch shift value
13+
let value = parseInt(e.target.value);
14+
pitchShift += value;
15+
if (pitchShift < -12) pitchShift = -12; else if (pitchShift > 12) pitchShift = 12;
16+
// displaying the value
17+
document.getElementById('pitch-shift-display').innerText = ' pitch shift: ' + ((pitchShift < 1) ? pitchShift : '+' + pitchShift) + ' ';
18+
// sending the new value to the audio node
19+
audioNode.sendMessageToAudioScope({ 'pitchShift': pitchShift });
20+
}
21+
22+
// on change by the rate slider
23+
function changeRate() {
24+
// displaying the new rate
25+
let value = document.getElementById('rateSlider').value, text;
26+
if (value == 10000) text = 'original tempo';
27+
else if (value < 10000) text = '-' + (100 - value / 100).toPrecision(2) + '%';
28+
else text = '+' + (value / 100 - 100).toPrecision(2) + '%';
29+
document.getElementById('rateDisplay').innerText = text;
30+
// sending the new rate to the audio node
31+
audioNode.sendMessageToAudioScope({ rate: value });
32+
}
33+
34+
function changePitchBend(e) {
35+
let faster = 1;
36+
const value = e.target.value;
37+
if (value < 0) {
38+
faster = 0;
39+
}
40+
audioNode.sendMessageToAudioScope({
41+
'pitchBend': true,
42+
maxPercent: Math.abs(value/100),
43+
bendStretch: 0,
44+
faster,
45+
holdMs: 100
46+
});
47+
}
48+
49+
// double click on the rate slider
50+
function changeRateDbl() {
51+
document.getElementById('rateSlider').value = 10000;
52+
changeRate();
53+
}
54+
55+
// double click on the rate slider
56+
function changeBendDbl() {
57+
document.getElementById('pitchBend').value = 0;
58+
audioNode.sendMessageToAudioScope({
59+
'pitchBend': true,
60+
maxPercent: 0,
61+
bendStretch: 0,
62+
faster: 0,
63+
holdMs: 100
64+
});
65+
}
66+
67+
// click on play/pause
68+
function togglePlayback(e) {
69+
let button = document.getElementById('playPause');
70+
if (button.value == 1) {
71+
button.value = 0;
72+
button.innerText = 'Play audio';
73+
webaudioManager.audioContext.suspend();
74+
} else {
75+
button.value = 1;
76+
button.innerText = 'Pause audio';
77+
webaudioManager.audioContext.resume();
78+
}
79+
}
80+
81+
function onMessageFromAudioScope(message) {
82+
if (message.loaded) {
83+
// UI: innerHTML may be ugly but keeps this example small
84+
content.innerHTML = '\
85+
<h1>Superpowered AAP pitch bending</h1>\
86+
<button id="playPause" value="0">Play audio</button>\
87+
<h2>Pitch bend percentage</h2>\
88+
<div style="display: flex; justify-content: space-between;"><span>-30%</span><span>0%</span><span>+30%</span></div>\
89+
<input id="pitchBend" type="range" min="-30" max="30" value="0" style="width: 100%">\
90+
<div style="background: #909090; width: 100%; postion: relative;" id="bend-container"><div style="width: 50%; height: 10px; background: black;" id="bend-value"></div></div>\
91+
<div style="text-align: center;"><span>Current pitch bend percentage <span id="pitch-bend-percentage">100</span>%</span></div><br />\
92+
<button id="reset-bend">Reset pitch bend</button>\
93+
<h2>Playback Rate:</h2>\
94+
<p id="rateDisplay">original tempo</p>\
95+
<div style="display: flex; justify-content: space-between;"><span>-50%</span><span>+100%</span></div>\
96+
<input id="rateSlider" type="range" min="5000" max="20000" value="10000" style="width: 100%">\
97+
<button id="reset-rate">Reset playback rate</button> <br /><br />\
98+
<button id="pitchMinus" value="-1">-</button>\
99+
<span id="pitch-shift-display"> pitch shift: 0 </span>\
100+
<button id="pitchPlus" value="1">+</button>\
101+
';
102+
document.getElementById('rateSlider').addEventListener('input', changeRate);
103+
document.getElementById('pitchBend').addEventListener('input', changePitchBend);
104+
document.getElementById('pitchBend').addEventListener('dblclick', changeBendDbl);
105+
document.getElementById('rateSlider').addEventListener('dblclick', changeRateDbl);
106+
document.getElementById('reset-bend').addEventListener('click', changeBendDbl);
107+
document.getElementById('reset-rate').addEventListener('click', changeRateDbl);
108+
document.getElementById('pitchMinus').addEventListener('click', changePitchShift);
109+
document.getElementById('pitchPlus').addEventListener('click', changePitchShift);
110+
document.getElementById('playPause').addEventListener('click', togglePlayback);
111+
pbPerc = document.getElementById('pitch-bend-percentage');
112+
}
113+
if (message.pitchBendDetails && document.getElementById('bend-value')) {
114+
if (pbPerc && (typeof message.pitchBendDetails.currentPitchBend !== 'undefined')) {
115+
pbPerc.innerText = message.pitchBendDetails.currentPitchBend * 100;
116+
document.getElementById('bend-value').style.width = convertRange(message.pitchBendDetails.currentPitchBend * 100, [70, 130], [0, 100]) + '%';
117+
document.getElementById('bend-value').style.background = message.pitchBendDetails.currentPitchBend === 1 ? 'black' : message.pitchBendDetails.currentPitchBend < 1 ? 'red' : 'green';
118+
}
119+
}
120+
}
121+
122+
function convertRange( value, r1, r2 ) {
123+
return ( value - r1[ 0 ] ) * ( r2[ 1 ] - r2[ 0 ] ) / ( r1[ 1 ] - r1[ 0 ] ) + r2[ 0 ];
124+
}
125+
126+
function requestPitchBendDetails() {
127+
audioNode.sendMessageToAudioScope({ requestPitchBend: true });
128+
requestAnimationFrame(requestPitchBendDetails)
129+
}
130+
131+
// when the START button is clicked
132+
async function start() {
133+
// content.innerText = 'Creating the audio context and node...';
134+
webaudioManager = new SuperpoweredWebAudio(44100, Superpowered);
135+
currentPath = window.location.href.substring(0, window.location.href.lastIndexOf('/'));
136+
audioNode = await webaudioManager.createAudioNodeAsync(currentPath + '/processor.js?date=' + Date.now(), 'MyProcessor', onMessageFromAudioScope);
137+
// audioNode -> audioContext.destination (audio output)
138+
webaudioManager.audioContext.suspend();
139+
audioNode.connect(webaudioManager.audioContext.destination);
140+
141+
// start polling of pitch bend details from audioworklet
142+
requestAnimationFrame(requestPitchBendDetails)
143+
}
144+
145+
async function loadFromMainThread() {
146+
Superpowered.downloadAndDecode(currentPath + '/track.mp3', audioNode);
147+
}
148+
149+
async function loadJS() {
150+
Superpowered = await SuperpoweredGlue.Instantiate('ExampleLicenseKey-WillExpire-OnNextUpdate', 'http://localhost:8080/superpowered-npm.wasm');
151+
152+
// display the START button
153+
content = document.getElementById('content');
154+
content.innerHTML = `<div>
155+
<button id="loadFromMainThread">Start</button>
156+
</div>`;
157+
document.getElementById('loadFromMainThread').addEventListener('click', loadFromMainThread);
158+
start();
159+
}
160+
161+
loadJS();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// A temporary polyfill to enable import ES6 modules in AudioWorklets using the browser version of Rollup
2+
// All credit goes to https://gist.github.com/lukaslihotzki/b50ccb61ff3a44b48fc4d5ed7e54303f
3+
4+
const wrappedFunc = Worklet.prototype.addModule;
5+
6+
Worklet.prototype.addModule = async function(url) {
7+
try {
8+
return await wrappedFunc.call(this, url);
9+
} catch (e) {
10+
if (e.name != 'AbortError') {
11+
// throw e;
12+
}
13+
// assume error is caused by https://bugzilla.mozilla.org/show_bug.cgi?id=1572644
14+
console.warn('addModule call failed, resorting to bundling with rollup');
15+
const {rollup} = await import('/lib/rollup.browser.js');
16+
const generated = await (await rollup({
17+
input: url,
18+
onwarn: console.warn,
19+
plugins: [
20+
{
21+
resolveId(importee, importer) {
22+
return new URL(importee, new URL(importer || window.location.href)).toString();
23+
},
24+
load(id) {
25+
return fetch(id).then(response => response.text());
26+
},
27+
}
28+
],
29+
})).generate({});
30+
const blob = new Blob([generated.output[0].code], {type: 'text/javascript'});
31+
const objectUrl = URL.createObjectURL(blob);
32+
try {
33+
return await wrappedFunc.call(this, objectUrl);
34+
} finally {
35+
URL.revokeObjectURL(objectUrl);
36+
}
37+
}
38+
};
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import "./Superpowered.js";
2+
3+
class MyProcessor extends SuperpoweredWebAudio.AudioWorkletProcessor {
4+
// runs after the constructor
5+
cancelledPitchBend = true;
6+
7+
onReady() {
8+
this.player = new this.Superpowered.AdvancedAudioPlayer(this.samplerate, 2, 2, 0, 0.501, 2, false);
9+
}
10+
11+
onDestruct() {
12+
this.player.destruct();
13+
}
14+
15+
onMessageFromMainScope(message) {
16+
// console.log('onMessageFromMainScope', message)
17+
if (message.SuperpoweredLoaded) {
18+
this.player.openMemory(this.Superpowered.arrayBufferToWASM(message.SuperpoweredLoaded.buffer), false, false);
19+
this.player.play();
20+
this.sendMessageToMainScope({ loaded: true });
21+
}
22+
if (typeof message.rate !== 'undefined') this.player.playbackRate = message.rate / 10000.0;
23+
if (typeof message.pitchShift !== 'undefined') this.player.pitchShiftCents = parseInt(message.pitchShift) * 100;
24+
if (typeof message.requestPitchBend !== 'undefined') this.sendMessageToMainScope({ pitchBendDetails: {currentPitchBend: this.currentPitchBend, currentPitchBendMsOffset: this.currentPitchBendMsOffset} })
25+
if (message.pitchBend) this.pitchBend = message.maxPercent !== 0 ? {
26+
maxPercent: message.maxPercent,
27+
bendStretch: message.bendStretch,
28+
faster: message.faster,
29+
holdMs: message.holdMs
30+
} : undefined;
31+
}
32+
33+
processAudio(inputBuffer, outputBuffer, buffersize, parameters) {
34+
if (this.pitchBend) {
35+
this.player.pitchBend(this.pitchBend.maxPercent, this.pitchBend.bendStretch, this.pitchBend.faster, this.pitchBend.holdMs);
36+
if (this.cancelledPitchBend) this.cancelledPitchBend = false;
37+
} else if (!this.cancelledPitchBend) {
38+
this.player.endContinuousPitchBend();
39+
this.cancelledPitchBend = true;
40+
}
41+
this.currentPitchBend = this.player.getCurrentPitchBendPercent();
42+
this.currentPitchBendMsOffset = this.player.getBendOffsetMs();
43+
if (!this.player.processStereo(outputBuffer.pointer, false, buffersize, 1)) this.Superpowered.memorySet(outputBuffer.pointer, 0, buffersize * 8);
44+
}
45+
}
46+
47+
if (typeof AudioWorkletProcessor === 'function') registerProcessor('MyProcessor', MyProcessor);
48+
export default MyProcessor;

examples/example_pitchbend/style.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pitchBend {
2+
transform: rotate(-90deg) translateY(calc(300 / 2));
3+
transform-origin: left;
4+
position: absolute;
5+
}
1.25 MB
Binary file not shown.

examples/example_pitchbend/track.mp3

1.84 MB
Binary file not shown.

examples/example_timestretching/Superpowered.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable */
2+
// @ts-nocheck
23

34
class SuperpoweredGlue {
45

5-
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.6/dist/superpowered-npm.wasm"
6+
static wasmCDNUrl = "https://cdn.jsdelivr.net/npm/@superpoweredsdk/web@2.6.7/dist/superpowered-npm.wasm"
67

78
niceSize(bytes) {
89
if (bytes == 0) return '0 byte'; else if (bytes == 1) return '1 byte';
@@ -748,7 +749,7 @@ class SuperpoweredWebAudio {
748749
}.bind(node);
749750
});
750751
} else {
751-
import(/* webpackIgnore: true */ url).then((processorModule) => {
752+
import(/* webpackIgnore: true */ /* viteIgnore: true */ url).then((processorModule) => {
752753
const node = this.audioContext.createScriptProcessor(1024, 2, 2);
753754
node.trackLoaderID = this.Superpowered.registerTrackLoader(node);
754755
node.samplerate = this.audioContext.sampleRate;

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@superpoweredsdk/web",
3-
"version": "2.6.6",
3+
"version": "2.6.7",
44
"description": "Superpowered interactive audio features in JavaScript + WebAssembly.",
55
"main": "dist/Superpowered.js",
66
"browser": "dist/Superpowered.js",

0 commit comments

Comments
 (0)