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 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