前端音波绘制

来源:互联网 发布:忘记网络密码怎样找回 编辑:程序博客网 时间:2024/06/11 18:55

好久不曾写博客了,忙忙碌碌大半年,毕业就工作果然还是有点意思。本人贼懒,但是只要是研究了点东西的话,还是分享一下供其他感兴趣的小伙伴前车之鉴吧。回归正题,用过手机百度音乐的朋友们(这里算打个广告吧)估计会注意音乐播放后左下角那个音波绘制的看起来蛮舒服的,于是乎我们要自己动手做一个!偷笑[插入]

首先还是先得到所绘制音波在不同时刻的振幅,这个直接上AudioContext好了,这个不是本文的重点,所以直接上代码:

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();var analyser = audioCtx.createAnalyser();var distortion = audioCtx.createWaveShaper();var gainNode = audioCtx.createGain();var biquadFilter = audioCtx.createBiquadFilter();var audio = document.querySelector('#audio');audio.onplay = function(){      source = audioCtx.createMediaElementSource(audio);      source.connect(analyser);      analyser.connect(distortion);      distortion.connect(biquadFilter);      biquadFilter.connect(gainNode);      gainNode.connect(audioCtx.destination);      analyser.fftSize = 2048;      var bufferLength = analyser.frequencyBinCount;      var dataArray = new Uint8Array(bufferLength);      analyser.getByteTimeDomainData(dataArray);}
这样dataArray这个数组中就装就是我们所需要的,不过考虑到我们要实现的效果,只是在需要的时候得到对应的振幅,因此我们每次使用getByteTimeDomainData的时候只需要一个一维数组即可。

有了波动的数据下面开始绘制(基本的绘制效果可参考这里),这里一开始我以为正弦波就能搞定,于是乎有了下面这个第一版。但是波峰与波谷连接处的拐点却是十分明显,显然这个是不符合预期的,于是乎,只好上万能bezier曲线了。

canvas API本来是具备直接绘制bezier曲线的api,但是这个api很有意思,他只是绘制一段曲线,假使我们要不停的绘制新的曲线使用这个api就导致逻辑将十分复杂。我们期望得到的是在t时刻对应bezier曲线上的(x, y),这样我们就不得不自己实现一个类似的函数。

众里寻他千百度,我们的主角来了:大笑

1.我们需要先补习一下bezier曲线(缅怀伟大的工程师皮埃尔·贝塞尔)。那么要想实现输入t输出(x, y),我们只需要知道bezier曲线的一个表达式:

有了这个表达式我们的设计大约就出来了bezier = new Bezier([Pstart, P1, P2, Pend]),now point = bezier.get(time) (time -> [0, 1])。好的,理论梳理清楚了开始动手

2.首先实现Vector方法,处理基本向量:

       var Vector = function(){this._className = 'Math.Vector';var args = arguments;switch(Object.prototype.toString.call(arguments[0])){case '[object Object]': {this.setValue(arguments[0].x, arguments[0].y);break}case '[object Array]': {this.setValue(arguments[0][0], arguments[0][1]);break}case '[object Number]': {this.setValue(arguments[0], arguments[1]);break}default: {console.error('Math.Vector: Bad arguments!')}}}Vector.prototype = {setValue: function(x, y){this.x = x || 0;this.y = y || 0;},add: function(vec){this.x += vec.x;this.y += vec.y;return this;},subtract: function(vec){this.x -= vec.x;this.y -= vec.y;return this;},multiply: function(vec) {this.x =  this.x.multiply(vec.x);this.y = this.y.multiply(vec.y);return this;},divide: function(vec){this.x = this.x.divide(vec.x);this.y = this.y.divide(vec.y);return this;},toObject: function(){return {x: this.x, y: this.y}},toArray: function(){return [this.x, this.y]},toString: function(){return 'x:' + this.x + ', y:' + this.y},clone: function(){return new Vector(this.x, this.y)}}
3.接下来是Matrix,那个公式一眼望去就是矩阵什么的,怎么能少了这个玩意:

    var Matrix = function(mtx){this._className = 'Math.Matrix';        mtx && this.setValue(mtx)    }    Matrix.prototype = {        setValue: function(mtx){        if (Object.prototype.toString.call(mtx) !== '[object Array]') {        console.error('Math.Matrix: Bad arguments!')        }            this.data = mtx;            return this;        },        /**         * 矩阵相加         * @param {matrix} m         */         add: function(mtx){        var vx = this.data[0].length, vy = this.data.length;        if (vy !== mtx.data.length && vx !== mtx.data[0].length) {        return console.error('[Math.Matrix add]: Bad Matrix!')        }        var _data = mtx.data, data = this.data, r = [];        for( var m = vy;m--; ){        r[m] = [];        for( var n = vx;x--; ){        r[m][n] = data[m][n] + m.data[m][n]        }        }        this.data = r;        return this;        },        /**         * 矩阵相乘         * @param {matrix} m 被乘的矩阵         */        multiply: function(mtx){            var r = [], data = this.data, _data = mtx.data, vx = _data[0].length, vy = data.length, ml = data[0].length;            if(_data.length !== ml) {                return console.error('[Math.Matrix mul]: Bad Matrix!')            }        for( var m = vy;m--; ){        r[m] = [];        for( var n = vx;n--; ){        r[m][n] = 0;        for( var p = ml;p--; ){        r[m][n] += data[m][p].multiply(_data[p][n]);        }        }        }        this.data = r;            return this;        },        value:function(){            return this.data        },        toString:function(){            return this.data.toString()        }    }
4.有时我们会发现JS很坑的地方:浮点运算不准确性。吹毛求疵一点,我们不允许这种误差发生!果断扩展一点Number,让我们用起来灵活一些:

Number.prototype.multiply = function(b){if ((typeof b) !== 'number') {return console.error("Number must multiply a Number!");};var m = 0,s1 = this.toString(),s2 = b.toString(); try{m+=s1.split(".")[1].length}catch(e){} try{m+=s2.split(".")[1].length}catch(e){} return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m) }Number.prototype.divide = function(b){var t1 = 0,t2 = 0; try{t1 = this.toString().split(".")[1].length}catch(e){} try{t2 = b.toString().split(".")[1].length}catch(e){}  return (Number(arg1.toString().replace(".", ""))/Number(arg2.toString().replace(".", "")))*Math.pow(10,t2-t1); }Number.prototype.inverse = function(){return -1*this;}
5.最后上CubicBezier(附加了一个SquareBezier):

    var SquareBezier = function(points){this._init(points)}SquareBezier.prototype = {_init: function(points){            var p = this.points = points;            this.m1 = new Math.Matrix();            this.m2 = new Math.Matrix([                [ 1,-2, 1],                [-2, 2, 0],                [ 1, 0, 0]]);            this.m3 = new Math.Matrix([                p[0].toArray(),                p[1].toArray(),                p[2].toArray(),                p[3].toArray()            ]);            this.m = null        },        /**         * 获取某个时间点计算出来的坐标值,时间线不由此类控制         */        get:function(t){        var t2 = t.multiply(t);            this.m1.setValue([                [t2, t, 1]            ]);            this.m = this.m1.multiply(this.m2).multiply(this.m3);            return new Math.Vector(this.m.value()[0][0], this.m.value()[0][1]);        }}var CubicBezier = function(points){        this._init(points)    }    CubicBezier.prototype = {        _init: function(points){            var p = this.points = points;            this.m1 = new Math.Matrix();            this.m2 = new Math.Matrix([                [-1, 3,-3, 1],                [ 3,-6, 3, 0],                [-3, 3, 0, 0],                [ 1, 0, 0, 0]]);            this.m3 = new Math.Matrix([                p[0].toArray(),                p[1].toArray(),                p[2].toArray(),                p[3].toArray()            ]);            this.m = null        },        /**         * 获取某个时间点计算出来的坐标值,时间线不由此类控制         */        get:function(t){        var t2 = t.multiply(t), t3 = t2.multiply(t);            this.m1.setValue([                [t3, t2, t, 1]            ]);            this.m = this.m1.multiply(this.m2).multiply(this.m3);            return new Math.Vector(this.m.value()[0][0], this.m.value()[0][1]);        }    }
有了以上的工作,我们就可以愉快的开始画波了,效果预览,附上整体全部的索引大家可以对比一下这里。

工程源码可以去这里查看阅读,希望会帮助到一些小伙伴~


0 0