diff --git a/README.md b/README.md index f59a229..036d845 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,10 @@ var rec; var recOpen=function(success){//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了 rec=Recorder({ type:"mp3",sampleRate:16000,bitRate:16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎 - ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){ + ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){ //录音实时回调,大约1秒调用12次本回调 + //可利用extensions/waveview.js扩展实时绘制波形 + //可利用extensions/sonic.js扩展实时变速变调,此扩展计算量巨大,onProcess需要返回true开启异步模式 } }); @@ -234,6 +236,7 @@ $.ajax({ 4. [【Demo库】【文件合并】-mp3多个片段文件合并](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.mp3_merge) 5. [【Demo库】【文件合并】-wav多个片段文件合并](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.wav_merge) 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) @@ -320,10 +323,16 @@ set={ ,sampleRate:16000 //采样率,必须是数字,wav格式(8位)文件大小=sampleRate*时间;mp3此项对低比特率文件大小有影响,高比特率几乎无影响。 //wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 - ,onProcess:NOOP //接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate) - //buffers=[[Int16,...],...]:缓冲的PCM数据,为从开始录音到现在的所有pcm片段,每次回调可能增加0-n个不定量的pcm片段;powerLevel:当前缓冲的音量级别0-100,bufferDuration:已缓冲时长,bufferSampleRate:缓冲使用的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同) + ,onProcess:NOOP //接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd) + //返回值:onProcess如果返回true代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用asyncEnd(不能真异步时需用setTimeout包裹);返回其他值或者不返回为同步模式(需避免在回调内执行耗时逻辑);如果开启异步模式,在onProcess执行后新增的buffer会全部替换成空数组,因此本回调开头应立即将newBufferIdx到本次回调结尾位置的buffer全部保存到另外一个数组内,处理完成后写回buffers中本次回调的结尾位置。 + //buffers=[[Int16,...],...]:缓冲的PCM数据,为从开始录音到现在的所有pcm片段,每次回调可能增加0-n个不定量的pcm片段。 + //powerLevel:当前缓冲的音量级别0-100。 + //bufferDuration:已缓冲时长。 + //bufferSampleRate:缓冲使用的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同)。 + //newBufferIdx:本次回调新增的buffer起始索引。 + //asyncEnd:fn() 如果onProcess是异步的(返回值为true时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用setTimeout包裹)。 //如果需要绘制波形之类功能,需要实现此方法即可,使用以计算好的powerLevel可以实现音量大小的直观展示,使用buffers可以达到更高级效果 - //注意,buffers数据的采样率和set.sampleRate不一定相同,可能为浏览器提供的原始采样率rec.srcSampleRate,也可能为已转换好的采样率set.sampleRate;如需浏览器原始采样率的数据,请使用rec.buffers原始数据,而不是本回调的参数;如需明确和set.sampleRate完全相同采样率的数据,请在onProcess中自行连续调用采样率转换函数Recorder.SampleData(),配合mock方法可实现实时转码和压缩语音传输;修改buffers内的数据将会改变最终生成的音频内容,比如简单有限的实现实时静音、降噪、混音等处理,详细参考下面的rec.buffers + //注意,buffers数据的采样率和set.sampleRate不一定相同,可能为浏览器提供的原始采样率rec.srcSampleRate,也可能为已转换好的采样率set.sampleRate;如需浏览器原始采样率的数据,请使用rec.buffers原始数据,而不是本回调的参数;如需明确和set.sampleRate完全相同采样率的数据,请在onProcess中自行连续调用采样率转换函数Recorder.SampleData(),配合mock方法可实现实时转码和压缩语音传输;修改或替换buffers内的数据将会改变最终生成的音频内容(注意不能改变第一维数组长度),比如简单有限的实现实时静音、降噪、混音等处理,详细参考下面的rec.buffers } ``` @@ -377,7 +386,7 @@ set={ ### 【属性】rec.buffers 此数据为从开始录音到现在为止的所有已缓冲的PCM片段列表,`buffers` `=` `[[Int16,...],...]` 为二维数组;在没有边录边转码的支持时(mock调用、非mp3等),录音stop时会使用此完整数据进行转码成指定的格式。 -buffers中的PCM数据为浏览器采集的原始音频数据,采样率为浏览器提供的原始采样率`rec.srcSampleRate`;在`rec.set.onProcess`回调中`buffers`参数就是此数据或者此数据重新采样后的新数据;修改`onProcess`回调中`buffers`参数可以改变最终生成的音频内容,但修改`rec.buffers`不一定会有效,因此你可以在`onProcess`中修改`buffers`参数里面的内容,注意不能改变数组的长度;以此可以简单有限的实现实时静音、降噪、混音等处理。 +buffers中的PCM数据为浏览器采集的原始音频数据,采样率为浏览器提供的原始采样率`rec.srcSampleRate`;在`rec.set.onProcess`回调中`buffers`参数就是此数据或者此数据重新采样后的新数据;修改或替换`onProcess`回调中`buffers`参数可以改变最终生成的音频内容,但修改`rec.buffers`不一定会有效,因此你可以在`onProcess`中修改或替换`buffers`参数里面的内容,注意只能修改或替换上次回调以来新增的buffer(不允许修改已处理过的,不允许增删第一维数组,允许将第二维数组任意修改替换成空数组也可以);以此可以简单有限的实现实时静音、降噪、混音等处理。 如果你需要长时间实时录音(如长时间语音通话),并且不需要得到最终完整编码的音频文件,Recorder初始化时应当使用一个未知的类型进行初始化(如: type:"unknown",仅仅用于初始化而已,实时转码可以手动转成有效格式,因为有效格式可能内部还有其他类型的缓冲),并且实时在`onProcess`中修改`rec.buffers`数组,只保留最后两个元素,其他元素设为null(代码:`rec.buffers[rec.buffers.length-3]=null`),以释放占用的内存,并且录音结束时可以不用调用`stop`,直接调用`close`丢弃所有数据即可。只要buffers[0]==null时调用`stop`永远会直接走fail回调。 @@ -599,6 +608,56 @@ set={ 输入音频数据,更新波形显示,这个方法调用的越快,波形越流畅。pcmData `[Int16,...]` 一维数组,为当前的录音数据片段,其他参数和`onProcess`回调相同。 + +## `Sonic`扩展 +`sonic.js`,37kb大小源码(压缩版gzip后4.5kb),音频变速变调转换,[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.sonic.transform)。此扩展从[Sonic.java](https://github.com/waywardgeek/sonic/blob/71c51195de71627d7443d05378c680ba756545e8/Sonic.java)移植,并做了适当精简。 + +可到[assets/sonic-java](https://github.com/xiangyuecn/Recorder/tree/master/assets/sonic-java)目录运行java代码测试原版效果。 + +### 本扩展支持 +1. Pitch:变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度 +2. Speed:变速不变调(快放慢放),只调整播放速度,不改变音调 +3. Rate:变速变调,会改变播放速度和音调 +4. Volume:支持调整音量 +5. 支持实时处理,可在onProcess中实时处理PCM(需开启异步),配合SampleData方法使用更佳 + +### Sonic文档 +Sonic有两个构造方法,一个是同步方法,Sonic.Async是异步方法,同步方法简单直接但处理量大时会消耗大量时间,主要用于一次性的处理;异步方法由WebWorker在后台进行运算处理,但异步方法不一定能成功开启(低版本浏览器),主要用于实时处理。 + +注意:由于同步方法转换操作需要占用比较多的CPU(但比转码小点),因此实时处理时在低端设备上可能会导致性能问题;在一次性处理大量pcm时,可采取切片+setTimeout进行处理,参考上面的demo片段。 + +注意:变速变调会大幅增减PCM数据长度,如果需要在onProcess中实时处理PCM,需要在rec.set中设置内部参数`rec.set.disableEnvInFix=true`来禁用设备卡顿时音频输入丢失补偿功能,否则可能导致错误的识别为设备卡顿。 + +注意:每次input输入的数据量应该尽量的大些,太少容易产生杂音,每次传入200ms以上的数据量就几乎没有影响了。 + +``` javascript +//【构造初始化】 +var sonic=Recorder.Sonic(set) //同步调用,用于一次性处理 +var sonic=Recorder.Sonic.Async(set) //异步调用,用于实时处理 + /*set:{ + sampleRate:待处理pcm的采样率,就是input输入的buffer的采样率 + }*/ + +//【功能配置调用函数】同步异步通用,以下num取值正常为0.1-2.0,超过这个范围也是可以的,但不推荐 +sonic.setPitch(num) //num:0.1-n,变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度,默认为1.0不调整 +sonic.setSpeed(num) //num:0.1-n,变速不变调(快放慢放),只调整播放速度,不改变音调,默认为1.0不调整 +sonic.setRate(num) //num:0.1-n,变速变调,越小越缓重,越大越尖锐,会改变播放速度和音调,默认为1.0不调整 +sonic.setVolume(num) //num:0.1-n,调整音量,默认为1.0不调整 +sonic.setChordPitch(bool) //bool:默认false,作用未知,不推荐使用 +sonic.setQuality(num) //num:0或1,默认0时会减小输入采样率来提供处理速度,变调时才会用到,不推荐使用 + +//【同步调用方法】 +sonic.input(buffer) //buffer:[Int16,...] 一维数组,输入pcm数据,返回转换后的部分pcm数据,完整输出需要调用flush;返回值[Int16,...]长度可能为0,代表没有数据被转换;此方法是耗时的方法,一次性处理大量pcm需要切片+setTimeout优化 +sonic.flush() //将残余的未转换的pcm数据完成转换并返回;返回值[Int16,...]长度可能为0,代表没有数据被转换 + +//【异步调用方法】 +sonic.input(buffer,callback) //callback:fn(pcm),和同步方法相同,只是返回值通过callback返回 +sonic.flush(callback) //callback:fn(pcm),和同步方法相同,只是返回值通过callback返回 +``` + + + + # :open_book:兼容性 对于支持录音的浏览器能够正常录音并返回录音数据;对于不支持的浏览器,引入js和执行相关方法都不会产生异常,并且进入相关的fail回调。一般在open的时候就能检测到是否支持或者被用户拒绝,可在用户开始录音之前提示浏览器不支持录音或授权。 diff --git a/app-support-sample/README.md b/app-support-sample/README.md index 4e0937c..4dd83a7 100644 --- a/app-support-sample/README.md +++ b/app-support-sample/README.md @@ -104,8 +104,10 @@ RecordApp.RequestPermission(function(){ RecordApp.Start({ type:"mp3",sampleRate:16000,bitRate:16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把支持文件到Platforms.Default内注册 - ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){ + ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){ //如果当前环境支持实时回调(RecordApp.Current.CanProcess()),收到录音数据时就会实时调用本回调方法 + //可利用extensions/waveview.js扩展实时绘制波形 + //可利用extensions/sonic.js扩展实时变速变调,此扩展计算量巨大,onProcess需要返回true开启异步模式 } },function(){ setTimeout(function(){ @@ -241,7 +243,7 @@ set配置默认值: sampleRate:16000//最佳采样率hz bitRate:16//最佳比特率kbps - onProcess:NOOP//如果当前环境支持实时回调(RecordApp.Current.CanProcess()),接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate),此回调和Recorder的回调行为完全一致 + onProcess:NOOP//如果当前环境支持实时回调(RecordApp.Current.CanProcess()),接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd),此回调和Recorder的回调行为完全一致 } 注意:此对象会被修改,因为平台实现时需要把实际使用的值存入此对象 diff --git a/app-support-sample/demo_ios/README.md b/app-support-sample/demo_ios/README.md index 10a4b7a..74ec47a 100644 --- a/app-support-sample/demo_ios/README.md +++ b/app-support-sample/demo_ios/README.md @@ -8,7 +8,7 @@ 可以直接copy目录内`RecordAppJsBridge.swift`使用,此文件为核心文件,其他文件都是没什么价值的;支持新开发WKWebView界面,或对已有的WKWebView实例升级支持RecordApp。 -**xcode测试项目clone后请修改`PRODUCT_BUNDLE_IDENTIFIER`,不然这个测试id被抢来抢去要闲置7天才能被使用,嫌弃苹果工程师水准** +**xcode测试项目clone后请修改`PRODUCT_BUNDLE_IDENTIFIER`,不然这个测试id被抢来抢去要闲置7天才能被使用,嫌弃苹果公司工程师水准** ## 【截图】 diff --git a/assets/npm-home/README.md b/assets/npm-home/README.md index ab0f680..b911308 100644 --- a/assets/npm-home/README.md +++ b/assets/npm-home/README.md @@ -40,11 +40,15 @@ npm install recorder-core ## WaveView的调用方式 -直接通过Recorder.WaveView调用即可,详细的使用请参考[GitHub仓库](https://github.com/xiangyuecn/Recorder)里面的README +直接通过Recorder.WaveView调用即可,录音时动态显示波形,详细的使用请参考[GitHub仓库](https://github.com/xiangyuecn/Recorder)里面的README @@Ref README.WaveView.Codes@@ +## Sonic的调用方式 +直接通过Recorder.Sonic调用即可,音频变速变调转换,详细的使用请参考[GitHub仓库](https://github.com/xiangyuecn/Recorder)里面的README + + ## RecordApp的调用方式 **方式一**:通过import/require引入 diff --git a/assets/runtime-codes/teach.sonic.transform.js b/assets/runtime-codes/teach.sonic.transform.js new file mode 100644 index 0000000..6b8658e --- /dev/null +++ b/assets/runtime-codes/teach.sonic.transform.js @@ -0,0 +1,252 @@ +/****************** +《【教程】变速变调音频转换》 +作者:高坚果 +时间:2020-1-8 10:53:23 + +通过extensions/sonic.js可实现:变调不变速(会说话的汤姆猫)、变速不变调(快放慢放)、变速变调、调节音量。 + +Recorder.Sonic有同步和异步两种调用方式,同步方法简单直接但处理量大时会消耗大量时间,主要用于一次性的处理;异步方法由WebWorker在后台进行运算处理,但异步方法不一定能成功开启(低版本浏览器),主要用于实时处理。 + +本示例是一次性完成转换,没有开启实时特性,因此采用同步方法来进行转换。实时异步处理的例子请参考在线测试完整demo中的sonicProcess方法。 +******************/ +var prevPcms,prevSampleRate; +var transform=function(pcms,pcmSampleRate){ + prevPcms=pcms; + prevSampleRate=pcmSampleRate; + + var sampleRate=16000; + var setSpeed=+$(".sonicCtrlSpeed").val(); + var setPitch=+$(".sonicCtrlPitch").val(); + var setRate=+$(".sonicCtrlRate").val(); + var setVolume=+$(".sonicCtrlVolume").val(); + + var t1=Date.now(); + var chunk=Recorder.SampleData(pcms,pcmSampleRate,sampleRate); + + //核心的sonic同步调用 + var sonic=Recorder.Sonic({sampleRate:sampleRate}); + //进行sonic配置 + sonic.setSpeed(setSpeed); + sonic.setPitch(setPitch); + sonic.setRate(setRate); + sonic.setVolume(setVolume); + + //进行同步转换处理,当pcm太大时使用切片+setTimeout异步化,避免界面卡住 + var buffers=[],newPcm,size=0,idx=0,blockLen=0; + var run1=function(endCall){ + var blockSize=pcmSampleRate;//块大小尽量大些,避免引入杂音 + var pcm=chunk.data,buffer; + if(idx>=pcm.length){ + buffer=sonic.flush();//把剩余的内容输出,如果有的话 + }else{ + //切片 + blockLen++; + var arr=new Int16Array(idx+blockSize>=pcm.length?pcm.length-idx:blockSize); + for(var i=0,pos=idx;i0){ + buffers.push(buffer); + size+=buffer.length; + }; + + if(idx>=pcm.length){ + if(blockLen>0){ + Runtime.Log("共切分"+blockLen+"片异步处理"); + }; + //所有分片都处理完成,结果拼接到一起 + newPcm=new Int16Array(size); + for(var i=0,pos=0;i
控制选项,同一时间应该只控制一个,否则叠加作用;请填写0.1-2.0的数字,1.0为不调整,当然超过2.0也是可以的(需手动输入)
\ +
\ +\ +
Pitch: 男声女声,变调不变速(会说话的汤姆猫)
\ +
Speed: 慢放快放,变速不变调(快放慢放)
\ +
Rate: 缓重尖锐,变速变调
\ +
Volume: 调低调高,调整音量
\ +
'} + ,{name:"重新转换",click:"recTransform"} + ,{name:"重置设置",click:"resetCtrl"} +]); + +$(".sonicCtrlRange").bind("change",function(e){ + $(e.target).parent().find(".sonicCtrlInput").val(/\d+\.\d+/.exec(e.target.value+".0")[0]); +}).change(); +var resetCtrl=function(){ + $(".sonicCtrlRange").val(1).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+"/src/extensions/sonic.js",check:function(){return !Recorder.Sonic}} +]); + +//调用录音 +var rec; +function recStart(){ + rec=Recorder({ + type:"mp3" + ,sampleRate:16000 + ,bitRate:16 + ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){ + Runtime.Process.apply(null,arguments); + } + }); + var t=setTimeout(function(){ + Runtime.Log("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)",1); + },8000); + + rec.open(function(){//打开麦克风授权获得相关资源 + clearTimeout(t); + rec.start();//开始录音 + },function(msg,isUserNotAllow){//用户拒绝未授权或不支持 + clearTimeout(t); + Runtime.Log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg, 1); + }); +}; +function recStop(){ + rec.stop(function(blob,duration){ + Runtime.LogAudio(blob,duration,rec); + + recTransform(); + },function(msg){ + Runtime.Log("录音失败:"+msg, 1); + },true); +}; + + + +//*****拖拽或者选择文件****** +$(".choiceFileBox").remove(); +Runtime.Log('
\ +
\ + 拖拽多个音乐文件到这里 / 点此选择,并转换\ +
\ + \ +
'); +$(".dropFile").bind("dragover",function(e){ + e.preventDefault(); +}).bind("drop",function(e){ + e.preventDefault(); + + readChoiceFile(e.originalEvent.dataTransfer.files); +}); +$(".choiceFile").bind("change",function(e){ + readChoiceFile(e.target.files); +}); +function readChoiceFile(files){ + if(!files.length){ + return; + }; + + Runtime.Log("发现"+files.length+"个文件,开始转换..."); + + var idx=-1; + var run=function(){ + idx++; + if(idx>=files.length){ + return; + }; + + var file = files[idx]; + var reader = new FileReader(); + reader.onload = function(e){ + decodeAudio(file.name,e.target.result,run); + } + reader.readAsArrayBuffer(file); + }; + run(); +}; +var decodeAudio=function(name,arr,call){ + if(!Recorder.Support()){//强制激活Recorder.Ctx 不支持大概率也不支持解码 + Runtime.Log("浏览器不支持音频解码",1); + return; + }; + var srcBlob=new Blob([arr],{type:"audio/"+(/[^.]+$/.exec(name)||[])[0]}); + var ctx=Recorder.Ctx; + ctx.decodeAudioData(arr,function(raw){ + var src=raw.getChannelData(0); + var sampleRate=raw.sampleRate; + console.log(name,raw,srcBlob); + + var pcm=new Int16Array(src.length); + for(var i=0;i 0) { + line.write(outBuffer, 0, numWritten); + } + } while(numWritten > 0); + } while(numRead > 0); + } + + public static void main( + String[] argv) throws UnsupportedAudioFileException, IOException, LineUnavailableException + { + Scanner input=new Scanner(System.in); + + System.out.println("test.wavļ"); + System.out.println("ʼֵд0.1-2.0֣1.0ΪȻ2.0ҲǿԵ"); + + System.out.print("speed(ٲ):"); + float speed = input.nextFloat(); + System.out.print("pitch():"); + float pitch = input.nextFloat(); + System.out.print("rate(ٱ):"); + float rate = input.nextFloat(); + System.out.print("volume():"); + float volume = input.nextFloat(); + + boolean emulateChordPitch = false; + int quality = 0; + + AudioInputStream stream = AudioSystem.getAudioInputStream(new File("test.wav")); + AudioFormat format = stream.getFormat(); + int sampleRate = (int)format.getSampleRate(); + int numChannels = format.getChannels(); + SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, format, + ((int)stream.getFrameLength()*format.getFrameSize())); + SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info); + line.open(stream.getFormat()); + line.start(); + runSonic(stream, line, speed, pitch, rate, volume, emulateChordPitch, quality, + sampleRate, numChannels); + line.drain(); + line.stop(); + } +} \ No newline at end of file diff --git a/assets/sonic-java/README.md b/assets/sonic-java/README.md new file mode 100644 index 0000000..f064a6b --- /dev/null +++ b/assets/sonic-java/README.md @@ -0,0 +1,12 @@ +# Sonic Java版测试 + +Sonic是一个音频变速变调的开源库,[waywardgeek/sonic](https://github.com/waywardgeek/sonic) + +Sonic.java已移植为sonic.js,并且做了精简,代码在[extensions/sonic.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/sonic.js) + + +## 测试方法 + +1. 录制一个test.wav放到本目录 +2. 先安装好jdk,用于编译和运行java文件 +3. 运行start.bat即可控制和播放test.wav diff --git a/assets/sonic-java/Sonic.java b/assets/sonic-java/Sonic.java new file mode 100644 index 0000000..bf4a3a8 --- /dev/null +++ b/assets/sonic-java/Sonic.java @@ -0,0 +1,1079 @@ +/* Sonic library + Copyright 2010, 2011 + Bill Cox + This file is part of the Sonic Library. + + This file is licensed under the Apache 2.0 license. +*/ + +public class Sonic { + + private static final int SONIC_MIN_PITCH = 65; + private static final int SONIC_MAX_PITCH = 400; + // This is used to down-sample some inputs to improve speed + private static final int SONIC_AMDF_FREQ = 4000; + // The number of points to use in the sinc FIR filter for resampling. + private static final int SINC_FILTER_POINTS = 12; + private static final int SINC_TABLE_SIZE = 601; + + // Lookup table for windowed sinc function of SINC_FILTER_POINTS points. + private static final short sincTable[] = { + 0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2, -3, -4, -6, -7, -9, -10, -12, -14, + -17, -19, -21, -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48, -50, + -51, -52, -53, -53, -53, -52, -50, -48, -46, -43, -39, -34, -29, -22, -16, + -8, 0, 9, 19, 29, 41, 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200, + 215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348, 357, 363, 369, 372, + 374, 375, 373, 369, 363, 355, 345, 332, 318, 300, 281, 259, 234, 208, 178, + 147, 113, 77, 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426, + -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951, -989, + -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151, + -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728, + -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342, 462, 584, 708, + 833, 958, 1084, 1209, 1333, 1455, 1575, 1693, 1807, 1916, 2022, 2122, 2216, + 2304, 2384, 2457, 2522, 2579, 2625, 2663, 2689, 2706, 2711, 2705, 2687, + 2657, 2614, 2559, 2491, 2411, 2317, 2211, 2092, 1960, 1815, 1658, 1489, + 1308, 1115, 912, 698, 474, 241, 0, -249, -506, -769, -1037, -1310, -1586, + -1864, -2144, -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291, + -4529, -4757, -4972, -5174, -5360, -5531, -5685, -5819, -5935, -6029, + -6101, -6150, -6175, -6175, -6149, -6096, -6015, -5905, -5767, -5599, + -5401, -5172, -4912, -4621, -4298, -3944, -3558, -3141, -2693, -2214, + -1705, -1166, -597, 0, 625, 1277, 1955, 2658, 3386, 4135, 4906, 5697, 6506, + 7332, 8173, 9027, 9893, 10769, 11654, 12544, 13439, 14335, 15232, 16128, + 17019, 17904, 18782, 19649, 20504, 21345, 22170, 22977, 23763, 24527, + 25268, 25982, 26669, 27327, 27953, 28547, 29107, 29632, 30119, 30569, + 30979, 31349, 31678, 31964, 32208, 32408, 32565, 32677, 32744, 32767, + 32744, 32677, 32565, 32408, 32208, 31964, 31678, 31349, 30979, 30569, + 30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982, 25268, 24527, + 23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019, 16128, + 15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332, 6506, + 5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597, -1166, -1705, + -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172, -5401, + -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101, + -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529, + -4291, -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864, + -1586, -1310, -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308, + 1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614, 2657, + 2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457, 2384, 2304, + 2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333, 1209, 1084, 958, 833, + 708, 584, 462, 342, 225, 111, 0, -107, -210, -309, -403, -492, -576, -655, + -728, -796, -857, -913, -963, -1007, -1046, -1078, -1105, -1125, -1141, + -1151, -1155, -1154, -1149, -1138, -1123, -1104, -1080, -1053, -1023, -989, + -951, -912, -870, -825, -779, -731, -682, -632, -581, -530, -478, -426, + -375, -324, -274, -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178, + 208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369, 373, 375, 374, 372, + 369, 363, 357, 348, 339, 328, 317, 304, 291, 276, 262, 247, 231, 215, 200, + 184, 168, 152, 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9, 0, -8, -16, + -22, -29, -34, -39, -43, -46, -48, -50, -52, -53, -53, -53, -52, -51, -50, + -48, -47, -44, -42, -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14, + -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0 + }; + + private short inputBuffer[]; + private short outputBuffer[]; + private short pitchBuffer[]; + private short downSampleBuffer[]; + private float speed; + private float volume; + private float pitch; + private float rate; + private int oldRatePosition; + private int newRatePosition; + private boolean useChordPitch; + private int quality; + private int numChannels; + private int inputBufferSize; + private int pitchBufferSize; + private int outputBufferSize; + private int numInputSamples; + private int numOutputSamples; + private int numPitchSamples; + private int minPeriod; + private int maxPeriod; + private int maxRequired; + private int remainingInputToCopy; + private int sampleRate; + private int prevPeriod; + private int prevMinDiff; + private int minDiff; + private int maxDiff; + + // Resize the array. + private short[] resize( + short[] oldArray, + int newLength) + { + newLength *= numChannels; + short[] newArray = new short[newLength]; + int length = oldArray.length <= newLength? oldArray.length : newLength; + + System.arraycopy(oldArray, 0, newArray, 0, length); + return newArray; + } + + // Move samples from one array to another. May move samples down within an array, but not up. + private void move( + short dest[], + int destPos, + short source[], + int sourcePos, + int numSamples) + { + System.arraycopy(source, sourcePos*numChannels, dest, destPos*numChannels, numSamples*numChannels); + } + + // Scale the samples by the factor. + private void scaleSamples( + short samples[], + int position, + int numSamples, + float volume) + { + int fixedPointVolume = (int)(volume*4096.0f); + int start = position*numChannels; + int stop = start + numSamples*numChannels; + + for(int xSample = start; xSample < stop; xSample++) { + int value = (samples[xSample]*fixedPointVolume) >> 12; + if(value > 32767) { + value = 32767; + } else if(value < -32767) { + value = -32767; + } + samples[xSample] = (short)value; + } + } + + // Get the speed of the stream. + public float getSpeed() + { + return speed; + } + + // Set the speed of the stream. + public void setSpeed( + float speed) + { + this.speed = speed; + } + + // Get the pitch of the stream. + public float getPitch() + { + return pitch; + } + + // Set the pitch of the stream. + public void setPitch( + float pitch) + { + this.pitch = pitch; + } + + // Get the rate of the stream. + public float getRate() + { + return rate; + } + + // Set the playback rate of the stream. This scales pitch and speed at the same time. + public void setRate( + float rate) + { + this.rate = rate; + this.oldRatePosition = 0; + this.newRatePosition = 0; + } + + // Get the vocal chord pitch setting. + public boolean getChordPitch() + { + return useChordPitch; + } + + // Set the vocal chord mode for pitch computation. Default is off. + public void setChordPitch( + boolean useChordPitch) + { + this.useChordPitch = useChordPitch; + } + + // Get the quality setting. + public int getQuality() + { + return quality; + } + + // Set the "quality". Default 0 is virtually as good as 1, but very much faster. + public void setQuality( + int quality) + { + this.quality = quality; + } + + // Get the scaling factor of the stream. + public float getVolume() + { + return volume; + } + + // Set the scaling factor of the stream. + public void setVolume( + float volume) + { + this.volume = volume; + } + + // Allocate stream buffers. + private void allocateStreamBuffers( + int sampleRate, + int numChannels) + { + minPeriod = sampleRate/SONIC_MAX_PITCH; + maxPeriod = sampleRate/SONIC_MIN_PITCH; + maxRequired = 2*maxPeriod; + inputBufferSize = maxRequired; + inputBuffer = new short[maxRequired*numChannels]; + outputBufferSize = maxRequired; + outputBuffer = new short[maxRequired*numChannels]; + pitchBufferSize = maxRequired; + pitchBuffer = new short[maxRequired*numChannels]; + downSampleBuffer = new short[maxRequired]; + this.sampleRate = sampleRate; + this.numChannels = numChannels; + oldRatePosition = 0; + newRatePosition = 0; + prevPeriod = 0; + } + + // Create a sonic stream. + public Sonic( + int sampleRate, + int numChannels) + { + allocateStreamBuffers(sampleRate, numChannels); + speed = 1.0f; + pitch = 1.0f; + volume = 1.0f; + rate = 1.0f; + oldRatePosition = 0; + newRatePosition = 0; + useChordPitch = false; + quality = 0; + } + + // Get the sample rate of the stream. + public int getSampleRate() + { + return sampleRate; + } + + // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost. + public void setSampleRate( + int sampleRate) + { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Get the number of channels. + public int getNumChannels() + { + return numChannels; + } + + // Set the num channels of the stream. This will cause samples buffered in the stream to be lost. + public void setNumChannels( + int numChannels) + { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Enlarge the output buffer if needed. + private void enlargeOutputBufferIfNeeded( + int numSamples) + { + if(numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize >> 1) + numSamples; + outputBuffer = resize(outputBuffer, outputBufferSize); + } + } + + // Enlarge the input buffer if needed. + private void enlargeInputBufferIfNeeded( + int numSamples) + { + if(numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize >> 1) + numSamples; + inputBuffer = resize(inputBuffer, inputBufferSize); + } + } + + // Add the input samples to the input buffer. + private void addFloatSamplesToInputBuffer( + float samples[], + int numSamples) + { + if(numSamples == 0) { + return; + } + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples*numChannels; + for(int xSample = 0; xSample < numSamples*numChannels; xSample++) { + inputBuffer[xBuffer++] = (short)(samples[xSample]*32767.0f); + } + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. + private void addShortSamplesToInputBuffer( + short samples[], + int numSamples) + { + if(numSamples == 0) { + return; + } + enlargeInputBufferIfNeeded(numSamples); + move(inputBuffer, numInputSamples, samples, 0, numSamples); + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. + private void addUnsignedByteSamplesToInputBuffer( + byte samples[], + int numSamples) + { + short sample; + + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples*numChannels; + for(int xSample = 0; xSample < numSamples*numChannels; xSample++) { + sample = (short)((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed + inputBuffer[xBuffer++] = (short) (sample << 8); + } + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte array. + private void addBytesToInputBuffer( + byte inBuffer[], + int numBytes) + { + int numSamples = numBytes/(2*numChannels); + short sample; + + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples*numChannels; + for(int xByte = 0; xByte + 1 < numBytes; xByte += 2) { + sample = (short)((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8)); + inputBuffer[xBuffer++] = sample; + } + numInputSamples += numSamples; + } + + // Remove input samples that we have already processed. + private void removeInputSamples( + int position) + { + int remainingSamples = numInputSamples - position; + + move(inputBuffer, 0, inputBuffer, position, remainingSamples); + numInputSamples = remainingSamples; + } + + // Just copy from the array to the output buffer + private void copyToOutput( + short samples[], + int position, + int numSamples) + { + enlargeOutputBufferIfNeeded(numSamples); + move(outputBuffer, numOutputSamples, samples, position, numSamples); + numOutputSamples += numSamples; + } + + // Just copy from the input buffer to the output buffer. Return num samples copied. + private int copyInputToOutput( + int position) + { + int numSamples = remainingInputToCopy; + + if(numSamples > maxRequired) { + numSamples = maxRequired; + } + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + + // Read data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readFloatFromStream( + float samples[], + int maxSamples) + { + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + for(int xSample = 0; xSample < numSamples*numChannels; xSample++) { + samples[xSample] = (outputBuffer[xSample])/32767.0f; + } + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return numSamples; + } + + // Read short data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readShortFromStream( + short samples[], + int maxSamples) + { + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + move(samples, 0, outputBuffer, 0, numSamples); + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return numSamples; + } + + // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readUnsignedByteFromStream( + byte samples[], + int maxSamples) + { + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + for(int xSample = 0; xSample < numSamples*numChannels; xSample++) { + samples[xSample] = (byte)((outputBuffer[xSample] >> 8) + 128); + } + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return numSamples; + } + + // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readBytesFromStream( + byte outBuffer[], + int maxBytes) + { + int maxSamples = maxBytes/(2*numChannels); + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if(numSamples == 0 || maxSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + for(int xSample = 0; xSample < numSamples*numChannels; xSample++) { + short sample = outputBuffer[xSample]; + outBuffer[xSample << 1] = (byte)(sample & 0xff); + outBuffer[(xSample << 1) + 1] = (byte)(sample >> 8); + } + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return 2*numSamples*numChannels; + } + + // Force the sonic stream to generate output using whatever data it currently + // has. No extra delay will be added to the output, but flushing in the middle of + // words could introduce distortion. + public void flushStream() + { + int remainingSamples = numInputSamples; + float s = speed/pitch; + float r = rate*pitch; + int expectedOutputSamples = numOutputSamples + (int)((remainingSamples/s + numPitchSamples)/r + 0.5f); + + // Add enough silence to flush both input and pitch buffers. + enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired); + for(int xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) { + inputBuffer[remainingSamples*numChannels + xSample] = 0; + } + numInputSamples += 2*maxRequired; + writeShortToStream(null, 0); + // Throw away any extra samples we generated due to the silence we added. + if(numOutputSamples > expectedOutputSamples) { + numOutputSamples = expectedOutputSamples; + } + // Empty input and pitch buffers. + numInputSamples = 0; + remainingInputToCopy = 0; + numPitchSamples = 0; + } + + // Return the number of samples in the output buffer + public int samplesAvailable() + { + return numOutputSamples; + } + + // If skip is greater than one, average skip samples together and write them to + // the down-sample buffer. If numChannels is greater than one, mix the channels + // together as we down sample. + private void downSampleInput( + short samples[], + int position, + int skip) + { + int numSamples = maxRequired/skip; + int samplesPerValue = numChannels*skip; + int value; + + position *= numChannels; + for(int i = 0; i < numSamples; i++) { + value = 0; + for(int j = 0; j < samplesPerValue; j++) { + value += samples[position + i*samplesPerValue + j]; + } + value /= samplesPerValue; + downSampleBuffer[i] = (short)value; + } + } + + // Find the best frequency match in the range, and given a sample skip multiple. + // For now, just find the pitch of the first channel. + private int findPitchPeriodInRange( + short samples[], + int position, + int minPeriod, + int maxPeriod) + { + int bestPeriod = 0, worstPeriod = 255; + int minDiff = 1, maxDiff = 0; + + position *= numChannels; + for(int period = minPeriod; period <= maxPeriod; period++) { + int diff = 0; + for(int i = 0; i < period; i++) { + short sVal = samples[position + i]; + short pVal = samples[position + period + i]; + diff += sVal >= pVal? sVal - pVal : pVal - sVal; + } + /* Note that the highest number of samples we add into diff will be less + than 256, since we skip samples. Thus, diff is a 24 bit number, and + we can safely multiply by numSamples without overflow */ + if(diff*bestPeriod < minDiff*period) { + minDiff = diff; + bestPeriod = period; + } + if(diff*worstPeriod > maxDiff*period) { + maxDiff = diff; + worstPeriod = period; + } + } + this.minDiff = minDiff/bestPeriod; + this.maxDiff = maxDiff/worstPeriod; + + return bestPeriod; + } + + // At abrupt ends of voiced words, we can have pitch periods that are better + // approximated by the previous pitch period estimate. Try to detect this case. + private boolean prevPeriodBetter( + int minDiff, + int maxDiff, + boolean preferNewPeriod) + { + if(minDiff == 0 || prevPeriod == 0) { + return false; + } + if(preferNewPeriod) { + if(maxDiff > minDiff*3) { + // Got a reasonable match this period + return false; + } + if(minDiff*2 <= prevMinDiff*3) { + // Mismatch is not that much greater this period + return false; + } + } else { + if(minDiff <= prevMinDiff) { + return false; + } + } + return true; + } + + // Find the pitch period. This is a critical step, and we may have to try + // multiple ways to get a good answer. This version uses AMDF. To improve + // speed, we down sample by an integer factor get in the 11KHz range, and then + // do it again with a narrower frequency range without down sampling + private int findPitchPeriod( + short samples[], + int position, + boolean preferNewPeriod) + { + int period, retPeriod; + int skip = 1; + + if(sampleRate > SONIC_AMDF_FREQ && quality == 0) { + skip = sampleRate/SONIC_AMDF_FREQ; + } + if(numChannels == 1 && skip == 1) { + period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); + } else { + downSampleInput(samples, position, skip); + period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod/skip, + maxPeriod/skip); + if(skip != 1) { + period *= skip; + int minP = period - (skip << 2); + int maxP = period + (skip << 2); + if(minP < minPeriod) { + minP = minPeriod; + } + if(maxP > maxPeriod) { + maxP = maxPeriod; + } + if(numChannels == 1) { + period = findPitchPeriodInRange(samples, position, minP, maxP); + } else { + downSampleInput(samples, position, 1); + period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP); + } + } + } + if(prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + retPeriod = prevPeriod; + } else { + retPeriod = period; + } + prevMinDiff = minDiff; + prevPeriod = period; + return retPeriod; + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + private void overlapAdd( + int numSamples, + int numChannels, + short out[], + int outPos, + short rampDown[], + int rampDownPos, + short rampUp[], + int rampUpPos) + { + for(int i = 0; i < numChannels; i++) { + int o = outPos*numChannels + i; + int u = rampUpPos*numChannels + i; + int d = rampDownPos*numChannels + i; + for(int t = 0; t < numSamples; t++) { + out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples); + o += numChannels; + d += numChannels; + u += numChannels; + } + } + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + private void overlapAddWithSeparation( + int numSamples, + int numChannels, + int separation, + short out[], + int outPos, + short rampDown[], + int rampDownPos, + short rampUp[], + int rampUpPos) + { + for(int i = 0; i < numChannels; i++) { + int o = outPos*numChannels + i; + int u = rampUpPos*numChannels + i; + int d = rampDownPos*numChannels + i; + for(int t = 0; t < numSamples + separation; t++) { + if(t < separation) { + out[o] = (short)(rampDown[d]*(numSamples - t)/numSamples); + d += numChannels; + } else if(t < numSamples) { + out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples); + d += numChannels; + u += numChannels; + } else { + out[o] = (short)(rampUp[u]*(t - separation)/numSamples); + u += numChannels; + } + o += numChannels; + } + } + } + + // Just move the new samples in the output buffer to the pitch buffer + private void moveNewSamplesToPitchBuffer( + int originalNumOutputSamples) + { + int numSamples = numOutputSamples - originalNumOutputSamples; + + if(numPitchSamples + numSamples > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize >> 1) + numSamples; + pitchBuffer = resize(pitchBuffer, pitchBufferSize); + } + move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); + numOutputSamples = originalNumOutputSamples; + numPitchSamples += numSamples; + } + + // Remove processed samples from the pitch buffer. + private void removePitchSamples( + int numSamples) + { + if(numSamples == 0) { + return; + } + move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples); + numPitchSamples -= numSamples; + } + + // Change the pitch. The latency this introduces could be reduced by looking at + // past samples to determine pitch, rather than future. + private void adjustPitch( + int originalNumOutputSamples) + { + int period, newPeriod, separation; + int position = 0; + + if(numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + while(numPitchSamples - position >= maxRequired) { + period = findPitchPeriod(pitchBuffer, position, false); + newPeriod = (int)(period/pitch); + enlargeOutputBufferIfNeeded(newPeriod); + if(pitch >= 1.0f) { + overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, + position, pitchBuffer, position + period - newPeriod); + } else { + separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, + pitchBuffer, position, pitchBuffer, position); + } + numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(position); + } + + // Aproximate the sinc function times a Hann window from the sinc table. + private int findSincCoefficient(int i, int ratio, int width) { + int lobePoints = (SINC_TABLE_SIZE-1)/SINC_FILTER_POINTS; + int left = i*lobePoints + (ratio*lobePoints)/width; + int right = left + 1; + int position = i*lobePoints*width + ratio*lobePoints - left*width; + int leftVal = sincTable[left]; + int rightVal = sincTable[right]; + + return ((leftVal*(width - position) + rightVal*position) << 1)/width; + } + + // Return 1 if value >= 0, else -1. This represents the sign of value. + private int getSign(int value) { + return value >= 0? 1 : -1; + } + + // Interpolate the new output sample. + private short interpolate( + short in[], + int inPos, // Index to first sample which already includes channel offset. + int oldSampleRate, + int newSampleRate) + { + // Compute N-point sinc FIR-filter here. Clip rather than overflow. + int i; + int total = 0; + int position = newRatePosition*oldSampleRate; + int leftPosition = oldRatePosition*newSampleRate; + int rightPosition = (oldRatePosition + 1)*newSampleRate; + int ratio = rightPosition - position - 1; + int width = rightPosition - leftPosition; + int weight, value; + int oldSign; + int overflowCount = 0; + + for (i = 0; i < SINC_FILTER_POINTS; i++) { + weight = findSincCoefficient(i, ratio, width); + /* printf("%u %f\n", i, weight); */ + value = in[inPos + i*numChannels]*weight; + oldSign = getSign(total); + total += value; + if (oldSign != getSign(total) && getSign(value) == oldSign) { + /* We must have overflowed. This can happen with a sinc filter. */ + overflowCount += oldSign; + } + } + /* It is better to clip than to wrap if there was a overflow. */ + if (overflowCount > 0) { + return Short.MAX_VALUE; + } else if (overflowCount < 0) { + return Short.MIN_VALUE; + } + return (short)(total >> 16); + } + + // Change the rate. + private void adjustRate( + float rate, + int originalNumOutputSamples) + { + int newSampleRate = (int)(sampleRate/rate); + int oldSampleRate = sampleRate; + int position; + + // Set these values to help with the integer math + while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate >>= 1; + oldSampleRate >>= 1; + } + if(numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + // Leave at least one pitch sample in the buffer + for(position = 0; position < numPitchSamples - 1; position++) { + while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) { + enlargeOutputBufferIfNeeded(1); + for(int i = 0; i < numChannels; i++) { + outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer, + position*numChannels + i, oldSampleRate, newSampleRate); + } + newRatePosition++; + numOutputSamples++; + } + oldRatePosition++; + if(oldRatePosition == oldSampleRate) { + oldRatePosition = 0; + if(newRatePosition != newSampleRate) { + System.out.printf("Assertion failed: newRatePosition != newSampleRate\n"); + assert false; + } + newRatePosition = 0; + } + } + removePitchSamples(position); + } + + + // Skip over a pitch period, and copy period/speed samples to the output + private int skipPitchPeriod( + short samples[], + int position, + float speed, + int period) + { + int newSamples; + + if(speed >= 2.0f) { + newSamples = (int)(period/(speed - 1.0f)); + } else { + newSamples = period; + remainingInputToCopy = (int)(period*(2.0f - speed)/(speed - 1.0f)); + } + enlargeOutputBufferIfNeeded(newSamples); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, + samples, position + period); + numOutputSamples += newSamples; + return newSamples; + } + + // Insert a pitch period, and determine how much input to copy directly. + private int insertPitchPeriod( + short samples[], + int position, + float speed, + int period) + { + int newSamples; + + if(speed < 0.5f) { + newSamples = (int)(period*speed/(1.0f - speed)); + } else { + newSamples = period; + remainingInputToCopy = (int)(period*(2.0f*speed - 1.0f)/(1.0f - speed)); + } + enlargeOutputBufferIfNeeded(period + newSamples); + move(outputBuffer, numOutputSamples, samples, position, period); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, + position + period, samples, position); + numOutputSamples += period + newSamples; + return newSamples; + } + + // Resample as many pitch periods as we have buffered on the input. Return 0 if + // we fail to resize an input or output buffer. Also scale the output by the volume. + private void changeSpeed( + float speed) + { + int numSamples = numInputSamples; + int position = 0, period, newSamples; + + if(numInputSamples < maxRequired) { + return; + } + do { + if(remainingInputToCopy > 0) { + newSamples = copyInputToOutput(position); + position += newSamples; + } else { + period = findPitchPeriod(inputBuffer, position, true); + if(speed > 1.0) { + newSamples = skipPitchPeriod(inputBuffer, position, speed, period); + position += period + newSamples; + } else { + newSamples = insertPitchPeriod(inputBuffer, position, speed, period); + position += newSamples; + } + } + } while(position + maxRequired <= numSamples); + removeInputSamples(position); + } + + // Resample as many pitch periods as we have buffered on the input. Scale the output by the volume. + private void processStreamInput() + { + int originalNumOutputSamples = numOutputSamples; + float s = speed/pitch; + float r = rate; + + if(!useChordPitch) { + r *= pitch; + } + if(s > 1.00001 || s < 0.99999) { + changeSpeed(s); + } else { + copyToOutput(inputBuffer, 0, numInputSamples); + numInputSamples = 0; + } + if(useChordPitch) { + if(pitch != 1.0f) { + adjustPitch(originalNumOutputSamples); + } + } else if(r != 1.0f) { + adjustRate(r, originalNumOutputSamples); + } + if(volume != 1.0f) { + // Adjust output volume. + scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples, + volume); + } + } + + // Write floating point data to the input buffer and process it. + public void writeFloatToStream( + float samples[], + int numSamples) + { + addFloatSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Write the data to the input stream, and process it. + public void writeShortToStream( + short samples[], + int numSamples) + { + addShortSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short + // conversion for you. + public void writeUnsignedByteToStream( + byte samples[], + int numSamples) + { + addUnsignedByteSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion. + public void writeBytesToStream( + byte inBuffer[], + int numBytes) + { + addBytesToInputBuffer(inBuffer, numBytes); + processStreamInput(); + } + + // This is a non-stream oriented interface to just change the speed of a sound sample + public static int changeFloatSpeed( + float samples[], + int numSamples, + float speed, + float pitch, + float rate, + float volume, + boolean useChordPitch, + int sampleRate, + int numChannels) + { + Sonic stream = new Sonic(sampleRate, numChannels); + + stream.setSpeed(speed); + stream.setPitch(pitch); + stream.setRate(rate); + stream.setVolume(volume); + stream.setChordPitch(useChordPitch); + stream.writeFloatToStream(samples, numSamples); + stream.flushStream(); + numSamples = stream.samplesAvailable(); + stream.readFloatFromStream(samples, numSamples); + return numSamples; + } + + /* This is a non-stream oriented interface to just change the speed of a sound sample */ + public int sonicChangeShortSpeed( + short samples[], + int numSamples, + float speed, + float pitch, + float rate, + float volume, + boolean useChordPitch, + int sampleRate, + int numChannels) + { + Sonic stream = new Sonic(sampleRate, numChannels); + + stream.setSpeed(speed); + stream.setPitch(pitch); + stream.setRate(rate); + stream.setVolume(volume); + stream.setChordPitch(useChordPitch); + stream.writeShortToStream(samples, numSamples); + stream.flushStream(); + numSamples = stream.samplesAvailable(); + stream.readShortFromStream(samples, numSamples); + return numSamples; + } +} \ No newline at end of file diff --git a/assets/sonic-java/start.bat b/assets/sonic-java/start.bat new file mode 100644 index 0000000..343fb99 --- /dev/null +++ b/assets/sonic-java/start.bat @@ -0,0 +1,24 @@ +@echo off + +:Run +cls +if not exist test.wav ( + echo test.wavļڣӦ¼һtest.wavļŵbatͬĿ¼ + goto Pause +) + +javac -version +if errorlevel 1 ( + echo ҪװJDKܱjavaļ + goto Pause +) + +javac *.java && java Main + +set /p step=Ƿ(y): +if "%step%"=="y" goto Run +goto End + +:Pause +pause +:End \ No newline at end of file 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 bb8e81e..fd1ce18 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" @@ -906,6 +906,7 @@ ,{n:"【Demo库】【文件合并】-wav多个片段文件合并",k:"lib.merge.wav_merge"} ,{n:"【教程】实时多路音频混音",k:"teach.realtime.mix_multiple"} +,{n:"【教程】变速变调音频转换",k:"teach.sonic.transform"} ]; diff --git a/dist/extensions/sonic.js b/dist/extensions/sonic.js new file mode 100644 index 0000000..7af1aa1 --- /dev/null +++ b/dist/extensions/sonic.js @@ -0,0 +1,6 @@ +/* +录音 +https://github.com/xiangyuecn/Recorder +src: extensions/sonic.js +*/ +!function(){"use strict";function o(t){var r=function(t){this.set=t;var r=function(t){var c,a,f,s,o=65,n=400,u=4e3,g=12,A=601,j=[0,0,0,0,0,0,0,-1,-1,-2,-2,-3,-4,-6,-7,-9,-10,-12,-14,-17,-19,-21,-24,-26,-29,-32,-34,-37,-40,-42,-44,-47,-48,-50,-51,-52,-53,-53,-53,-52,-50,-48,-46,-43,-39,-34,-29,-22,-16,-8,0,9,19,29,41,53,65,79,92,107,121,137,152,168,184,200,215,231,247,262,276,291,304,317,328,339,348,357,363,369,372,374,375,373,369,363,355,345,332,318,300,281,259,234,208,178,147,113,77,39,0,-41,-85,-130,-177,-225,-274,-324,-375,-426,-478,-530,-581,-632,-682,-731,-779,-825,-870,-912,-951,-989,-1023,-1053,-1080,-1104,-1123,-1138,-1149,-1154,-1155,-1151,-1141,-1125,-1105,-1078,-1046,-1007,-963,-913,-857,-796,-728,-655,-576,-492,-403,-309,-210,-107,0,111,225,342,462,584,708,833,958,1084,1209,1333,1455,1575,1693,1807,1916,2022,2122,2216,2304,2384,2457,2522,2579,2625,2663,2689,2706,2711,2705,2687,2657,2614,2559,2491,2411,2317,2211,2092,1960,1815,1658,1489,1308,1115,912,698,474,241,0,-249,-506,-769,-1037,-1310,-1586,-1864,-2144,-2424,-2703,-2980,-3254,-3523,-3787,-4043,-4291,-4529,-4757,-4972,-5174,-5360,-5531,-5685,-5819,-5935,-6029,-6101,-6150,-6175,-6175,-6149,-6096,-6015,-5905,-5767,-5599,-5401,-5172,-4912,-4621,-4298,-3944,-3558,-3141,-2693,-2214,-1705,-1166,-597,0,625,1277,1955,2658,3386,4135,4906,5697,6506,7332,8173,9027,9893,10769,11654,12544,13439,14335,15232,16128,17019,17904,18782,19649,20504,21345,22170,22977,23763,24527,25268,25982,26669,27327,27953,28547,29107,29632,30119,30569,30979,31349,31678,31964,32208,32408,32565,32677,32744,32767,32744,32677,32565,32408,32208,31964,31678,31349,30979,30569,30119,29632,29107,28547,27953,27327,26669,25982,25268,24527,23763,22977,22170,21345,20504,19649,18782,17904,17019,16128,15232,14335,13439,12544,11654,10769,9893,9027,8173,7332,6506,5697,4906,4135,3386,2658,1955,1277,625,0,-597,-1166,-1705,-2214,-2693,-3141,-3558,-3944,-4298,-4621,-4912,-5172,-5401,-5599,-5767,-5905,-6015,-6096,-6149,-6175,-6175,-6150,-6101,-6029,-5935,-5819,-5685,-5531,-5360,-5174,-4972,-4757,-4529,-4291,-4043,-3787,-3523,-3254,-2980,-2703,-2424,-2144,-1864,-1586,-1310,-1037,-769,-506,-249,0,241,474,698,912,1115,1308,1489,1658,1815,1960,2092,2211,2317,2411,2491,2559,2614,2657,2687,2705,2711,2706,2689,2663,2625,2579,2522,2457,2384,2304,2216,2122,2022,1916,1807,1693,1575,1455,1333,1209,1084,958,833,708,584,462,342,225,111,0,-107,-210,-309,-403,-492,-576,-655,-728,-796,-857,-913,-963,-1007,-1046,-1078,-1105,-1125,-1141,-1151,-1155,-1154,-1149,-1138,-1123,-1104,-1080,-1053,-1023,-989,-951,-912,-870,-825,-779,-731,-682,-632,-581,-530,-478,-426,-375,-324,-274,-225,-177,-130,-85,-41,0,39,77,113,147,178,208,234,259,281,300,318,332,345,355,363,369,373,375,374,372,369,363,357,348,339,328,317,304,291,276,262,247,231,215,200,184,168,152,137,121,107,92,79,65,53,41,29,19,9,0,-8,-16,-22,-29,-34,-39,-43,-46,-48,-50,-52,-53,-53,-53,-52,-51,-50,-48,-47,-44,-42,-40,-37,-34,-32,-29,-26,-24,-21,-19,-17,-14,-12,-10,-9,-7,-6,-4,-3,-2,-2,-1,-1,0,0,0,0,0,0,0],i=0,e=0,l=0,h=0,I=0,O=0,v=!1,d=0,x=0,p=0,w=0,m=0,M=0,y=0,k=0,b=0,R=0,S=0,L=0,U=0,_=0,P=0,F=0,W=0;function B(t,r){r*=x;var o=new Int16Array(r),n=t.length<=r?t.length:r;return ot.arraycopy(t,0,o,0,n),o}function C(t,r,o,n,e){ot.arraycopy(o,n*x,t,r*x,e*x)}function D(t,r){b=Math.floor(t/n),R=Math.floor(t/o),p=S=2*R,c=new Int16Array(S*r),m=S,a=new Int16Array(S*r),w=S,f=new Int16Array(S*r),s=new Int16Array(S),U=t,x=r,_=O=I=0}function N(t){m>1)+t))}function Q(t){p>1)+t))}function T(t,r,o){N(o),C(a,y,t,r,o),y+=o}function V(t){var r=L;return S>1)+r)),C(f,k,a,t,r),y=t,k+=r}function K(t){0!=t&&(C(f,0,f,t,k-t),k-=t)}function X(t){return 0<=t?1:-1}function Y(t,r,o,n){var e,i,a,c,f,s,u,l,h,v,d,p,w,m=0,M=O*o,y=I*n,k=(I+1)*n,b=k-M-1,R=k-y,S=0;for(e=0;e>16&65535}function Z(t,r,o,n){var e;return 2<=o?e=Math.floor(n/(o-1)):(e=n,L=Math.floor(n*(2-o)/(o-1))),N(e),G(e,x,a,y,t,r,t,r+n),y+=e,e}function $(t,r,o,n){var e;return o<.5?e=Math.floor(n*o/(1-o)):(e=n,L=Math.floor(n*(2*o-1)/(1-o))),N(n+e),C(a,y,t,r,n),G(e,x,a,y+n,t,r+n,t,r),y+=n+e,e}function tt(t){var r,o,n=M,e=0;if(!(M>=1,e>>=1;if(y==r)return;for(J(r),o=0;o>12;32767"+p.length+" 花:"+(Date.now()-a)+"ms"),setTimeout(function(){a=Date.now(),r[o.type](p,function(e){c(e,u)},function(e){i(e)})})}else i("未加载"+o.type+"编码器");else i("音频被释放");else i("未采集到录音")}},v.Recorder&&v.Recorder.Destroy(),(v.Recorder=R).LM="2019-11-7 21:47:48",R.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",R.Traffic=function(){var e=R.TrafficImgUrl;if(e){var t=R.Traffic,n=location.href.replace(/#.*/,"");if(!t[n]){t[n]=1;var a=new Image;a.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder); \ No newline at end of file +!function(p){"use strict";var v=function(){},O=function(e){return new t(e)};O.IsOpen=function(){var e=O.Stream;if(e){var t=e.getTracks&&e.getTracks()||e.audioTracks||[],n=t[0];if(n){var a=n.readyState;return"live"==a||a==n.LIVE}}return!1},O.BufferSize=4096,O.Destroy=function(){for(var e in console.log("Recorder Destroy"),n)n[e]()};var n={};O.BindDestroy=function(e,t){n[e]=t},O.Support=function(){var e=p.AudioContext;if(e||(e=p.webkitAudioContext),!e)return!1;var t=navigator.mediaDevices||{};return t.getUserMedia||(t=navigator).getUserMedia||(t.getUserMedia=t.webkitGetUserMedia||t.mozGetUserMedia||t.msGetUserMedia),!!t.getUserMedia&&(O.Scope=t,O.Ctx&&"closed"!=O.Ctx.state||(O.Ctx=new e,O.BindDestroy("Ctx",function(){var e=O.Ctx;e&&e.close&&e.close()})),!0)},O.SampleData=function(e,t,n,a,r){a||(a={});var o=a.index||0,i=a.offset||0,s=a.frameNext||[];r||(r={});var c=r.frameSize||1;r.frameType&&(c="mp3"==r.frameType?1152:1);for(var f=0,l=o;l"+v.length+" 花:"+(Date.now()-a)+"ms"),setTimeout(function(){a=Date.now(),r[o.type](v,function(e){c(e,u)},function(e){s(e)})})}else s("未加载"+o.type+"编码器");else s("音频被释放");else s("未采集到录音")}},p.Recorder&&p.Recorder.Destroy(),(p.Recorder=O).LM="2020-1-8 10:53:14",O.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",O.Traffic=function(){var e=O.TrafficImgUrl;if(e){var t=O.Traffic,n=location.href.replace(/#.*/,"");if(!t[n]){t[n]=1;var a=new Image;a.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder); \ No newline at end of file diff --git a/index.html b/index.html index d11ff0c..746ed27 100644 --- a/index.html +++ b/index.html @@ -40,6 +40,7 @@ + +
Pitch: 男声女声,变调不变速(会说话的汤姆猫)
+
Speed: 慢放快放,变速不变调(快放慢放),由于会增减PCM数据,实时处理时本功能需要禁用丢失补偿
+
Rate: 缓重尖锐,变速变调,由于会增减PCM数据,实时处理时本功能需要禁用丢失补偿
+
Volume: 调低调高,调整音量
+ +
处理缓冲:ms 0ms1000ms,控制缓冲大小减少转换引入的杂音,0不缓冲
+ +
+ + +
@@ -264,6 +286,7 @@ var sample=+$(".sample").val(); var wave,waveSet=$(".recwaveSet")[0].checked; + window.sonicAsync=null; var disableEnvInFixSet=$(".disableEnvInFixSet")[0].checked; if(disableEnvInFixSet){ @@ -278,15 +301,20 @@ ,bitRate:bit ,sampleRate:sample ,disableEnvInFix:disableEnvInFixSet - ,onProcess:function(buffers,level,time,sampleRate){ - $(".recpowerx").css("width",level+"%"); - $(".recpowert").html(time+"/"+level); + ,onProcess:function(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd){ + //优先进行pcm处理,可能会发生数据修改,对于需要大量运算的处理需要开启异步模式,onProcess返回true即可开启,异步操作完成后必须回调asyncEnd + var beginAsync=sonicProcess(buffers,sampleRate,newBufferIdx,asyncEnd); - waveSet && wave.input(buffers[buffers.length-1],level,sampleRate); + $(".recpowerx").css("width",powerLevel+"%"); + $(".recpowert").html(duration+"/"+powerLevel); + + waveSet && wave.input(buffers[buffers.length-1],powerLevel,sampleRate); if(realTimeSendSet&&window.realTimeSendTry){ realTimeSendTry(rec.set,realTimeSendTime,buffers,sampleRate); }; + + return beginAsync;//返回true转成异步操作 } }); @@ -352,6 +380,9 @@ if(rec){ window.realTimeSendTryReset&&realTimeSendTryReset(); + sonicAsync&&sonicAsync.flush();//丢弃不管,省的去同步麻烦 + sonicAsync=null; + rec.start(); var set=rec.set; reclog("录制中:"+set.type+" "+set.sampleRate+"hz "+set.bitRate+"kbps"); @@ -550,7 +581,8 @@ }; reclog("点击打开录音开始哦,此浏览器":"red'>不")+"支持录音"); -reclog("WaveView Extensions已启用"); +reclog("WaveView Extensions已启用:显示波形功能即为此扩展,源码:src/extensions/waveview.js"); +reclog("Sonic Extensions已启用:变速变调功能即为此扩展,源码:src/extensions/sonic.js"); function goiframe(){ @@ -586,6 +618,123 @@ $("head")[0].appendChild(elem); }; }); + + + +//变速变调 +var sonicCtrlSet={}; +$(".sonicCtrlInput").bind("change",function(e){ + sonicCtrlSet[/sonicCtrl[^ ]+$/.exec(e.target.className)[0]]=+e.target.value; +}); +$(".sonicCtrlRange").bind("change",function(e){ + $(e.target).parent().find(".sonicCtrlInput").val(/\d+\.\d+/.exec(e.target.value+".0")[0]).change(); +}).change(); +var resetSonicCtrl=function(){ + $(".sonicCtrlRange").val(1).change(); + $(".sonicCtrlBufferRange").val(200).change(); +}; +var sonicRecTransform=function(){ + if(!rec||!rec.buffers){ + reclog("请先录音",1); + return; + }; + var type=rec.set.type; + var sampleRate=rec.set.sampleRate; + var bitRate=rec.set.bitRate; + if(type!="mp3"){ + reclog("目前只支持mp3格式的录音重新转换,因为其他格式buffers已被污染",1); + return; + }; + + sonicAsync&&sonicAsync.flush(); + sonicAsync=null; + + var srcBuffers=rec.buffers; + var buffers=[]; + var idx=-1; + var run=function(){ + idx++; + if(idx>=srcBuffers.length){ + var mockRec=Recorder({type:type,sampleRate:sampleRate,bitRate:bitRate}); + mockRec.mock(Recorder.SampleData(buffers,sampleRate,sampleRate).data,sampleRate); + recstopFn(null,0,function(){ + return "已转换"; + },mockRec); + return; + }; + + buffers.push(Recorder.SampleData([srcBuffers[idx]],rec.srcSampleRate,sampleRate).data); + var beginAsync=sonicProcess(buffers,sampleRate,idx,run); + if(!beginAsync){ + reclog("不存在变速变调设置,或不能开启转换",1); + }; + buffers[idx]=new Int16Array(0); + }; + run(); +}; +var sonicInfo; +var sonicProcess=function(buffers,sampleRate,newBufferIdx,asyncEnd){ + if(sonicCtrlSet.sonicCtrlPitch==1 + &&sonicCtrlSet.sonicCtrlRate==1 + &&sonicCtrlSet.sonicCtrlSpeed==1 + &&sonicCtrlSet.sonicCtrlVolume==1){//不存在变速变调设置 + return; + }; + + if(sonicAsync==-1){ + return; + }; + if(!sonicAsync||sonicAsync.set.sampleRate!=sampleRate){ + //实时处理只能用异步操作,不能用同步方法,否则必然卡顿 + sonicAsync=Recorder.Sonic.Async({sampleRate:sampleRate}); + sonicInfo={}; + if(!sonicAsync){ + sonicAsync=-1; + reclog("不能开启Sonic.Async,浏览器不支持WebWorker操作,降级不变速变调",1); + return; + }; + }; + + sonicAsync.setPitch(sonicCtrlSet.sonicCtrlPitch); + sonicAsync.setRate(sonicCtrlSet.sonicCtrlRate); + sonicAsync.setSpeed(sonicCtrlSet.sonicCtrlSpeed); + sonicAsync.setVolume(sonicCtrlSet.sonicCtrlVolume); + + var newBuffers=sonicInfo.buffers||[]; + var newBufferSize=sonicInfo.bufferSize||0; + var blockSize=sampleRate/1000*sonicCtrlSet.sonicCtrlBuffer;//缓冲0-1000ms的数据进行处理,200ms以上可避免引入大量杂音 + var lastIdx=buffers.length-1; + for(var i=newBufferIdx;i<=lastIdx;i++){ + newBuffers.push(buffers[i]);//copy出来,异步onProcess会清空这些数组 + newBufferSize+=buffers[i].length; + }; + + if(newBufferSize1){ + buffer=Recorder.SampleData(newBuffers,sampleRate,sampleRate).data; + }; + newBuffers=[]; + newBufferSize=0; + var sizeOld=buffer.length,sizeNew=0; + + //推入后台异步转换 + sonicAsync.input(buffer,function(pcm){ + buffers[lastIdx]=pcm;//写回buffers,放到调用时的最后一个位置即可 ,其他内容已在开启异步模式时已经被自动替换成了空数组 + + asyncEnd();//完成处理必须进行回调 + }); + }; + + sonicInfo.buffers=newBuffers; + sonicInfo.bufferSize=newBufferSize; + + return true; +}; @@ -802,6 +951,7 @@

ConsoleX:

if(i>=engines.length){ loadEngineState[type]=minjs; Recorder.WaveView=WaveViewBak; + Recorder.Sonic=SonicBak; reclog(""+type+"编码器"+(minjs?"压缩版":"源码版")+"已加载,可以录音了"); end(); @@ -823,6 +973,7 @@

ConsoleX:

}; loadEngineState={}; var WaveViewBak=Recorder.WaveView; +var SonicBak=Recorder.Sonic; (function(){try{ var minjs=$(".loadMinJs"); diff --git a/recorder.mp3.min.js b/recorder.mp3.min.js index 32755b6..36c354b 100644 --- a/recorder.mp3.min.js +++ b/recorder.mp3.min.js @@ -3,4 +3,4 @@ https://github.com/xiangyuecn/Recorder src: recorder-core.js,engine/mp3.js,engine/mp3-engine.js */ -!function(h){"use strict";var u=function(){},A=function(e){return new t(e)};A.IsOpen=function(){var e=A.Stream;if(e){var t=e.getTracks&&e.getTracks()||e.audioTracks||[],a=t[0];if(a){var s=a.readyState;return"live"==s||s==a.LIVE}}return!1},A.BufferSize=4096,A.Destroy=function(){for(var e in console.log("Recorder Destroy"),a)a[e]()};var a={};A.BindDestroy=function(e,t){a[e]=t},A.Support=function(){var e=h.AudioContext;if(e||(e=h.webkitAudioContext),!e)return!1;var t=navigator.mediaDevices||{};return t.getUserMedia||(t=navigator).getUserMedia||(t.getUserMedia=t.webkitGetUserMedia||t.mozGetUserMedia||t.msGetUserMedia),!!t.getUserMedia&&(A.Scope=t,A.Ctx&&"closed"!=A.Ctx.state||(A.Ctx=new e,A.BindDestroy("Ctx",function(){var e=A.Ctx;e&&e.close&&e.close()})),!0)},A.SampleData=function(e,t,a,s,n){s||(s={});var r=s.index||0,i=s.offset||0,_=s.frameNext||[];n||(n={});var o=n.frameSize||1;n.frameType&&(o="mp3"==n.frameType?1152:1);for(var l=0,f=r;f"+u.length+" 花:"+(Date.now()-s)+"ms"),setTimeout(function(){s=Date.now(),n[r.type](u,function(e){o(e,c)},function(e){_(e)})})}else _("未加载"+r.type+"编码器");else _("音频被释放");else _("未采集到录音")}},h.Recorder&&h.Recorder.Destroy(),(h.Recorder=A).LM="2019-11-7 21:47:48",A.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",A.Traffic=function(){var e=A.TrafficImgUrl;if(e){var t=A.Traffic,a=location.href.replace(/#.*/,"");if(!t[a]){t[a]=1;var s=new Image;s.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder),function(){"use strict";var i;Recorder.prototype.enc_mp3={stable:!0,testmsg:"采样率范围48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000"},Recorder.prototype.mp3=function(a,s,e){var n=this.set,r=a.length,t=this.mp3_start(n);if(t)return this.mp3_encode(t,a),void this.mp3_complete(t,s,e,1);var i=new Recorder.lamejs.Mp3Encoder(1,n.sampleRate,n.bitRate),_=[],o=0,l=0,f=function(){if(o=c.byteLength?(_-=c.byteLength,l.push(c),e.splice(f,1),f--):(e[f]=c.slice(_),o=c,_=0)}if(!this.rm(e,t)){o&&(e[0]=o);for(f=0;f "+s.duration+"ms",2>=1;0!=e--;)n[r++]=i>a[s++]?0:1,n[r++]=i>a[s++]?0:1}function w(e,t,a,s,n,r){var i=(e>>=1)%2;for(e>>=1;0!=e--;){var _,o,l,f,c,h,u,m;_=a[s++]*t,o=a[s++]*t,c=0|_,l=a[s++]*t,h=0|o,f=a[s++]*t,u=0|l,_+=M.adj43[c],m=0|f,o+=M.adj43[h],n[r++]=0|_,l+=M.adj43[u],n[r++]=0|o,f+=M.adj43[m],n[r++]=0|l,n[r++]=0|f}0!=i&&(c=0|(_=a[s++]*t),h=0|(o=a[s++]*t),_+=M.adj43[c],o+=M.adj43[h],n[r++]=0|_,n[r++]=0|o)}var _=[1,2,5,7,7,10,10,13,13,13,13,13,13,13,13];function d(e,t,a,s){var n=function(e,t,a){var s=0,n=0;do{var r=e[t++],i=e[t++];s>=16)&&(_=r,s++),n.bits+=_,s}(e,t,a,_[n-1],s);case 4:case 5:case 6:case 7:case 8:case 9:case 10:case 11:case 12:case 13:case 14:case 15:return function(e,t,a,s,n){var r=0,i=0,_=0,o=j.ht[s].xlen,l=j.ht[s].hlen,f=j.ht[s+1].hlen,c=j.ht[s+2].hlen;do{var h=e[t+0]*o+e[t+1];t+=2,r+=l[h],i+=f[h],_+=c[h]}while(t=n);r++);for(i=r-8;i<24&&!(j.ht[i].linmax>=n);i++);return function(e,t,a,s,n,r){var i,_=65536*j.ht[s].xlen+j.ht[n].xlen,o=0;do{var l=e[t++],f=e[t++];0!=l&&(14>=16)&&(o=i,s=n),r.bits+=o,s}(e,t,a,i,r,s)}}function u(e,t,a,s,n,r,i,_){for(var o=t.big_values,l=2;l>1<<1);for(null!=a&&(a.sfb_count1=0);1t.big_values&&(r=t.big_values),i=t.big_values;else if(t.block_type==Pe.NORM_TYPE){if(r=t.region0_count=e.bv_scf[n-2],i=t.region1_count=e.bv_scf[n-1],i=e.scalefac_band.l[r+i+2],r=e.scalefac_band.l[r+1],ir)return y.LARGE_BITS;if(function(e,t,a,s,n){var r,i,_,o=0,l=0,f=0,c=0,h=t,u=0,m=h,b=0,p=e,v=0;for(_=null!=n&&s.global_gain==n.global_gain,i=s.block_type==Pe.SHORT_TYPE?38:21,r=0;r<=i;r++){var d=-1;if((_||s.block_type==Pe.NORM_TYPE)&&(d=s.global_gain-(s.scalefac[r]+(0!=s.preflag?M.pretab[r]:0)<s.max_nonzero_coeff&&(g=s.max_nonzero_coeff-o+1,xe.fill(t,s.max_nonzero_coeff,576,0),(S=g)<0&&(S=0),r=i+1),0==l&&0==f&&(m=h,b=u,p=e,v=c),null!=n&&0=n.sfb_count1&&0=n.step[r]?(0!=l&&(w(l,a,p,v,m,b),l=0,m=h,b=u,p=e,v=c),f+=S):(0!=f&&(R(f,a,p,v,m,b),f=0,m=h,b=u,p=e,v=c),l+=S),S<=0){0!=f&&(R(f,a,p,v,m,b),f=0),0!=l&&(w(l,a,p,v,m,b),l=0);break}}r<=i&&(u+=s.width[r],c+=s.width[r],o+=s.width[r])}0!=l&&(w(l,a,p,v,m,b),l=0),0!=f&&(R(f,a,p,v,m,b),f=0)}(t,n,M.IPOW20(a.global_gain),a,s),0!=(2&e.substep_shaping))for(var i=0,_=a.global_gain+a.scalefac_scale,o=.634521682242439/M.IPOW20(_),l=0;l=o?n[f]:0}return this.noquant_count_bits(e,a,s)},this.best_huffman_divide=function(e,t){var a=new k,s=t.l3_enc,n=Be(23),r=Be(23),i=Be(23),_=Be(23);if(t.block_type!=Pe.SHORT_TYPE||1!=e.mode_gr){a.assign(t),t.block_type==Pe.NORM_TYPE&&(!function(e,t,a,s,n,r,i){for(var _=t.big_values,o=0;o<=22;o++)s[o]=y.LARGE_BITS;for(o=0;o<16;o++){var l=e.scalefac_band.l[o+1];if(_<=l)break;var f=0,c=new v(f),h=d(a,0,l,c);f=c.bits;for(var u=0;u<8;u++){var m=e.scalefac_band.l[o+u+2];if(_<=m)break;var b=f,p=d(a,l,m,c=new v(b));b=c.bits,s[o+u]>b&&(s[o+u]=b,r[(n[o+u]=o)+u]=h,i[o+u]=p)}}}(e,t,s,n,r,i,_),u(e,a,t,s,n,r,i,_));var o=a.big_values;if(!(0==o||1<(s[o-2]|s[o-1])||576<(o=t.count1+2))){a.assign(t),a.count1=o;for(var l=0,f=0;o>a.big_values;o-=4){var c=2*(2*(2*s[o-4]+s[o-3])+s[o-2])+s[o-1];l+=j.t32l[c],f+=j.t33l[c]}if(a.big_values=o,a.count1table_select=0,fa.part2_3_length&&t.assign(a)}}}};var h=[1,1,1,1,8,2,2,2,4,4,4,8,8,8,16,16],m=[1,2,4,8,1,2,4,8,2,4,8,2,4,8,4,8],b=[0,0,0,0,3,1,1,1,2,2,2,3,3,3,4,4],p=[0,1,2,3,0,1,2,3,1,2,3,1,2,3,2,3];T.slen1_tab=b,T.slen2_tab=p,this.best_scalefac_store=function(e,t,a,s){var n,r,i,_,o=s.tt[t][a],l=0;for(n=i=0;n>=1);o.scalefac_scale=l=1}}if(0==o.preflag&&o.block_type!=Pe.SHORT_TYPE&&2==e.mode_gr){for(n=11;nf&&(s.part2_length=f,s.scalefac_compress=r)}}(a,s),l=0),n=0;ns[t]&&(e.part2_length=s[t],e.scalefac_compress=t);return e.part2_length==y.LARGE_BITS};var g=[[15,15,7,7],[15,15,7,0],[7,3,0,0],[15,31,31,0],[7,7,7,0],[3,3,0,0]];this.scale_bitcount_lsf=function(e,t){var a,s,n,r,i,_,o,l,f=Be(4),c=t.scalefac;for(a=0!=t.preflag?2:0,o=0;o<4;o++)f[o]=0;if(t.block_type==Pe.SHORT_TYPE){s=1;var h=M.nr_of_sfb_block[a][s];for(n=l=0;n<4;n++)for(r=h[n]/3,o=0;of[n]&&(f[n]=c[3*l+i])}else{s=0;h=M.nr_of_sfb_block[a][s];for(n=l=0;n<4;n++)for(r=h[n],o=0;of[n]&&(f[n]=c[l])}for(_=!1,n=0;n<4;n++)f[n]>g[a][n]&&(_=!0);if(!_){var u,m,b,p;for(t.sfb_partition_table=M.nr_of_sfb_block[a][s],n=0;n<4;n++)t.slen[n]=S[f[n]];switch(u=t.slen[0],m=t.slen[1],b=t.slen[2],p=t.slen[3],a){case 0:t.scalefac_compress=(5*u+m<<4)+(b<<2)+p;break;case 1:t.scalefac_compress=400+(5*u+m<<2)+b;break;case 2:t.scalefac_compress=500+3*u+m;break;default:$.err.printf("intensity stereo not implemented yet\n")}}if(!_)for(n=t.part2_length=0;n<4;n++)t.part2_length+=t.slen[n]*t.sfb_partition_table[n];return _};var S=[0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4];this.huffman_init=function(e){for(var t=2;t<=576;t+=2){for(var a,s=0;e.scalefac_band.l[++s]t;)a--;for(a<0&&(a=n[s][0]),e.bv_scf[t-2]=a,a=n[s][1];e.scalefac_band.l[a+e.bv_scf[t-2]+2]>t;)a--;a<0&&(a=n[s][1]),e.bv_scf[t-1]=a}}}function X(){}function M(){this.setModules=function(e,t,a){e,t,a};var _=[0,49345,49537,320,49921,960,640,49729,50689,1728,1920,51009,1280,50625,50305,1088,52225,3264,3456,52545,3840,53185,52865,3648,2560,51905,52097,2880,51457,2496,2176,51265,55297,6336,6528,55617,6912,56257,55937,6720,7680,57025,57217,8e3,56577,7616,7296,56385,5120,54465,54657,5440,55041,6080,5760,54849,53761,4800,4992,54081,4352,53697,53377,4160,61441,12480,12672,61761,13056,62401,62081,12864,13824,63169,63361,14144,62721,13760,13440,62529,15360,64705,64897,15680,65281,16320,16e3,65089,64001,15040,15232,64321,14592,63937,63617,14400,10240,59585,59777,10560,60161,11200,10880,59969,60929,11968,12160,61249,11520,60865,60545,11328,58369,9408,9600,58689,9984,59329,59009,9792,8704,58049,58241,9024,57601,8640,8320,57409,40961,24768,24960,41281,25344,41921,41601,25152,26112,42689,42881,26432,42241,26048,25728,42049,27648,44225,44417,27968,44801,28608,28288,44609,43521,27328,27520,43841,26880,43457,43137,26688,30720,47297,47489,31040,47873,31680,31360,47681,48641,32448,32640,48961,32e3,48577,48257,31808,46081,29888,30080,46401,30464,47041,46721,30272,29184,45761,45953,29504,45313,29120,28800,45121,20480,37057,37249,20800,37633,21440,21120,37441,38401,22208,22400,38721,21760,38337,38017,21568,39937,23744,23936,40257,24320,40897,40577,24128,23040,39617,39809,23360,39169,22976,22656,38977,34817,18624,18816,35137,19200,35777,35457,19008,19968,36545,36737,20288,36097,19904,19584,35905,17408,33985,34177,17728,34561,18368,18048,34369,33281,17088,17280,33601,16640,33217,32897,16448];this.updateMusicCRC=function(e,t,a,s){for(var n=0;n>8^_[255&(i^r)]);var r,i}}function q(){var o=this,s=32773,c=null,h=null,r=null,u=null;this.setModules=function(e,t,a,s){c=e,h=t,r=a,u=s};var m=null,l=0,b=0,p=0;function v(e,t,a){for(;0>a<>a<>3]|=t>>a<<8-(7&s)-n,s+=n}e.header[e.h_ptr].ptr=s}function n(e,t){e<<=8;for(var a=0;a<8;a++)0!=(65536&((t<<=1)^(e<<=1)))&&(t^=s);return t}function d(e,t){var a,s=j.ht[t.count1table_select+32],n=0,r=t.big_values,i=t.big_values;for(a=(t.count1-t.big_values)/4;0t.big_values&&(a=t.big_values);var s=g(e,t.table_select[0],0,a,t);return s+=g(e,t.table_select[1],a,t.big_values,t)}function M(e,t){var a,s,n,r;a=t.big_values;var i=t.region0_count+1;return n=e.scalefac_band.l[i],i+=t.region1_count+1,a>8),t[5]=byte(255&a)},this.flush_bitstream=function(e){var t,a,s=e.internal_flags,n=s.h_ptr-1;if(-1==n&&(n=Z.MAX_HEADER_BUF-1),t=s.l3_side,!((a=w(e,new R))<0)){if(_(e,a),s.ResvSize=0,t.main_data_begin=0,s.findReplayGain){var r=c.GetTitleGain(s.rgdata);s.RadioGain=0|Math.floor(10*r+.5)}s.findPeakSample&&(s.noclipGainChange=0|Math.ceil(20*A(s.PeakSample/32767)*10),0 ResvSize"),8*t.main_data_begin!=a.ResvSize&&($.err.printf("bit reservoir error: \nl3_side.main_data_begin: %d \nResvoir size: %d \nresv drain (post) %d \nresv drain (pre) %d \nheader and sideinfo: %d \ndata bits: %d \ntotal bits: %d (remainder: %d) \nbitsperframe: %d \n",8*t.main_data_begin,a.ResvSize,t.resvDrain_post,t.resvDrain_pre,8*a.sideinfo_len,n-t.resvDrain_post-8*a.sideinfo_len,n,n%8,s),$.err.println("This is a fatal error. It has several possible causes:"),$.err.println("90%% LAME compiled with buggy version of gcc using advanced optimizations"),$.err.println(" 9%% Your system is overclocked"),$.err.println(" 1%% bug in LAME encoding library"),a.ResvSize=8*t.main_data_begin),1e9e.PeakSample?e.PeakSample=o[0][_]:-o[0][_]>e.PeakSample&&(e.PeakSample=-o[0][_]);if(1e.PeakSample?e.PeakSample=o[1][_]:-o[1][_]>e.PeakSample&&(e.PeakSample=-o[1][_])}if(e.findReplayGain&&c.AnalyzeSamples(e.rgdata,o[0],0,o[1],0,f,e.channels_out)==X.GAIN_ANALYSIS_ERROR)return-6}}return r},this.init_bit_stream_w=function(e){m=B(U.LAME_MAXMP3BUFFER),e.h_ptr=e.w_ptr=0,e.header[e.h_ptr].write_timing=0,b=-1,l=p=0}}function e(e,t,a,s){this.xlen=e,this.linmax=t,this.table=a,this.hlen=s}Ee.STEREO=new Ee(0),Ee.JOINT_STEREO=new Ee(1),Ee.DUAL_CHANNEL=new Ee(2),Ee.MONO=new Ee(3),Ee.NOT_SET=new Ee(4),X.STEPS_per_dB=100,X.MAX_dB=120,X.GAIN_NOT_ENOUGH_SAMPLES=-24601,X.GAIN_ANALYSIS_ERROR=0,X.GAIN_ANALYSIS_OK=1,X.INIT_GAIN_ANALYSIS_ERROR=0,X.INIT_GAIN_ANALYSIS_OK=1,X.MAX_ORDER=X.YULE_ORDER=10,X.MAX_SAMPLES_PER_WINDOW=(X.MAX_SAMP_FREQ=48e3)*(X.RMS_WINDOW_TIME_NUMERATOR=1)/(X.RMS_WINDOW_TIME_DENOMINATOR=20)+1,M.NUMTOCENTRIES=100,M.MAXFRAMESIZE=2880,q.EQ=function(e,t){return Math.abs(e)>Math.abs(t)?Math.abs(e-t)<=1e-6*Math.abs(e):Math.abs(e-t)<=1e-6*Math.abs(t)},q.NEQ=function(e,t){return!q.EQ(e,t)};var j={};function F(e){this.bits=e}function x(){this.over_noise=0,this.tot_noise=0,this.max_noise=0,this.over_count=0,this.over_SSD=0,this.bits=0}function r(e,t,a,s){this.l=Be(1+Pe.SBMAX_l),this.s=Be(1+Pe.SBMAX_s),this.psfb21=Be(1+Pe.PSFB21),this.psfb12=Be(1+Pe.PSFB12);var n=this.l,r=this.s;4==arguments.length&&(this.arrL=e,this.arrS=t,this.arr21=a,this.arr12=s,$.arraycopy(this.arrL,0,n,0,Math.min(this.arrL.length,this.l.length)),$.arraycopy(this.arrS,0,r,0,Math.min(this.arrS.length,this.s.length)),$.arraycopy(this.arr21,0,this.psfb21,0,Math.min(this.arr21.length,this.psfb21.length)),$.arraycopy(this.arr12,0,this.psfb12,0,Math.min(this.arr12.length,this.psfb12.length)))}function y(){var l=null,m=null,s=null;this.setModules=function(e,t,a){l=e,m=t,s=a},this.IPOW20=function(e){return u[e]};var k=2.220446049250313e-16,f=y.IXMAX_VAL+2,c=y.Q_MAX,h=y.Q_MAX2,n=100;this.nr_of_sfb_block=[[[6,5,5,5],[9,9,9,9],[6,9,9,9]],[[6,5,7,3],[9,9,12,6],[6,9,12,6]],[[11,10,0,0],[18,18,0,0],[15,18,0,0]],[[7,7,7,0],[12,12,12,0],[6,15,12,0]],[[6,6,6,3],[12,9,9,6],[6,12,9,6]],[[8,8,5,0],[15,12,9,0],[6,18,9,0]]];var R=[0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0];this.pretab=R,this.sfBandIndex=[new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,24,32,42,56,74,100,132,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,114,136,162,194,232,278,332,394,464,540,576],[0,4,8,12,18,26,36,48,62,80,104,136,180,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,44,52,62,74,90,110,134,162,196,238,288,342,418,576],[0,4,8,12,16,22,30,40,52,66,84,106,136,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,42,50,60,72,88,106,128,156,190,230,276,330,384,576],[0,4,8,12,16,22,28,38,50,64,80,100,126,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,44,54,66,82,102,126,156,194,240,296,364,448,550,576],[0,4,8,12,16,22,30,42,58,78,104,138,180,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,12,24,36,48,60,72,88,108,132,160,192,232,280,336,400,476,566,568,570,572,574,576],[0,8,16,24,36,52,72,96,124,160,162,164,166,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0])];var w=Ae(c+h+1),u=Ae(c),b=Ae(f),p=Ae(f);function v(e,t){var a=s.ATHformula(t,e);return a-=n,a=Math.pow(10,a/10+e.ATHlower)}function B(e){this.s=e}this.adj43=p,this.iteration_init=function(e){var t,a=e.internal_flags,s=a.l3_side;if(0==a.iteration_init_init){for(a.iteration_init_init=1,s.main_data_begin=0,function(e){for(var t=e.internal_flags.ATH.l,a=e.internal_flags.ATH.psfb21,s=e.internal_flags.ATH.s,n=e.internal_flags.ATH.psfb12,r=e.internal_flags,i=e.out_samplerate,_=0;_>2&63)&&(t-=64),n=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>8&63)&&(t-=64),r=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>14&63)&&(t-=64),i=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>20&63)&&(t-=64),_=i*Math.pow(10,t/4/10),t=0;t3*s/4&&(f[_]=3*s/4),f[_]<0&&(f[_]=0),f[_]+a[_]>Z.MAX_BITS_PER_CHANNEL&&(f[_]=Math.max(0,Z.MAX_BITS_PER_CHANNEL-a[_])),i+=f[_];if(hZ.MAX_BITS_PER_CHANNEL-e[0]&&(r=Z.MAX_BITS_PER_CHANNEL-e[0]),r<0&&(r=0),125<=e[1]&&(125>1,B=0;do{B+=T=f[_]*f[_],M+=T>1;S=d/p,M=k;do{var T,x;B+=T=f[_]*f[_],M+=Ts[r-3+1]&&(s[r-3+1]+=(s[r-3]-s[r-3+1])*i.decay),s[r-3+1]>s[r-3+2]&&(s[r-3+2]+=(s[r-3+1]-s[r-3+2])*i.decay))}return o},this.calc_noise_core=function(e,t,a,s){var n=0,r=t.s,i=e.l3_enc;if(r>e.count1)for(;0!=a--;){o=e.xr[r],r++,n+=o*o,o=e.xr[r],r++,n+=o*o}else if(r>e.big_values){var _=Ae(2);for(_[0]=0,_[1]=s;0!=a--;){o=Math.abs(e.xr[r])-_[i[r]],r++,n+=o*o,o=Math.abs(e.xr[r])-_[i[r]],r++,n+=o*o}}else for(;0!=a--;){var o;o=Math.abs(e.xr[r])-b[i[r]]*s,r++,n+=o*o,o=Math.abs(e.xr[r])-b[i[r]]*s,r++,n+=o*o}return t.s=r,n},this.calc_noise=function(e,t,a,s,n){var r,i,_=0,o=0,l=0,f=0,c=0,h=-20,u=0,m=e.scalefac,b=0;for(r=s.over_SSD=0;r>1,u+e.width[r]>e.max_nonzero_coeff)i=0<(g=e.max_nonzero_coeff-u+1)?g>>1:0;var M=new B(u);d=this.calc_noise_core(e,M,i,S),u=M.s,null!=n&&(n.step[r]=v,n.noise[r]=d),d=a[_++]=d/t[o++],d=ee.FAST_LOG10(Math.max(d,1e-20)),null!=n&&(n.noise_log[r]=d)}if(null!=n&&(n.global_gain=e.global_gain),c+=d,0a.max_noise-.2&&a.tot_noisea.max_noise-.2&&a.tot_noisea.max_noise-.1&&a.tot_noise+a.over_noisea.max_noise-.15&&a.tot_noise+a.over_noise+a.over_noiset.xrpow_max&&(t.xrpow_max=s[f+c]);if(2==i.noise_shaping_amp)return}}}(e,t,a,s,n);var i=_(t);return!i&&(!(i=2==r.mode_gr?R.scale_bitcount(t):R.scale_bitcount_lsf(r,t))||(1e.xrpow_max&&(e.xrpow_max=t[a+i])}e.scalefac[s]=r>>1}e.preflag=0,e.scalefac_scale=1}(t,s),i=!1):t.block_type==Pe.SHORT_TYPE&&0>t.scalefac_scale))n[s]=f,o+=3*l;else{n[s]=0;var c=210+(f<t.xrpow_max&&(t.xrpow_max=a[o+h]);o+=l*(3-r-1)}}var u=M.IPOW20(202);for(o+=t.width[s]*(r+1),h=-t.width[s];h<0;h++)a[o+h]*=u,a[o+h]>t.xrpow_max&&(t.xrpow_max=a[o+h])}}return!1}(r,t,s)||_(t))),i||(i=2==r.mode_gr?R.scale_bitcount(t):R.scale_bitcount_lsf(r,t)),!i))}this.setModules=function(e,t,a,s){v=e,g=t,this.rv=t,M=a,this.qupvt=a,R=s,n.setModules(M,R)},this.ms_convert=function(e,t){for(var a=0;a<576;++a){var s=e.tt[t][0].xr[a],n=e.tt[t][1].xr[a];e.tt[t][0].xr[a]=(s+n)*(.5*ee.SQRT2),e.tt[t][1].xr[a]=(s-n)*(.5*ee.SQRT2)}},this.init_xrpow=function(e,t,a){var s=0,n=0|t.max_nonzero_coeff;if(t.xrpow_max=0,xe.fill(a,n,576,0),1e-20<(s=function(e,t,a,s){for(var n=s=0;n<=a;++n){var r=Math.abs(e.xr[n]);s+=r,t[n]=Math.sqrt(r*Math.sqrt(r)),t[n]>e.xrpow_max&&(e.xrpow_max=t[n])}return s}(t,a,n,s))){var r=0;0!=(2&e.substep_shaping)&&(r=1);for(var i=0;iS&&_.global_gain<=g;)_.global_gain++;if(_.global_gain>g)break;if(0==f.over_count){for(;(_.part2_3_length=R.count_bits(i,s,_,c))>h&&_.global_gain<=g;)_.global_gain++;if(_.global_gain>g)break}if(M.calc_noise(_,a,l,d,c),d.bits=_.part2_3_length,0!=(B(t.block_type!=Pe.SHORT_TYPE?e.quant_comp:e.quant_comp_short,f,d,_,l)?1:0))h=t.part2_3_length,f=d,t.assign(_),p=0,$.arraycopy(s,0,o,0,576);else if(0==i.full_outer_loop){if(++p>v&&0==f.over_count)break;if(3==i.noise_shaping_amp&&m&&30r[f.VBR_max_bitrate]&&(_[b][v]*=r[f.VBR_max_bitrate],_[b][v]/=u),i[b][v]>_[b][v]&&(i[b][v]=_[b][v]);return h},this.bitpressure_strategy=function(e,t,a,s){for(var n=0;nZ.MAX_BITS_PER_CHANNEL&&(s[_][o]=Z.MAX_BITS_PER_CHANNEL),m+=s[_][o]}if(Z.MAX_BITS_PER_GRANULEZ.MAX_BITS_PER_CHANNEL&&(s[_][o]=Z.MAX_BITS_PER_CHANNEL),l+=s[_][o];if(l>r[0])for(_=0;_=s?(e.ATH.adjust*=.075*s+.925,e.ATH.adjust=s?e.ATH.adjust=s:e.ATH.adjust>1,u=(h=(c=s)<<1)+c,s=h<<1,r=(n=t)+m;M=e[n+0]-e[n+c],S=e[n+0]+e[n+c],A=e[n+h]-e[n+u],w=e[n+h]+e[n+u],e[n+h]=S-w,e[n+0]=S+w,e[n+u]=M-A,e[n+c]=M+A,M=e[r+0]-e[r+c],S=e[r+0]+e[r+c],A=ee.SQRT2*e[r+u],w=ee.SQRT2*e[r+h],e[r+h]=S-w,e[r+0]=S+w,e[r+u]=M-A,e[r+c]=M+A,r+=s,(n+=s)<_;);for(l=x[i+0],o=x[i+1],f=1;fO[u+3-2]?Q/=O[u+3-2]:Q=O[u+3-2]>10*Q?O[u+3-2]/(10*Q):0,D[u+3]=Q}if(e.analysis){var W=D[0];for(u=1;u<12;u++)WV&&(q[u/3]=u%3+1);for(u=1;u<4;u++){(N[u-1]>N[u]?N[u-1]/N[u]:N[u]/N[u-1])<1.7&&(q[u]=0,1==u&&(q[0]=0))}for(0!=q[0]&&0!=S.nsPsy.lastAttacks[c]&&(q[0]=0),3!=S.nsPsy.lastAttacks[c]&&q[0]+q[1]+q[2]+q[3]==0||((Y=0)!=q[1]&&0!=q[0]&&(q[1]=0),0!=q[2]&&0!=q[1]&&(q[2]=0),0!=q[3]&&0!=q[2]&&(q[3]=0)),c<2?x[c]=Y:0==Y&&(x[0]=x[1]=0),o[c]=S.tot_ener[c],he(e,j,F,M,1&c,R,1&c,s,c,t,a),Me(S,j,w,C,X),Re(S,C,X,y),v=0;v<3;v++){var J,$;for(ve(e,F,B,A,c,v),be(S,B,A,c,v),p=0;p1.58*e.thm[1].l[t]||e.thm[1].l[t]>1.58*e.thm[0].l[t])){var a=e.mld_l[t]*e.en[3].l[t],s=Math.max(e.thm[2].l[t],Math.min(e.thm[3].l[t],a));a=e.mld_l[t]*e.en[2].l[t];var n=Math.max(e.thm[3].l[t],Math.min(e.thm[2].l[t],a));e.thm[2].l[t]=s,e.thm[3].l[t]=n}for(t=0;t1.58*e.thm[1].s[t][r]||e.thm[1].s[t][r]>1.58*e.thm[0].s[t][r]||(a=e.mld_s[t]*e.en[3].s[t][r],s=Math.max(e.thm[2].s[t][r],Math.min(e.thm[3].s[t][r],a)),a=e.mld_s[t]*e.en[2].s[t][r],n=Math.max(e.thm[3].s[t][r],Math.min(e.thm[2].s[t][r],a)),e.thm[2].s[t][r]=s,e.thm[3].s[t][r]=n)}(S),g=e.msfix,0g&&(s[o]=g),1a[o]&&(s[o]=a[o]),l.masking_lower<1&&(s[o]*=l.masking_lower)}for(;of&&(s[r]=f),1a[r]&&(s[r]=a[r]),e.masking_lower<1&&(s[r]*=e.masking_lower)}for(;rM[p+3-2]?E/=M[p+3-2]:E=M[p+3-2]>10*E?M[p+3-2]/(10*E):0,S[p+3]=E}for(p=0;p<3;++p){var P=M[3*p+3]+M[3*p+4]+M[3*p+5],H=1;6*M[3*p+5]A&&(o[m][p/3]=p%3+1);for(p=1;p<4;p++){var L=R[p-1],V=R[p];Math.max(L,V)<4e4&&L<1.7*V&&V<1.7*L&&(1==p&&o[m][0]<=o[m][p]&&(o[m][0]=0),o[m][p]=0)}o[m][0]<=c.nsPsy.lastAttacks[m]&&(o[m][0]=0),3!=c.nsPsy.lastAttacks[m]&&o[m][0]+o[m][1]+o[m][2]+o[m][3]==0||((T=0)!=o[m][1]&&0!=o[m][0]&&(o[m][1]=0),0!=o[m][2]&&0!=o[m][1]&&(o[m][2]=0),0!=o[m][3]&&0!=o[m][2]&&(o[m][3]=0)),m<2?l[m]=T:0==T&&(l[0]=l[1]=0),i[m]=c.tot_ener[m]}}(e,t,a,s,n,r,o,R,w,B),function(e,t){var a=e.internal_flags;e.short_blocks!=ke.short_block_coupled||0!=t[0]&&0!=t[1]||(t[0]=t[1]=0);for(var s=0;s=n&&(b=i*(l[t]-n)/(24-n)+r*(24-l[t])/(24-n)),c[t]=Math.pow(10,b/10),0=n&&(b=o*(l[t]-n)/(24-n)+_*(24-l[t])/(24-n)),c[t]=Math.pow(10,b/10),g=K.MAX_VALUE;for(v=0;va.npart_l-1&&(a.s3ind[M][1]=a.npart_l-1);var R=576*a.mode_gr/h;if(a.ATH.decay=Math.pow(10,-1.2*R),a.ATH.adjust=.01,-(a.ATH.adjustLimit=1)!=e.ATHtype){var w=e.out_samplerate/Pe.BLKSIZE,B=0;for(t=d=0;t=v)for(l=0;le.in_samplerate&&(e.lowpassfreq=e.in_samplerate/2),e.out_samplerate=(t=0|e.lowpassfreq,a=e.in_samplerate,s=44100,48e3<=a?s=48e3:44100<=a?s=44100:32e3<=a?s=32e3:24e3<=a?s=24e3:22050<=a?s=22050:16e3<=a?s=16e3:12e3<=a?s=12e3:11025<=a?s=11025:8e3<=a&&(s=8e3),-1==t?s:(t<=15960&&(s=44100),t<=15250&&(s=32e3),t<=11220&&(s=24e3),t<=9970&&(s=22050),t<=7230&&(s=16e3),t<=5420&&(s=12e3),t<=4510&&(s=11025),t<=3970&&(s=8e3),a=t.lowpass2&&(a=Math.min(a,r)),t.lowpass1t.highpass1?E((t.highpass2-l)/(t.highpass2-t.highpass1+1e-20)):1,o=t.lowpass2>t.lowpass1?E((l-t.lowpass1)/(t.lowpass2-t.lowpass1+1e-20)):1,t.amp_filter[r]=_*o}}(e),n.samplerate_index=P(e.out_samplerate,e),n.samplerate_index<0)return e.internal_flags=null,-1;if(e.VBR==ye.vbr_off){if(e.free_format)n.bitrate_index=0;else if(e.brate=H(e.brate,e.version,e.out_samplerate),n.bitrate_index=I(e.brate,e.version,e.out_samplerate),n.bitrate_index<=0)return e.internal_flags=null,-1}else n.bitrate_index=1;e.analysis&&(e.bWriteVbrTag=!1),null!=n.pinfo&&(e.bWriteVbrTag=!1),w.init_bit_stream_w(n);for(var c,h,u,m=n.samplerate_index+3*e.version+6*(e.out_samplerate<16e3?1:0),b=0;b=f){var M=i-u;if(0==i&&(M=0),(_=O(e,m[0],m[1],n,r,M))<0)return _;for(r+=_,u+=_,h.mf_size-=e.framesize,h.mf_samples_to_encode-=e.framesize,l=0;li&&(s.ResvMax=i),(s.ResvMax<0||e.disable_reservoir)&&(s.ResvMax=0);var _=t.bits*s.mode_gr+Math.min(s.ResvSize,s.ResvMax);return a<_&&(_=a),n.resvDrain_pre=0,null!=s.pinfo&&(s.pinfo.mean_bits=t.bits/2,s.pinfo.resvsize=s.ResvSize),_},this.ResvMaxBits=function(e,t,a,s){var n,r=e.internal_flags,i=r.ResvSize,_=r.ResvMax;0!=s&&(i+=t),0!=(1&r.substep_shaping)&&(_*=.9),a.bits=t,9*_<10*i?(n=i-9*_/10,a.bits+=n,r.substep_shaping|=128):(n=0,r.substep_shaping&=127,e.disable_reservoir||0!=(1&r.substep_shaping)||(a.bits-=.1*t));var o=i<6*r.ResvMax/10?i:6*r.ResvMax/10;return(o-=n)<0&&(o=0),o},this.ResvAdjust=function(e,t){e.ResvSize-=t.part2_3_length+t.part2_length},this.ResvFrameEnd=function(e,t){var a,s=e.l3_side;e.ResvSize+=t*e.mode_gr;var n=0;s.resvDrain_post=0,(s.resvDrain_pre=0)!=(a=e.ResvSize%8)&&(n+=a),0<(a=e.ResvSize-n-e.ResvMax)&&(n+=a);var r=Math.min(8*s.main_data_begin,n)/8;s.resvDrain_pre+=8*r,n-=8*r,e.ResvSize-=8*r,s.main_data_begin-=r,s.resvDrain_post+=n,e.ResvSize-=n}},m=new T,b=new function(){this.setModules=function(e,t,a){}},p=new function(){};n.setModules(r,i,_,o,l,f,c,h,p),i.setModules(r,p,c,f),h.setModules(i,c),_.setModules(n),l.setModules(i,u,o,m),o.setModules(m,u,n.enc.psy),u.setModules(i),m.setModules(o),f.setModules(n,i,c),a.setModules(b,p),b.setModules(c,h,_);var v=n.lame_init();v.num_channels=s,v.in_samplerate=e,v.out_samplerate=e,v.brate=t,v.mode=Ee.STEREO,v.quality=3,v.bWriteVbrTag=!1,v.disable_reservoir=!0,v.write_id3tag_automatic=!1,n.lame_init_params(v);var d=1152,g=0|1.25*d+7200,S=B(g);this.encodeBuffer=function(e,t){1==s&&(t=e),e.length>d&&(d=e.length,S=B(g=0|1.25*d+7200));var a=n.lame_encode_buffer(v,e,t,e.length,S,0,g);return new Int8Array(S.subarray(0,a))},this.flush=function(){var e=n.lame_encode_flush(v,S,0,g);return new Int8Array(S.subarray(0,e))}}}t(),Recorder.lamejs=t}(); \ No newline at end of file +!function(h){"use strict";var u=function(){},E=function(e){return new t(e)};E.IsOpen=function(){var e=E.Stream;if(e){var t=e.getTracks&&e.getTracks()||e.audioTracks||[],a=t[0];if(a){var s=a.readyState;return"live"==s||s==a.LIVE}}return!1},E.BufferSize=4096,E.Destroy=function(){for(var e in console.log("Recorder Destroy"),a)a[e]()};var a={};E.BindDestroy=function(e,t){a[e]=t},E.Support=function(){var e=h.AudioContext;if(e||(e=h.webkitAudioContext),!e)return!1;var t=navigator.mediaDevices||{};return t.getUserMedia||(t=navigator).getUserMedia||(t.getUserMedia=t.webkitGetUserMedia||t.mozGetUserMedia||t.msGetUserMedia),!!t.getUserMedia&&(E.Scope=t,E.Ctx&&"closed"!=E.Ctx.state||(E.Ctx=new e,E.BindDestroy("Ctx",function(){var e=E.Ctx;e&&e.close&&e.close()})),!0)},E.SampleData=function(e,t,a,s,n){s||(s={});var r=s.index||0,i=s.offset||0,_=s.frameNext||[];n||(n={});var o=n.frameSize||1;n.frameType&&(o="mp3"==n.frameType?1152:1);for(var l=0,f=r;f"+u.length+" 花:"+(Date.now()-s)+"ms"),setTimeout(function(){s=Date.now(),n[r.type](u,function(e){o(e,c)},function(e){_(e)})})}else _("未加载"+r.type+"编码器");else _("音频被释放");else _("未采集到录音")}},h.Recorder&&h.Recorder.Destroy(),(h.Recorder=E).LM="2020-1-8 10:53:14",E.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",E.Traffic=function(){var e=E.TrafficImgUrl;if(e){var t=E.Traffic,a=location.href.replace(/#.*/,"");if(!t[a]){t[a]=1;var s=new Image;s.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder),function(){"use strict";var i;Recorder.prototype.enc_mp3={stable:!0,testmsg:"采样率范围48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000"},Recorder.prototype.mp3=function(a,s,e){var n=this.set,r=a.length,t=this.mp3_start(n);if(t)return this.mp3_encode(t,a),void this.mp3_complete(t,s,e,1);var i=new Recorder.lamejs.Mp3Encoder(1,n.sampleRate,n.bitRate),_=[],o=0,l=0,f=function(){if(o=c.byteLength?(_-=c.byteLength,l.push(c),e.splice(f,1),f--):(e[f]=c.slice(_),o=c,_=0)}if(!this.rm(e,t)){o&&(e[0]=o);for(f=0;f "+s.duration+"ms",2>=1;0!=e--;)n[r++]=i>a[s++]?0:1,n[r++]=i>a[s++]?0:1}function w(e,t,a,s,n,r){var i=(e>>=1)%2;for(e>>=1;0!=e--;){var _,o,l,f,c,h,u,m;_=a[s++]*t,o=a[s++]*t,c=0|_,l=a[s++]*t,h=0|o,f=a[s++]*t,u=0|l,_+=M.adj43[c],m=0|f,o+=M.adj43[h],n[r++]=0|_,l+=M.adj43[u],n[r++]=0|o,f+=M.adj43[m],n[r++]=0|l,n[r++]=0|f}0!=i&&(c=0|(_=a[s++]*t),h=0|(o=a[s++]*t),_+=M.adj43[c],o+=M.adj43[h],n[r++]=0|_,n[r++]=0|o)}var _=[1,2,5,7,7,10,10,13,13,13,13,13,13,13,13];function d(e,t,a,s){var n=function(e,t,a){var s=0,n=0;do{var r=e[t++],i=e[t++];s>=16)&&(_=r,s++),n.bits+=_,s}(e,t,a,_[n-1],s);case 4:case 5:case 6:case 7:case 8:case 9:case 10:case 11:case 12:case 13:case 14:case 15:return function(e,t,a,s,n){var r=0,i=0,_=0,o=j.ht[s].xlen,l=j.ht[s].hlen,f=j.ht[s+1].hlen,c=j.ht[s+2].hlen;do{var h=e[t+0]*o+e[t+1];t+=2,r+=l[h],i+=f[h],_+=c[h]}while(t=n);r++);for(i=r-8;i<24&&!(j.ht[i].linmax>=n);i++);return function(e,t,a,s,n,r){var i,_=65536*j.ht[s].xlen+j.ht[n].xlen,o=0;do{var l=e[t++],f=e[t++];0!=l&&(14>=16)&&(o=i,s=n),r.bits+=o,s}(e,t,a,i,r,s)}}function u(e,t,a,s,n,r,i,_){for(var o=t.big_values,l=2;l>1<<1);for(null!=a&&(a.sfb_count1=0);1t.big_values&&(r=t.big_values),i=t.big_values;else if(t.block_type==Pe.NORM_TYPE){if(r=t.region0_count=e.bv_scf[n-2],i=t.region1_count=e.bv_scf[n-1],i=e.scalefac_band.l[r+i+2],r=e.scalefac_band.l[r+1],ir)return y.LARGE_BITS;if(function(e,t,a,s,n){var r,i,_,o=0,l=0,f=0,c=0,h=t,u=0,m=h,b=0,p=e,v=0;for(_=null!=n&&s.global_gain==n.global_gain,i=s.block_type==Pe.SHORT_TYPE?38:21,r=0;r<=i;r++){var d=-1;if((_||s.block_type==Pe.NORM_TYPE)&&(d=s.global_gain-(s.scalefac[r]+(0!=s.preflag?M.pretab[r]:0)<s.max_nonzero_coeff&&(g=s.max_nonzero_coeff-o+1,xe.fill(t,s.max_nonzero_coeff,576,0),(S=g)<0&&(S=0),r=i+1),0==l&&0==f&&(m=h,b=u,p=e,v=c),null!=n&&0=n.sfb_count1&&0=n.step[r]?(0!=l&&(w(l,a,p,v,m,b),l=0,m=h,b=u,p=e,v=c),f+=S):(0!=f&&(R(f,a,p,v,m,b),f=0,m=h,b=u,p=e,v=c),l+=S),S<=0){0!=f&&(R(f,a,p,v,m,b),f=0),0!=l&&(w(l,a,p,v,m,b),l=0);break}}r<=i&&(u+=s.width[r],c+=s.width[r],o+=s.width[r])}0!=l&&(w(l,a,p,v,m,b),l=0),0!=f&&(R(f,a,p,v,m,b),f=0)}(t,n,M.IPOW20(a.global_gain),a,s),0!=(2&e.substep_shaping))for(var i=0,_=a.global_gain+a.scalefac_scale,o=.634521682242439/M.IPOW20(_),l=0;l=o?n[f]:0}return this.noquant_count_bits(e,a,s)},this.best_huffman_divide=function(e,t){var a=new k,s=t.l3_enc,n=Be(23),r=Be(23),i=Be(23),_=Be(23);if(t.block_type!=Pe.SHORT_TYPE||1!=e.mode_gr){a.assign(t),t.block_type==Pe.NORM_TYPE&&(!function(e,t,a,s,n,r,i){for(var _=t.big_values,o=0;o<=22;o++)s[o]=y.LARGE_BITS;for(o=0;o<16;o++){var l=e.scalefac_band.l[o+1];if(_<=l)break;var f=0,c=new v(f),h=d(a,0,l,c);f=c.bits;for(var u=0;u<8;u++){var m=e.scalefac_band.l[o+u+2];if(_<=m)break;var b=f,p=d(a,l,m,c=new v(b));b=c.bits,s[o+u]>b&&(s[o+u]=b,r[(n[o+u]=o)+u]=h,i[o+u]=p)}}}(e,t,s,n,r,i,_),u(e,a,t,s,n,r,i,_));var o=a.big_values;if(!(0==o||1<(s[o-2]|s[o-1])||576<(o=t.count1+2))){a.assign(t),a.count1=o;for(var l=0,f=0;o>a.big_values;o-=4){var c=2*(2*(2*s[o-4]+s[o-3])+s[o-2])+s[o-1];l+=j.t32l[c],f+=j.t33l[c]}if(a.big_values=o,a.count1table_select=0,fa.part2_3_length&&t.assign(a)}}}};var h=[1,1,1,1,8,2,2,2,4,4,4,8,8,8,16,16],m=[1,2,4,8,1,2,4,8,2,4,8,2,4,8,4,8],b=[0,0,0,0,3,1,1,1,2,2,2,3,3,3,4,4],p=[0,1,2,3,0,1,2,3,1,2,3,1,2,3,2,3];T.slen1_tab=b,T.slen2_tab=p,this.best_scalefac_store=function(e,t,a,s){var n,r,i,_,o=s.tt[t][a],l=0;for(n=i=0;n>=1);o.scalefac_scale=l=1}}if(0==o.preflag&&o.block_type!=Pe.SHORT_TYPE&&2==e.mode_gr){for(n=11;nf&&(s.part2_length=f,s.scalefac_compress=r)}}(a,s),l=0),n=0;ns[t]&&(e.part2_length=s[t],e.scalefac_compress=t);return e.part2_length==y.LARGE_BITS};var g=[[15,15,7,7],[15,15,7,0],[7,3,0,0],[15,31,31,0],[7,7,7,0],[3,3,0,0]];this.scale_bitcount_lsf=function(e,t){var a,s,n,r,i,_,o,l,f=Be(4),c=t.scalefac;for(a=0!=t.preflag?2:0,o=0;o<4;o++)f[o]=0;if(t.block_type==Pe.SHORT_TYPE){s=1;var h=M.nr_of_sfb_block[a][s];for(n=l=0;n<4;n++)for(r=h[n]/3,o=0;of[n]&&(f[n]=c[3*l+i])}else{s=0;h=M.nr_of_sfb_block[a][s];for(n=l=0;n<4;n++)for(r=h[n],o=0;of[n]&&(f[n]=c[l])}for(_=!1,n=0;n<4;n++)f[n]>g[a][n]&&(_=!0);if(!_){var u,m,b,p;for(t.sfb_partition_table=M.nr_of_sfb_block[a][s],n=0;n<4;n++)t.slen[n]=S[f[n]];switch(u=t.slen[0],m=t.slen[1],b=t.slen[2],p=t.slen[3],a){case 0:t.scalefac_compress=(5*u+m<<4)+(b<<2)+p;break;case 1:t.scalefac_compress=400+(5*u+m<<2)+b;break;case 2:t.scalefac_compress=500+3*u+m;break;default:$.err.printf("intensity stereo not implemented yet\n")}}if(!_)for(n=t.part2_length=0;n<4;n++)t.part2_length+=t.slen[n]*t.sfb_partition_table[n];return _};var S=[0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4];this.huffman_init=function(e){for(var t=2;t<=576;t+=2){for(var a,s=0;e.scalefac_band.l[++s]t;)a--;for(a<0&&(a=n[s][0]),e.bv_scf[t-2]=a,a=n[s][1];e.scalefac_band.l[a+e.bv_scf[t-2]+2]>t;)a--;a<0&&(a=n[s][1]),e.bv_scf[t-1]=a}}}function X(){}function M(){this.setModules=function(e,t,a){e,t,a};var _=[0,49345,49537,320,49921,960,640,49729,50689,1728,1920,51009,1280,50625,50305,1088,52225,3264,3456,52545,3840,53185,52865,3648,2560,51905,52097,2880,51457,2496,2176,51265,55297,6336,6528,55617,6912,56257,55937,6720,7680,57025,57217,8e3,56577,7616,7296,56385,5120,54465,54657,5440,55041,6080,5760,54849,53761,4800,4992,54081,4352,53697,53377,4160,61441,12480,12672,61761,13056,62401,62081,12864,13824,63169,63361,14144,62721,13760,13440,62529,15360,64705,64897,15680,65281,16320,16e3,65089,64001,15040,15232,64321,14592,63937,63617,14400,10240,59585,59777,10560,60161,11200,10880,59969,60929,11968,12160,61249,11520,60865,60545,11328,58369,9408,9600,58689,9984,59329,59009,9792,8704,58049,58241,9024,57601,8640,8320,57409,40961,24768,24960,41281,25344,41921,41601,25152,26112,42689,42881,26432,42241,26048,25728,42049,27648,44225,44417,27968,44801,28608,28288,44609,43521,27328,27520,43841,26880,43457,43137,26688,30720,47297,47489,31040,47873,31680,31360,47681,48641,32448,32640,48961,32e3,48577,48257,31808,46081,29888,30080,46401,30464,47041,46721,30272,29184,45761,45953,29504,45313,29120,28800,45121,20480,37057,37249,20800,37633,21440,21120,37441,38401,22208,22400,38721,21760,38337,38017,21568,39937,23744,23936,40257,24320,40897,40577,24128,23040,39617,39809,23360,39169,22976,22656,38977,34817,18624,18816,35137,19200,35777,35457,19008,19968,36545,36737,20288,36097,19904,19584,35905,17408,33985,34177,17728,34561,18368,18048,34369,33281,17088,17280,33601,16640,33217,32897,16448];this.updateMusicCRC=function(e,t,a,s){for(var n=0;n>8^_[255&(i^r)]);var r,i}}function q(){var o=this,s=32773,c=null,h=null,r=null,u=null;this.setModules=function(e,t,a,s){c=e,h=t,r=a,u=s};var m=null,l=0,b=0,p=0;function v(e,t,a){for(;0>a<>a<>3]|=t>>a<<8-(7&s)-n,s+=n}e.header[e.h_ptr].ptr=s}function n(e,t){e<<=8;for(var a=0;a<8;a++)0!=(65536&((t<<=1)^(e<<=1)))&&(t^=s);return t}function d(e,t){var a,s=j.ht[t.count1table_select+32],n=0,r=t.big_values,i=t.big_values;for(a=(t.count1-t.big_values)/4;0t.big_values&&(a=t.big_values);var s=g(e,t.table_select[0],0,a,t);return s+=g(e,t.table_select[1],a,t.big_values,t)}function M(e,t){var a,s,n,r;a=t.big_values;var i=t.region0_count+1;return n=e.scalefac_band.l[i],i+=t.region1_count+1,a>8),t[5]=byte(255&a)},this.flush_bitstream=function(e){var t,a,s=e.internal_flags,n=s.h_ptr-1;if(-1==n&&(n=Z.MAX_HEADER_BUF-1),t=s.l3_side,!((a=w(e,new R))<0)){if(_(e,a),s.ResvSize=0,t.main_data_begin=0,s.findReplayGain){var r=c.GetTitleGain(s.rgdata);s.RadioGain=0|Math.floor(10*r+.5)}s.findPeakSample&&(s.noclipGainChange=0|Math.ceil(20*A(s.PeakSample/32767)*10),0 ResvSize"),8*t.main_data_begin!=a.ResvSize&&($.err.printf("bit reservoir error: \nl3_side.main_data_begin: %d \nResvoir size: %d \nresv drain (post) %d \nresv drain (pre) %d \nheader and sideinfo: %d \ndata bits: %d \ntotal bits: %d (remainder: %d) \nbitsperframe: %d \n",8*t.main_data_begin,a.ResvSize,t.resvDrain_post,t.resvDrain_pre,8*a.sideinfo_len,n-t.resvDrain_post-8*a.sideinfo_len,n,n%8,s),$.err.println("This is a fatal error. It has several possible causes:"),$.err.println("90%% LAME compiled with buggy version of gcc using advanced optimizations"),$.err.println(" 9%% Your system is overclocked"),$.err.println(" 1%% bug in LAME encoding library"),a.ResvSize=8*t.main_data_begin),1e9e.PeakSample?e.PeakSample=o[0][_]:-o[0][_]>e.PeakSample&&(e.PeakSample=-o[0][_]);if(1e.PeakSample?e.PeakSample=o[1][_]:-o[1][_]>e.PeakSample&&(e.PeakSample=-o[1][_])}if(e.findReplayGain&&c.AnalyzeSamples(e.rgdata,o[0],0,o[1],0,f,e.channels_out)==X.GAIN_ANALYSIS_ERROR)return-6}}return r},this.init_bit_stream_w=function(e){m=B(U.LAME_MAXMP3BUFFER),e.h_ptr=e.w_ptr=0,e.header[e.h_ptr].write_timing=0,b=-1,l=p=0}}function e(e,t,a,s){this.xlen=e,this.linmax=t,this.table=a,this.hlen=s}Ee.STEREO=new Ee(0),Ee.JOINT_STEREO=new Ee(1),Ee.DUAL_CHANNEL=new Ee(2),Ee.MONO=new Ee(3),Ee.NOT_SET=new Ee(4),X.STEPS_per_dB=100,X.MAX_dB=120,X.GAIN_NOT_ENOUGH_SAMPLES=-24601,X.GAIN_ANALYSIS_ERROR=0,X.GAIN_ANALYSIS_OK=1,X.INIT_GAIN_ANALYSIS_ERROR=0,X.INIT_GAIN_ANALYSIS_OK=1,X.MAX_ORDER=X.YULE_ORDER=10,X.MAX_SAMPLES_PER_WINDOW=(X.MAX_SAMP_FREQ=48e3)*(X.RMS_WINDOW_TIME_NUMERATOR=1)/(X.RMS_WINDOW_TIME_DENOMINATOR=20)+1,M.NUMTOCENTRIES=100,M.MAXFRAMESIZE=2880,q.EQ=function(e,t){return Math.abs(e)>Math.abs(t)?Math.abs(e-t)<=1e-6*Math.abs(e):Math.abs(e-t)<=1e-6*Math.abs(t)},q.NEQ=function(e,t){return!q.EQ(e,t)};var j={};function F(e){this.bits=e}function x(){this.over_noise=0,this.tot_noise=0,this.max_noise=0,this.over_count=0,this.over_SSD=0,this.bits=0}function r(e,t,a,s){this.l=Be(1+Pe.SBMAX_l),this.s=Be(1+Pe.SBMAX_s),this.psfb21=Be(1+Pe.PSFB21),this.psfb12=Be(1+Pe.PSFB12);var n=this.l,r=this.s;4==arguments.length&&(this.arrL=e,this.arrS=t,this.arr21=a,this.arr12=s,$.arraycopy(this.arrL,0,n,0,Math.min(this.arrL.length,this.l.length)),$.arraycopy(this.arrS,0,r,0,Math.min(this.arrS.length,this.s.length)),$.arraycopy(this.arr21,0,this.psfb21,0,Math.min(this.arr21.length,this.psfb21.length)),$.arraycopy(this.arr12,0,this.psfb12,0,Math.min(this.arr12.length,this.psfb12.length)))}function y(){var l=null,m=null,s=null;this.setModules=function(e,t,a){l=e,m=t,s=a},this.IPOW20=function(e){return u[e]};var k=2.220446049250313e-16,f=y.IXMAX_VAL+2,c=y.Q_MAX,h=y.Q_MAX2,n=100;this.nr_of_sfb_block=[[[6,5,5,5],[9,9,9,9],[6,9,9,9]],[[6,5,7,3],[9,9,12,6],[6,9,12,6]],[[11,10,0,0],[18,18,0,0],[15,18,0,0]],[[7,7,7,0],[12,12,12,0],[6,15,12,0]],[[6,6,6,3],[12,9,9,6],[6,12,9,6]],[[8,8,5,0],[15,12,9,0],[6,18,9,0]]];var R=[0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0];this.pretab=R,this.sfBandIndex=[new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,24,32,42,56,74,100,132,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,114,136,162,194,232,278,332,394,464,540,576],[0,4,8,12,18,26,36,48,62,80,104,136,180,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,44,52,62,74,90,110,134,162,196,238,288,342,418,576],[0,4,8,12,16,22,30,40,52,66,84,106,136,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,42,50,60,72,88,106,128,156,190,230,276,330,384,576],[0,4,8,12,16,22,28,38,50,64,80,100,126,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,4,8,12,16,20,24,30,36,44,54,66,82,102,126,156,194,240,296,364,448,550,576],[0,4,8,12,16,22,30,42,58,78,104,138,180,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576],[0,4,8,12,18,26,36,48,62,80,104,134,174,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]),new r([0,12,24,36,48,60,72,88,108,132,160,192,232,280,336,400,476,566,568,570,572,574,576],[0,8,16,24,36,52,72,96,124,160,162,164,166,192],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0])];var w=Ae(c+h+1),u=Ae(c),b=Ae(f),p=Ae(f);function v(e,t){var a=s.ATHformula(t,e);return a-=n,a=Math.pow(10,a/10+e.ATHlower)}function B(e){this.s=e}this.adj43=p,this.iteration_init=function(e){var t,a=e.internal_flags,s=a.l3_side;if(0==a.iteration_init_init){for(a.iteration_init_init=1,s.main_data_begin=0,function(e){for(var t=e.internal_flags.ATH.l,a=e.internal_flags.ATH.psfb21,s=e.internal_flags.ATH.s,n=e.internal_flags.ATH.psfb12,r=e.internal_flags,i=e.out_samplerate,_=0;_>2&63)&&(t-=64),n=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>8&63)&&(t-=64),r=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>14&63)&&(t-=64),i=Math.pow(10,t/4/10),32<=(t=e.exp_nspsytune>>20&63)&&(t-=64),_=i*Math.pow(10,t/4/10),t=0;t3*s/4&&(f[_]=3*s/4),f[_]<0&&(f[_]=0),f[_]+a[_]>Z.MAX_BITS_PER_CHANNEL&&(f[_]=Math.max(0,Z.MAX_BITS_PER_CHANNEL-a[_])),i+=f[_];if(hZ.MAX_BITS_PER_CHANNEL-e[0]&&(r=Z.MAX_BITS_PER_CHANNEL-e[0]),r<0&&(r=0),125<=e[1]&&(125>1,B=0;do{B+=T=f[_]*f[_],M+=T>1;S=d/p,M=k;do{var T,x;B+=T=f[_]*f[_],M+=Ts[r-3+1]&&(s[r-3+1]+=(s[r-3]-s[r-3+1])*i.decay),s[r-3+1]>s[r-3+2]&&(s[r-3+2]+=(s[r-3+1]-s[r-3+2])*i.decay))}return o},this.calc_noise_core=function(e,t,a,s){var n=0,r=t.s,i=e.l3_enc;if(r>e.count1)for(;0!=a--;){o=e.xr[r],r++,n+=o*o,o=e.xr[r],r++,n+=o*o}else if(r>e.big_values){var _=Ae(2);for(_[0]=0,_[1]=s;0!=a--;){o=Math.abs(e.xr[r])-_[i[r]],r++,n+=o*o,o=Math.abs(e.xr[r])-_[i[r]],r++,n+=o*o}}else for(;0!=a--;){var o;o=Math.abs(e.xr[r])-b[i[r]]*s,r++,n+=o*o,o=Math.abs(e.xr[r])-b[i[r]]*s,r++,n+=o*o}return t.s=r,n},this.calc_noise=function(e,t,a,s,n){var r,i,_=0,o=0,l=0,f=0,c=0,h=-20,u=0,m=e.scalefac,b=0;for(r=s.over_SSD=0;r>1,u+e.width[r]>e.max_nonzero_coeff)i=0<(g=e.max_nonzero_coeff-u+1)?g>>1:0;var M=new B(u);d=this.calc_noise_core(e,M,i,S),u=M.s,null!=n&&(n.step[r]=v,n.noise[r]=d),d=a[_++]=d/t[o++],d=ee.FAST_LOG10(Math.max(d,1e-20)),null!=n&&(n.noise_log[r]=d)}if(null!=n&&(n.global_gain=e.global_gain),c+=d,0a.max_noise-.2&&a.tot_noisea.max_noise-.2&&a.tot_noisea.max_noise-.1&&a.tot_noise+a.over_noisea.max_noise-.15&&a.tot_noise+a.over_noise+a.over_noiset.xrpow_max&&(t.xrpow_max=s[f+c]);if(2==i.noise_shaping_amp)return}}}(e,t,a,s,n);var i=_(t);return!i&&(!(i=2==r.mode_gr?R.scale_bitcount(t):R.scale_bitcount_lsf(r,t))||(1e.xrpow_max&&(e.xrpow_max=t[a+i])}e.scalefac[s]=r>>1}e.preflag=0,e.scalefac_scale=1}(t,s),i=!1):t.block_type==Pe.SHORT_TYPE&&0>t.scalefac_scale))n[s]=f,o+=3*l;else{n[s]=0;var c=210+(f<t.xrpow_max&&(t.xrpow_max=a[o+h]);o+=l*(3-r-1)}}var u=M.IPOW20(202);for(o+=t.width[s]*(r+1),h=-t.width[s];h<0;h++)a[o+h]*=u,a[o+h]>t.xrpow_max&&(t.xrpow_max=a[o+h])}}return!1}(r,t,s)||_(t))),i||(i=2==r.mode_gr?R.scale_bitcount(t):R.scale_bitcount_lsf(r,t)),!i))}this.setModules=function(e,t,a,s){v=e,g=t,this.rv=t,M=a,this.qupvt=a,R=s,n.setModules(M,R)},this.ms_convert=function(e,t){for(var a=0;a<576;++a){var s=e.tt[t][0].xr[a],n=e.tt[t][1].xr[a];e.tt[t][0].xr[a]=(s+n)*(.5*ee.SQRT2),e.tt[t][1].xr[a]=(s-n)*(.5*ee.SQRT2)}},this.init_xrpow=function(e,t,a){var s=0,n=0|t.max_nonzero_coeff;if(t.xrpow_max=0,xe.fill(a,n,576,0),1e-20<(s=function(e,t,a,s){for(var n=s=0;n<=a;++n){var r=Math.abs(e.xr[n]);s+=r,t[n]=Math.sqrt(r*Math.sqrt(r)),t[n]>e.xrpow_max&&(e.xrpow_max=t[n])}return s}(t,a,n,s))){var r=0;0!=(2&e.substep_shaping)&&(r=1);for(var i=0;iS&&_.global_gain<=g;)_.global_gain++;if(_.global_gain>g)break;if(0==f.over_count){for(;(_.part2_3_length=R.count_bits(i,s,_,c))>h&&_.global_gain<=g;)_.global_gain++;if(_.global_gain>g)break}if(M.calc_noise(_,a,l,d,c),d.bits=_.part2_3_length,0!=(B(t.block_type!=Pe.SHORT_TYPE?e.quant_comp:e.quant_comp_short,f,d,_,l)?1:0))h=t.part2_3_length,f=d,t.assign(_),p=0,$.arraycopy(s,0,o,0,576);else if(0==i.full_outer_loop){if(++p>v&&0==f.over_count)break;if(3==i.noise_shaping_amp&&m&&30r[f.VBR_max_bitrate]&&(_[b][v]*=r[f.VBR_max_bitrate],_[b][v]/=u),i[b][v]>_[b][v]&&(i[b][v]=_[b][v]);return h},this.bitpressure_strategy=function(e,t,a,s){for(var n=0;nZ.MAX_BITS_PER_CHANNEL&&(s[_][o]=Z.MAX_BITS_PER_CHANNEL),m+=s[_][o]}if(Z.MAX_BITS_PER_GRANULEZ.MAX_BITS_PER_CHANNEL&&(s[_][o]=Z.MAX_BITS_PER_CHANNEL),l+=s[_][o];if(l>r[0])for(_=0;_=s?(e.ATH.adjust*=.075*s+.925,e.ATH.adjust=s?e.ATH.adjust=s:e.ATH.adjust>1,u=(h=(c=s)<<1)+c,s=h<<1,r=(n=t)+m;M=e[n+0]-e[n+c],S=e[n+0]+e[n+c],A=e[n+h]-e[n+u],w=e[n+h]+e[n+u],e[n+h]=S-w,e[n+0]=S+w,e[n+u]=M-A,e[n+c]=M+A,M=e[r+0]-e[r+c],S=e[r+0]+e[r+c],A=ee.SQRT2*e[r+u],w=ee.SQRT2*e[r+h],e[r+h]=S-w,e[r+0]=S+w,e[r+u]=M-A,e[r+c]=M+A,r+=s,(n+=s)<_;);for(l=x[i+0],o=x[i+1],f=1;fO[u+3-2]?Q/=O[u+3-2]:Q=O[u+3-2]>10*Q?O[u+3-2]/(10*Q):0,D[u+3]=Q}if(e.analysis){var W=D[0];for(u=1;u<12;u++)WV&&(q[u/3]=u%3+1);for(u=1;u<4;u++){(N[u-1]>N[u]?N[u-1]/N[u]:N[u]/N[u-1])<1.7&&(q[u]=0,1==u&&(q[0]=0))}for(0!=q[0]&&0!=S.nsPsy.lastAttacks[c]&&(q[0]=0),3!=S.nsPsy.lastAttacks[c]&&q[0]+q[1]+q[2]+q[3]==0||((Y=0)!=q[1]&&0!=q[0]&&(q[1]=0),0!=q[2]&&0!=q[1]&&(q[2]=0),0!=q[3]&&0!=q[2]&&(q[3]=0)),c<2?x[c]=Y:0==Y&&(x[0]=x[1]=0),o[c]=S.tot_ener[c],he(e,j,F,M,1&c,R,1&c,s,c,t,a),Me(S,j,w,C,X),Re(S,C,X,y),v=0;v<3;v++){var J,$;for(ve(e,F,B,A,c,v),be(S,B,A,c,v),p=0;p1.58*e.thm[1].l[t]||e.thm[1].l[t]>1.58*e.thm[0].l[t])){var a=e.mld_l[t]*e.en[3].l[t],s=Math.max(e.thm[2].l[t],Math.min(e.thm[3].l[t],a));a=e.mld_l[t]*e.en[2].l[t];var n=Math.max(e.thm[3].l[t],Math.min(e.thm[2].l[t],a));e.thm[2].l[t]=s,e.thm[3].l[t]=n}for(t=0;t1.58*e.thm[1].s[t][r]||e.thm[1].s[t][r]>1.58*e.thm[0].s[t][r]||(a=e.mld_s[t]*e.en[3].s[t][r],s=Math.max(e.thm[2].s[t][r],Math.min(e.thm[3].s[t][r],a)),a=e.mld_s[t]*e.en[2].s[t][r],n=Math.max(e.thm[3].s[t][r],Math.min(e.thm[2].s[t][r],a)),e.thm[2].s[t][r]=s,e.thm[3].s[t][r]=n)}(S),g=e.msfix,0g&&(s[o]=g),1a[o]&&(s[o]=a[o]),l.masking_lower<1&&(s[o]*=l.masking_lower)}for(;of&&(s[r]=f),1a[r]&&(s[r]=a[r]),e.masking_lower<1&&(s[r]*=e.masking_lower)}for(;rM[p+3-2]?E/=M[p+3-2]:E=M[p+3-2]>10*E?M[p+3-2]/(10*E):0,S[p+3]=E}for(p=0;p<3;++p){var P=M[3*p+3]+M[3*p+4]+M[3*p+5],H=1;6*M[3*p+5]A&&(o[m][p/3]=p%3+1);for(p=1;p<4;p++){var L=R[p-1],V=R[p];Math.max(L,V)<4e4&&L<1.7*V&&V<1.7*L&&(1==p&&o[m][0]<=o[m][p]&&(o[m][0]=0),o[m][p]=0)}o[m][0]<=c.nsPsy.lastAttacks[m]&&(o[m][0]=0),3!=c.nsPsy.lastAttacks[m]&&o[m][0]+o[m][1]+o[m][2]+o[m][3]==0||((T=0)!=o[m][1]&&0!=o[m][0]&&(o[m][1]=0),0!=o[m][2]&&0!=o[m][1]&&(o[m][2]=0),0!=o[m][3]&&0!=o[m][2]&&(o[m][3]=0)),m<2?l[m]=T:0==T&&(l[0]=l[1]=0),i[m]=c.tot_ener[m]}}(e,t,a,s,n,r,o,R,w,B),function(e,t){var a=e.internal_flags;e.short_blocks!=ke.short_block_coupled||0!=t[0]&&0!=t[1]||(t[0]=t[1]=0);for(var s=0;s=n&&(b=i*(l[t]-n)/(24-n)+r*(24-l[t])/(24-n)),c[t]=Math.pow(10,b/10),0=n&&(b=o*(l[t]-n)/(24-n)+_*(24-l[t])/(24-n)),c[t]=Math.pow(10,b/10),g=K.MAX_VALUE;for(v=0;va.npart_l-1&&(a.s3ind[M][1]=a.npart_l-1);var R=576*a.mode_gr/h;if(a.ATH.decay=Math.pow(10,-1.2*R),a.ATH.adjust=.01,-(a.ATH.adjustLimit=1)!=e.ATHtype){var w=e.out_samplerate/Pe.BLKSIZE,B=0;for(t=d=0;t=v)for(l=0;le.in_samplerate&&(e.lowpassfreq=e.in_samplerate/2),e.out_samplerate=(t=0|e.lowpassfreq,a=e.in_samplerate,s=44100,48e3<=a?s=48e3:44100<=a?s=44100:32e3<=a?s=32e3:24e3<=a?s=24e3:22050<=a?s=22050:16e3<=a?s=16e3:12e3<=a?s=12e3:11025<=a?s=11025:8e3<=a&&(s=8e3),-1==t?s:(t<=15960&&(s=44100),t<=15250&&(s=32e3),t<=11220&&(s=24e3),t<=9970&&(s=22050),t<=7230&&(s=16e3),t<=5420&&(s=12e3),t<=4510&&(s=11025),t<=3970&&(s=8e3),a=t.lowpass2&&(a=Math.min(a,r)),t.lowpass1t.highpass1?E((t.highpass2-l)/(t.highpass2-t.highpass1+1e-20)):1,o=t.lowpass2>t.lowpass1?E((l-t.lowpass1)/(t.lowpass2-t.lowpass1+1e-20)):1,t.amp_filter[r]=_*o}}(e),n.samplerate_index=P(e.out_samplerate,e),n.samplerate_index<0)return e.internal_flags=null,-1;if(e.VBR==ye.vbr_off){if(e.free_format)n.bitrate_index=0;else if(e.brate=H(e.brate,e.version,e.out_samplerate),n.bitrate_index=I(e.brate,e.version,e.out_samplerate),n.bitrate_index<=0)return e.internal_flags=null,-1}else n.bitrate_index=1;e.analysis&&(e.bWriteVbrTag=!1),null!=n.pinfo&&(e.bWriteVbrTag=!1),w.init_bit_stream_w(n);for(var c,h,u,m=n.samplerate_index+3*e.version+6*(e.out_samplerate<16e3?1:0),b=0;b=f){var M=i-u;if(0==i&&(M=0),(_=O(e,m[0],m[1],n,r,M))<0)return _;for(r+=_,u+=_,h.mf_size-=e.framesize,h.mf_samples_to_encode-=e.framesize,l=0;li&&(s.ResvMax=i),(s.ResvMax<0||e.disable_reservoir)&&(s.ResvMax=0);var _=t.bits*s.mode_gr+Math.min(s.ResvSize,s.ResvMax);return a<_&&(_=a),n.resvDrain_pre=0,null!=s.pinfo&&(s.pinfo.mean_bits=t.bits/2,s.pinfo.resvsize=s.ResvSize),_},this.ResvMaxBits=function(e,t,a,s){var n,r=e.internal_flags,i=r.ResvSize,_=r.ResvMax;0!=s&&(i+=t),0!=(1&r.substep_shaping)&&(_*=.9),a.bits=t,9*_<10*i?(n=i-9*_/10,a.bits+=n,r.substep_shaping|=128):(n=0,r.substep_shaping&=127,e.disable_reservoir||0!=(1&r.substep_shaping)||(a.bits-=.1*t));var o=i<6*r.ResvMax/10?i:6*r.ResvMax/10;return(o-=n)<0&&(o=0),o},this.ResvAdjust=function(e,t){e.ResvSize-=t.part2_3_length+t.part2_length},this.ResvFrameEnd=function(e,t){var a,s=e.l3_side;e.ResvSize+=t*e.mode_gr;var n=0;s.resvDrain_post=0,(s.resvDrain_pre=0)!=(a=e.ResvSize%8)&&(n+=a),0<(a=e.ResvSize-n-e.ResvMax)&&(n+=a);var r=Math.min(8*s.main_data_begin,n)/8;s.resvDrain_pre+=8*r,n-=8*r,e.ResvSize-=8*r,s.main_data_begin-=r,s.resvDrain_post+=n,e.ResvSize-=n}},m=new T,b=new function(){this.setModules=function(e,t,a){}},p=new function(){};n.setModules(r,i,_,o,l,f,c,h,p),i.setModules(r,p,c,f),h.setModules(i,c),_.setModules(n),l.setModules(i,u,o,m),o.setModules(m,u,n.enc.psy),u.setModules(i),m.setModules(o),f.setModules(n,i,c),a.setModules(b,p),b.setModules(c,h,_);var v=n.lame_init();v.num_channels=s,v.in_samplerate=e,v.out_samplerate=e,v.brate=t,v.mode=Ee.STEREO,v.quality=3,v.bWriteVbrTag=!1,v.disable_reservoir=!0,v.write_id3tag_automatic=!1,n.lame_init_params(v);var d=1152,g=0|1.25*d+7200,S=B(g);this.encodeBuffer=function(e,t){1==s&&(t=e),e.length>d&&(d=e.length,S=B(g=0|1.25*d+7200));var a=n.lame_encode_buffer(v,e,t,e.length,S,0,g);return new Int8Array(S.subarray(0,a))},this.flush=function(){var e=n.lame_encode_flush(v,S,0,g);return new Int8Array(S.subarray(0,e))}}}t(),Recorder.lamejs=t}(); \ No newline at end of file diff --git a/recorder.wav.min.js b/recorder.wav.min.js index 45aa3a6..488fbfc 100644 --- a/recorder.wav.min.js +++ b/recorder.wav.min.js @@ -3,4 +3,4 @@ https://github.com/xiangyuecn/Recorder src: recorder-core.js,engine/wav.js */ -!function(v){"use strict";var p=function(){},R=function(e){return new t(e)};R.IsOpen=function(){var e=R.Stream;if(e){var t=e.getTracks&&e.getTracks()||e.audioTracks||[],n=t[0];if(n){var a=n.readyState;return"live"==a||a==n.LIVE}}return!1},R.BufferSize=4096,R.Destroy=function(){for(var e in console.log("Recorder Destroy"),n)n[e]()};var n={};R.BindDestroy=function(e,t){n[e]=t},R.Support=function(){var e=v.AudioContext;if(e||(e=v.webkitAudioContext),!e)return!1;var t=navigator.mediaDevices||{};return t.getUserMedia||(t=navigator).getUserMedia||(t.getUserMedia=t.webkitGetUserMedia||t.mozGetUserMedia||t.msGetUserMedia),!!t.getUserMedia&&(R.Scope=t,R.Ctx&&"closed"!=R.Ctx.state||(R.Ctx=new e,R.BindDestroy("Ctx",function(){var e=R.Ctx;e&&e.close&&e.close()})),!0)},R.SampleData=function(e,t,n,a,r){a||(a={});var o=a.index||0,s=a.offset||0,i=a.frameNext||[];r||(r={});var c=r.frameSize||1;r.frameType&&(c="mp3"==r.frameType?1152:1);for(var f=0,l=o;l"+p.length+" 花:"+(Date.now()-a)+"ms"),setTimeout(function(){a=Date.now(),r[o.type](p,function(e){c(e,u)},function(e){i(e)})})}else i("未加载"+o.type+"编码器");else i("音频被释放");else i("未采集到录音")}},v.Recorder&&v.Recorder.Destroy(),(v.Recorder=R).LM="2019-11-7 21:47:48",R.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",R.Traffic=function(){var e=R.TrafficImgUrl;if(e){var t=R.Traffic,n=location.href.replace(/#.*/,"");if(!t[n]){t[n]=1;var a=new Image;a.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder),function(){"use strict";Recorder.prototype.enc_wav={stable:!0,testmsg:"比特率取值范围8位、16位"},Recorder.prototype.wav=function(e,t,n){var a=this.set,r=e.length,o=a.sampleRate,s=8==a.bitRate?8:16,i=r*(s/8),c=new ArrayBuffer(44+i),f=new DataView(c),l=0,u=function(e){for(var t=0;t>8);f.setInt8(l,h,!0)}else for(m=0;m"+p.length+" 花:"+(Date.now()-a)+"ms"),setTimeout(function(){a=Date.now(),r[o.type](p,function(e){c(e,u)},function(e){s(e)})})}else s("未加载"+o.type+"编码器");else s("音频被释放");else s("未采集到录音")}},v.Recorder&&v.Recorder.Destroy(),(v.Recorder=z).LM="2020-1-8 10:53:14",z.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1",z.Traffic=function(){var e=z.TrafficImgUrl;if(e){var t=z.Traffic,n=location.href.replace(/#.*/,"");if(!t[n]){t[n]=1;var a=new Image;a.src=e,console.log("Traffic Analysis Image: Recorder.TrafficImgUrl="+e)}}}}(window),"function"==typeof define&&define.amd&&define(function(){return Recorder}),"object"==typeof module&&module.exports&&(module.exports=Recorder),function(){"use strict";Recorder.prototype.enc_wav={stable:!0,testmsg:"比特率取值范围8位、16位"},Recorder.prototype.wav=function(e,t,n){var a=this.set,r=e.length,o=a.sampleRate,i=8==a.bitRate?8:16,s=r*(i/8),c=new ArrayBuffer(44+s),f=new DataView(c),l=0,u=function(e){for(var t=0;t>8);f.setInt8(l,h,!0)}else for(m=0;m> 12; + if(value > 32767) { + value = 32767; + } else if(value < -32767) { + value = -32767; + } + samples[xSample] = value; + } + } + + // Get the speed of the stream. + function getSpeed() + { + return speed; + } + + // Set the speed of the stream. + function setSpeed( + speed_) + { + speed = speed_; + } + + // Get the pitch of the stream. + function getPitch() + { + return pitch; + } + + // Set the pitch of the stream. + function setPitch( + pitch_) + { + pitch = pitch_; + } + + // Get the rate of the stream. + function getRate() + { + return rate; + } + + // Set the playback rate of the stream. This scales pitch and speed at the same time. + function setRate( + rate_) + { + if(rate!=rate_){//允许任意设置 + rate = rate_; + oldRatePosition = 0; + newRatePosition = 0; + } + } + + // Get the vocal chord pitch setting. + function getChordPitch() + { + return useChordPitch; + } + + // Set the vocal chord mode for pitch computation. Default is off. + function setChordPitch( + useChordPitch_) + { + useChordPitch = useChordPitch_; + } + + // Get the quality setting. + function getQuality() + { + return quality; + } + + // Set the "quality". Default 0 is virtually as good as 1, but very much faster. + function setQuality( + quality_) + { + quality = quality_; + } + + // Get the scaling factor of the stream. + function getVolume() + { + return volume; + } + + // Set the scaling factor of the stream. + function setVolume( + volume_) + { + volume = volume_; + } + + // Allocate stream buffers. + function allocateStreamBuffers( + sampleRate_, + numChannels_) + { + minPeriod = Math.floor(sampleRate_/SONIC_MAX_PITCH); + maxPeriod = Math.floor(sampleRate_/SONIC_MIN_PITCH); + maxRequired = 2*maxPeriod; + inputBufferSize = maxRequired; + inputBuffer = new Int16Array(maxRequired*numChannels_); + outputBufferSize = maxRequired; + outputBuffer = new Int16Array(maxRequired*numChannels_); + pitchBufferSize = maxRequired; + pitchBuffer = new Int16Array(maxRequired*numChannels_); + downSampleBuffer = new Int16Array(maxRequired); + sampleRate = sampleRate_; + numChannels = numChannels_; + oldRatePosition = 0; + newRatePosition = 0; + prevPeriod = 0; + } + + // Create a sonic stream. + function Sonic( + sampleRate, + numChannels) + { + allocateStreamBuffers(sampleRate, numChannels); + speed = 1.0; + pitch = 1.0; + volume = 1.0; + rate = 1.0; + oldRatePosition = 0; + newRatePosition = 0; + useChordPitch = false; + quality = 0; + } + + // Get the sample rate of the stream. + function getSampleRate() + { + return sampleRate; + } + + // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost. + function setSampleRate( + sampleRate) + { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Get the number of channels. + function getNumChannels() + { + return numChannels; + } + + // Set the num channels of the stream. This will cause samples buffered in the stream to be lost. + function setNumChannels( + numChannels) + { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Enlarge the output buffer if needed. + function enlargeOutputBufferIfNeeded( + numSamples) + { + if(numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize >> 1) + numSamples; + outputBuffer = resize(outputBuffer, outputBufferSize); + } + } + + // Enlarge the input buffer if needed. + function enlargeInputBufferIfNeeded( + numSamples) + { + if(numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize >> 1) + numSamples; + inputBuffer = resize(inputBuffer, inputBufferSize); + } + } + + // Add the input samples to the input buffer. + function addShortSamplesToInputBuffer( + samples, + numSamples) + { + if(numSamples == 0) { + return; + } + enlargeInputBufferIfNeeded(numSamples); + move(inputBuffer, numInputSamples, samples, 0, numSamples); + numInputSamples += numSamples; + } + + // Remove input samples that we have already processed. + function removeInputSamples( + position) + { + var remainingSamples = numInputSamples - position; + + move(inputBuffer, 0, inputBuffer, position, remainingSamples); + numInputSamples = remainingSamples; + } + + // Just copy from the array to the output buffer + function copyToOutput( + samples, + position, + numSamples) + { + enlargeOutputBufferIfNeeded(numSamples); + move(outputBuffer, numOutputSamples, samples, position, numSamples); + numOutputSamples += numSamples; + } + + // Just copy from the input buffer to the output buffer. Return num samples copied. + function copyInputToOutput( + position) + { + var numSamples = remainingInputToCopy; + + if(numSamples > maxRequired) { + numSamples = maxRequired; + } + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + + + // Read short data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + function readShortFromStream() //已改成直接返回所有的Int16Array + { + var numSamples = numOutputSamples; + var samples=new Int16Array(numSamples); + var remainingSamples = 0; + + if(numSamples == 0) { + return samples; + } + move(samples, 0, outputBuffer, 0, numSamples); + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return samples; + } + + // Force the sonic stream to generate output using whatever data it currently + // has. No extra delay will be added to the output, but flushing in the middle of + // words could introduce distortion. + function flushStream() + { + var remainingSamples = numInputSamples; + var s = speed/pitch; + var r = rate*pitch; + var expectedOutputSamples = Math.floor(numOutputSamples + Math.floor((remainingSamples/s + numPitchSamples)/r + 0.5)); + + // Add enough silence to flush both input and pitch buffers. + enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired); + for(var xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) { + inputBuffer[remainingSamples*numChannels + xSample] = 0; + } + numInputSamples += 2*maxRequired; + writeShortToStream(null, 0); + // Throw away any extra samples we generated due to the silence we added. + if(numOutputSamples > expectedOutputSamples) { + numOutputSamples = expectedOutputSamples; + } + // Empty input and pitch buffers. + numInputSamples = 0; + remainingInputToCopy = 0; + numPitchSamples = 0; + } + + // Return the number of samples in the output buffer + function samplesAvailable() + { + return numOutputSamples; + } + + // If skip is greater than one, average skip samples together and write them to + // the down-sample buffer. If numChannels is greater than one, mix the channels + // together as we down sample. + function downSampleInput( + samples, + position, + skip) + { + var numSamples = Math.floor(maxRequired/skip); + var samplesPerValue = numChannels*skip; + var value; + + position *= numChannels; + for(var i = 0; i < numSamples; i++) { + value = 0; + for(var j = 0; j < samplesPerValue; j++) { + value += samples[position + i*samplesPerValue + j]; + } + value = Math.floor(value/samplesPerValue); + downSampleBuffer[i] = value; + } + } + + // Find the best frequency match in the range, and given a sample skip multiple. + // For now, just find the pitch of the first channel. + function findPitchPeriodInRange( + samples, + position, + minPeriod, + maxPeriod) + { + var bestPeriod = 0, worstPeriod = 255; + var minDiff_ = 1, maxDiff_ = 0; + + position *= numChannels; + for(var period = minPeriod; period <= maxPeriod; period++) { + var diff = 0; + for(var i = 0; i < period; i++) { + var sVal = samples[position + i]; + var pVal = samples[position + period + i]; + diff += sVal >= pVal? sVal - pVal : pVal - sVal; + } + /* Note that the highest number of samples we add into diff will be less + than 256, since we skip samples. Thus, diff is a 24 bit number, and + we can safely multiply by numSamples without overflow */ + if(diff*bestPeriod < minDiff_*period) { + minDiff_ = diff; + bestPeriod = period; + } + if(diff*worstPeriod > maxDiff_*period) { + maxDiff_ = diff; + worstPeriod = period; + } + } + minDiff = Math.floor(minDiff_/bestPeriod); + maxDiff = Math.floor(maxDiff_/worstPeriod); + + return bestPeriod; + } + + // At abrupt ends of voiced words, we can have pitch periods that are better + // approximated by the previous pitch period estimate. Try to detect this case. + function prevPeriodBetter( + minDiff, + maxDiff, + preferNewPeriod) + { + if(minDiff == 0 || prevPeriod == 0) { + return false; + } + if(preferNewPeriod) { + if(maxDiff > minDiff*3) { + // Got a reasonable match this period + return false; + } + if(minDiff*2 <= prevMinDiff*3) { + // Mismatch is not that much greater this period + return false; + } + } else { + if(minDiff <= prevMinDiff) { + return false; + } + } + return true; + } + + // Find the pitch period. This is a critical step, and we may have to try + // multiple ways to get a good answer. This version uses AMDF. To improve + // speed, we down sample by an integer factor get in the 11KHz range, and then + // do it again with a narrower frequency range without down sampling + function findPitchPeriod( + samples, + position, + preferNewPeriod) + { + var period, retPeriod; + var skip = 1; + + if(sampleRate > SONIC_AMDF_FREQ && quality == 0) { + skip = Math.floor(sampleRate/SONIC_AMDF_FREQ); + } + if(numChannels == 1 && skip == 1) { + period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); + } else { + downSampleInput(samples, position, skip); + period = findPitchPeriodInRange(downSampleBuffer, 0, Math.floor(minPeriod/skip), + Math.floor(maxPeriod/skip)); + if(skip != 1) { + period *= skip; + var minP = period - (skip << 2); + var maxP = period + (skip << 2); + if(minP < minPeriod) { + minP = minPeriod; + } + if(maxP > maxPeriod) { + maxP = maxPeriod; + } + if(numChannels == 1) { + period = findPitchPeriodInRange(samples, position, minP, maxP); + } else { + downSampleInput(samples, position, 1); + period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP); + } + } + } + if(prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + retPeriod = prevPeriod; + } else { + retPeriod = period; + } + prevMinDiff = minDiff; + prevPeriod = period; + return retPeriod; + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + function overlapAdd( + numSamples, + numChannels, + out, + outPos, + rampDown, + rampDownPos, + rampUp, + rampUpPos) + { + for(var i = 0; i < numChannels; i++) { + var o = outPos*numChannels + i; + var u = rampUpPos*numChannels + i; + var d = rampDownPos*numChannels + i; + for(var t = 0; t < numSamples; t++) { + out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples); + o += numChannels; + d += numChannels; + u += numChannels; + } + } + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + function overlapAddWithSeparation( + numSamples, + numChannels, + separation, + out, + outPos, + rampDown, + rampDownPos, + rampUp, + rampUpPos) + { + for(var i = 0; i < numChannels; i++) { + var o = outPos*numChannels + i; + var u = rampUpPos*numChannels + i; + var d = rampDownPos*numChannels + i; + for(var t = 0; t < numSamples + separation; t++) { + if(t < separation) { + out[o] = Math.floor(rampDown[d]*(numSamples - t)/numSamples); + d += numChannels; + } else if(t < numSamples) { + out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples); + d += numChannels; + u += numChannels; + } else { + out[o] = Math.floor(rampUp[u]*(t - separation)/numSamples); + u += numChannels; + } + o += numChannels; + } + } + } + + // Just move the new samples in the output buffer to the pitch buffer + function moveNewSamplesToPitchBuffer( + originalNumOutputSamples) + { + var numSamples = numOutputSamples - originalNumOutputSamples; + + if(numPitchSamples + numSamples > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize >> 1) + numSamples; + pitchBuffer = resize(pitchBuffer, pitchBufferSize); + } + move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); + numOutputSamples = originalNumOutputSamples; + numPitchSamples += numSamples; + } + + // Remove processed samples from the pitch buffer. + function removePitchSamples( + numSamples) + { + if(numSamples == 0) { + return; + } + move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples); + numPitchSamples -= numSamples; + } + + // Change the pitch. The latency this introduces could be reduced by looking at + // past samples to determine pitch, rather than future. + function adjustPitch( + originalNumOutputSamples) + { + var period, newPeriod, separation; + var position = 0; + + if(numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + while(numPitchSamples - position >= maxRequired) { + period = findPitchPeriod(pitchBuffer, position, false); + newPeriod = Math.floor(period/pitch); + enlargeOutputBufferIfNeeded(newPeriod); + if(pitch >= 1.0) { + overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, + position, pitchBuffer, position + period - newPeriod); + } else { + separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, + pitchBuffer, position, pitchBuffer, position); + } + numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(position); + } + + // Aproximate the sinc function times a Hann window from the sinc table. + function findSincCoefficient(i, ratio, width) { + var lobePoints = Math.floor((SINC_TABLE_SIZE-1)/SINC_FILTER_POINTS); + var left = Math.floor(i*lobePoints + (ratio*lobePoints)/width); + var right = left + 1; + var position = i*lobePoints*width + ratio*lobePoints - left*width; + var leftVal = sincTable[left]; + var rightVal = sincTable[right]; + + return Math.floor(((leftVal*(width - position) + rightVal*position) << 1)/width); + } + + // Return 1 if value >= 0, else -1. This represents the sign of value. + function getSign(value) { + return value >= 0? 1 : -1; + } + + // Interpolate the new output sample. + function interpolate( + in_, + inPos, // Index to first sample which already includes channel offset. + oldSampleRate, + newSampleRate) + { + // Compute N-point sinc FIR-filter here. Clip rather than overflow. + var i; + var total = 0; + var position = newRatePosition*oldSampleRate; + var leftPosition = oldRatePosition*newSampleRate; + var rightPosition = (oldRatePosition + 1)*newSampleRate; + var ratio = rightPosition - position - 1; + var width = rightPosition - leftPosition; + var weight, value; + var oldSign; + var overflowCount = 0; + + for (i = 0; i < SINC_FILTER_POINTS; i++) { + weight = findSincCoefficient(i, ratio, width); + /* printf("%u %f\n", i, weight); */ + value = in_[inPos + i*numChannels]*weight; + oldSign = getSign(total); + total += value; + if (oldSign != getSign(total) && getSign(value) == oldSign) { + /* We must have overflowed. This can happen with a sinc filter. */ + overflowCount += oldSign; + } + } + /* It is better to clip than to wrap if there was a overflow. */ + if (overflowCount > 0) { + return 0x7FFF; + } else if (overflowCount < 0) { + return -0x8000; + } + return (total >> 16)&0xffff; + } + + // Change the rate. + function adjustRate( + rate, + originalNumOutputSamples) + { + var newSampleRate = Math.floor(sampleRate/rate); + var oldSampleRate = sampleRate; + var position; + + // Set these values to help with the integer math + while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate >>= 1; + oldSampleRate >>= 1; + } + if(numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + // Leave at least one pitch sample in the buffer + for(position = 0; position < numPitchSamples - 1; position++) { + while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) { + enlargeOutputBufferIfNeeded(1); + for(var i = 0; i < numChannels; i++) { + outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer, + position*numChannels + i, oldSampleRate, newSampleRate); + } + newRatePosition++; + numOutputSamples++; + } + oldRatePosition++; + if(oldRatePosition == oldSampleRate) { + oldRatePosition = 0; + if(newRatePosition != newSampleRate) { + throw new Error("Assertion failed: newRatePosition != newSampleRate\n"); + //assert false; + } + newRatePosition = 0; + } + } + removePitchSamples(position); + } + + + // Skip over a pitch period, and copy period/speed samples to the output + function skipPitchPeriod( + samples, + position, + speed, + period) + { + var newSamples; + + if(speed >= 2.0) { + newSamples = Math.floor(period/(speed - 1.0)); + } else { + newSamples = period; + remainingInputToCopy = Math.floor(period*(2.0 - speed)/(speed - 1.0)); + } + enlargeOutputBufferIfNeeded(newSamples); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, + samples, position + period); + numOutputSamples += newSamples; + return newSamples; + } + + // Insert a pitch period, and determine how much input to copy directly. + function insertPitchPeriod( + samples, + position, + speed, + period) + { + var newSamples; + + if(speed < 0.5) { + newSamples = Math.floor(period*speed/(1.0 - speed)); + } else { + newSamples = period; + remainingInputToCopy = Math.floor(period*(2.0*speed - 1.0)/(1.0 - speed)); + } + enlargeOutputBufferIfNeeded(period + newSamples); + move(outputBuffer, numOutputSamples, samples, position, period); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, + position + period, samples, position); + numOutputSamples += period + newSamples; + return newSamples; + } + + // Resample as many pitch periods as we have buffered on the input. Return 0 if + // we fail to resize an input or output buffer. Also scale the output by the volume. + function changeSpeed( + speed) + { + var numSamples = numInputSamples; + var position = 0, period, newSamples; + + if(numInputSamples < maxRequired) { + return; + } + do { + if(remainingInputToCopy > 0) { + newSamples = copyInputToOutput(position); + position += newSamples; + } else { + period = findPitchPeriod(inputBuffer, position, true); + if(speed > 1.0) { + newSamples = skipPitchPeriod(inputBuffer, position, speed, period); + position += period + newSamples; + } else { + newSamples = insertPitchPeriod(inputBuffer, position, speed, period); + position += newSamples; + } + } + } while(position + maxRequired <= numSamples); + removeInputSamples(position); + } + + // Resample as many pitch periods as we have buffered on the input. Scale the output by the volume. + function processStreamInput() + { + var originalNumOutputSamples = numOutputSamples; + var s = speed/pitch; + var r = rate; + + if(!useChordPitch) { + r *= pitch; + } + if(s > 1.00001 || s < 0.99999) { + changeSpeed(s); + } else { + copyToOutput(inputBuffer, 0, numInputSamples); + numInputSamples = 0; + } + if(useChordPitch) { + if(pitch != 1.0) { + adjustPitch(originalNumOutputSamples); + } + } else if(r != 1.0) { + adjustRate(r, originalNumOutputSamples); + } + if(volume != 1.0) { + // Adjust output volume. + scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples, + volume); + } + } + + // Write the data to the input stream, and process it. + function writeShortToStream( + samples) + { + addShortSamplesToInputBuffer(samples, samples?samples.length:0); + processStreamInput(); + } + + + + + /**导出Sonic对象**/ + FnObj.setPitch=setPitch; + FnObj.setRate=setRate; + FnObj.setSpeed=setSpeed; + FnObj.setVolume=setVolume; + FnObj.setChordPitch=setChordPitch; + FnObj.setQuality=setQuality; + return { + New:Sonic + + ,flushStream:flushStream + ,writeShortToStream:writeShortToStream + ,readShortFromStream:readShortFromStream + }; +} + +return new fn(SonicFunction_set); +}; + + + + + + + + + + +Recorder.Sonic=SonicFunction; + +//Worker异步化 +var sonicWorker; +Recorder.BindDestroy("sonicWorker",function(){ + console.log("sonicWorker Destroy"); + sonicWorker&&sonicWorker.terminate(); + sonicWorker=null; +}); +//开启异步,如果返回null代表不支持,开启成功后必须调用flush方法,否则会内存泄露 +var openList={id:0}; +SonicFunction.Async=function(set){ + var worker=sonicWorker; + try{ + var onmsg=function(e){ + var ed=e.data; + var cur=wk_ctxs[ed.id]; + if(ed.action=="init"){ + wk_ctxs[ed.id]={ + sampleRate:ed.sampleRate + + ,sonicObj:wk_sonic({sampleRate:ed.sampleRate}) + }; + }else if(!cur){ + return; + }; + + switch(ed.action){ + case "flush": + var pcm=cur.sonicObj.flush(); + self.postMessage({ + action:ed.action + ,id:ed.id + ,call:ed.call + ,pcm:pcm + }); + + cur.sonicObj=null; + delete wk_ctxs[ed.id]; + break; + case "input": + var pcm=cur.sonicObj.input(ed.pcm); + self.postMessage({ + action:ed.action + ,id:ed.id + ,call:ed.call + ,pcm:pcm + }); + break; + default: + if(/^set/.test(ed.action)){ + cur.sonicObj[ed.action](ed.param); + }; + }; + }; + if(!worker){ + //创建一个新Worker + var jsCode=");var wk_ctxs={};self.onmessage="+onmsg; + + var sonicCode=Recorder.Sonic.toString(); + var url=(window.URL||webkitURL).createObjectURL(new Blob(["var wk_sonic=(",sonicCode,jsCode], {type:"text/javascript"})); + + worker=new Worker(url); + (window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存 + + worker.onmessage=function(e){ + var ctx=openList[e.data.id]; + if(ctx){ + var fn=ctx.cbs[e.data.call]; + fn&&fn(e.data); + }; + }; + }; + + var ctx=new sonicWorkerCtx(worker,set); + ctx.id=++openList.id; + openList[ctx.id]=ctx; + + worker.postMessage({ + action:"init" + ,id:ctx.id + ,sampleRate:set.sampleRate + + ,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray + }); + + + sonicWorker=worker; + return ctx; + }catch(e){//出错了就不要提供了 + worker&&worker.terminate(); + + console.error(e); + return null; + }; +}; + +var sonicWorkerCtx=function(worker,set){ + this.worker=worker; + this.set=set; + + this.cbs={i:0}; +}; +sonicWorkerCtx.prototype={ + cb:function(call){ + var This=this; + var id="cb"+(++This.cbs.i); + This.cbs[id]=function(data){ + delete This.cbs[id]; + call(data); + }; + return id; + } + ,flush:function(call){ + var This=this;if(!This.worker)return; + + This.worker.postMessage({ + action:"flush" + ,id:This.id + ,call:This.cb(function(data){ + call&&call(data.pcm); + + This.worker=null; + delete openList[This.id]; + + //疑似泄露检测 排除id + var opens=-1; + for(var k in openList){ + opens++; + }; + if(opens){ + console.warn("sonic worker剩"+opens+"个在串行等待"); + }; + }) + }); + } + ,input:function(pcm,call){ + var This=this;if(!This.worker)return; + + This.worker.postMessage({ + action:"input" + ,id:This.id + ,pcm:pcm + ,call:This.cb(function(data){ + call&&call(data.pcm); + }) + }); + } +}; +var addWorkerCtxSet=function(key){ + sonicWorkerCtx.prototype[key]=function(param){ + var This=this;if(!This.worker)return; + + This.worker.postMessage({ + action:key + ,id:This.id + ,param:param + }); + } +}; +addWorkerCtxSet("setPitch"); +addWorkerCtxSet("setRate"); +addWorkerCtxSet("setSpeed"); +addWorkerCtxSet("setVolume"); +addWorkerCtxSet("setChordPitch"); +addWorkerCtxSet("setQuality"); + +})(); \ No newline at end of file diff --git a/src/package-build.js b/src/package-build.js index 94508f8..4fdd33a 100644 --- a/src/package-build.js +++ b/src/package-build.js @@ -55,6 +55,7 @@ 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/sonic.js",["extensions/sonic.js"]); console.log("\x1B[33m%s\x1B[0m","处理完成"); }; diff --git a/src/recorder-core.js b/src/recorder-core.js index 508172b..7452ac6 100644 --- a/src/recorder-core.js +++ b/src/recorder-core.js @@ -17,7 +17,7 @@ https://github.com/xiangyuecn/Recorder "use strict"; //兼容环境 -var LM="2019-11-7 21:47:48"; +var LM="2020-1-8 10:53:14"; var NOOP=function(){}; //end 兼容环境 ****从以下开始copy源码***** @@ -263,7 +263,7 @@ function initFn(set){ //wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 //采样率参考https://www.cnblogs.com/devin87/p/mp3-recorder.html - ,onProcess:NOOP //fn(buffers,powerLevel,bufferDuration,bufferSampleRate) buffers=[[Int16,...],...]:缓冲的PCM数据,为从开始录音到现在的所有pcm片段;powerLevel:当前缓冲的音量级别0-100,bufferDuration:已缓冲时长,bufferSampleRate:缓冲使用的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同) + ,onProcess:NOOP //fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd) buffers=[[Int16,...],...]:缓冲的PCM数据,为从开始录音到现在的所有pcm片段;powerLevel:当前缓冲的音量级别0-100,bufferDuration:已缓冲时长,bufferSampleRate:缓冲使用的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同);newBufferIdx:本次回调新增的buffer起始索引;asyncEnd:fn() 如果onProcess是异步的(返回值为true时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用setTimeout包裹)。onProcess返回值:如果返回true代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用asyncEnd(不能真异步时需用setTimeout包裹),在onProcess执行后新增的buffer会全部替换成空数组,因此本回调开头应立即将newBufferIdx到本次回调结尾位置的buffer全部保存到另外一个数组内,处理完成后写回buffers中本次回调的结尾位置。 //,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能 }; @@ -441,18 +441,14 @@ Recorder.prototype=initFn.prototype={ } ,envIn:function(pcm,sum){//和平台环境无关的pcm[Int16]输入 var This=this,set=This.set,engineCtx=This.engineCtx; + var bufferSampleRate=This.srcSampleRate; var size=pcm.length; - This.recSize+=size; + var powerLevel=Recorder.PowerLevel(sum,size); var buffers=This.buffers; + var bufferFirstIdx=buffers.length;//之前的buffer都是经过onProcess处理好的,不允许再修改 buffers.push(pcm); - var powerLevel=Recorder.PowerLevel(sum,size); - var bufferSampleRate=This.srcSampleRate; - var bufferSize=This.recSize; - - - //卡顿丢失补偿:因为设备很卡的时候导致H5接收到的数据量不够造成播放时候变速,结果比实际的时长要短,此处保证了不会变短,但不能修复丢失的音频数据造成音质变差。当前算法采用输入时间侦测下一帧是否需要添加补偿帧,需要(6次输入||超过1秒)以上才会开始侦测,如果滑动窗口内丢失超过1/3就会进行补偿 var now=Date.now(); var pcmTime=Math.round(size/bufferSampleRate*1000); @@ -488,13 +484,17 @@ Recorder.prototype=initFn.prototype={ //用静默进行补偿 if(fixOpen){ var addPcm=new Int16Array(addTime*bufferSampleRate/1000); - This.recSize+=addPcm.length; + size+=addPcm.length; buffers.push(addPcm); }; }; }; + var sizeOld=This.recSize,addSize=size; + var bufferSize=sizeOld+addSize; + This.recSize=bufferSize;//此值在onProcess后需要修正,可能新数据被修改 + //此类型有边录边转码(Worker)支持,开启实时转码 if(engineCtx){ @@ -502,21 +502,55 @@ Recorder.prototype=initFn.prototype={ var chunkInfo=Recorder.SampleData(buffers,bufferSampleRate,set.sampleRate,engineCtx.chunkInfo); engineCtx.chunkInfo=chunkInfo; - engineCtx.pcmSize+=chunkInfo.data.length; - bufferSize=engineCtx.pcmSize; + sizeOld=engineCtx.pcmSize; + addSize=chunkInfo.data.length; + bufferSize=sizeOld+addSize; + engineCtx.pcmSize=bufferSize;//此值在onProcess后需要修正,可能新数据被修改 + buffers=engineCtx.pcmDatas; - var bufferIdx=buffers.length; + bufferFirstIdx=buffers.length; buffers.push(chunkInfo.data); bufferSampleRate=chunkInfo.sampleRate; }; var duration=Math.round(bufferSize/bufferSampleRate*1000); - //实时回调处理数据 - set.onProcess(buffers,powerLevel,duration,bufferSampleRate); + var bufferNextIdx=buffers.length; + + //允许异步处理buffer数据 + var asyncEnd=function(){ + //重新计算size,去掉本次添加的然后重新计算 + var num=asyncBegin?0:-addSize; + for(var i=bufferFirstIdx;i