/*** 简单用 正弦波、方波、锯齿波、三角波 函数生成一段音乐简谱的pcm数据,主要用于测试时提供音频数据。本可音频生成插件可以移植到其他语言环境,如需定制可联系作者 https://github.com/xiangyuecn/Recorder 此插件在线生成测试:assets/runtime-codes/test.create-audio.nmn2pcm.js var pcmData=Recorder.NMN2PCM(set); set配置:{ texts:""|["",""] 简谱格式化文本,如果格式不符合要求,将会抛异常 sampleRate: 生成pcm的采样率,默认48000;取值不能过低,否则会削除高音 timbre: 音色,默认2.0(使用音符对应频率的一个倍频),取值>=1.0 meterDuration: 一拍时长,毫秒,默认600ms muteDuration: 音符之间的静默,毫秒,0时无静默,默认meterDur/4(最大50ms) beginDuration: 开头的静默时长,毫秒,0时无静默,默认为200ms endDuration: 结尾的静默时长,毫秒,0时无静默,默认为200ms volume: 音量,默认0.3,取值范围0.0-1.0(最大值1) waveType: 波形发生器类型,默认"sine",取值:sine(正弦波)、square(方波,volume应当减半)、sawtooth(锯齿波)、triangle(三角波) } texts格式:单个文本,或文本数组 - 四分音符(一拍):低音: 1.-7. 中音: 1-7 高音: 1'-7' 休止符(静音):0 - 音符后面用 "." 表示低音(尽量改用".":".." 倍低音,"..." 超低音) - 音符后面用 "'" 表示高音(尽量改用"'":"''" 倍高音,"'''" 超高音) - 音符之间用 "|" 或 " " 分隔一拍 - 一拍里面多个音符用 "," 分隔,每个音按权重分配这一拍的时长占比,如:“6,7”为一拍,6、7各占1/2拍,相当于八分音符 - 音符后面用 "-" 表示二分音符,简单计算为1+1=2拍时长,几个-就加几拍 - 音符后面用 "_" 表示八分音符;两两在一拍里面的音符可以免写_,自动会按1/2分配;一拍里面只有一个音时这拍会被简单计算为1/2=0.5拍;其他情况计算会按权重分配这一拍的时长(复杂),如:“6,7_”为1/2+1/2/2=0.75拍(“6*,7_”才是(1+0.5)/2+1/2/2=1拍),其中6权重1分配1/2=0.5拍,7权重0.5分配1/2/2=0.25拍;多加一个"_"就多除个2:“6_,7_”是1/2+1/2=1拍(等同于“6,7”可免写_);“6__,7__”是1/2/2+1/2/2=0.5拍;只要权重加起来是整数就算作完整的1拍 - 音符后面用 "*" 表示1+0.5=1.5拍,多出来的1/2计算和_相同(复杂),"**"两个表示加0.25 - 可以使用 "S"(sine) "Q"(square) "A"(sawtooth) "T"(triangle) 来切换后续波形发生器类型(按一拍来书写,但不占用时长),类型后面可以接 "(2.0)" 来设置音色,接 "[0.5]" 来设置音量(为set.volume*0.5);特殊值 "R"(reset) 可重置类型成set配置值,如果R后面没有接音色或音量也会被重置;比如:"1 2|A(4.0)[0.6] 3 4 R|5 6",其中12 56使用set配置的类型和音色音量,34使用锯齿波、音色4.0、音量0.18=0.3*0.6 - 如果同时有多个音,必须提供数组格式,每个音单独提供一个完整简谱(必须同步对齐) 返回结果:{ pcm: Int16Array,pcm数据 duration: 123 pcm的时长,单位毫秒 set: {...} 使用的set配置 warns: [] 不适合抛异常的提示消息 } Recorder.NMN2PCM.GetExamples() 可获取内置的简谱 ***/ (function(factory){ var browser=typeof window=="object" && !!window.document; var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面 var rec=win.Recorder,ni=rec.i18n; factory(rec,ni,ni.$T,browser); }(function(Recorder,i18n,$T,isBrowser){ "use strict"; var NMN2PCM=function(set){ var texts=set.texts||[]; if(typeof(texts)=="string") texts=[texts]; var setSR=set.sampleRate, sampleRate=setSR; if(!sampleRate || sampleRate<1)sampleRate=48000; var meterDur=set.meterDuration||600; var timbre=set.timbre||2; if(timbre<1)timbre=1; var volume=set.volume; if(volume==null)volume=0.3; volume=Math.max(0,volume); volume=Math.min(1,volume); var waveType=set.waveType||""; if(",sine,square,sawtooth,triangle,".indexOf(","+waveType+",")==-1)waveType=""; waveType=waveType||"sine"; var muteDur=set.muteDuration; if(muteDur==null || muteDur<0){ muteDur=meterDur/4; if(muteDur>50)muteDur=50; } var mute0=new Int16Array(sampleRate*muteDur/1000); var beginDur=set.beginDuration; if(beginDur==null || beginDur<0) beginDur=200; var beginMute=new Int16Array(sampleRate*beginDur/1000); var endDur=set.endDuration; if(endDur==null || endDur<0) endDur=200; var endMute=new Int16Array(sampleRate*endDur/1000); //生成C调频率 A=440 国际标准音 var s=function(s){ return 440/Math.pow(2,s/12) }; var Freqs=[s(9),s(7),s(5),s(4),s(2),s(0),s(-2)]; var FreqMP={}; for(var i=1;i<=7;i++){ var v=Freqs[i-1]; FreqMP[i+"..."]=v/8; FreqMP[i+".."]=v/4; FreqMP[i+"."]=v/2; FreqMP[i]=v; FreqMP[i+"'"]=v*2; FreqMP[i+"''"]=v*4; FreqMP[i+"'''"]=v*8; } var tracks=[],freqMax=0,freqMin=90000; for(var iT=0;setSR!=-1 && iT55){//不是0-7,切换波形或音色 var m=/^(\w)(?:\((.+)\)|\[(.+)\])*$/.exec(txt0)||[],mT=m[1]; var m=/\((.+)\)/.exec(txt0)||[],mTb=m[1]; var m=/\[(.+)\]/.exec(txt0)||[],mVol=m[1]; if(mT=="R"){ wType=waveType;wTimbre=timbre;wVol=volume; } else if(mT=="S") wType="sine"; else if(mT=="Q") wType="square"; else if(mT=="A") wType="sawtooth"; else if(mT=="T") wType="triangle"; else mT=""; if(!mT||mTb&&!+mTb||mVol&&!+mVol)throw new Error("Invalid: "+txt0); if(mTb)wTimbre=+mTb; if(mVol)wVol=volume*mVol; continue; } var ys=txt0.split(",");//一拍里面的音符 var durTotal=meterDur; //一拍的时长,如果里面有+,代表多拍 var bTotal=0,hasG=0,hasX=0; for(var i2=0;i20){ if(hasG){//"_"不够数量,减掉时间 durTotal*=bTotal/Math.ceil(bTotal); }else if(hasX){//"*"加上1/2|1/4拍的时间 durTotal+=meterDur*hasX; } } durTotal-=ys.length*muteDur;//减掉中间的静默 for(var i2=0;i20){ buffers.push(endMute); size+=endMute.length; tracks.push({buffers:buffers,size:size}); } } tracks.sort(function(a,b){return b.size-a.size}); var pcm=new Int16Array(tracks[0]&&tracks[0].size||0); for(var iT=0;iT10){//10毫秒误差 throw new Error($T("7qAD::多个音时必须对齐,相差{1}ms",0,diffMs)); }; for(var i=0,offset=0;i