Last active
April 21, 2022 02:58
-
-
Save charlieroberts/f17737cd51b0dffc5ffa2de59a39830e to your computer and use it in GitHub Desktop.
Benchmark comparisons of common synthesis tasks between Genish.js and the Web Audio API, for the 2017 Web Audio Conference
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<!-- npm install mathjs [email protected] --> | |
<script src='./node_modules/genish.js/dist/gen.lib.js'></script> | |
<script src='./node_modules/mathjs/dist/math.js'></script> | |
</head> | |
<body></body> | |
<script> | |
if( typeof OfflineAudioContext === 'undefined' ) window.OfflineAudioContext = webkitOfflineAudioContext | |
const results = {} | |
const debug = false | |
const tests = { | |
sine_genish: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100 * this.seconds, 44100 ) | |
let g = genish | |
g.utilities.ctx = ctx | |
let sines = [] | |
for( let i = 0; i < count; i++ ) { | |
let s = g.cycle( 440 ) | |
sines.push( s ) | |
} | |
g.utilities.playGraph( g.add( ...sines ) ) | |
g.utilities.createScriptProcessor() | |
let start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
let end = performance.now() | |
if( debug ) console.log( 'genish sine rendering time:', end - start ) | |
tests.sine_genish.times.push( end - start ) | |
run() | |
} | |
}, | |
times:[], | |
count:100, | |
seconds:60 | |
}, | |
sine_waapi: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100*60, 44100 ) | |
for( let i = 0; i < count; i++ ) { | |
let s = ctx.createOscillator() | |
s.connect( ctx.destination ) | |
s.start() | |
} | |
let start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
let end = performance.now() | |
if( debug ) console.log( 'waapi sine rendering time:', end - start ) | |
tests.sine_waapi.times.push( end-start) | |
run() | |
} | |
}, | |
times:[], | |
count:100, | |
seconds:60, | |
}, | |
vibrato_genish: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100*this.seconds, 44100 ) | |
let g = genish | |
g.utilities.ctx = ctx | |
g.utilities.createScriptProcessor() | |
let sines = [] | |
for( let i = 0; i < count; i++ ) { | |
let m = g.cycle( 4 ) | |
let c = g.cycle( g.add( 440 + i, g.mul( m, 10 ) ) ) | |
sines.push( c ) | |
} | |
g.utilities.playGraph( g.add( ...sines ) ) | |
const start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
const end = performance.now() | |
if( debug ) console.log( 'genish vibrato rendering time:', end - start ) | |
tests.vibrato_genish.times.push( end-start ) | |
run() | |
} | |
}, | |
times:[], | |
count:50, | |
seconds:60 | |
}, | |
vibrato_waapi: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100*this.seconds, 44100 ) | |
for( let i = 0; i < count; i++ ) { | |
let osc = ctx.createOscillator() | |
osc.frequency.value = 440 + i | |
let mod = ctx.createOscillator() | |
mod.frequency.value = 4 | |
let gainNode = ctx.createGain() | |
gainNode.gain.value = 10 | |
mod.connect( gainNode ) | |
gainNode.connect( osc.frequency ) | |
gainNode.connect( ctx.destination ) | |
osc.start() | |
mod.start() | |
} | |
const start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
const end = performance.now() | |
if( debug ) console.log( 'waapi vibrato rendering time:', end - start ) | |
tests.vibrato_waapi.times.push( end-start ) | |
run() | |
} | |
}, | |
times:[], | |
count:50, | |
seconds:60 | |
}, | |
envelopedSaw_genish: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100 * this.seconds, 44100 ) | |
let g = genish | |
g.utilities.ctx = ctx | |
let syns = [] | |
for( let i = 0; i < count; i++ ) { | |
let env = g.ad( 44100 * this.seconds / 2, 44100 * this.seconds / 2 ) | |
let osc = g.phasor( 440 ) | |
let graph = g.mul( env, osc ) | |
graph.env = env | |
syns.push( graph ) | |
} | |
g.utilities.playGraph( g.add( ...syns ) ) | |
for( let graph of syns ) graph.env.trigger() | |
g.utilities.createScriptProcessor() | |
const start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
const end = performance.now() | |
if( debug ) console.log( 'genish vibrato rendering time:', end - start ) | |
tests.envelopedSaw_genish.times.push( end-start ) | |
run() | |
} | |
}, | |
times:[], | |
count:50, | |
seconds:60 | |
}, | |
envelopedSaw_waapi: { | |
run() { | |
let count = this.count | |
let ctx = new OfflineAudioContext( 2, 44100*this.seconds, 44100 ) | |
let attack = 44100 * this.seconds / 2 | |
let decay = 44100 * this.seconds / 2 | |
for( let i = 0; i < count; i++ ) { | |
osc = ctx.createOscillator() | |
osc.type = 'sawtooth' | |
osc.frequency.value = 440 | |
gainNode = ctx.createGain() | |
gainNode.gain.setValueAtTime( 0, ctx.currentTime ) | |
gainNode.gain.linearRampToValueAtTime( 1, ctx.currentTime + attack ) | |
gainNode.gain.linearRampToValueAtTime( 0, ctx.currentTime + attack + decay ) | |
osc.connect( gainNode ) | |
gainNode.connect( ctx.destination ) | |
osc.start() | |
} | |
const start = performance.now() | |
ctx.startRendering() | |
ctx.oncomplete = e => { | |
const end = performance.now() | |
if( debug ) console.log( 'waapi vibrato rendering time:', end - start ) | |
tests.envelopedSaw_waapi.times.push( end-start ) | |
run() | |
} | |
}, | |
times:[], | |
count:50, | |
seconds:60 | |
}, | |
} | |
const run = () => { | |
let test = benchmarks.next().value | |
if( test !== undefined ) | |
test.run() | |
else | |
console.log( 'tests completed' ) | |
} | |
const factory = function*() { | |
let keys = Object.keys( tests ) | |
let index = 0 | |
let count = 0 | |
let max = 11 | |
while( index < keys.length && count < max ) { | |
let test = tests[ keys[ index++ ] ] | |
if( index === keys.length ) { | |
if( count++ < max ) { | |
console.log( 'round', count, 'of', max-1, 'complete.' ) | |
index = 0 | |
}else{ | |
break; | |
} | |
} | |
yield test | |
} | |
console.log(` | |
************************** | |
results (${max-1} samples) | |
************************** | |
`) | |
for( let testName in tests ) { | |
let test = tests[ testName ] | |
// ignore first results via calls to slice(), which are bad for genish | |
// due to initial compile times | |
let std = math.std( tests[ testName ].times.slice(0,-1) ) | |
let mean = math.mean( tests[ testName ].times.slice(0,-1) ) | |
let median = math.median( tests[ testName ].times.slice(0,-1) ) | |
let count_seconds = `count:${test.count}, seconds:${test.seconds}` | |
let output = `${testName} : | |
${count_seconds} | |
mean : ${mean} | |
median : ${median} | |
std : ${std} | |
` | |
console.log( output ) | |
} | |
} | |
window.benchmarks = factory() | |
console.log( 'starting benchmarks...' ) | |
benchmarks.next().value.run() | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment