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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93bc7981f8bb7b27219304680ac4218c9491227e
4
- data.tar.gz: f5029c4b4b60d87a155c3bab7123aeafa2d239ef
3
+ metadata.gz: ff9968c09be7f4ea961dc509b1ea4c66ec8469b8
4
+ data.tar.gz: 9b984ee9664a07ac9fb2e82a2e6295a2757d7610
5
5
  SHA512:
6
- metadata.gz: bd167be9fe703445d8224d4c3b36122fd3f4fcdd0ab29942997bf68ef57a6081d632e3ff64e410ca6e1ab0b8ef2ecb6c866ddbe6f6d944fc40c57ad05f440d5d
7
- data.tar.gz: 17399fc0b91c5cb1040ce215bb38f3778f1d379ece43d1492fd0f6c332332b1304f331973ff19f4c2af044c0618229c3c47b8c9eb2a949158cebf00bdb8c348a
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)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'opal-sid'
3
- s.version = '0.0.3'
3
+ s.version = '0.0.4'
4
4
  s.authors = ['Michał Kalbarczyk']
5
5
  s.email = 'fazibear@gmail.com'
6
6
  s.homepage = 'http://github.com/fazibear/opal-sid'
@@ -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
@@ -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.3
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-04-11 00:00:00.000000000 Z
11
+ date: 2016-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal