JS | Web Audio API (下) 我的音乐浪

“沧海一声唱 滔滔两岸潮
浮沉随浪歌舞今朝
苍天唱 纷纷世上潮
谁负谁胜出天知晓
江山唱 烟雨遥
涛浪淘尽红尘俗世几多娇
清风唱 竟惹寂寥
豪情还剩了一襟晚照
苍生唱 不再寂寥
豪情仍在痴痴唱唱”
—— 题记,《沧海一声唱》

正文

在上文 JS | Web Audio API (上) 你的音谱 中,我们了解到Audio API简单的音频知识点,重在理论,今天搞点有趣的试验,偏重实践。大家知道,光有光谱,电磁波有频谱,音乐呢?当然也有自己的谱。想奥斯特实验揭示电流周围存在磁场,分散的铁屑显现磁铁的磁场分布,那音乐如何看到自身的频率,所以,本文的主题来了,音频可视化,让你的音乐浪起来。先附上效果图,接下来会主要围绕效果例子出发:

效果图.png

这种音波似的效果,我们可能会在音乐室或音乐人或音乐播放器那儿看到,并不少见,当第一次发现可以实现时,ohMyGod,震撼,神奇,而对于喜欢的事物,总会想为我所用,闲话不多说,一起看看它是怎么实现的吧。根据已有的web audio API知识,实践音频可视化,自我总结,步骤大致分为以下几步:

  1. 创建音频环境
  2. 获取音频,创建buffer节点
  3. 解码音频,分析音频
  4. 连接音频输入输出
  5. canvas绘制频谱
  6. 连接播放
创建音频环境AudioContext

音频环境是所有音效操作的前提,好比canvas的画布,先有个做画之地,再来笔墨横姿

// Webkit/blink browser require a prefix, and it needs the window object specifically declared to work in Safari
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;

// declare new audio context
try {
    var audioCtx = new AudioContext();
} catch (e) {
    alert('Your browser does not support AudioContext!');
    console.log(e);
}
获取音频,创建buffer节点

首先获取音频,也就是说拿到这个素材输入之后,我们可以赶制加工,这里通过XMLHttpRequest获取,将请求的返回类型设为“arraybuffer”,方便音频数据处理;另外,创建音频节点createBufferSource,来获取输入的音频。

// use XHR to load an audio track, and
// decodeAudioData to decode it and stick it in a buffer.
// Then we put the buffer into the source
var xhr = new XMLHttpRequest();

// 初始化 HTTP 请求参数, 配置请求类型,文件路径等
xhr.open('GET', 'audio/music1.mp3');

// 将responseType设为arraybuffer,二进制数据
xhr.responseType = "arraybuffer";

// 获取完成,对音频进一步操作,解码
xhr.onload = function() {
    var audioData = xhr.response;
    // Get an AudioBufferSourceNode.
    // This is the AudioNode to use when we want to play an AudioBuffer
    var source = audioCtx.createBufferSource();
    ……
}
解码音频,分析音频

好的,现在我们拿到了音乐,但计算机仍然不懂,需要对其进行解码decodeAudioData
一看到解码后的数据,我们不能让计算机“啪啪啪”就来吧,观个全局,做个自我分析,createAnalyser

audioCtx.decodeAudioData(audioData, function(buffer) {
        // set the buffer in the AudioBufferSourceNode
        source.buffer = buffer;
        
        // create audio node to play the audio in the buffer
        var analyser = audioCtx.createAnalyser();
}
连接音频输入输出

必经之路,input ——> 音频处理 ——> 输出,connect连接。

// connect the analyser to the destination(the speaker), or we won't hear the sound
// from audioCtx.createBuffer, or audioCtx.decodeAudioData
source.connect(analyser);
analyser.connect(audioCtx.destination);
canvas绘制频谱

大头戏,音乐播放捣鼓捣鼓还是有声音的,频谱怎么着,一头雾水。不着急,慢慢来,首先我们需要数据,数据怎么来:

var bufferLength = analyser.frequencyBinCount,
    dataArray = new Uint8Array(bufferLength);

analyser.getByteFrequencyData(dataArray);

好,数据有了,计算机也能懂,怎么画,先说个简单的:

var canvas = document.getElementById('audio_canvas'),
    ctx = canvas.getContext("2d"),
    c_width = canvas.width,
    c_height = canvas.height;

**************
for(var i = 0; i < bufferLength; i++) {
     value = dataArray[i];
     ctx.fillStyle = '#f99';
     ctx.fillRect(i, c_height - value, 1, value);
}

好了,频谱图有了,但没有动效,不会变化,别急,利用requestAnimationFrame,同时这侧面反应了获取的dataArray数组的数值,出来的效果如此这般:

数值

可是我们想,如果把所有的数值都展现出来,一来太多,二来更耗资源,而且频率邻值是相似的,非智者所为,怎么处理呢?数学中有学过采样频率的方法,采样对于信息信号来说,是个常用的方式。根据画布长度,美观起见,让每一频占据一定宽度,各个频之间留些空隙,同时用数学逻辑思维换算,计算出画布可放的频数,也就是说画布上选择哪几个频率值显示,取相对应“编号”的频率,进行绘制。

// 条形的宽度
var bar_width = 10,
    bar_gap = 2,
    bar_part = bar_width + bar_gap,
    bar_num = Math.round(c_width / bar_part);

***************************************
      function drawVisual() {
            var i = 0, value;
            
            var bufferLength = analyser.frequencyBinCount,
                dataArray = new Uint8Array(bufferLength);

            // 每段包含的频谱宽
            var array_width = Math.round(bufferLength / bar_num);

            analyser.getByteFrequencyData(dataArray);

            ctx.clearRect(0,0,c_width,c_height)

            for(i; i < bar_num; i++) {
                value = dataArray[i * array_width];
               
                ctx.fillStyle = '#f99';
                ctx.fillRect(bar_part * i, c_height - value, bar_width, value);
            }

            animation_id = requestAnimationFrame(drawVisual);
            // console.log(animation_id)
        }
类似
思考

如此一来,大致效果已经实现。在做的过程中,有一个问题需要思考: 动画什么时候停止,也就是说,如何在音乐播放结束的情况下,页面频谱流畅地回归空白,浏览器也不会继续动画,做到“该停止时就停止”。【实践结果证明,如果在音乐播放结束就停止动画或者清空,达不到想要的效果】

立刻停止页面

为了美观及更有趣味性,我们可以加个缓慢降落的条形;甚者,采取上传文件的形式,根据上传的音乐“舞动”自己的音浪,因频制浪。这里有个稍难的点:已经播放一首音乐的时候,如何做到继续上传,原音乐停止,新音乐播放并出现相应的频谱。

加条形.png
上传文件形式.png

【代码存在于 github,仅供参考,敬请交流】

参考文章:
https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0ahUKEwj9xpzE2K_SAhUNtJQKHQQMCu4QFggaMAA&url=https%3a%2f%2fdeveloper%2emozilla%2eorg%2fzh-CN%2fdocs%2fWeb%2fAPI%2fFileReader&usg=AFQjCNGz5Veo8Ux5iQ_w_1oFQc3fqNlynA
http://www.cnblogs.com/Wayou/p/html5_audio_api_visualizer.html
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API
https://forestmist.org/blog/web-audio-api-loops#source

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 文章是我在学习Web Audio 时翻译的 原文在此 在html5 元素出现之前,一个网页想要发声,就得需要f...
    霹雳球阅读 11,093评论 4 31
  • “如毒似魅,如解似仙。程序世界,代码纷纷,web audio是流经存在的邂逅。九十刹那为一念,一念中一刹那经九百生...
    smilewalker阅读 9,907评论 2 6
  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,654评论 0 3
  • 一、html5 audio标签 参考html audio标签使用HTML5的Audio标签打造WEB音频播放器打造...
    合肥黑阅读 4,757评论 0 3
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707