opal-sid 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/opal-sid.gemspec +1 -1
- data/opal/sid.rb +5 -0
- data/opal/vendor/jssid.js +1070 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff9968c09be7f4ea961dc509b1ea4c66ec8469b8
|
4
|
+
data.tar.gz: 9b984ee9664a07ac9fb2e82a2e6295a2757d7610
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8bd572ed8c9095b78ea1011118fd2f30a499e0f588c94726a593ac654fd8f9fc55a02ba69f86189a53be0fd3fa7adea956a96a66d35f977dc591e9d900586926
|
7
|
+
data.tar.gz: 6355e52f3119748c5dd895eaf8b9d6e1b7641155cf4eff43cb66cb85fa75283d43ffbf15be25ba69b76a105f92f44391a477f8351fe369010f6360a7a276c145
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'open-uri'
|
|
6
6
|
|
7
7
|
desc 'Update JS dependencies'
|
8
8
|
task :js_deps do
|
9
|
-
js_lib_url = 'https://raw.githubusercontent.com/hermitsoft/jsSID/master/jsSID.js'
|
9
|
+
js_lib_url = 'https://raw.githubusercontent.com/hermitsoft/jsSID/master/source/jsSID.js'
|
10
10
|
js_lib_dest = File.join(File.dirname(__FILE__), './opal/vendor/jssid.js')
|
11
11
|
open(js_lib_url) do |f|
|
12
12
|
File.write(js_lib_dest, f.readlines.join)
|
data/opal-sid.gemspec
CHANGED
data/opal/sid.rb
CHANGED
@@ -26,6 +26,7 @@ class SID
|
|
26
26
|
alias_native :setloadcallback
|
27
27
|
alias_native :setstartcallback
|
28
28
|
alias_native :setendcallback
|
29
|
+
alias_native :setmemorywritecallback
|
29
30
|
|
30
31
|
def initialize(buffersize = 16384, background_noise = 0.0005)
|
31
32
|
@native = `new jsSID(#{buffersize}, #{background_noise})`
|
@@ -42,4 +43,8 @@ class SID
|
|
42
43
|
def on_end(time, &block)
|
43
44
|
setendcallback(block, time)
|
44
45
|
end
|
46
|
+
|
47
|
+
def on_memory_write(&block)
|
48
|
+
setmemorywritecallback(block)
|
49
|
+
end
|
45
50
|
end
|
data/opal/vendor/jssid.js
CHANGED
@@ -1 +1,1070 @@
|
|
1
|
-
function playSID(sidurl,subtune){if(typeof SIDplayer==='undefined')SIDplayer=new jsSID(16384,0.0005);SIDplayer.loadstart(sidurl,subtune);}function jsSID(bufln,bgnoi){this.author='Hermit';this.sourcecode='http://hermit.sidrip.com';this.version='0.9.1';this.year='2016';if(typeof AudioContext!=='undefined'){var aCtx=new AudioContext();}else {var aCtx=new webkitAudioContext();};var smpr=aCtx.sampleRate;if(typeof aCtx.createJavaScriptNode==='function'){var scrNod=aCtx.createJavaScriptNode(bufln,0,1);}else {var scrNod=aCtx.createScriptProcessor(bufln,0,1);}scrNod.onaudioprocess=function (e){var oBuf=e.outputBuffer;var oDat=oBuf.getChannelData(0);for(var sample=0;sample<oBuf.length;sample++){oDat[sample]=play();}};this.loadstart=function (sidurl,subt){this.loadinit(sidurl,subt);if(startcallback!==null)startcallback();this.playcont();};this.loadinit=function (sidurl,subt){ldd=0;this.pause();initSID();subtune=subt;var req=new XMLHttpRequest();req.open('GET',sidurl,true);req.responseType='arraybuffer';req.onload=function (){if(req.status == 200){var fdat=new Uint8Array(req.response);var i,strend,offs=fdat[7];ldad=fdat[8]+fdat[9]?fdat[8]*256+fdat[9]:fdat[offs]+fdat[offs+1]*256;for(i=0;i<32;i++)tmod[31-i]=fdat[0x12+(i>>3)]&Math.pow(2,7-i%8);for(i=0;i<M.length;i++)M[i]=0;for(i=offs+2;i<fdat.byteLength;i++){if(ldad+i-(offs+2)<M.length)M[ldad+i-(offs+2)]=fdat[i];}strend=1;for(i=0;i<32;i++){if(strend!=0)strend=tit[i]=fdat[0x16+i];else strend=tit[i]=0;}strend=1;for(i=0;i<32;i++){if(strend!=0)strend=auth[i]=fdat[0x36+i];else strend=auth[i]=0;}strend=1;for(i=0;i<32;i++){if(strend!=0)strend=inf[i]=fdat[0x56+i];else strend=inf[i]=0;}ina=fdat[0xA]+fdat[0xB]?fdat[0xA]*256+fdat[0xB]:ldad;pla=plf=fdat[0xC]*256+fdat[0xD];subtune_amount=fdat[0xF];prSIDm[0]=(fdat[0x77]&0x30)>=0x20?8580:6581;prSIDm[1]=(fdat[0x77]&0xC0)>=0x80?8580:6581;prSIDm[2]=(fdat[0x76]&3)>=3?8580:6581;SID_address[1]=fdat[0x7A]>=0x42&&(fdat[0x7A]<0x80||fdat[0x7A]>=0xE0)?0xD000+fdat[0x7A]*16:0;SID_address[2]=fdat[0x7B]>=0x42&&(fdat[0x7B]<0x80||fdat[0x7B]>=0xE0)?0xD000+fdat[0x7B]*16:0;SIDamount=1+(SID_address[1]>0)+(SID_address[2]>0);ldd=1;if(loadcallback!==null)loadcallback();init(subtune);}};req.send(null);};this.start=function (subt){init(subt);if(startcallback!==null)startcallback();this.playcont();};this.playcont=function (){scrNod.connect(aCtx.destination);};this.pause=function (){if(ldd&&ind)scrNod.disconnect(aCtx.destination);};this.stop=function (){this.pause();init(subtune);};this.gettitle=function (){return String.fromCharCode.apply(null,tit);};this.getauthor=function (){return String.fromCharCode.apply(null,auth);};this.getinfo=function (){return String.fromCharCode.apply(null,inf);};this.getsubtunes=function (){return subtune_amount;};this.getprefmodel=function (){return prSIDm[0];};this.getmodel=function (){return SIDm;};this.getoutput=function (){return (output/SCALE)*(M[0xD418]&0xF);};this.getplaytime=function (){return parseInt(playtime);};this.setmodel=function (model){SIDm=model;};this.setvolume=function (vol){volume=vol;};this.setloadcallback=function (fname){loadcallback=fname;};this.setstartcallback=function (fname){startcallback=fname;};this.setendcallback=function (fname,seconds){endcallback=fname;playlength=seconds;};var CLK=985248,FR=50,CHA=3,SCALE=0x10000*CHA*16;var SIDamount_vol=[0,1,0.6,0.4];var tit=new Uint8Array(0x20);var auth=new Uint8Array(0x20);var inf=new Uint8Array(0x20);var tmod=new Uint8Array(0x20);var ldad=0x1000,ina=0x1000,plf=0x1003,pla=0x1003,subtune=0,subtune_amount=1,playlength=0;var prSIDm=[8580.0,8580.0,8580.0];var SIDm=8580.0;var SID_address=[0xD400,0,0];var M=new Uint8Array(65536);var ldd=0,ind=0,fin=0,loadcallback=null,startcallback=null;endcallback=null,playtime=0,ended=0;var ckr=CLK/smpr;var fspd=smpr/FR;var fcnt=1,volume=1.0,CPUtime=0,pPC;var SIDamount=1,mix=0;function init(subt){if(ldd){ind=0;subtune=subt;initCPU(ina);initSID();A=subtune;M[1]=0x37;M[0xDC05]=0;for(var tout=100000;tout>=0;tout--){if(CPU())break;}if(tmod[subtune]||M[0xDC05]){if(!M[0xDC05]){M[0xDC04]=0x24;M[0xDC05]=0x40;}fspd=(M[0xDC04]+M[0xDC05]*256)/ckr;}else fspd=smpr/FR;if(plf==0)pla=((M[1]&3)<2)?M[0xFFFE]+M[0xFFFF]*256:M[0x314]+M[0x315]*256;else {pla=plf;if(pla>=0xE000&&M[1]==0x37)M[1]=0x35;}initCPU(pla);fcnt=1;fin=0;CPUtime=0;playtime=0;ended=0;ind=1;}}function play(){if(ldd&&ind){fcnt--;playtime+=1/smpr;if(fcnt<=0){fcnt=fspd;fin=0;PC=pla;SP=0xFF;}if(fin==0){while(CPUtime<=ckr){pPC=PC;if(CPU()>=0xFE){fin=1;break;}else CPUtime+=cyc;if((M[1]&3)>1&&pPC<0xE000&&(PC==0xEA31||PC==0xEA81)){fin=1;break;}if((addr==0xDC05||addr==0xDC04)&&(M[1]&3)&&tmod[subtune])fspd=(M[0xDC04]+M[0xDC05]*256)/ckr;if(sta>=0xD420&&sta<0xD800&&(M[1]&3)){if(!(SID_address[1]<=sta&&sta<SID_address[1]+0x1F)&&!(SID_address[2]<=sta&&sta<SID_address[2]+0x1F))M[sta&0xD41F]=M[sta];}if(addr==0xD404&&!(M[0xD404]&1))Ast[0]&=0x3E;if(addr==0xD40B&&!(M[0xD40B]&1))Ast[1]&=0x3E;if(addr==0xD412&&!(M[0xD412]&1))Ast[2]&=0x3E;}CPUtime-=ckr;}}if(playlength>0&&parseInt(playtime)==parseInt(playlength)&&endcallback!==null&&ended==0){ended=1;endcallback();}mix=SID(0,0xD400);if(SID_address[1])mix+=SID(1,SID_address[1]);if(SID_address[2])mix+=SID(2,SID_address[2]);return mix*volume*SIDamount_vol[SIDamount]+(Math.random()*bgnoi-bgnoi/2);};var flagsw=[0x01,0x21,0x04,0x24,0x00,0x40,0x08,0x28],brf=[0x80,0x40,0x01,0x02];var PC=0,A=0,T=0,X=0,Y=0,SP=0xFF,IR=0,addr=0,ST=0x00,cyc=0,sta=0;function initCPU(mempos){PC=mempos;A=0;X=0;Y=0;ST=0;SP=0xFF;}function CPU(){IR=M[PC];cyc=2;sta=0;if(IR&1){switch(IR&0x1F){case 1:case 3:addr=M[M[++PC]+X]+M[M[PC]+X+1]*256;cyc=6;break;case 0x11:case 0x13:addr=M[M[++PC]]+M[M[PC]+1]*256+Y;cyc=6;break;case 0x19:case 0x1F:addr=M[++PC]+M[++PC]*256+Y;cyc=5;break;case 0x1D:addr=M[++PC]+M[++PC]*256+X;cyc=5;break;case 0xD:case 0xF:addr=M[++PC]+M[++PC]*256;cyc=4;break;case 0x15:addr=M[++PC]+X;cyc=4;break;case 5:case 7:addr=M[++PC];cyc=3;break;case 0x17:addr=M[++PC]+Y;cyc=4;break;case 9:case 0xB:addr=++PC;cyc=2;}addr&=0xFFFF;switch(IR&0xE0){case 0x60:T=A;A+=M[addr]+(ST&1);ST&=20;ST|=(A&128)|(A>255);A&=0xFF;ST|=(!A)<<1|(!((T^M[addr])&0x80)&&((T^A)&0x80))>>1;break;case 0xE0:T=A;A-=M[addr]+!(ST&1);ST&=20;ST|=(A&128)|(A>=0);A&=0xFF;ST|=(!A)<<1|(((T^M[addr])&0x80)&&((T^A)&0x80))>>1;break;case 0xC0:T=A-M[addr];ST&=124;ST|=(!(T&0xFF))<<1|(T&128)|(T>=0);break;case 0x00:A|=M[addr];ST&=125;ST|=(!A)<<1|(A&128);break;case 0x20:A&=M[addr];ST&=125;ST|=(!A)<<1|(A&128);break;case 0x40:A^=M[addr];ST&=125;ST|=(!A)<<1|(A&128);break;case 0xA0:A=M[addr];ST&=125;ST|=(!A)<<1|(A&128);if((IR&3)==3)X=A;break;case 0x80:M[addr]=A&(((IR&3)==3)?X:0xFF);sta=addr;}}else if(IR&2){switch(IR&0x1F){case 0x1E:addr=M[++PC]+M[++PC]*256+(((IR&0xC0)!=0x80)?X:Y);cyc=5;break;case 0xE:addr=M[++PC]+M[++PC]*256;cyc=4;break;case 0x16:addr=M[++PC]+(((IR&0xC0)!=0x80)?X:Y);cyc=4;break;case 6:addr=M[++PC];cyc=3;break;case 2:addr=++PC;cyc=2;}addr&=0xFFFF;switch(IR&0xE0){case 0x00:ST&=0xFE;case 0x20:if((IR&0xF)==0xA){A=(A<<1)+(ST&1);ST&=60;ST|=(A&128)|(A>255);A&=0xFF;ST|=(!A)<<1;}else {T=(M[addr]<<1)+(ST&1);ST&=60;ST|=(T&128)|(T>255);T&=0xFF;ST|=(!T)<<1;M[addr]=T;cyc+=2;}break;case 0x40:ST&=0xFE;case 0x60:if((IR&0xF)==0xA){T=A;A=(A>>1)+(ST&1)*128;ST&=60;ST|=(A&128)|(T&1);A&=0xFF;ST|=(!A)<<1;}else {T=(M[addr]>>1)+(ST&1)*128;ST&=60;ST|=(T&128)|(M[addr]&1);T&=0xFF;ST|=(!T)<<1;M[addr]=T;cyc+=2;}break;case 0xC0:if(IR&4){M[addr]--;M[addr]&=0xFF;ST&=125;ST|=(!M[addr])<<1|(M[addr]&128);cyc+=2;}else {X--;X&=0xFF;ST&=125;ST|=(!X)<<1|(X&128);}break;case 0xA0:if((IR&0xF)!=0xA)X=M[addr];else if(IR&0x10){X=SP;break;}else X=A;ST&=125;ST|=(!X)<<1|(X&128);break;case 0x80:if(IR&4){M[addr]=X;sta=addr;}else if(IR&0x10)SP=X;else {A=X;ST&=125;ST|=(!A)<<1|(A&128);}break;case 0xE0:if(IR&4){M[addr]++;M[addr]&=0xFF;ST&=125;ST|=(!M[addr])<<1|(M[addr]&128);cyc+=2;}}}else if((IR&0xC)==8){switch(IR&0xF0){case 0x60:SP++;SP&=0xFF;A=M[0x100+SP];ST&=125;ST|=(!A)<<1|(A&128);cyc=4;break;case 0xC0:Y++;Y&=0xFF;ST&=125;ST|=(!Y)<<1|(Y&128);break;case 0xE0:X++;X&=0xFF;ST&=125;ST|=(!X)<<1|(X&128);break;case 0x80:Y--;Y&=0xFF;ST&=125;ST|=(!Y)<<1|(Y&128);break;case 0x00:M[0x100+SP]=ST;SP--;SP&=0xFF;cyc=3;break;case 0x20:SP++;SP&=0xFF;ST=M[0x100+SP];cyc=4;break;case 0x40:M[0x100+SP]=A;SP--;SP&=0xFF;cyc=3;break;case 0x90:A=Y;ST&=125;ST|=(!A)<<1|(A&128);break;case 0xA0:Y=A;ST&=125;ST|=(!Y)<<1|(Y&128);break;default:if(flagsw[IR>>5]&0x20)ST|=(flagsw[IR>>5]&0xDF);else ST&=255-(flagsw[IR>>5]&0xDF);}}else {if((IR&0x1F)==0x10){PC++;T=M[PC];if(T&0x80)T-=0x100;if(IR&0x20){if(ST&brf[IR>>6]){PC+=T;cyc=3;}}else {if(!(ST&brf[IR>>6])){PC+=T;cyc=3;}}}else {switch(IR&0x1F){case 0:addr=++PC;cyc=2;break;case 0x1C:addr=M[++PC]+M[++PC]*256+X;cyc=5;break;case 0xC:addr=M[++PC]+M[++PC]*256;cyc=4;break;case 0x14:addr=M[++PC]+X;cyc=4;break;case 4:addr=M[++PC];cyc=3;}addr&=0xFFFF;switch(IR&0xE0){case 0x00:M[0x100+SP]=PC%256;SP--;SP&=0xFF;M[0x100+SP]=PC/256;SP--;SP&=0xFF;M[0x100+SP]=ST;SP--;SP&=0xFF;PC=M[0xFFFE]+M[0xFFFF]*256-1;cyc=7;break;case 0x20:if(IR&0xF){ST&=0x3D;ST|=(M[addr]&0xC0)|(!(A&M[addr]))<<1;}else {M[0x100+SP]=(PC+2)%256;SP--;SP&=0xFF;M[0x100+SP]=(PC+2)/256;SP--;SP&=0xFF;PC=M[addr]+M[addr+1]*256-1;cyc=6;}break;case 0x40:if(IR&0xF){PC=addr-1;cyc=3;}else {if(SP>=0xFF)return 0xFE;SP++;SP&=0xFF;ST=M[0x100+SP];SP++;SP&=0xFF;T=M[0x100+SP];SP++;SP&=0xFF;PC=M[0x100+SP]+T*256-1;cyc=6;}break;case 0x60:if(IR&0xF){PC=M[addr]+M[addr+1]*256-1;cyc=5;}else {if(SP>=0xFF)return 0xFF;SP++;SP&=0xFF;T=M[0x100+SP];SP++;SP&=0xFF;PC=M[0x100+SP]+T*256-1;cyc=6;}break;case 0xC0:T=Y-M[addr];ST&=124;ST|=(!(T&0xFF))<<1|(T&128)|(T>=0);break;case 0xE0:T=X-M[addr];ST&=124;ST|=(!(T&0xFF))<<1|(T&128)|(T>=0);break;case 0xA0:Y=M[addr];ST&=125;ST|=(!Y)<<1|(Y&128);break;case 0x80:M[addr]=Y;sta=addr;}}}PC++;PC&=0xFFFF;return 0;};var GAT=0x01,SYN=0x02,RNG=0x04,TST=0x08,TRI=0x10,SAW=0x20,PUL=0x40,NOI=0x80,HZ=0x10,DECSUS=0x40,ATK=0x80,FSW=[1,2,4,1,2,4,1,2,4],LP=0x10,BP=0x20,HP=0x40,OFF3=0x80;var Ast=[0,0,0,0,0,0,0,0,0],rcnt=[0,0,0,0,0,0,0,0,0],envcnt=[0,0,0,0,0,0,0,0,0],expcnt=[0,0,0,0,0,0,0,0,0],pSR=[0,0,0,0,0,0,0,0,0];var pacc=[0,0,0,0,0,0,0,0,0],pracc=[0,0,0,0,0,0,0,0,0],sMSBrise=[0,0,0],sMSB=[0,0,0];var nLFSR=[0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8,0x7FFFF8];var prevwfout=[0,0,0,0,0,0,0,0,0],pwv=[0,0,0,0,0,0,0,0,0],combiwf;var plp=[0,0,0],pbp=[0,0,0],ctfr=-2*3.14*(12500/256)/smpr,ctf_ratio_6581=-2*3.14*(20000/256)/smpr;var pgt,chnadd,ctrl,wf,test,prd,step,SR,aAdd,MSB,tmp,pw,lim,wfout,ctf,reso,flin,output;function initSID(){for(var i=0xD400;i<=0xD7FF;i++)M[i]=0;for(var i=0xDE00;i<=0xDFFF;i++)M[i]=0;for(var i=0;i<9;i++){Ast[i]=HZ;rcnt[i]=envcnt[i]=expcnt[i]=pSR[i]=0;}}function SID(num,SIDaddr){flin=0;output=0;for(var chn=num*CHA;chn<(num+1)*CHA;chn++){pgt=(Ast[chn]&GAT);chnadd=SIDaddr+(chn-num*CHA)*7,ctrl=M[chnadd+4];wf=ctrl&0xF0;test=ctrl&TST;SR=M[chnadd+6];tmp=0;if(pgt!=(ctrl&GAT)){if(pgt){Ast[chn]&=0xFF-(GAT|ATK|DECSUS);}else {Ast[chn]=(GAT|ATK|DECSUS);if((SR&0xF)>(pSR[chn]&0xF))tmp=1;}}pSR[chn]=SR;rcnt[chn]+=ckr;if(rcnt[chn]>=0x8000)rcnt[chn]-=0x8000;if(Ast[chn]&ATK){step=M[chnadd+5]>>4;prd=Aprd[step];}else if(Ast[chn]&DECSUS){step=M[chnadd+5]&0xF;prd=Aprd[step];}else {step=SR&0xF;prd=Aprd[step];}step=Astp[step];if(rcnt[chn]>=prd&&rcnt[chn]<prd+ckr&&tmp==0){rcnt[chn]-=prd;if((Ast[chn]&ATK)||++expcnt[chn]==Aexp[envcnt[chn]]){if(!(Ast[chn]&HZ)){if(Ast[chn]&ATK){envcnt[chn]+=step;if(envcnt[chn]>=0xFF){envcnt[chn]=0xFF;Ast[chn]&=0xFF-ATK;}}else if(!(Ast[chn]&DECSUS)||envcnt[chn]>(SR>>4)+(SR&0xF0)){envcnt[chn]-=step;if(envcnt[chn]<=0&&envcnt[chn]+step!=0){envcnt[chn]=0;Ast[chn]|=HZ;}}}expcnt[chn]=0;}}envcnt[chn]&=0xFF;aAdd=(M[chnadd]+M[chnadd+1]*256)*ckr;if(test||((ctrl&SYN)&&sMSBrise[num])){pacc[chn]=0;}else {pacc[chn]+=aAdd;if(pacc[chn]>0xFFFFFF)pacc[chn]-=0x1000000;}MSB=pacc[chn]&0x800000;sMSBrise[num]=(MSB>(pracc[chn]&0x800000))?1:0;if(wf&NOI){tmp=nLFSR[chn];if(((pacc[chn]&0x100000)!=(pracc[chn]&0x100000))||aAdd>=0x100000){step=(tmp&0x400000)^((tmp&0x20000)<<5);tmp=((tmp<<1)+(step>0||test))&0x7FFFFF;nLFSR[chn]=tmp;}wfout=(wf&0x70)?0:((tmp&0x100000)>>5)+((tmp&0x40000)>>4)+((tmp&0x4000)>>1)+((tmp&0x800)<<1)+((tmp&0x200)<<2)+((tmp&0x20)<<5)+((tmp&0x04)<<7)+((tmp&0x01)<<8);}else if(wf&PUL){pw=(M[chnadd+2]+(M[chnadd+3]&0xF)*256)*16;tmp=aAdd>>9;if(0<pw&&pw<tmp)pw=tmp;tmp^=0xFFFF;if(pw>tmp)pw=tmp;tmp=pacc[chn]>>8;if(wf==PUL){step=256/(aAdd>>16);if(test)wfout=0xFFFF;else if(tmp<pw){lim=(0xFFFF-pw)*step;if(lim>0xFFFF)lim=0xFFFF;wfout=lim-(pw-tmp)*step;if(wfout<0)wfout=0;}else {lim=pw*step;if(lim>0xFFFF)lim=0xFFFF;wfout=(0xFFFF-tmp)*step-lim;if(wfout>=0)wfout=0xFFFF;wfout&=0xFFFF;}}else {wfout=(tmp>=pw||test)?0xFFFF:0;if(wf&TRI){if(wf&SAW){wfout=(wfout)?cmbWF(chn,Pulsetrsaw,tmp>>4,1):0;}else {tmp=pacc[chn]^(ctrl&RNG?sMSB[num]:0);wfout=(wfout)?cmbWF(chn,pusaw,(tmp^(tmp&0x800000?0xFFFFFF:0))>>11,0):0;}}else if(wf&SAW)wfout=(wfout)?cmbWF(chn,pusaw,tmp>>4,1):0;}}else if(wf&SAW){wfout=pacc[chn]>>8;if(wf&TRI)wfout=cmbWF(chn,trsaw,wfout>>4,1);else {step=aAdd/0x1200000;wfout+=wfout*step;if(wfout>0xFFFF)wfout=0xFFFF-(wfout-0x10000)/step;}}else if(wf&TRI){tmp=pacc[chn]^(ctrl&RNG?sMSB[num]:0);wfout=(tmp^(tmp&0x800000?0xFFFFFF:0))>>7;}if(wf)prevwfout[chn]=wfout;else {wfout=prevwfout[chn];}pracc[chn]=pacc[chn];sMSB[num]=MSB;if(M[SIDaddr+0x17]&FSW[chn])flin+=(wfout-0x8000)*(envcnt[chn]/256);else if((chn%CHA)!=2||!(M[SIDaddr+0x18]&OFF3))output+=(wfout-0x8000)*(envcnt[chn]/256);}if(M[1]&3)M[SIDaddr+0x1B]=wfout>>8;M[SIDaddr+0x1C]=envcnt[3];ctf=(M[SIDaddr+0x15]&7)/8+M[SIDaddr+0x16]+0.2;if(SIDm==8580.0){ctf=1-Math.exp(ctf*ctfr);reso=Math.pow(2,((4-(M[SIDaddr+0x17]>>4))/8));}else {if(ctf<24)ctf=0.035;else ctf=1-1.263*Math.exp(ctf*ctf_ratio_6581);reso=(M[SIDaddr+0x17]>0x5F)?8/(M[SIDaddr+0x17]>>4):1.41;}tmp=flin+pbp[num]*reso+plp[num];if(M[SIDaddr+0x18]&HP)output-=tmp;tmp=pbp[num]-tmp*ctf;pbp[num]=tmp;if(M[SIDaddr+0x18]&BP)output-=tmp;tmp=plp[num]+tmp*ctf;plp[num]=tmp;if(M[SIDaddr+0x18]&LP)output+=tmp;return (output/SCALE)*(M[SIDaddr+0x18]&0xF);}function cmbWF(chn,wfa,index,differ6581){if(differ6581&&SIDm==6581.0)index&=0x7FF;combiwf=(wfa[index]+pwv[chn])/2;pwv[chn]=wfa[index];return combiwf;}function cCmbWF(wfa,bitmul,bstr,trh){for(var i=0;i<4096;i++){wfa[i]=0;for(var j=0;j<12;j++){var blvl=0;for(var k=0;k<12;k++){blvl+=(bitmul/Math.pow(bstr,Math.abs(k-j)))*(((i>>k)&1)-0.5);}wfa[i]+=(blvl>=trh)?Math.pow(2,j):0;}wfa[i]*=12;}}trsaw=new Array(4096);cCmbWF(trsaw,0.8,2.4,0.64);pusaw=new Array(4096);cCmbWF(pusaw,1.4,1.9,0.68);Pulsetrsaw=new Array(4096);cCmbWF(Pulsetrsaw,0.8,2.5,0.64);var prd0=Math.max(ckr,9);var Aprd=[prd0,32*1,63*1,95*1,149*1,220*1,267*1,313*1,392*1,977*1,1954*1,3126*1,3907*1,11720*1,19532*1,31251*1];var Astp=[Math.ceil(prd0/9),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];var Aexp=[1,30,30,30,30,30,30,16,16,16,16,16,16,16,16,8,8,8,8,8,8,8,8,8,8,8,8,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];}
|
1
|
+
//jsSID by Hermit (Mihaly Horvath) : a javascript SID emulator and player for the Web Audio API
|
2
|
+
//(Year 2016) http://hermit.sidrip.com
|
3
|
+
function playSID(sidurl, subtune) {
|
4
|
+
//convenience function to create default-named jsSID object and play in one call, easily includable as inline JS function call in HTML
|
5
|
+
if (typeof SIDplayer === 'undefined') {
|
6
|
+
SIDplayer = new jsSID(16384, 0.0005); //create the object if doesn't exist yet
|
7
|
+
}
|
8
|
+
SIDplayer.loadstart(sidurl, subtune);
|
9
|
+
}
|
10
|
+
|
11
|
+
|
12
|
+
function jsSID(bufferlen, background_noise) {
|
13
|
+
|
14
|
+
this.author = 'Hermit';
|
15
|
+
this.sourcecode = 'http://hermit.sidrip.com';
|
16
|
+
this.version = '0.9.1';
|
17
|
+
this.year = '2016';
|
18
|
+
|
19
|
+
//create Web Audio context and scriptNode at jsSID object initialization (at the moment only mono output)
|
20
|
+
if (typeof AudioContext !== 'undefined') {
|
21
|
+
var jsSID_audioCtx = new AudioContext();
|
22
|
+
} else {
|
23
|
+
var jsSID_audioCtx = new webkitAudioContext();
|
24
|
+
}
|
25
|
+
var samplerate = jsSID_audioCtx.sampleRate;
|
26
|
+
if (typeof jsSID_audioCtx.createJavaScriptNode === 'function') {
|
27
|
+
var jsSID_scriptNode = jsSID_audioCtx.createJavaScriptNode(bufferlen, 0, 1);
|
28
|
+
} else {
|
29
|
+
var jsSID_scriptNode = jsSID_audioCtx.createScriptProcessor(bufferlen, 0, 1);
|
30
|
+
}
|
31
|
+
|
32
|
+
jsSID_scriptNode.onaudioprocess = function(e) {
|
33
|
+
//scriptNode will be replaced by AudioWorker in new browsers sooner or later
|
34
|
+
var outBuffer = e.outputBuffer;
|
35
|
+
var outData = outBuffer.getChannelData(0);
|
36
|
+
for (var sample = 0; sample < outBuffer.length; sample++) {
|
37
|
+
outData[sample] = play();
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
//user functions callable from outside
|
43
|
+
this.loadstart = function(sidurl, subt) {
|
44
|
+
this.loadinit(sidurl, subt);
|
45
|
+
if (startcallback !== null) startcallback();
|
46
|
+
this.playcont();
|
47
|
+
}
|
48
|
+
|
49
|
+
this.loadinit = function(sidurl, subt) {
|
50
|
+
loaded = 0;
|
51
|
+
this.pause();
|
52
|
+
initSID();
|
53
|
+
subtune = subt; //stop playback before loading new tune
|
54
|
+
var request = new XMLHttpRequest();
|
55
|
+
request.open('GET', sidurl, true);
|
56
|
+
request.responseType = 'arraybuffer';
|
57
|
+
|
58
|
+
request.onload = function() { //request.onreadystatechange=function(){ if (this.readyState!==4) return; ... could be used too
|
59
|
+
var filedata = new Uint8Array(request.response); //SID-file format information can be found at HVSC
|
60
|
+
var i, strend, offs = filedata[7];
|
61
|
+
loadaddr = filedata[8] + filedata[9] ? filedata[8] * 256 + filedata[9] : filedata[offs] + filedata[
|
62
|
+
offs + 1] * 256;
|
63
|
+
for (i = 0; i < 32; i++) timermode[31 - i] = filedata[0x12 + (i >> 3)] & Math.pow(2, 7 - i % 8);
|
64
|
+
for (i = 0; i < memory.length; i++) memory[i] = 0;
|
65
|
+
for (i = offs + 2; i < filedata.byteLength; i++) {
|
66
|
+
if (loadaddr + i - (offs + 2) < memory.length) memory[loadaddr + i - (offs + 2)] = filedata[i];
|
67
|
+
}
|
68
|
+
strend = 1;
|
69
|
+
for (i = 0; i < 32; i++) {
|
70
|
+
if (strend != 0) strend = SIDtitle[i] = filedata[0x16 + i];
|
71
|
+
else strend = SIDtitle[i] = 0;
|
72
|
+
}
|
73
|
+
strend = 1;
|
74
|
+
for (i = 0; i < 32; i++) {
|
75
|
+
if (strend != 0) strend = SIDauthor[i] = filedata[0x36 + i];
|
76
|
+
else strend = SIDauthor[i] = 0;
|
77
|
+
}
|
78
|
+
strend = 1;
|
79
|
+
for (i = 0; i < 32; i++) {
|
80
|
+
if (strend != 0) strend = SIDinfo[i] = filedata[0x56 + i];
|
81
|
+
else strend = SIDinfo[i] = 0;
|
82
|
+
}
|
83
|
+
initaddr = filedata[0xA] + filedata[0xB] ? filedata[0xA] * 256 + filedata[0xB] : loadaddr;
|
84
|
+
playaddr = playaddf = filedata[0xC] * 256 + filedata[0xD];
|
85
|
+
subtune_amount = filedata[0xF];
|
86
|
+
preferred_SID_model[0] = (filedata[0x77] & 0x30) >= 0x20 ? 8580 : 6581;
|
87
|
+
preferred_SID_model[1] = (filedata[0x77] & 0xC0) >= 0x80 ? 8580 : 6581;
|
88
|
+
preferred_SID_model[2] = (filedata[0x76] & 3) >= 3 ? 8580 : 6581;
|
89
|
+
SID_address[1] = filedata[0x7A] >= 0x42 && (filedata[0x7A] < 0x80 || filedata[0x7A] >= 0xE0) ?
|
90
|
+
0xD000 + filedata[0x7A] * 16 : 0;
|
91
|
+
SID_address[2] = filedata[0x7B] >= 0x42 && (filedata[0x7B] < 0x80 || filedata[0x7B] >= 0xE0) ?
|
92
|
+
0xD000 + filedata[0x7B] * 16 : 0;
|
93
|
+
SIDamount = 1 + (SID_address[1] > 0) + (SID_address[2] > 0);
|
94
|
+
loaded = 1;
|
95
|
+
if (loadcallback !== null) loadcallback();
|
96
|
+
init(subtune);
|
97
|
+
}; // ';' is needed here (and similar places) so that minimized/compacted jsSID.js generated by Makefile will work in the browser
|
98
|
+
|
99
|
+
request.send(null);
|
100
|
+
}
|
101
|
+
|
102
|
+
this.start = function(subt) {
|
103
|
+
init(subt);
|
104
|
+
if (startcallback !== null) startcallback();
|
105
|
+
this.playcont();
|
106
|
+
}
|
107
|
+
this.playcont = function() {
|
108
|
+
jsSID_scriptNode.connect(jsSID_audioCtx.destination);
|
109
|
+
}
|
110
|
+
this.pause = function() {
|
111
|
+
if (loaded && initialized) jsSID_scriptNode.disconnect(jsSID_audioCtx.destination);
|
112
|
+
}
|
113
|
+
//(Checking state before disconnecting is a workaround for Opera: gave error when code tried disconnecting what is not connected.
|
114
|
+
//Checking inner state variables here, but maybe audioContext status info could be more reliable. I just didn't want to rely too many Audio API function.)
|
115
|
+
this.stop = function() {
|
116
|
+
this.pause();
|
117
|
+
init(subtune);
|
118
|
+
}
|
119
|
+
//using functions to get states instead of variables. this enables value conversions and gives easier/explicite scoping
|
120
|
+
this.gettitle = function() {
|
121
|
+
return String.fromCharCode.apply(null, SIDtitle);
|
122
|
+
}
|
123
|
+
this.getauthor = function() {
|
124
|
+
return String.fromCharCode.apply(null, SIDauthor);
|
125
|
+
}
|
126
|
+
this.getinfo = function() {
|
127
|
+
return String.fromCharCode.apply(null, SIDinfo);
|
128
|
+
}
|
129
|
+
this.getsubtunes = function() {
|
130
|
+
return subtune_amount;
|
131
|
+
}
|
132
|
+
this.getprefmodel = function() {
|
133
|
+
return preferred_SID_model[0];
|
134
|
+
}
|
135
|
+
this.getmodel = function() {
|
136
|
+
return SID_model;
|
137
|
+
}
|
138
|
+
this.getoutput = function() {
|
139
|
+
return (output / OUTPUT_SCALEDOWN) * (memory[0xD418] & 0xF);
|
140
|
+
}
|
141
|
+
this.getplaytime = function() {
|
142
|
+
return parseInt(playtime);
|
143
|
+
}
|
144
|
+
this.setmodel = function(model) {
|
145
|
+
SID_model = model;
|
146
|
+
}
|
147
|
+
this.setvolume = function(vol) {
|
148
|
+
volume = vol;
|
149
|
+
}
|
150
|
+
this.setloadcallback = function(fname) {
|
151
|
+
loadcallback = fname;
|
152
|
+
}
|
153
|
+
this.setstartcallback = function(fname) {
|
154
|
+
startcallback = fname;
|
155
|
+
}
|
156
|
+
this.setendcallback = function(fname, seconds) {
|
157
|
+
endcallback = fname;
|
158
|
+
playlength = seconds;
|
159
|
+
}
|
160
|
+
this.setmemorywritecallback = function(fname) {
|
161
|
+
memorywritecallback = fname;
|
162
|
+
}
|
163
|
+
var //emulated machine constants
|
164
|
+
C64_PAL_CPUCLK = 985248, //Hz
|
165
|
+
PAL_FRAMERATE = 50, //NTSC_FRAMERATE = 60;
|
166
|
+
SID_CHANNEL_AMOUNT = 3,
|
167
|
+
OUTPUT_SCALEDOWN = 0x10000 * SID_CHANNEL_AMOUNT * 16;
|
168
|
+
var SIDamount_vol = [0, 1, 0.6, 0.4]; //how much to attenuate with more 2SID/3SID to avoid master-output overflows
|
169
|
+
|
170
|
+
//SID playback related arrays/variables - avoiding internal/automatic variables to retain speed
|
171
|
+
var SIDtitle = new Uint8Array(0x20);
|
172
|
+
var SIDauthor = new Uint8Array(0x20);
|
173
|
+
var SIDinfo = new Uint8Array(0x20);
|
174
|
+
var timermode = new Uint8Array(0x20);
|
175
|
+
var loadaddr = 0x1000,
|
176
|
+
initaddr = 0x1000,
|
177
|
+
playaddf = 0x1003,
|
178
|
+
playaddr = 0x1003,
|
179
|
+
subtune = 0,
|
180
|
+
subtune_amount = 1,
|
181
|
+
playlength = 0; //framespeed = 1;
|
182
|
+
var preferred_SID_model = [8580.0, 8580.0, 8580.0];
|
183
|
+
var SID_model = 8580.0;
|
184
|
+
var SID_address = [0xD400, 0, 0];
|
185
|
+
var memory = new Uint8Array(65536); //for(var i=0;i<memory.length;i++) memory[i]=0;
|
186
|
+
var loaded = 0,
|
187
|
+
initialized = 0,
|
188
|
+
finished = 0,
|
189
|
+
loadcallback = null,
|
190
|
+
startcallback = null,
|
191
|
+
endcallback = null,
|
192
|
+
memorywritecallback = null,
|
193
|
+
playtime = 0,
|
194
|
+
ended = 0;
|
195
|
+
var clk_ratio = C64_PAL_CPUCLK / samplerate;
|
196
|
+
var frame_sampleperiod = samplerate / PAL_FRAMERATE; //samplerate/(PAL_FRAMERATE*framespeed);
|
197
|
+
var framecnt = 1,
|
198
|
+
volume = 1.0,
|
199
|
+
CPUtime = 0,
|
200
|
+
pPC;
|
201
|
+
var SIDamount = 1,
|
202
|
+
mix = 0;
|
203
|
+
|
204
|
+
function init(subt) {
|
205
|
+
if (loaded) {
|
206
|
+
initialized = 0;
|
207
|
+
subtune = subt;
|
208
|
+
initCPU(initaddr);
|
209
|
+
initSID();
|
210
|
+
A = subtune;
|
211
|
+
memory[1] = 0x37;
|
212
|
+
memory[0xDC05] = 0;
|
213
|
+
for (var timeout = 100000; timeout >= 0; timeout--) {
|
214
|
+
if (CPU()) break;
|
215
|
+
}
|
216
|
+
if (timermode[subtune] || memory[0xDC05]) { //&& playaddf { //CIA timing
|
217
|
+
if (!memory[0xDC05]) {
|
218
|
+
memory[0xDC04] = 0x24;
|
219
|
+
memory[0xDC05] = 0x40;
|
220
|
+
}
|
221
|
+
frame_sampleperiod = (memory[0xDC04] + memory[0xDC05] * 256) / clk_ratio;
|
222
|
+
} else frame_sampleperiod = samplerate / PAL_FRAMERATE; //Vsync timing
|
223
|
+
//frame_sampleperiod = (memory[0xDC05]!=0 || (!timermode[subtune] && playaddf))? samplerate/PAL_FRAMERATE : (memory[0xDC04] + memory[0xDC05]*256) / clk_ratio;
|
224
|
+
if (playaddf == 0) playaddr = ((memory[1] & 3) < 2) ? memory[0xFFFE] + memory[0xFFFF] * 256 : memory[
|
225
|
+
0x314] + memory[0x315] * 256;
|
226
|
+
else {
|
227
|
+
playaddr = playaddf;
|
228
|
+
if (playaddr >= 0xE000 && memory[1] == 0x37) memory[1] = 0x35;
|
229
|
+
} //player under KERNAL (Crystal Kingdom Dizzy)
|
230
|
+
initCPU(playaddr);
|
231
|
+
framecnt = 1;
|
232
|
+
finished = 0;
|
233
|
+
CPUtime = 0;
|
234
|
+
playtime = 0;
|
235
|
+
ended = 0;
|
236
|
+
initialized = 1;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
function play() {
|
241
|
+
//called internally by the Web Audio API scriptNode callback;
|
242
|
+
//handles SID-register reading/processing and SID emulation
|
243
|
+
if (loaded && initialized) {
|
244
|
+
framecnt--;
|
245
|
+
playtime += 1 / samplerate;
|
246
|
+
if (framecnt <= 0) {
|
247
|
+
framecnt = frame_sampleperiod;
|
248
|
+
finished = 0;
|
249
|
+
PC = playaddr;
|
250
|
+
SP = 0xFF;
|
251
|
+
}
|
252
|
+
if (finished == 0) {
|
253
|
+
while (CPUtime <= clk_ratio) {
|
254
|
+
pPC = PC;
|
255
|
+
if (CPU() >= 0xFE) {
|
256
|
+
finished = 1;
|
257
|
+
break;
|
258
|
+
} else CPUtime += cycles;
|
259
|
+
if ((memory[1] & 3) > 1 && pPC < 0xE000 && (PC == 0xEA31 || PC == 0xEA81)) {
|
260
|
+
finished = 1;
|
261
|
+
break;
|
262
|
+
} //IRQ player ROM return handling
|
263
|
+
if ((addr == 0xDC05 || addr == 0xDC04) && (memory[1] & 3) && timermode[subtune]) frame_sampleperiod =
|
264
|
+
(memory[0xDC04] + memory[0xDC05] * 256) / clk_ratio; //Galway/Rubicon workaround
|
265
|
+
if (storadd >= 0xD420 && storadd < 0xD800 && (memory[1] & 3)) { //CJ in the USA workaround (writing above $d420, except SID2/SID3)
|
266
|
+
if (!(SID_address[1] <= storadd && storadd < SID_address[1] + 0x1F) && !(SID_address[2] <=
|
267
|
+
storadd && storadd < SID_address[2] + 0x1F))
|
268
|
+
memoryWrite(storadd & 0xD41F, memory[storadd]);
|
269
|
+
}
|
270
|
+
if (addr == 0xD404 && !(memory[0xD404] & 1)) ADSRstate[0] &= 0x3E;
|
271
|
+
if (addr == 0xD40B && !(memory[0xD40B] & 1)) ADSRstate[1] &= 0x3E;
|
272
|
+
if (addr == 0xD412 && !(memory[0xD412] & 1)) ADSRstate[2] &= 0x3E; //Whittaker player workaround
|
273
|
+
}
|
274
|
+
CPUtime -= clk_ratio;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
if (playlength > 0 && parseInt(playtime) == parseInt(playlength) && endcallback !== null && ended == 0) {
|
279
|
+
ended = 1;
|
280
|
+
endcallback();
|
281
|
+
}
|
282
|
+
mix = SID(0, 0xD400);
|
283
|
+
if (SID_address[1]) mix += SID(1, SID_address[1]);
|
284
|
+
if (SID_address[2]) mix += SID(2, SID_address[2]);
|
285
|
+
|
286
|
+
return mix * volume * SIDamount_vol[SIDamount] + (Math.random() * background_noise - background_noise / 2);
|
287
|
+
}
|
288
|
+
|
289
|
+
|
290
|
+
var //CPU (and CIA/VIC-IRQ) emulation constants and variables - avoiding internal/automatic variables to retain speed
|
291
|
+
flagsw = [0x01, 0x21, 0x04, 0x24, 0x00, 0x40, 0x08, 0x28],
|
292
|
+
branchflag = [0x80, 0x40, 0x01, 0x02];
|
293
|
+
var PC = 0,
|
294
|
+
A = 0,
|
295
|
+
T = 0,
|
296
|
+
X = 0,
|
297
|
+
Y = 0,
|
298
|
+
SP = 0xFF,
|
299
|
+
IR = 0,
|
300
|
+
addr = 0,
|
301
|
+
ST = 0x00,
|
302
|
+
cycles = 0,
|
303
|
+
storadd = 0; //STATUS-flags: N V - B D I Z C
|
304
|
+
|
305
|
+
function initCPU(mempos) {
|
306
|
+
PC = mempos;
|
307
|
+
A = 0;
|
308
|
+
X = 0;
|
309
|
+
Y = 0;
|
310
|
+
ST = 0;
|
311
|
+
SP = 0xFF;
|
312
|
+
}
|
313
|
+
|
314
|
+
//My CPU implementation is based on the instruction table by Graham at codebase64.
|
315
|
+
//After some examination of the table it was clearly seen that columns of the table (instructions' 2nd nybbles)
|
316
|
+
// mainly correspond to addressing modes, and double-rows usually have the same instructions.
|
317
|
+
//The code below is laid out like this, with some exceptions present.
|
318
|
+
//Thanks to the hardware being in my mind when coding this, more of the illegal instructions can be added fairly easily...
|
319
|
+
|
320
|
+
function memoryWrite(addr, val){
|
321
|
+
memory[addr] = val
|
322
|
+
if(memorywritecallback){
|
323
|
+
memorywritecallback(addr, val);
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
function CPU() //the CPU emulation for SID/PRG playback (ToDo: CIA/VIC-IRQ/NMI/RESET vectors, BCD-mode)
|
328
|
+
{ //'IR' is the instruction-register, naming after the hardware-equivalent
|
329
|
+
IR = memory[PC];
|
330
|
+
cycles = 2;
|
331
|
+
storadd = 0; //'cycle': ensure smallest 6510 runtime (for implied/register instructions)
|
332
|
+
|
333
|
+
if (IR & 1) { //nybble2: 1/5/9/D:accu.instructions, 3/7/B/F:illegal opcodes
|
334
|
+
switch (IR & 0x1F) { //addressing modes (begin with more complex cases), PC wraparound not handled inside to save codespace
|
335
|
+
case 1:
|
336
|
+
case 3:
|
337
|
+
addr = memory[memory[++PC] + X] + memory[memory[PC] + X + 1] * 256;
|
338
|
+
cycles = 6;
|
339
|
+
break; //(zp,x)
|
340
|
+
case 0x11:
|
341
|
+
case 0x13:
|
342
|
+
addr = memory[memory[++PC]] + memory[memory[PC] + 1] * 256 + Y;
|
343
|
+
cycles = 6;
|
344
|
+
break; //(zp),y
|
345
|
+
case 0x19:
|
346
|
+
case 0x1F:
|
347
|
+
addr = memory[++PC] + memory[++PC] * 256 + Y;
|
348
|
+
cycles = 5;
|
349
|
+
break; //abs,y
|
350
|
+
case 0x1D:
|
351
|
+
addr = memory[++PC] + memory[++PC] * 256 + X;
|
352
|
+
cycles = 5;
|
353
|
+
break; //abs,x
|
354
|
+
case 0xD:
|
355
|
+
case 0xF:
|
356
|
+
addr = memory[++PC] + memory[++PC] * 256;
|
357
|
+
cycles = 4;
|
358
|
+
break; //abs
|
359
|
+
case 0x15:
|
360
|
+
addr = memory[++PC] + X;
|
361
|
+
cycles = 4;
|
362
|
+
break; //zp,x
|
363
|
+
case 5:
|
364
|
+
case 7:
|
365
|
+
addr = memory[++PC];
|
366
|
+
cycles = 3;
|
367
|
+
break; //zp
|
368
|
+
case 0x17:
|
369
|
+
addr = memory[++PC] + Y;
|
370
|
+
cycles = 4;
|
371
|
+
break; //zp,y for LAX/SAX illegal opcodes
|
372
|
+
case 9:
|
373
|
+
case 0xB:
|
374
|
+
addr = ++PC;
|
375
|
+
cycles = 2; //immediate
|
376
|
+
}
|
377
|
+
addr &= 0xFFFF;
|
378
|
+
switch (IR & 0xE0) {
|
379
|
+
case 0x60:
|
380
|
+
T = A;
|
381
|
+
A += memory[addr] + (ST & 1);
|
382
|
+
ST &= 20;
|
383
|
+
ST |= (A & 128) | (A > 255);
|
384
|
+
A &= 0xFF;
|
385
|
+
ST |= (!A) << 1 | (!((T ^ memory[addr]) & 0x80) && ((T ^ A) & 0x80)) >> 1;
|
386
|
+
break; //ADC
|
387
|
+
case 0xE0:
|
388
|
+
T = A;
|
389
|
+
A -= memory[addr] + !(ST & 1);
|
390
|
+
ST &= 20;
|
391
|
+
ST |= (A & 128) | (A >= 0);
|
392
|
+
A &= 0xFF;
|
393
|
+
ST |= (!A) << 1 | (((T ^ memory[addr]) & 0x80) && ((T ^ A) & 0x80)) >> 1;
|
394
|
+
break; //SBC
|
395
|
+
case 0xC0:
|
396
|
+
T = A - memory[addr];
|
397
|
+
ST &= 124;
|
398
|
+
ST |= (!(T & 0xFF)) << 1 | (T & 128) | (T >= 0);
|
399
|
+
break; //CMP
|
400
|
+
case 0x00:
|
401
|
+
A |= memory[addr];
|
402
|
+
ST &= 125;
|
403
|
+
ST |= (!A) << 1 | (A & 128);
|
404
|
+
break; //ORA
|
405
|
+
case 0x20:
|
406
|
+
A &= memory[addr];
|
407
|
+
ST &= 125;
|
408
|
+
ST |= (!A) << 1 | (A & 128);
|
409
|
+
break; //AND
|
410
|
+
case 0x40:
|
411
|
+
A ^= memory[addr];
|
412
|
+
ST &= 125;
|
413
|
+
ST |= (!A) << 1 | (A & 128);
|
414
|
+
break; //EOR
|
415
|
+
case 0xA0:
|
416
|
+
A = memory[addr];
|
417
|
+
ST &= 125;
|
418
|
+
ST |= (!A) << 1 | (A & 128);
|
419
|
+
if ((IR & 3) == 3) X = A;
|
420
|
+
break; //LDA / LAX (illegal, used by my 1 rasterline player)
|
421
|
+
case 0x80:
|
422
|
+
memoryWrite(addr, A & (((IR & 3) == 3) ? X : 0xFF));
|
423
|
+
storadd = addr; //STA / SAX (illegal)
|
424
|
+
}
|
425
|
+
} else if (IR & 2) { //nybble2: 2:illegal/LDX, 6:A/X/INC/DEC, A:Accu-shift/reg.transfer/NOP, E:shift/X/INC/DEC
|
426
|
+
switch (IR & 0x1F) { //addressing modes
|
427
|
+
case 0x1E:
|
428
|
+
addr = memory[++PC] + memory[++PC] * 256 + (((IR & 0xC0) != 0x80) ? X : Y);
|
429
|
+
cycles = 5;
|
430
|
+
break; //abs,x / abs,y
|
431
|
+
case 0xE:
|
432
|
+
addr = memory[++PC] + memory[++PC] * 256;
|
433
|
+
cycles = 4;
|
434
|
+
break; //abs
|
435
|
+
case 0x16:
|
436
|
+
addr = memory[++PC] + (((IR & 0xC0) != 0x80) ? X : Y);
|
437
|
+
cycles = 4;
|
438
|
+
break; //zp,x / zp,y
|
439
|
+
case 6:
|
440
|
+
addr = memory[++PC];
|
441
|
+
cycles = 3;
|
442
|
+
break; //zp
|
443
|
+
case 2:
|
444
|
+
addr = ++PC;
|
445
|
+
cycles = 2; //imm.
|
446
|
+
}
|
447
|
+
addr &= 0xFFFF;
|
448
|
+
switch (IR & 0xE0) {
|
449
|
+
case 0x00:
|
450
|
+
ST &= 0xFE;
|
451
|
+
case 0x20:
|
452
|
+
if ((IR & 0xF) == 0xA) {
|
453
|
+
A = (A << 1) + (ST & 1);
|
454
|
+
ST &= 60;
|
455
|
+
ST |= (A & 128) | (A > 255);
|
456
|
+
A &= 0xFF;
|
457
|
+
ST |= (!A) << 1;
|
458
|
+
} //ASL/ROL (Accu)
|
459
|
+
else {
|
460
|
+
T = (memory[addr] << 1) + (ST & 1);
|
461
|
+
ST &= 60;
|
462
|
+
ST |= (T & 128) | (T > 255);
|
463
|
+
T &= 0xFF;
|
464
|
+
ST |= (!T) << 1;
|
465
|
+
memoryWrite(addr, T);
|
466
|
+
cycles += 2;
|
467
|
+
}
|
468
|
+
break; //RMW (Read-Write-Modify)
|
469
|
+
case 0x40:
|
470
|
+
ST &= 0xFE;
|
471
|
+
case 0x60:
|
472
|
+
if ((IR & 0xF) == 0xA) {
|
473
|
+
T = A;
|
474
|
+
A = (A >> 1) + (ST & 1) * 128;
|
475
|
+
ST &= 60;
|
476
|
+
ST |= (A & 128) | (T & 1);
|
477
|
+
A &= 0xFF;
|
478
|
+
ST |= (!A) << 1;
|
479
|
+
} //LSR/ROR (Accu)
|
480
|
+
else {
|
481
|
+
T = (memory[addr] >> 1) + (ST & 1) * 128;
|
482
|
+
ST &= 60;
|
483
|
+
ST |= (T & 128) | (memory[addr] & 1);
|
484
|
+
T &= 0xFF;
|
485
|
+
ST |= (!T) << 1;
|
486
|
+
memoryWrite(addr, T);
|
487
|
+
cycles += 2;
|
488
|
+
}
|
489
|
+
break; //RMW
|
490
|
+
case 0xC0:
|
491
|
+
if (IR & 4) {
|
492
|
+
memory[addr]--;
|
493
|
+
memory[addr] &= 0xFF;
|
494
|
+
ST &= 125;
|
495
|
+
ST |= (!memory[addr]) << 1 | (memory[addr] & 128);
|
496
|
+
cycles += 2;
|
497
|
+
} //DEC
|
498
|
+
else {
|
499
|
+
X--;
|
500
|
+
X &= 0xFF;
|
501
|
+
ST &= 125;
|
502
|
+
ST |= (!X) << 1 | (X & 128);
|
503
|
+
}
|
504
|
+
break; //DEX
|
505
|
+
case 0xA0:
|
506
|
+
if ((IR & 0xF) != 0xA) X = memory[addr];
|
507
|
+
else if (IR & 0x10) {
|
508
|
+
X = SP;
|
509
|
+
break;
|
510
|
+
} else X = A;
|
511
|
+
ST &= 125;
|
512
|
+
ST |= (!X) << 1 | (X & 128);
|
513
|
+
break; //LDX/TSX/TAX
|
514
|
+
case 0x80:
|
515
|
+
if (IR & 4) {
|
516
|
+
memoryWrite(addr, X);
|
517
|
+
storadd = addr;
|
518
|
+
} else if (IR & 0x10) SP = X;
|
519
|
+
else {
|
520
|
+
A = X;
|
521
|
+
ST &= 125;
|
522
|
+
ST |= (!A) << 1 | (A & 128);
|
523
|
+
}
|
524
|
+
break; //STX/TXS/TXA
|
525
|
+
case 0xE0:
|
526
|
+
if (IR & 4) {
|
527
|
+
memoryWrite(addr, memory[addr] + 1);
|
528
|
+
memoryWrite(addr, memory[addr] & 0xFF);
|
529
|
+
ST &= 125;
|
530
|
+
ST |= (!memory[addr]) << 1 | (memory[addr] & 128);
|
531
|
+
cycles += 2;
|
532
|
+
} //INC/NOP
|
533
|
+
}
|
534
|
+
} else if ((IR & 0xC) == 8) { //nybble2: 8:register/status
|
535
|
+
switch (IR & 0xF0) {
|
536
|
+
case 0x60:
|
537
|
+
SP++;
|
538
|
+
SP &= 0xFF;
|
539
|
+
A = memory[0x100 + SP];
|
540
|
+
ST &= 125;
|
541
|
+
ST |= (!A) << 1 | (A & 128);
|
542
|
+
cycles = 4;
|
543
|
+
break; //PLA
|
544
|
+
case 0xC0:
|
545
|
+
Y++;
|
546
|
+
Y &= 0xFF;
|
547
|
+
ST &= 125;
|
548
|
+
ST |= (!Y) << 1 | (Y & 128);
|
549
|
+
break; //INY
|
550
|
+
case 0xE0:
|
551
|
+
X++;
|
552
|
+
X &= 0xFF;
|
553
|
+
ST &= 125;
|
554
|
+
ST |= (!X) << 1 | (X & 128);
|
555
|
+
break; //INX
|
556
|
+
case 0x80:
|
557
|
+
Y--;
|
558
|
+
Y &= 0xFF;
|
559
|
+
ST &= 125;
|
560
|
+
ST |= (!Y) << 1 | (Y & 128);
|
561
|
+
break; //DEY
|
562
|
+
case 0x00:
|
563
|
+
memoryWrite(0x100 + SP, ST);
|
564
|
+
SP--;
|
565
|
+
SP &= 0xFF;
|
566
|
+
cycles = 3;
|
567
|
+
break; //PHP
|
568
|
+
case 0x20:
|
569
|
+
SP++;
|
570
|
+
SP &= 0xFF;
|
571
|
+
ST = memory[0x100 + SP];
|
572
|
+
cycles = 4;
|
573
|
+
break; //PLP
|
574
|
+
case 0x40:
|
575
|
+
memoryWrite(0x100 + SP, A);
|
576
|
+
SP--;
|
577
|
+
SP &= 0xFF;
|
578
|
+
cycles = 3;
|
579
|
+
break; //PHA
|
580
|
+
case 0x90:
|
581
|
+
A = Y;
|
582
|
+
ST &= 125;
|
583
|
+
ST |= (!A) << 1 | (A & 128);
|
584
|
+
break; //TYA
|
585
|
+
case 0xA0:
|
586
|
+
Y = A;
|
587
|
+
ST &= 125;
|
588
|
+
ST |= (!Y) << 1 | (Y & 128);
|
589
|
+
break; //TAY
|
590
|
+
default:
|
591
|
+
if (flagsw[IR >> 5] & 0x20) ST |= (flagsw[IR >> 5] & 0xDF);
|
592
|
+
else ST &= 255 - (flagsw[IR >> 5] & 0xDF); //CLC/SEC/CLI/SEI/CLV/CLD/SED
|
593
|
+
}
|
594
|
+
} else { //nybble2: 0: control/branch/Y/compare 4: Y/compare C:Y/compare/JMP
|
595
|
+
if ((IR & 0x1F) == 0x10) {
|
596
|
+
PC++;
|
597
|
+
T = memory[PC];
|
598
|
+
if (T & 0x80) T -= 0x100; //BPL/BMI/BVC/BVS/BCC/BCS/BNE/BEQ relative branch
|
599
|
+
if (IR & 0x20) {
|
600
|
+
if (ST & branchflag[IR >> 6]) {
|
601
|
+
PC += T;
|
602
|
+
cycles = 3;
|
603
|
+
}
|
604
|
+
} else {
|
605
|
+
if (!(ST & branchflag[IR >> 6])) {
|
606
|
+
PC += T;
|
607
|
+
cycles = 3;
|
608
|
+
}
|
609
|
+
}
|
610
|
+
} else { //nybble2: 0:Y/control/Y/compare 4:Y/compare C:Y/compare/JMP
|
611
|
+
switch (IR & 0x1F) { //addressing modes
|
612
|
+
case 0:
|
613
|
+
addr = ++PC;
|
614
|
+
cycles = 2;
|
615
|
+
break; //imm. (or abs.low for JSR/BRK)
|
616
|
+
case 0x1C:
|
617
|
+
addr = memory[++PC] + memory[++PC] * 256 + X;
|
618
|
+
cycles = 5;
|
619
|
+
break; //abs,x
|
620
|
+
case 0xC:
|
621
|
+
addr = memory[++PC] + memory[++PC] * 256;
|
622
|
+
cycles = 4;
|
623
|
+
break; //abs
|
624
|
+
case 0x14:
|
625
|
+
addr = memory[++PC] + X;
|
626
|
+
cycles = 4;
|
627
|
+
break; //zp,x
|
628
|
+
case 4:
|
629
|
+
addr = memory[++PC];
|
630
|
+
cycles = 3; //zp
|
631
|
+
}
|
632
|
+
addr &= 0xFFFF;
|
633
|
+
switch (IR & 0xE0) {
|
634
|
+
case 0x00:
|
635
|
+
memoryWrite(0x100 + SP, PC % 256);
|
636
|
+
SP--;
|
637
|
+
SP &= 0xFF;
|
638
|
+
memoryWrite(0x100 + SP, PC / 256);
|
639
|
+
SP--;
|
640
|
+
SP &= 0xFF;
|
641
|
+
memoryWrite(0x100 + SP, ST);
|
642
|
+
SP--;
|
643
|
+
SP &= 0xFF;
|
644
|
+
PC = memory[0xFFFE] + memory[0xFFFF] * 256 - 1;
|
645
|
+
cycles = 7;
|
646
|
+
break; //BRK
|
647
|
+
case 0x20:
|
648
|
+
if (IR & 0xF) {
|
649
|
+
ST &= 0x3D;
|
650
|
+
ST |= (memory[addr] & 0xC0) | (!(A & memory[addr])) << 1;
|
651
|
+
} //BIT
|
652
|
+
else {
|
653
|
+
memoryWrite(0x100 + SP, (PC + 2) % 256);
|
654
|
+
SP--;
|
655
|
+
SP &= 0xFF;
|
656
|
+
memoryWrite(0x100 + SP, (PC + 2) / 256);
|
657
|
+
SP--;
|
658
|
+
SP &= 0xFF;
|
659
|
+
PC = memory[addr] + memory[addr + 1] * 256 - 1;
|
660
|
+
cycles = 6;
|
661
|
+
}
|
662
|
+
break; //JSR
|
663
|
+
case 0x40:
|
664
|
+
if (IR & 0xF) {
|
665
|
+
PC = addr - 1;
|
666
|
+
cycles = 3;
|
667
|
+
} //JMP
|
668
|
+
else {
|
669
|
+
if (SP >= 0xFF) return 0xFE;
|
670
|
+
SP++;
|
671
|
+
SP &= 0xFF;
|
672
|
+
ST = memory[0x100 + SP];
|
673
|
+
SP++;
|
674
|
+
SP &= 0xFF;
|
675
|
+
T = memory[0x100 + SP];
|
676
|
+
SP++;
|
677
|
+
SP &= 0xFF;
|
678
|
+
PC = memory[0x100 + SP] + T * 256 - 1;
|
679
|
+
cycles = 6;
|
680
|
+
}
|
681
|
+
break; //RTI
|
682
|
+
case 0x60:
|
683
|
+
if (IR & 0xF) {
|
684
|
+
PC = memory[addr] + memory[addr + 1] * 256 - 1;
|
685
|
+
cycles = 5;
|
686
|
+
} //JMP() (indirect)
|
687
|
+
else {
|
688
|
+
if (SP >= 0xFF) return 0xFF;
|
689
|
+
SP++;
|
690
|
+
SP &= 0xFF;
|
691
|
+
T = memory[0x100 + SP];
|
692
|
+
SP++;
|
693
|
+
SP &= 0xFF;
|
694
|
+
PC = memory[0x100 + SP] + T * 256 - 1;
|
695
|
+
cycles = 6;
|
696
|
+
}
|
697
|
+
break; //RTS
|
698
|
+
case 0xC0:
|
699
|
+
T = Y - memory[addr];
|
700
|
+
ST &= 124;
|
701
|
+
ST |= (!(T & 0xFF)) << 1 | (T & 128) | (T >= 0);
|
702
|
+
break; //CPY
|
703
|
+
case 0xE0:
|
704
|
+
T = X - memory[addr];
|
705
|
+
ST &= 124;
|
706
|
+
ST |= (!(T & 0xFF)) << 1 | (T & 128) | (T >= 0);
|
707
|
+
break; //CPX
|
708
|
+
case 0xA0:
|
709
|
+
Y = memory[addr];
|
710
|
+
ST &= 125;
|
711
|
+
ST |= (!Y) << 1 | (Y & 128);
|
712
|
+
break; //LDY
|
713
|
+
case 0x80:
|
714
|
+
memoryWrite(addr, Y);
|
715
|
+
storadd = addr; //STY
|
716
|
+
}
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
PC++;
|
721
|
+
PC &= 0xFFFF;
|
722
|
+
return 0; //memory[addr]&=0xFF;
|
723
|
+
}
|
724
|
+
|
725
|
+
|
726
|
+
//My SID implementation is similar to what I worked out in a SwinSID variant during 3..4 months of development. (So jsSID only took 2 weeks armed with this experience.)
|
727
|
+
//I learned the workings of ADSR/WAVE/filter operations mainly from the quite well documented resid and resid-fp codes.
|
728
|
+
//(The SID reverse-engineering sites were also good sources.)
|
729
|
+
//Note that I avoided internal/automatic variables from the SID function, assuming that JavaScript is better this way. (Not using stack as much, but I'm not sure and it may depend on platform...)
|
730
|
+
//So I advise to keep them here. As they have 'var' in the declaration, they are in this scope and won't interfere with anything outside jsSID.
|
731
|
+
//(The same is true for CPU emulation and player.)
|
732
|
+
|
733
|
+
var //SID emulation constants and variables
|
734
|
+
GATE_BITMASK = 0x01,
|
735
|
+
SYNC_BITMASK = 0x02,
|
736
|
+
RING_BITMASK = 0x04,
|
737
|
+
TEST_BITMASK = 0x08,
|
738
|
+
TRI_BITMASK = 0x10,
|
739
|
+
SAW_BITMASK = 0x20,
|
740
|
+
PULSE_BITMASK = 0x40,
|
741
|
+
NOISE_BITMASK = 0x80,
|
742
|
+
HOLDZERO_BITMASK = 0x10,
|
743
|
+
DECAYSUSTAIN_BITMASK = 0x40,
|
744
|
+
ATTACK_BITMASK = 0x80,
|
745
|
+
FILTSW = [1, 2, 4, 1, 2, 4, 1, 2, 4],
|
746
|
+
LOWPASS_BITMASK = 0x10,
|
747
|
+
BANDPASS_BITMASK = 0x20,
|
748
|
+
HIGHPASS_BITMASK = 0x40,
|
749
|
+
OFF3_BITMASK = 0x80;
|
750
|
+
var ADSRstate = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
751
|
+
ratecnt = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
752
|
+
envcnt = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
753
|
+
expcnt = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
754
|
+
prevSR = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
755
|
+
var phaseaccu = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
756
|
+
prevaccu = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
757
|
+
sourceMSBrise = [0, 0, 0],
|
758
|
+
sourceMSB = [0, 0, 0];
|
759
|
+
var noise_LFSR = [0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8, 0x7FFFF8];
|
760
|
+
var prevwfout = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
761
|
+
prevwavdata = [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
762
|
+
combiwf;
|
763
|
+
var prevlowpass = [0, 0, 0],
|
764
|
+
prevbandpass = [0, 0, 0],
|
765
|
+
cutoff_ratio_8580 = -2 * 3.14 * (12500 / 256) / samplerate,
|
766
|
+
cutoff_ratio_6581 = -2 * 3.14 * (20000 / 256) / samplerate;
|
767
|
+
var prevgate, chnadd, ctrl, wf, test, period, step, SR, accuadd, MSB, tmp, pw, lim, wfout, cutoff,
|
768
|
+
resonance, filtin, output;
|
769
|
+
//registers: 0:freql1 1:freqh1 2:pwml1 3:pwmh1 4:ctrl1 5:ad1 6:sr1 7:freql2 8:freqh2 9:pwml2 10:pwmh2 11:ctrl2 12:ad2 13:sr 14:freql3 15:freqh3 16:pwml3 17:pwmh3 18:ctrl3 19:ad3 20:sr3
|
770
|
+
// 21:cutoffl 22:cutoffh 23:flsw_reso 24:vol_ftype 25:potX 26:potY 27:OSC3 28:ENV3
|
771
|
+
|
772
|
+
function initSID() {
|
773
|
+
for (var i = 0xD400; i <= 0xD7FF; i++) memory[i] = 0;
|
774
|
+
for (var i = 0xDE00; i <= 0xDFFF; i++) memory[i] = 0;
|
775
|
+
for (var i = 0; i < 9; i++) {
|
776
|
+
ADSRstate[i] = HOLDZERO_BITMASK;
|
777
|
+
ratecnt[i] = envcnt[i] = expcnt[i] = prevSR[i] = 0;
|
778
|
+
}
|
779
|
+
}
|
780
|
+
|
781
|
+
|
782
|
+
function SID(num, SIDaddr) //the SID emulation itself ('num' is the number of SID to iterate (0..2)
|
783
|
+
{
|
784
|
+
filtin = 0;
|
785
|
+
output = 0;
|
786
|
+
|
787
|
+
//treating 2SID and 3SID channels uniformly (0..5 / 0..8), this probably avoids some extra code
|
788
|
+
for (var channel = num * SID_CHANNEL_AMOUNT; channel < (num + 1) * SID_CHANNEL_AMOUNT; channel++) {
|
789
|
+
prevgate = (ADSRstate[channel] & GATE_BITMASK);
|
790
|
+
chnadd = SIDaddr + (channel - num * SID_CHANNEL_AMOUNT) * 7;
|
791
|
+
ctrl = memory[chnadd + 4];
|
792
|
+
wf = ctrl & 0xF0;
|
793
|
+
test = ctrl & TEST_BITMASK;
|
794
|
+
SR = memory[chnadd + 6];
|
795
|
+
tmp = 0;
|
796
|
+
|
797
|
+
//ADSR envelope generator:
|
798
|
+
if (prevgate != (ctrl & GATE_BITMASK)) { //gatebit-change?
|
799
|
+
if (prevgate) {
|
800
|
+
ADSRstate[channel] &= 0xFF - (GATE_BITMASK | ATTACK_BITMASK | DECAYSUSTAIN_BITMASK);
|
801
|
+
} //falling edge (with Whittaker workaround this never happens, but should be here)
|
802
|
+
else {
|
803
|
+
ADSRstate[channel] = (GATE_BITMASK | ATTACK_BITMASK | DECAYSUSTAIN_BITMASK); //rising edge, also sets hold_zero_bit=0
|
804
|
+
if ((SR & 0xF) > (prevSR[channel] & 0xF)) tmp = 1; //assume SR->GATE write order: workaround to have crisp soundstarts by triggering delay-bug
|
805
|
+
} //(this is for the possible missed CTRL(GATE) vs SR register write order situations (1MHz CPU is cca 20 times faster than samplerate)
|
806
|
+
}
|
807
|
+
prevSR[channel] = SR;
|
808
|
+
|
809
|
+
ratecnt[channel] += clk_ratio;
|
810
|
+
if (ratecnt[channel] >= 0x8000) ratecnt[channel] -= 0x8000; //can wrap around (ADSR delay-bug: short 1st frame is usually achieved by utilizing this bug)
|
811
|
+
|
812
|
+
//set ADSR period that should be checked against rate-counter (depending on ADSR state Attack/DecaySustain/Release)
|
813
|
+
if (ADSRstate[channel] & ATTACK_BITMASK) {
|
814
|
+
step = memory[chnadd + 5] >> 4;
|
815
|
+
period = ADSRperiods[step];
|
816
|
+
} else if (ADSRstate[channel] & DECAYSUSTAIN_BITMASK) {
|
817
|
+
step = memory[chnadd + 5] & 0xF;
|
818
|
+
period = ADSRperiods[step];
|
819
|
+
} else {
|
820
|
+
step = SR & 0xF;
|
821
|
+
period = ADSRperiods[step];
|
822
|
+
}
|
823
|
+
step = ADSRstep[step];
|
824
|
+
|
825
|
+
if (ratecnt[channel] >= period && ratecnt[channel] < period + clk_ratio && tmp == 0) { //ratecounter shot (matches rateperiod) (in genuine SID ratecounter is LFSR)
|
826
|
+
ratecnt[channel] -= period; //compensation for timing instead of simply setting 0 on rate-counter overload
|
827
|
+
if ((ADSRstate[channel] & ATTACK_BITMASK) || ++expcnt[channel] == ADSR_exptable[envcnt[channel]]) {
|
828
|
+
if (!(ADSRstate[channel] & HOLDZERO_BITMASK)) {
|
829
|
+
if (ADSRstate[channel] & ATTACK_BITMASK) {
|
830
|
+
envcnt[channel] += step;
|
831
|
+
if (envcnt[channel] >= 0xFF) {
|
832
|
+
envcnt[channel] = 0xFF;
|
833
|
+
ADSRstate[channel] &= 0xFF - ATTACK_BITMASK;
|
834
|
+
}
|
835
|
+
} else if (!(ADSRstate[channel] & DECAYSUSTAIN_BITMASK) || envcnt[channel] > (SR >> 4) + (SR &
|
836
|
+
0xF0)) {
|
837
|
+
envcnt[channel] -= step;
|
838
|
+
if (envcnt[channel] <= 0 && envcnt[channel] + step != 0) {
|
839
|
+
envcnt[channel] = 0;
|
840
|
+
ADSRstate[channel] |= HOLDZERO_BITMASK;
|
841
|
+
}
|
842
|
+
}
|
843
|
+
}
|
844
|
+
expcnt[channel] = 0;
|
845
|
+
}
|
846
|
+
}
|
847
|
+
|
848
|
+
envcnt[channel] &= 0xFF; //'envcnt' may wrap around in some cases, mostly 0 -> FF (e.g.: Cloudless Rain, Boombox Alley)
|
849
|
+
|
850
|
+
//WAVE generation codes (phase accumulator and waveform-selector): (They are explained in resid source, I won't go in details, the code speaks for itself.)
|
851
|
+
accuadd = (memory[chnadd] + memory[chnadd + 1] * 256) * clk_ratio;
|
852
|
+
if (test || ((ctrl & SYNC_BITMASK) && sourceMSBrise[num])) {
|
853
|
+
phaseaccu[channel] = 0;
|
854
|
+
} else {
|
855
|
+
phaseaccu[channel] += accuadd;
|
856
|
+
if (phaseaccu[channel] > 0xFFFFFF) phaseaccu[channel] -= 0x1000000;
|
857
|
+
}
|
858
|
+
MSB = phaseaccu[channel] & 0x800000;
|
859
|
+
sourceMSBrise[num] = (MSB > (prevaccu[channel] & 0x800000)) ? 1 : 0; //phaseaccu[channel] &= 0xFFFFFF;
|
860
|
+
|
861
|
+
//waveform-selector:
|
862
|
+
if (wf & NOISE_BITMASK) { //noise waveform
|
863
|
+
tmp = noise_LFSR[channel];
|
864
|
+
if (((phaseaccu[channel] & 0x100000) != (prevaccu[channel] & 0x100000)) || accuadd >= 0x100000) { //clock LFSR all time if clockrate exceeds observable at given samplerate
|
865
|
+
step = (tmp & 0x400000) ^ ((tmp & 0x20000) << 5);
|
866
|
+
tmp = ((tmp << 1) + (step > 0 || test)) & 0x7FFFFF;
|
867
|
+
noise_LFSR[channel] = tmp;
|
868
|
+
}
|
869
|
+
//we simply zero output when other waveform is mixed with noise. On real SID LFSR continuously gets filled by zero and locks up. ($C1 waveform with pw<8 can keep it for a while...)
|
870
|
+
wfout = (wf & 0x70) ? 0 : ((tmp & 0x100000) >> 5) + ((tmp & 0x40000) >> 4) + ((tmp & 0x4000) >> 1) +
|
871
|
+
((tmp & 0x800) << 1) + ((tmp & 0x200) << 2) + ((tmp & 0x20) << 5) + ((tmp & 0x04) << 7) + ((tmp &
|
872
|
+
0x01) << 8);
|
873
|
+
} else if (wf & PULSE_BITMASK) { //simple pulse
|
874
|
+
pw = (memory[chnadd + 2] + (memory[chnadd + 3] & 0xF) * 256) * 16;
|
875
|
+
tmp = accuadd >> 9;
|
876
|
+
if (0 < pw && pw < tmp) pw = tmp;
|
877
|
+
tmp ^= 0xFFFF;
|
878
|
+
if (pw > tmp) pw = tmp;
|
879
|
+
tmp = phaseaccu[channel] >> 8;
|
880
|
+
if (wf == PULSE_BITMASK) {
|
881
|
+
step = 256 / (accuadd >> 16); //simple pulse, most often used waveform, make it sound as clean as possible without oversampling
|
882
|
+
//One of my biggest success with the SwinSID-variant was that I could clean the high-pitched and thin sounds.
|
883
|
+
//(You might have faced with the unpleasant sound quality of high-pitched sounds without oversampling. We need so-called 'band-limited' synthesis instead.
|
884
|
+
// There are a lot of articles about this issue on the internet. In a nutshell, the harsh edges produce harmonics that exceed the
|
885
|
+
// Nyquist frequency (samplerate/2) and they are folded back into hearable range, producing unvanted ringmodulation-like effect.)
|
886
|
+
//After so many trials with dithering/filtering/oversampling/etc. it turned out I can't eliminate the fukkin aliasing in time-domain, as suggested at pages.
|
887
|
+
//Oversampling (running the wave-generation 8 times more) was not a way at 32MHz SwinSID. It might be an option on PC but I don't prefer it in JavaScript.)
|
888
|
+
//The only solution that worked for me in the end, what I came up eventually: The harsh rising and falling edges of the pulse are
|
889
|
+
//elongated making it a bit trapezoid. But not in time-domain, but altering the transfer-characteristics. This had to be done
|
890
|
+
//in a frequency-dependent way, proportionally to pitch, to keep the deep sounds crisp. The following code does this (my favourite testcase is Robocop3 intro):
|
891
|
+
if (test) wfout = 0xFFFF;
|
892
|
+
else if (tmp < pw) {
|
893
|
+
lim = (0xFFFF - pw) * step;
|
894
|
+
if (lim > 0xFFFF) lim = 0xFFFF;
|
895
|
+
wfout = lim - (pw - tmp) * step;
|
896
|
+
if (wfout < 0) wfout = 0;
|
897
|
+
} //rising edge
|
898
|
+
else {
|
899
|
+
lim = pw * step;
|
900
|
+
if (lim > 0xFFFF) lim = 0xFFFF;
|
901
|
+
wfout = (0xFFFF - tmp) * step - lim;
|
902
|
+
if (wfout >= 0) wfout = 0xFFFF;
|
903
|
+
wfout &= 0xFFFF;
|
904
|
+
} //falling edge
|
905
|
+
} else { //combined pulse
|
906
|
+
wfout = (tmp >= pw || test) ? 0xFFFF : 0; //(this would be enough for simple but aliased-at-high-pitches pulse)
|
907
|
+
if (wf & TRI_BITMASK) {
|
908
|
+
if (wf & SAW_BITMASK) {
|
909
|
+
wfout = (wfout) ? combinedWF(channel, PulseTriSaw_8580, tmp >> 4, 1) : 0;
|
910
|
+
} //pulse+saw+triangle (waveform nearly identical to tri+saw)
|
911
|
+
else {
|
912
|
+
tmp = phaseaccu[channel] ^ (ctrl & RING_BITMASK ? sourceMSB[num] : 0);
|
913
|
+
wfout = (wfout) ? combinedWF(channel, PulseSaw_8580, (tmp ^ (tmp & 0x800000 ? 0xFFFFFF : 0)) >>
|
914
|
+
11, 0) : 0;
|
915
|
+
}
|
916
|
+
} //pulse+triangle
|
917
|
+
else if (wf & SAW_BITMASK) wfout = (wfout) ? combinedWF(channel, PulseSaw_8580, tmp >> 4, 1) : 0; //pulse+saw
|
918
|
+
}
|
919
|
+
} else if (wf & SAW_BITMASK) { //saw
|
920
|
+
wfout = phaseaccu[channel] >> 8; //saw (this row would be enough for simple but aliased-at-high-pitch saw)
|
921
|
+
//The anti-aliasing (cleaning) of high-pitched sawtooth wave works by the same principle as mentioned above for the pulse,
|
922
|
+
//but the sawtooth has even harsher edge/transition, and as the falling edge gets longer, tha rising edge should became shorter,
|
923
|
+
//and to keep the amplitude, it should be multiplied a little bit (with reciprocal of rising-edge steepness).
|
924
|
+
//The waveform at the output essentially becomes an asymmetric triangle, more-and-more approaching symmetric shape towards high frequencies.
|
925
|
+
//(If you check a recording from the real SID, you can see a similar shape, the high-pitch sawtooth waves are triangle-like...)
|
926
|
+
//But for deep sounds the sawtooth is really close to a sawtooth, as there is no aliasing there, but deep sounds should be sharp...
|
927
|
+
if (wf & TRI_BITMASK) wfout = combinedWF(channel, TriSaw_8580, wfout >> 4, 1); //saw+triangle
|
928
|
+
else {
|
929
|
+
step = accuadd / 0x1200000;
|
930
|
+
wfout += wfout * step;
|
931
|
+
if (wfout > 0xFFFF) wfout = 0xFFFF - (wfout - 0x10000) / step;
|
932
|
+
} //simple cleaned (bandlimited) saw
|
933
|
+
} else if (wf & TRI_BITMASK) { //triangle (this waveform has no harsh edges, so it doesn't suffer from strong aliasing at high pitches)
|
934
|
+
tmp = phaseaccu[channel] ^ (ctrl & RING_BITMASK ? sourceMSB[num] : 0);
|
935
|
+
wfout = (tmp ^ (tmp & 0x800000 ? 0xFFFFFF : 0)) >> 7;
|
936
|
+
}
|
937
|
+
|
938
|
+
if (wf) prevwfout[channel] = wfout;
|
939
|
+
else {
|
940
|
+
wfout = prevwfout[channel];
|
941
|
+
} //emulate waveform 00 floating wave-DAC (on real SID waveform00 decays after 15s..50s depending on temperature?)
|
942
|
+
prevaccu[channel] = phaseaccu[channel];
|
943
|
+
sourceMSB[num] = MSB; //(So the decay is not an exact value. Anyway, we just simply keep the value to avoid clicks and support SounDemon digi later...)
|
944
|
+
|
945
|
+
//routing the channel signal to either the filter or the unfiltered master output depending on filter-switch SID-registers
|
946
|
+
if (memory[SIDaddr + 0x17] & FILTSW[channel]) filtin += (wfout - 0x8000) * (envcnt[channel] / 256);
|
947
|
+
else if ((channel % SID_CHANNEL_AMOUNT) != 2 || !(memory[SIDaddr + 0x18] & OFF3_BITMASK)) output += (
|
948
|
+
wfout - 0x8000) * (envcnt[channel] / 256);
|
949
|
+
}
|
950
|
+
|
951
|
+
//update readable SID-registers (some SID tunes might use 3rd channel ENV3/OSC3 value as control)
|
952
|
+
//HERE TODO STH !!!
|
953
|
+
//if (memory[1] & 3) memory[SIDaddr + 0x1B] = wfout >> 8;
|
954
|
+
//memory[SIDaddr + 0x1C] = envcnt[3]; //OSC3, ENV3 (some players rely on it)
|
955
|
+
|
956
|
+
//FILTER: two integrator loop bi-quadratic filter, workings learned from resid code, but I kindof simplified the equations
|
957
|
+
//The phases of lowpass and highpass outputs are inverted compared to the input, but bandpass IS in phase with the input signal.
|
958
|
+
//The 8580 cutoff frequency control-curve is ideal, while the 6581 has a treshold, and below it it outputs a constant lowpass frequency.
|
959
|
+
cutoff = (memory[SIDaddr + 0x15] & 7) / 8 + memory[SIDaddr + 0x16] + 0.2;
|
960
|
+
if (SID_model == 8580.0) {
|
961
|
+
cutoff = 1 - Math.exp(cutoff * cutoff_ratio_8580);
|
962
|
+
resonance = Math.pow(2, ((4 - (memory[SIDaddr + 0x17] >> 4)) / 8));
|
963
|
+
} else {
|
964
|
+
if (cutoff < 24) cutoff = 0.035;
|
965
|
+
else cutoff = 1 - 1.263 * Math.exp(cutoff * cutoff_ratio_6581);
|
966
|
+
resonance = (memory[SIDaddr + 0x17] > 0x5F) ? 8 / (memory[SIDaddr + 0x17] >> 4) : 1.41;
|
967
|
+
}
|
968
|
+
tmp = filtin + prevbandpass[num] * resonance + prevlowpass[num];
|
969
|
+
if (memory[SIDaddr + 0x18] & HIGHPASS_BITMASK) output -= tmp;
|
970
|
+
tmp = prevbandpass[num] - tmp * cutoff;
|
971
|
+
prevbandpass[num] = tmp;
|
972
|
+
if (memory[SIDaddr + 0x18] & BANDPASS_BITMASK) output -= tmp;
|
973
|
+
tmp = prevlowpass[num] + tmp * cutoff;
|
974
|
+
prevlowpass[num] = tmp;
|
975
|
+
if (memory[SIDaddr + 0x18] & LOWPASS_BITMASK) output += tmp;
|
976
|
+
|
977
|
+
//when it comes to $D418 volume-register digi playback, I made an AC / DC separation for $D418 value in the SwinSID at low (20Hz or so) cutoff-frequency,
|
978
|
+
//and sent the AC (highpass) value to a 4th 'digi' channel mixed to the master output, and set ONLY the DC (lowpass) value to the volume-control.
|
979
|
+
//This solved 2 issues: Thanks to the lowpass filtering of the volume-control, SID tunes where digi is played together with normal SID channels,
|
980
|
+
//won't sound distorted anymore, and the volume-clicks disappear when setting SID-volume. (This is useful for fade-in/out tunes like Hades Nebula, where clicking ruins the intro.)
|
981
|
+
return (output / OUTPUT_SCALEDOWN) * (memory[SIDaddr + 0x18] & 0xF); // SID output
|
982
|
+
}
|
983
|
+
|
984
|
+
|
985
|
+
//And now, the combined waveforms. The resid source simply uses 4kbyte 8bit samples from wavetable arrays, says these waveforms are mystic due to the analog behaviour.
|
986
|
+
//It's true, the analog things inside SID play a significant role in how the combined waveforms look like, but process variations are not so huge that cause much differences in SIDs.
|
987
|
+
//After checking these waveforms by eyes, it turned out for me that these waveform are fractal-like, recursively approachable waveforms.
|
988
|
+
//My 1st thought and trial was to store only a portion of the waveforms in table, and magnify them depending on phase-accumulator's state.
|
989
|
+
//But I wanted to understand how these waveforms are produced. I felt from the waveform-diagrams that the bits of the waveforms affect each other,
|
990
|
+
//hence the recursive look. A short C code proved by assumption, I could generate something like a pulse+saw combined waveform.
|
991
|
+
//Recursive calculations were not feasible for MCU of SwinSID, but for jsSID I could utilize what I found out and code below generates the combined waveforms into wavetables.
|
992
|
+
//To approach the combined waveforms as much as possible, I checked out the SID schematic that can be found at some reverse-engineering sites...
|
993
|
+
//The SID's R-2R ladder WAVE DAC is driven by operation-amplifier like complementary FET output drivers, so that's not the place where I first thought the magic happens.
|
994
|
+
//These 'opamps' (for all 12 wave-bits) have single FETs as inputs, and they switch on above a certain level of input-voltage, causing 0 or 1 bit as R-2R DAC input.
|
995
|
+
//So the first keyword for the workings is TRESHOLD. These FET inputs are driven through serial switch FETs (wave-selector) that normally enables one waveform at a time.
|
996
|
+
//The phase-accumulator's output is brought to 3 kinds of circuitries for the 3 basic waveforms. The pulse simply drives
|
997
|
+
//all wave-selector inputs with a 0/1 depending on pulsewidth, the sawtooth has a XOR for triangle/ringmod generation, but what
|
998
|
+
//is common for all waveforms, they have an open-drain driver before the wave-selector, which has FETs towards GND and 'FET resistor' towards the power-supply rail.
|
999
|
+
//These outputs are clearly not designed to drive high loads, and normally they only have to drive the FETs input mentioned above.
|
1000
|
+
//But when more of these output drivers are switched together by the switch-FETs in the wave-selector, they affect each other by loading each other.
|
1001
|
+
//The pulse waveform, when selected, connects all of them together through a fairly strong connection, and its signal also affects the analog level (pulls below the treshold)...
|
1002
|
+
//The farther a specific DAC bit driver is from the other, the less it affects its output. It turned out it's not powers of 2 but something else,
|
1003
|
+
//that creates similar combined waveforms to that of real SID's...
|
1004
|
+
//The analog levels that get generated by the various bit drivers, that pull each other up/down depends on the resistances the components inside the SID have.
|
1005
|
+
//And finally, what is output on the DAC depends on whether these analog levels are below or above the FET gate's treshold-level,
|
1006
|
+
//That's how the combined waveform is generated. Maybe I couldn't explain well enough, but the code below is simple enough to understand the mechanism algoritmically.
|
1007
|
+
//This simplified schematic exapmle might make it easier to understand sawtooth+pulse combination (must be observed with monospace fonts):
|
1008
|
+
// _____ |- .--------------. /\/\--.
|
1009
|
+
// Vsupply / .----| |---------*---|- / Vsupply ! R ! As can be seen on this schematic,
|
1010
|
+
// ------. other ! ! _____ ! TRES \ \ ! / the pulse wave-selector FETs
|
1011
|
+
// ! saw bit *--!----| |---------' HOLD / ! |- 2R \ connect the neighbouring sawtooth
|
1012
|
+
// / output ! ! ! |------|- / outputs with a fairly strong
|
1013
|
+
// Rd \ |- !WAVEFORM-SELECTOR *--*---|- ! R ! connection to each other through
|
1014
|
+
// / |- !SWITCHING FETs ! ! ! *---/\/\--* their own wave-selector FETs.
|
1015
|
+
// ! saw-bit ! _____ |- ! --- ! ! So the adjacent sawtooth outputs
|
1016
|
+
// *------------------!-----| |-----------*-----|- ! |- / pull each other upper/lower
|
1017
|
+
// ! (weak drive,so ! saw switch ! TRES-! `----------|- 2R \ depending on their low/high state and
|
1018
|
+
// |- can be shifted ! ! HOLD ! ! / distance from each other, causing
|
1019
|
+
// -----|- by neighbours ! _____ ! ! ! R ! the resulting analog level that
|
1020
|
+
// ! up or down) *-----| |-----------' --- --- /\/\-* will either turn the output on or not.
|
1021
|
+
// GND --- ! pulse switch ! (Depending on their relation to treshold.)
|
1022
|
+
//
|
1023
|
+
//(As triangle waveform connects adjacent bits by default, the above explained effect becomes even stronger, that's why combined waveforms with thriangle are at 0 level most of the time.)
|
1024
|
+
|
1025
|
+
function combinedWF(channel, wfarray, index, differ6581) { //on 6581 most combined waveforms are essentially halved 8580-like waves
|
1026
|
+
if (differ6581 && SID_model == 6581.0) index &= 0x7FF;
|
1027
|
+
combiwf = (wfarray[index] + prevwavdata[channel]) / 2;
|
1028
|
+
prevwavdata[channel] = wfarray[index];
|
1029
|
+
return combiwf;
|
1030
|
+
}
|
1031
|
+
|
1032
|
+
function createCombinedWF(wfarray, bitmul, bitstrength, treshold) { //I found out how the combined waveform works (neighboring bits affect each other recursively)
|
1033
|
+
for (var i = 0; i < 4096; i++) {
|
1034
|
+
wfarray[i] = 0; //neighbour-bit strength and DAC MOSFET treshold is approximately set by ears'n'trials
|
1035
|
+
for (var j = 0; j < 12; j++) {
|
1036
|
+
var bitlevel = 0;
|
1037
|
+
for (var k = 0; k < 12; k++) {
|
1038
|
+
bitlevel += (bitmul / Math.pow(bitstrength, Math.abs(k - j))) * (((i >> k) & 1) - 0.5);
|
1039
|
+
}
|
1040
|
+
wfarray[i] += (bitlevel >= treshold) ? Math.pow(2, j) : 0;
|
1041
|
+
}
|
1042
|
+
wfarray[i] *= 12;
|
1043
|
+
}
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
TriSaw_8580 = new Array(4096);
|
1047
|
+
createCombinedWF(TriSaw_8580, 0.8, 2.4, 0.64); //precalculate combined waveform
|
1048
|
+
PulseSaw_8580 = new Array(4096);
|
1049
|
+
createCombinedWF(PulseSaw_8580, 1.4, 1.9, 0.68);
|
1050
|
+
PulseTriSaw_8580 = new Array(4096);
|
1051
|
+
createCombinedWF(PulseTriSaw_8580, 0.8, 2.5, 0.64);
|
1052
|
+
|
1053
|
+
var period0 = Math.max(clk_ratio, 9);
|
1054
|
+
var ADSRperiods = [period0, 32 * 1, 63 * 1, 95 * 1, 149 * 1, 220 * 1, 267 * 1, 313 * 1, 392 * 1, 977 * 1,
|
1055
|
+
1954 * 1, 3126 * 1, 3907 * 1, 11720 * 1, 19532 * 1, 31251 * 1
|
1056
|
+
];
|
1057
|
+
var ADSRstep = [Math.ceil(period0 / 9), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
1058
|
+
|
1059
|
+
//prescaler values that slow down the envelope-counter as it decays and approaches zero level
|
1060
|
+
var ADSR_exptable = [1, 30, 30, 30, 30, 30, 30, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
1061
|
+
8, 8, 8, 4, 4, 4, 4, 4, //pos0:1 pos6:30 pos14:16 pos26:8
|
1062
|
+
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
1063
|
+
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, //pos54:4 //pos93:2
|
1064
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1065
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1066
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1067
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1068
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
1069
|
+
];
|
1070
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opal-sid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michał Kalbarczyk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|