opal-sid 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|