diff --git a/README.md b/README.md
index a984a38..7b99354 100644
--- a/README.md
+++ b/README.md
@@ -238,6 +238,7 @@ $.ajax({
6. [【教程】实时多路音频混音](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.mix_multiple)
7. [【教程】变速变调音频转换](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.sonic.transform)
8. [【Demo库】PCM采样率提升](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.samplerate.raise)
+9. [【测试】音频可视化相关扩展测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization)
@@ -563,6 +564,11 @@ Recorder({type:"aac"})
# :open_book:扩展
在`src/extensions`目录内为扩展支持库,这些扩展库默认都没有合并到生成代码中,需单独引用(`dist`或`src`中的)才能使用。
+【附】部分扩展使用效果图:
+
+![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.gif)
+
+
## `WaveView`扩展
[waveview.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/waveview.js),4kb大小源码,录音时动态显示波形,具体样子参考演示地址页面。此扩展参考[MCVoiceWave](https://github.com/HaloMartin/MCVoiceWave)库编写的,具体代码在`https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m`中。
@@ -581,7 +587,6 @@ rec.open(function(){
});
```
-![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.png)
[](?RefEnd)
### 【构造】wave=Recorder.WaveView(set)
@@ -611,12 +616,48 @@ set={
输入音频数据,更新波形显示,这个方法调用的越快,波形越流畅。pcmData `[Int16,...]` 一维数组,为当前的录音数据片段,其他参数和`onProcess`回调相同。
+## `WaveSurferView`扩展
+[wavesurfer.view.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/wavesurfer.view.js),7kb大小源码,音频可视化波形显示,具体样子参考演示地址页面。
+
+此扩展的使用方式和`WaveView`扩展完全相同,请参考上面的`WaveView`来使用;本扩展的波形绘制直接简单的使用PCM的采样数值大小来进行线条的绘制,同一段音频绘制出的波形和Audition内显示的波形外观上几乎没有差异。
+
+### 【构造】surfer=Recorder.WaveSurferView(set)
+构造函数,`set`参数为配置对象,默认配置值如下:
+``` javascript
+set={
+ elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
+ //或者配置显示大小,手动把frequencyObj.elem显示到别的地方
+ ,width:0 //显示宽度
+ ,height:0 //显示高度
+
+ //以上配置二选一
+
+ scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
+
+ ,fps:50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
+
+ ,duration:2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
+ ,direction:1 //波形前进方向,取值:1由左往右,-1由右往左
+ ,position:0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
+
+ ,centerHeight:1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0
+
+ //波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
+ ,linear:[0,"rgba(0,187,17,1)",0.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
+ ,lineColor:"" //中线css颜色,留空取波形第一个渐变颜色
+}
+```
+
+### 【方法】surfer.input(pcmData,powerLevel,sampleRate)
+输入音频数据,更新波形显示。pcmData `[Int16,...]` 一维数组,为当前的录音数据片段,其他参数和`onProcess`回调相同。
+
+
+
## `FrequencyHistogramView`扩展
[frequency.histogram.view.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/frequency.histogram.view.js) + [lib.fft.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/lib.fft.js),12kb大小源码,音频可视化频率直方图显示,具体样子参考演示地址页面。此扩展核心算法参考Java开源库[jmp123](https://sourceforge.net/projects/jmp123/files/)的代码编写的,`jmp123`版本`0.3`;直方图特意优化主要显示0-10khz语音部分,其他高频显示区域较小,不适合用来展示音乐频谱。
此扩展的使用方式和`WaveView`扩展完全相同,请参考上面的`WaveView`来使用;请注意:必须同时引入`lib.fft.js`才能正常工作。
-![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_histogram.png)
### 【构造】histogram=Recorder.FrequencyHistogramView(set)
构造函数,`set`参数为配置对象,默认配置值如下:
diff --git a/assets/npm-home/README.md b/assets/npm-home/README.md
index 1ed83d7..0d4f060 100644
--- a/assets/npm-home/README.md
+++ b/assets/npm-home/README.md
@@ -40,19 +40,25 @@ npm install recorder-core
## WaveView的调用方式
-直接通过Recorder.WaveView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。
+引入`src/extensions/waveview.js`,再通过Recorder.WaveView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。
@@Ref README.WaveView.Codes@@
+【附】部分扩展使用效果图:
-## FrequencyHistogramView的调用方式
-直接通过Recorder.FrequencyHistogramView调用即可,音频可视化频率直方图显示,详细的使用请参考下面详细的README。
+![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.gif)
+
+
+## WaveSurferView的调用方式
+引入`src/extensions/wavesurfer.view.js`,再通过Recorder.WaveSurferView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。
-![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_histogram.png)
+
+## FrequencyHistogramView的调用方式
+引入`src/extensions/frequency.histogram.view.js`+`lib.fft.js`,再通过Recorder.FrequencyHistogramView调用即可,音频可视化频率直方图显示,详细的使用请参考下面详细的README。
## Sonic的调用方式
-直接通过Recorder.Sonic调用即可,音频变速变调转换,详细的使用请参考下面详细的README。
+引入`src/extensions/sonic.js`,再通过Recorder.Sonic调用即可,音频变速变调转换,详细的使用请参考下面详细的README。
## RecordApp的调用方式
diff --git a/assets/npm-home/hash-history.txt b/assets/npm-home/hash-history.txt
index 9d79c3c..45dad32 100644
--- a/assets/npm-home/hash-history.txt
+++ b/assets/npm-home/hash-history.txt
@@ -1,4 +1,8 @@
[
+ {
+ "sha1": "d494fe71ad5175c130c56b1ce09d4953b0fc045a",
+ "time": "2020-1-17 16:12:00"
+ },
{
"sha1": "d7949810ec706e70159f0e09860b6f627197a29d",
"time": "2020-1-16 16:19:04"
@@ -14,9 +18,5 @@
{
"sha1": "bad5a11c405b2766887f5a68bab7c0ce6ea14013",
"time": "2020-1-14 17:40:50"
- },
- {
- "sha1": "e3d0df0e66aa924cf04a56dafc858c40c6054bf6",
- "time": "2020-1-14 03:12:04"
}
]
\ No newline at end of file
diff --git a/assets/runtime-codes/test.extensions.visualization.js b/assets/runtime-codes/test.extensions.visualization.js
new file mode 100644
index 0000000..92cd99f
--- /dev/null
+++ b/assets/runtime-codes/test.extensions.visualization.js
@@ -0,0 +1,372 @@
+/******************
+《【测试】音频可视化相关扩展测试》
+作者:高坚果
+时间:2020-1-17 11:59:47
+
+本测试将测试Recorder所有音频可视化相关的扩展。
+******************/
+var waveConfigs={
+ WaveView:[
+ {testTitle:"颜色"
+ ,linear1:[0,"#0b1",1,"#0b1"]
+ ,linear2:[0,"#0b1",1,"#0b1"]
+ ,linearBg:[0,"gold",1,"#0b1"]}
+ ]
+ ,WaveSurferView:[
+ {testTitle:"底部显示+慢速"
+ ,position:-1
+ ,duration:10000
+ ,linear:[0,"#666",1,"#666"]}
+ ,{testTitle:"由右到左+快速"
+ ,direction:-1
+ ,duration:1000
+ ,linear:[0,"#f00",1,"#f00"]}
+ ]
+ ,FrequencyHistogramView:[
+ {testTitle:"中部显示"
+ ,lineCount:70
+ ,position:0
+ ,minHeight:1
+ ,stripeEnable:false
+ ,linear:[0,"#06c",1,"#06c"]}
+ ,{testTitle:"顶部显示+粗大"
+ ,lineCount:15
+ ,position:1
+ ,lineWidth:999
+ ,stripeEnable:false
+ ,linear:[0,"#ab00ff",1,"#ab00ff"]}
+ ]
+};
+var waveStore={};
+var visualizationInput=function(buffers,powerLevel,duration,sampleRate){
+ var buffer=buffers[buffers.length-1];
+ for(var k in waveStore){
+ var o=waveStore[k];
+ if(o&&o.wave){
+ (function(o){
+ setTimeout(function(){
+ o.wave.input(buffer,powerLevel,sampleRate);
+ });
+ })(o);
+ };
+ };
+};
+var visualizationCreate=function(){
+ if(waveStore.width==ctrlSet.width&&waveStore.height==ctrlSet.height){
+ //配置无变化
+ return;
+ };
+ waveStore={width:ctrlSet.width,height:ctrlSet.height};
+ var box=$(".visualizationBox").html("");
+
+ for(var k in Recorder){
+ var o=Recorder[k];
+ if(!(/view/i.test(k)&&o&&o.prototype&&o.prototype.input)){
+ continue;
+ };
+ box.append('
'+k+'
');
+
+ var configs=$.merge([{testTitle:"默认配置"}],waveConfigs[k]);
+ for(var i=0;i\
+ '+config.testTitle+'
\
+ \
+');
+
+ //实例化可视化对象
+ config.elem="."+cls;
+ waveStore[cls]={wave:o(config),config:config};
+ };
+ };
+};
+
+
+
+
+//******音频数据源,采集原始音频用的******
+//显示控制按钮
+Runtime.Ctrls([
+ {name:"打开音频输入",click:"recStart"}
+ ,{name:"结束",click:"recStop"}
+
+ ,{html:'
音效控制\
+\
+
'}
+
+ ,{name:"麦克风静音",click:"muteChange",cls:"mixMinBtn mixMinBtnOff mixBtn-mute"}
+ ,{name:"BGM静音",click:"muteChange('BGM');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-muteBGM"}
+ ,{name:"爆笑音效",click:"bgmSet('xiao');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-xiao"}
+ ,{name:"晕倒音效",click:"bgmSet('yun');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-yun"}
+ ,{name:"转场音效",click:"bgmSet('scene');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-scene"}
+
+ ,{choiceFile:{
+ title:"替换混音BGM"
+ ,process:function(fileName,arrayBuffer,filesCount,fileIdx,endCall){
+ if(fileIdx==0){
+ loadWait=0;
+ musics=[];
+ };
+ decodeAudio(fileName,arrayBuffer,function(){
+ if(fileIdx+1>=filesCount){
+ Runtime.Log("素材替换完毕,可以开始录音了",2);
+ };
+ endCall();
+ });
+ }
+ }}
+
+ ,{html:'
音频可视化
\
+\
+\
+
'}
+]);
+
+var ctrlSet={};
+$(".ctrlInput").bind("change",function(e){
+ ctrlSet[$(e.target).attr("key")]=+e.target.value;
+ setTimeout(function(){visualizationCreate()},300);
+});
+$(".ctrlRange").bind("change",function(e){
+ $(e.target).parent().find(".ctrlInput").val(e.target.value).change();
+}).change();
+var resetCtrl=function(){
+ $(".ctrlRange").each(function(k,v){
+ $(v).val($(v).attr("value")).change();
+ });
+};
+
+
+//加载录音框架
+Runtime.Import([
+ {url:RootFolder+"/src/recorder-core.js",check:function(){return !window.Recorder}}
+ ,{url:RootFolder+"/src/engine/mp3.js",check:function(){return !Recorder.prototype.mp3}}
+ ,{url:RootFolder+"/src/engine/mp3-engine.js",check:function(){return !Recorder.lamejs}}
+
+ ,{url:RootFolder+"/assets/runtime-codes/fragment.playbuffer.js",check:function(){return !window.DemoFragment||!DemoFragment.PlayBuffer}}//引入DemoFragment.PlayBuffer
+]);
+
+
+//调用录音
+var rec;
+function recStart(){
+ voiceSet={};
+ $(".mixMinBtn").addClass("mixMinBtnOff");
+ var mixChunk={};
+
+ rec=Recorder({
+ type:"mp3"
+ ,sampleRate:32000
+ ,bitRate:96
+ ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){
+ mixChunk=mixProcess(buffers,bufferSampleRate,mixChunk);
+ powerLevel=mixChunk.powerLevel;
+
+ Runtime.Process(buffers,powerLevel,bufferDuration,bufferSampleRate);
+
+ visualizationInput(buffers,powerLevel,bufferDuration,bufferSampleRate);
+ }
+ });
+ var t=setTimeout(function(){
+ Runtime.Log("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)",1);
+ },8000);
+
+ rec.open(function(){//打开麦克风授权获得相关资源
+ clearTimeout(t);
+ rec.start();//开始录音
+ Runtime.Log("正在进行录音,随时猛击音效控制按钮添加音效...");
+ },function(msg,isUserNotAllow){//用户拒绝未授权或不支持
+ clearTimeout(t);
+ Runtime.Log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg, 1);
+ });
+};
+function recStop(){
+ Runtime.Log("正在进行编码...");
+ rec.stop(function(blob,duration){
+ Runtime.LogAudio(blob,duration,rec,"混音结果");
+ rec=null;
+ },function(msg){
+ Runtime.Log("录音失败:"+msg, 1);
+ },1);
+};
+
+
+
+
+
+
+
+
+//copy自teach.realtime.mix_multiple.js
+
+var musics=[];//混音BGM素材列表
+var musicBGs={};//音效素材列表
+var musicPercent=1/1;//混音素材音量降低这么多,免得把主要的录音混的听不清
+
+//*****简单混音*******
+var Mix=function(buffer,sampleRate,mute,bgm,posFloat,loop){
+ var step=bgm.sampleRate/sampleRate;
+ var curInt=-1,sum=0;
+ for(var j=0;jcurInt){
+ var data_mix,data1=buffer[j],data2=(bgm.pcm[cur]||0)*musicPercent;
+
+ //简单混音算法 https://blog.csdn.net/dancing_night/article/details/53080819
+ if(data1<0 && data2<0){
+ data_mix = data1+data2 - (data1 * data2 / -0x7FFF);
+ }else{
+ data_mix = data1+data2 - (data1 * data2 / 0x7FFF);
+ };
+
+ buffer[j]=data_mix;
+ };
+ curInt=cur;
+ posFloat+=step;
+ if(loop && posFloat>=bgm.pcm.length){
+ posFloat=0;
+ curInt=-1;//洗脑循环 直接回到开头可否 ????
+ };
+
+ sum+=Math.abs(buffer[j]);
+ };
+ return {pos:posFloat,sum:sum};
+};
+var mixProcess=function(buffers,sampleRate,chunk){
+ var idx=chunk.idx||0;
+ for(;idx=bgm.pcm.length){
+ bgms.splice(i,1);
+ i--;
+ };
+ };
+
+ //播放bgmBuffer,录制端能听到实时bgm反馈
+ DemoFragment.PlayBuffer(chunk,bgmBuffer,sampleRate);
+
+ //将bgmBuffer混入buffer中
+ var info=Mix(buffer,sampleRate,voiceSet.mute,{pcm:bgmBuffer,sampleRate:sampleRate},0,false);
+
+ chunk.powerLevel=Recorder.PowerLevel(info.sum,buffer.length);
+};
+
+var voiceSet={};
+var muteChange=function(bgm){
+ if(!rec){
+ Runtime.Log("未开始混音",1);
+ return
+ };
+ bgm=bgm||"";
+ voiceSet["mute"+bgm]=!voiceSet["mute"+bgm];
+ $(".mixBtn-mute"+bgm)[voiceSet["mute"+bgm]?"removeClass":"addClass"]("mixMinBtnOff");
+};
+var bgmSet=function(bgm){
+ if(!rec){
+ Runtime.Log("未开始混音",1);
+ return
+ };
+ var bgms=voiceSet.bgms=voiceSet.bgms||[];
+ bgms.push({key:bgm});
+};
+
+
+//*****加载和解码素材********
+var loadWait=0;
+var load=function(name,bgName,call){
+ Runtime.Log("开始加载混音音频素材"+name+",请勿操作...");
+ loadWait++;
+ var xhr=new XMLHttpRequest();
+ xhr.onloadend=function(){
+ if(xhr.status==200){
+ loadWait--;
+ decodeAudio(name,xhr.response,call,bgName);
+ }else{
+ Runtime.Log("加载音频失败["+xhr.status+"]:"+name,1);
+ };
+ };
+ xhr.open("GET",RootFolder+"/assets/audio/"+name,true);
+ //xhr.timeout=16000;
+ xhr.responseType="arraybuffer";
+ xhr.send();
+};
+var decodeAudio=function(name,arr,call,bgName){
+ Runtime.DecodeAudio(name,arr,function(data){
+ Runtime.LogAudio(data.srcBlob,data.duration,{set:data},"已解码"+name);
+
+ if(bgName){
+ musicBGs[bgName]={pcm:data.data,sampleRate:data.sampleRate};
+ }else{
+ musics.push({pcm:data.data,sampleRate:data.sampleRate});
+ };
+ call();
+ },function(msg){
+ Runtime.Log(msg,1);
+ call();
+ });
+};
+var loadAll=function(){
+ load("music-在人间-张韶涵.mp3",0,function(){
+ load("bgm-爆笑.mp3","xiao",function(){
+ load("bgm-晕倒.mp3","yun",function(){
+ load("bgm-转场.mp3","scene",function(){
+ Runtime.Log("待混音音频素材已准备完毕,可以开始录音了",2);
+ });
+ });
+ });
+ });
+};
+
+//加载素材
+setTimeout(loadAll);
\ No newline at end of file
diff --git a/assets/use_histogram.png b/assets/use_histogram.png
deleted file mode 100644
index 8b8fd86..0000000
Binary files a/assets/use_histogram.png and /dev/null differ
diff --git a/assets/use_wave.gif b/assets/use_wave.gif
new file mode 100644
index 0000000..bdd5a9c
Binary files /dev/null and b/assets/use_wave.gif differ
diff --git a/assets/use_wave.png b/assets/use_wave.png
deleted file mode 100644
index c02ecde..0000000
Binary files a/assets/use_wave.png and /dev/null differ
diff --git "a/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html" "b/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html"
index 0333e4c..a4cc707 100644
--- "a/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html"
+++ "b/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html"
@@ -1080,6 +1080,7 @@
,{n:"【教程】变速变调音频转换",k:"teach.sonic.transform"}
,{n:"【Demo库】PCM采样率提升",k:"lib.samplerate.raise"}
+,{n:"【测试】音频可视化相关扩展测试",k:"test.extensions.visualization"}
];
diff --git a/dist/extensions/wavesurfer.view.js b/dist/extensions/wavesurfer.view.js
index 3e056e2..a01fa3c 100644
--- a/dist/extensions/wavesurfer.view.js
+++ b/dist/extensions/wavesurfer.view.js
@@ -3,4 +3,4 @@
https://github.com/xiangyuecn/Recorder
src: extensions/wavesurfer.view.js
*/
-!function(){"use strict";var e=function(e){return new t(e)},t=function(e){var t=this,a={scale:2,fps:50,duration:2500,direction:1,position:0,centerHeight:1,linear:[0,"rgba(0,187,17,1)",.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"],lineColor:""};for(var r in e)a[r]=e[r];t.set=e=a;var i=e.elem;i&&("string"==typeof i?i=document.querySelector(i):i.length&&(i=i[0])),i&&(e.width=i.offsetWidth,e.height=i.offsetHeight);var n=e.scale,o=e.width*n,l=e.height*n,h=t.elem=document.createElement("div"),c=["","transform-origin:0 0;","transform:scale("+1/n+");"];h.innerHTML='';var s=t.canvas=h.querySelector("canvas");t.ctx=s.getContext("2d");s.width=o,s.height=l;var d=t.canvas2=document.createElement("canvas");t.ctx2=d.getContext("2d");d.width=2*o,d.height=l,i&&(i.innerHTML="",i.appendChild(h)),t.x=0};t.prototype=e.prototype={genLinear:function(e,t,a,r){for(var i=e.createLinearGradient(0,a,0,r),n=0;n';var s=t.canvas=h.querySelector("canvas");t.ctx=s.getContext("2d");s.width=o,s.height=l;var d=t.canvas2=document.createElement("canvas");t.ctx2=d.getContext("2d");d.width=2*o,d.height=l,i&&(i.innerHTML="",i.appendChild(h)),t.x=0};t.prototype=e.prototype={genLinear:function(e,t,a,r){for(var i=e.createLinearGradient(0,a,0,r),n=0;n