@@ -341,7 +372,7 @@
var bit=+$(".bit").val();
var sample=+$(".sample").val();
- var wave;
+ var waveStore={};
window.sonicAsync=null;
var disableEnvInFixSet=$(".disableEnvInFixSet")[0].checked;
@@ -364,8 +395,14 @@
$(".recpowerx").css("width",powerLevel+"%");
$(".recpowert").text(formatMs(duration,1)+" / "+powerLevel);
- wave.input(buffers[buffers.length-1],powerLevel,sampleRate);
+ //可视化图形绘制
+ if(waveStore.choice!=recwaveChoiceKey){
+ waveStore.choice=recwaveChoiceKey;
+ $(".recwave").html("").append(waveStore[recwaveChoiceKey].elem);
+ };
+ waveStore[recwaveChoiceKey].input(buffers[buffers.length-1],powerLevel,sampleRate);
+ //实时传输
if(realTimeSendSet&&window.realTimeSendTry){
realTimeSendTry(rec.set,realTimeSendTime,buffers,sampleRate);
};
@@ -382,7 +419,16 @@
dialogCancel();
reclog("
已打开:"+type+" "+sample+"hz "+bit+"kbps");
- wave=Recorder.WaveView({elem:".recwave"});
+ //此处创建这些音频可视化图形绘制浏览器支持妥妥的
+ waveStore.WaveView=Recorder.WaveView({elem:".recwave"});
+ waveStore.Histogram1=Recorder.FrequencyHistogramView({elem:".recwave"});
+ waveStore.Histogram2=Recorder.FrequencyHistogramView({
+ elem:".recwave"
+ ,position:0
+ ,minHeight:1
+ ,shadowBlur:6
+ ,stripeEnable:false
+ });
},function(e,isUserNotAllow){
dialogCancel();
reclog((isUserNotAllow?"UserNotAllow,":"")+"打开失败:"+e);
@@ -635,8 +681,20 @@
reader.readAsDataURL(o.blob);
};
+
+
$(".recinfoCode").text($(".recinfoCode").text().replace(/\$\{(.+?)\}/g,function(a,b){return eval(b)}));
+var recwaveChoiceKey=localStorage["RecWaveChoiceKey"]||"WaveView";
+$(".recwaveChoice").bind("click",function(e){
+ var elem=$(e.target);
+ $(".recwaveChoice").removeClass("slc");
+ recwaveChoiceKey=elem.addClass("slc").attr("key");
+ localStorage["RecWaveChoiceKey"]=recwaveChoiceKey;
+});
+$(".recwaveChoice[key="+recwaveChoiceKey+"]").click();
+
+
if(window.isSecureContext===false){
reclog("当前网页不是安全环境(HTTPS),将无法获取录音权限,
MDN Privacy and security",1);
}else if(window.isSecureContext){
@@ -644,8 +702,12 @@
};
reclog("点击打开录音开始哦,此浏览器
":"red'>不")+"支持录音");
-reclog("WaveView Extensions已启用:显示波形功能即为此扩展,源码:src/extensions/waveview.js");
-reclog("Sonic Extensions已启用:变速变调功能即为此扩展,源码:src/extensions/sonic.js");
+var s="https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/";
+reclog('已启用Extensions:\
+
WaveView (
waveview.js 音频可视化波形)\
+、
FrequencyHistogramView (
frequency.histogram.view.js +
lib.fft.js 音频可视化频率直方图)\
+、
Sonic (
sonic.js 变速变调)\
+');
function goiframe(){
@@ -665,8 +727,6 @@
};
-
-
//实时传输数据模拟开关
$(".realTimeSendSet").bind("change",function(e){
var open=e.target.checked;
@@ -899,6 +959,8 @@
if(i>=engines.length){
loadEngineState[type]=minjs;
Recorder.WaveView=WaveViewBak;
+ Recorder.LibFFT=LibFFTBak;
+ Recorder.FrequencyHistogramView=FrequencyHistogramViewBak;
Recorder.Sonic=SonicBak;
reclog("
"+type+"编码器"+(minjs?"压缩版":"源码版")+"已加载,可以录音了");
@@ -921,6 +983,8 @@
};
loadEngineState={};
var WaveViewBak=Recorder.WaveView;
+var LibFFTBak=Recorder.LibFFT;
+var FrequencyHistogramViewBak=Recorder.FrequencyHistogramView;
var SonicBak=Recorder.Sonic;
(function(){try{
diff --git a/src/extensions/frequency.histogram.view.js b/src/extensions/frequency.histogram.view.js
new file mode 100644
index 0000000..ca5712f
--- /dev/null
+++ b/src/extensions/frequency.histogram.view.js
@@ -0,0 +1,308 @@
+/*
+录音 Recorder扩展,频率直方图显示
+使用本扩展需要引入lib.fft.js支持
+
+https://github.com/xiangyuecn/Recorder
+
+本扩展核心算法主要参考了Java开源库jmp123 版本0.3 的代码:
+https://www.iteye.com/topic/851459
+https://sourceforge.net/projects/jmp123/files/
+*/
+(function(){
+"use strict";
+
+var FrequencyHistogramView=function(set){
+ return new fn(set);
+};
+var fn=function(set){
+ var This=this;
+ var o={
+ /*
+ elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
+ //或者配置显示大小,手动把frequencyObj.elem显示到别的地方
+ ,width:0 //显示宽度
+ ,height:0 //显示高度
+
+ 以上配置二选一
+ */
+
+ scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
+
+ ,fps:20 //绘制帧率,不可过高
+
+ ,lineCount:15 //直方图柱子数量
+ ,lineWidth:10 //柱子线条基础粗细
+ ,minHeight:0 //柱子保留基础高度,position不为±1时应该保留点高度
+ ,position:-1 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
+
+ ,stripeEnable:true //是否启用柱子顶上的峰值小横条,position不是-1时应当关闭,否则会很丑
+ ,stripeHeight:5 //峰值小横条基础高度
+ ,stripeMargin:8 //峰值小横条和柱子保持的基础距离
+
+ ,fallDuration:600 //柱子从最顶上下降到最底部最长时间ms
+ ,stripeFallDuration:1200 //峰值小横条从最顶上下降到底部最长时间ms
+
+ //柱子颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
+ ,linear:[0,"rgba(0,187,17,1)",0.5,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
+ //峰值小横条css颜色 #abc,留空为柱子的渐变颜色
+ ,stripeColor:""
+
+ ,shadowBlur:0 //柱子阴影基础大小,设为0不显示阴影
+ ,shadowColor:"#bbb" //柱子阴影颜色
+ ,stripeShadowBlur:-1 //峰值小横条阴影基础大小,设为0不显示阴影,-1为柱子的大小
+ ,stripeShadowColor:"" //峰值小横条阴影颜色,留空为柱子的阴影颜色
+
+ //当发生绘制时会回调此方法,参数为当前绘制的频率数据和采样率,可实现多个直方图同时绘制,只消耗一个input输入和计算时间
+ ,onDraw:function(frequencyData,sampleRate){}
+ };
+ for(var k in set){
+ o[k]=set[k];
+ };
+ This.set=set=o;
+
+ var elem=set.elem;
+ if(elem){
+ if(typeof(elem)=="string"){
+ elem=document.querySelector(elem);
+ }else if(elem.length){
+ elem=elem[0];
+ };
+ };
+ if(elem){
+ set.width=elem.offsetWidth;
+ set.height=elem.offsetHeight;
+ };
+
+ var scale=set.scale;
+ var width=set.width*scale;
+ var height=set.height*scale;
+
+ var thisElem=This.elem=document.createElement("div");
+ var lowerCss=["","transform-origin:0 0;","transform:scale("+(1/scale)+");"];
+ thisElem.innerHTML='
';
+
+ var canvas=This.canvas=thisElem.querySelector("canvas");
+ var ctx=This.ctx=canvas.getContext("2d");
+ canvas.width=width;
+ canvas.height=height;
+
+ if(elem){
+ elem.innerHTML="";
+ elem.appendChild(thisElem);
+ };
+
+ if(!Recorder.LibFFT){
+ throw new Error("需要lib.fft.js支持");
+ };
+ This.fft=Recorder.LibFFT(1024);
+
+ //柱子所在高度
+ This.lastH=[];
+ //峰值小横条所在高度
+ This.stripesH=[];
+};
+fn.prototype=FrequencyHistogramView.prototype={
+ genLinear:function(ctx,colors,from,to){
+ var rtv=ctx.createLinearGradient(0,from,0,to);
+ for(var i=0;i
set.stripeFallDuration+500){
+ //超时没有输入,顶部横条已全部落下,干掉定时器
+ clearInterval(This.timer);
+ This.timer=0;
+ return;
+ };
+ if(now-drawTime> 1;
+ var fsband = bufferSize >> 1;
+ if(maxFs > 20000){
+ var deltaFs = maxFs / fsband;
+ fsband -= (maxFs - 20000) / deltaFs;
+ };
+ for(var i =0; i<=lineCount; i++){
+ fftIdxs[i] = Math.floor(0.5 + Math.pow(fsband, i / lineCount));
+ if (i > 0 && fftIdxs[i] <= fftIdxs[i - 1]){
+ fftIdxs[i]=fftIdxs[i - 1]+1;
+ };
+ };
+
+
+ //计算高度位置
+ var position=set.position;
+ var posAbs=Math.abs(set.position);
+ var originY=position==1?0:height;//y轴原点
+ var heightY=height;//最高的一边高度
+ if(posAbs<1){
+ heightY=heightY/2;
+ originY=heightY;
+ heightY=Math.floor(heightY*(1+posAbs));
+ originY=Math.floor(position>0?originY*(1-posAbs):originY*(1+posAbs));
+ };
+
+ var lastH=This.lastH;
+ var stripesH=This.stripesH;
+ var speed=Math.ceil(heightY/(set.fallDuration/(1000/set.fps)));
+ var stripeSpeed=Math.ceil(heightY/(set.stripeFallDuration/(1000/set.fps)));
+ var stripeMargin=set.stripeMargin*scale;
+ var Y0=1 << (Math.round(Math.log(bufferSize)/Math.log(2) + 3) << 1);
+ var logY0 = Math.log(Y0)/Math.log(10);
+ var dBmax=20*Math.log(0x7fff)/Math.log(10);
+ for(var i=0;i Y0) ? Math.floor((Math.log(maxAmp)/Math.log(10) - logY0) * 17) : 0;
+ var h=heightY*Math.min(dB/dBmax,1);
+
+ //使柱子匀速下降
+ lastH[i]=(lastH[i]||0)-speed;
+ if(hshi) {
+ stripesH[i]=h+stripeMargin;
+ }else{
+ //使峰值小横条匀速度下落
+ var sh =shi-stripeSpeed;
+ if(sh < 0){sh = 0;};
+ stripesH[i] = sh;
+ };
+ };
+
+ //开始绘制图形
+ ctx.clearRect(0,0,width,height);
+
+ var linear1=This.genLinear(ctx,set.linear,originY,originY-heightY);//上半部分的填充
+ var linear2=This.genLinear(ctx,set.linear,originY,originY+heightY);//下半部分的填充
+
+ //绘制柱子
+ ctx.shadowBlur=set.shadowBlur*scale;
+ ctx.shadowColor=set.shadowColor;
+ var lineWidth=set.lineWidth*scale;
+ if(lineWidth*lineCount+(lineCount+1)*scale>width){//放不下了,调小点
+ lineWidth=Math.floor((width-(lineCount+1)*scale)/lineCount);
+ };
+ var spaceFloat=(width-lineCount*lineWidth)/(lineCount+1);//均匀间隔,首尾都留空
+ var minHeight=set.minHeight*scale;
+ for(var i=0,xFloat=0,x,y,h;iheight){
+ y=height-stripeHeight;
+ };
+ ctx.fillStyle=set.stripeColor||linear2;
+ ctx.fillRect(x, y, lineWidth, stripeHeight);
+ };
+
+ xFloat+=lineWidth;
+ };
+ };
+
+ set.onDraw(frequencyData);
+ }
+};
+Recorder.FrequencyHistogramView=FrequencyHistogramView;
+
+
+})();
\ No newline at end of file
diff --git a/src/extensions/lib.fft.js b/src/extensions/lib.fft.js
new file mode 100644
index 0000000..ddd71d5
--- /dev/null
+++ b/src/extensions/lib.fft.js
@@ -0,0 +1,111 @@
+/*
+时域转频域,快速傅里叶变换(FFT)
+https://github.com/xiangyuecn/Recorder
+
+var fft=Recorder.LibFFT(bufferSize)
+ bufferSize取值2的n次方
+
+fft.bufferSize 实际采用的bufferSize
+fft.transform(inBuffer)
+ inBuffer:[Int16,...] 数组长度必须是bufferSize
+ 返回[Float64(Long),...],长度为bufferSize/2
+*/
+
+/*
+从FFT.java 移植,Java开源库:jmp123 版本0.3
+https://www.iteye.com/topic/851459
+https://sourceforge.net/projects/jmp123/files/
+*/
+Recorder.LibFFT=function(bufferSize){
+ "use strict";
+
+ var FFT_N_LOG,FFT_N,MINY;
+ var real, imag, sintable, costable;
+ var bitReverse;
+
+ var FFT_Fn=function(bufferSize) {//bufferSize只能取值2的n次方
+ FFT_N_LOG=Math.round(Math.log(bufferSize)/Math.log(2));
+ FFT_N = 1 << FFT_N_LOG;
+ MINY = ((FFT_N << 2) * Math.sqrt(2));
+
+ real = [];
+ imag = [];
+ sintable = [0];
+ costable = [0];
+ bitReverse = [];
+
+ var i, j, k, reve;
+ for (i = 0; i < FFT_N; i++) {
+ k = i;
+ for (j = 0, reve = 0; j != FFT_N_LOG; j++) {
+ reve <<= 1;
+ reve |= (k & 1);
+ k >>>= 1;
+ }
+ bitReverse[i] = reve;
+ }
+
+ var theta, dt = 2 * Math.PI / FFT_N;
+ for (i = (FFT_N >> 1) - 1; i > 0; i--) {
+ theta = i * dt;
+ costable[i] = Math.cos(theta);
+ sintable[i] = Math.sin(theta);
+ }
+ }
+
+ /*
+ 用于频谱显示的快速傅里叶变换
+ inBuffer 输入FFT_N个实数,返回 FFT_N/2个输出值(复数模的平方)。
+ */
+ var getModulus=function(inBuffer) {
+ var i, j, k, ir, j0 = 1, idx = FFT_N_LOG - 1;
+ var cosv, sinv, tmpr, tmpi;
+ for (i = 0; i != FFT_N; i++) {
+ real[i] = inBuffer[bitReverse[i]];
+ imag[i] = 0;
+ }
+
+ for (i = FFT_N_LOG; i != 0; i--) {
+ for (j = 0; j != j0; j++) {
+ cosv = costable[j << idx];
+ sinv = sintable[j << idx];
+ for (k = j; k < FFT_N; k += j0 << 1) {
+ ir = k + j0;
+ tmpr = cosv * real[ir] - sinv * imag[ir];
+ tmpi = cosv * imag[ir] + sinv * real[ir];
+ real[ir] = real[k] - tmpr;
+ imag[ir] = imag[k] - tmpi;
+ real[k] += tmpr;
+ imag[k] += tmpi;
+ }
+ }
+ j0 <<= 1;
+ idx--;
+ }
+
+ j = FFT_N >> 1;
+ var outBuffer=new Float64Array(j);
+ /*
+ * 输出模的平方:
+ * for(i = 1; i <= j; i++)
+ * inBuffer[i-1] = real[i] * real[i] + imag[i] * imag[i];
+ *
+ * 如果FFT只用于频谱显示,可以"淘汰"幅值较小的而减少浮点乘法运算. MINY的值
+ * 和Spectrum.Y0,Spectrum.logY0对应.
+ */
+ sinv = MINY;
+ cosv = -MINY;
+ for (i = j; i != 0; i--) {
+ tmpr = real[i];
+ tmpi = imag[i];
+ if (tmpr > cosv && tmpr < sinv && tmpi > cosv && tmpi < sinv)
+ outBuffer[i - 1] = 0;
+ else
+ outBuffer[i - 1] = Math.round(tmpr * tmpr + tmpi * tmpi);
+ }
+ return outBuffer;
+ }
+
+ FFT_Fn(bufferSize);
+ return {transform:getModulus,bufferSize:FFT_N};
+};
diff --git a/src/package-build.js b/src/package-build.js
index 4fdd33a..47bdb00 100644
--- a/src/package-build.js
+++ b/src/package-build.js
@@ -55,6 +55,8 @@ function Run_minify(){
minify("../dist/engine/beta-amr.js",["engine/beta-amr.js","engine/beta-amr-engine.js","engine/wav.js"]);
minify("../dist/extensions/waveview.js",["extensions/waveview.js"]);
+ minify("../dist/extensions/lib.fft.js",["extensions/lib.fft.js"]);
+ minify("../dist/extensions/frequency.histogram.view.js",["extensions/frequency.histogram.view.js"]);
minify("../dist/extensions/sonic.js",["extensions/sonic.js"]);
console.log("\x1B[33m%s\x1B[0m","处理完成");