How I built the SiriWaveJS library: a look at the math and the code

Flavio De Stefano
Oct 26, 2018 · 10 min read

The classic wave style

Classic style
Siri wave-form in iOS 7/8
Plot for y = sin(x)

Basic code concepts

const ctx = canvas.getContext('2d');ctx.beginPath();
ctx.strokeStyle = 'white';
for (let i = -2; i <= 2; i += 0.01) {
const x = _xpos(i);
const y = _ypos(i);
ctx.lineTo(x, y);
}
ctx.stroke();
Plot drawn with precision = 0.2

Implement _xpos(i)

_xpos(i) = w * [ (i + B) / 2B ]

Implement _ypos

const K = 4;
const FREQ = 6;
function _attFn(x) {
return Math.pow(K / (K + Math.pow(x, K)), K);
}
function _ypos(i) {
return Math.sin(FREQ * i - phase) *
_attFn(i) *
canvasHeight *
globalAmplitude *
(1 / attenuation);
}

Phase

phase = (phase + (Math.PI / 2) * speed) % (2 * Math.PI);

Finalizing

return [
{ attenuation: -2, lineWidth: 1.0, opacity: 0.1 },
{ attenuation: -6, lineWidth: 1.0, opacity: 0.2 },
{ attenuation: 4, lineWidth: 1.0, opacity: 0.4 },
{ attenuation: 2, lineWidth: 1.0, opacity: 0.6},
// basic line
{ attenuation: 1, lineWidth: 1.5, opacity: 1.0},
];

The iOS 9+ style

GIF of SiriwaveJS iOS9+
Original Siri iOS 9+ wave-form
const K = 4;
const NO_OF_CURVES = 3;
// This parameters should be generated randomly
const widths = [ 0.4, 0.6, 0.3 ];
const offsets = [ 1, 4, -3 ];
const amplitudes = [ 0.5, 0.7, 0.2 ];
const phases = [ 0, 0, 0 ];
function _globalAttFn(x) {
return Math.pow(K / (K + Math.pow(x, 2)), K);
}
function _ypos(i) {
let y = 0;
for (let ci = 0; ci < NO_OF_CURVES; ci++) {
const t = offsets[ci];
const k = 1 / widths[ci];
const x = (i * k) - t;

y += Math.abs(
amplitudes[ci] *
Math.sin(x - phases[ci]) *
_globalAttFn(x)
);
}
y = y / NO_OF_CURVES;
return canvasHeightMax * globalAmplitude * y;
}

Composite operation

composite operation: source-over
Composite operation: lighter

Build with RollupJS

{
input: 'src/siriwave.js',
output: {
file: pkg.unpkg,
name: pkg.amdName,
format: 'umd'
},
plugins: [
resolve(),
commonjs(),
babel({ exclude: 'node_modules/**' }),
]
}
{
input: 'src/siriwave.js',
output: {
file: pkg.unpkg.replace('.js', '.min.js'),
name: pkg.amdName,
format: 'umd'
},
plugins: [
resolve(),
commonjs(),
babel({ exclude: 'node_modules/**' }),
uglify()
]
}
{ 
input: ‘src/siriwave.js’,
output: {
file: pkg.module,
format: ‘esm’
},
plugins: [
babel({ exclude: ‘node_modules/**’ })
]
}

Watch and Hot code reload

import livereload from 'rollup-plugin-livereload';
import serve from 'rollup-plugin-serve';
if (process.env.NODE_ENV !== 'production') {
additional_plugins.push(
serve({
open: true,
contentBase: '.'
})
);
additional_plugins.push(
livereload({
watch: 'dist'
})
);
}
"module": "dist/siriwave.m.js",
"jsnext:main": "dist/siriwave.m.js",
"unpkg": "dist/siriwave.js",
"amdName": "SiriWave",
"scripts": {
"build": "NODE_ENV=production rollup -c",
"dev": "rollup -c -w"
},

Thanks to Daniele Sportillo

Flavio De Stefano

Written by

Software Engineer for @Spotify in @Stockholm; with passion

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade