ProTracker.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. /*
  2. amiga protracker module player for web audio api
  3. (c) 2012-2014 firehawk/tda (firehawk@haxor.fi)
  4. originally hacked together in a weekend, so please excuse
  5. me for the spaghetti code. :)
  6. feel free to use this player in your website/demo/whatever
  7. if you find it useful. drop me an email if you do.
  8. AMIGAAAAAAAAH!!
  9. all code licensed under MIT license:
  10. http://opensource.org/licenses/MIT
  11. kinda sorta changelog:
  12. (sep 2014)
  13. - fixed bug with E8x sync and added 80x to also function for sync
  14. events due to problems with some protracker versions (thanks spot)
  15. (aug 2014)
  16. - added sync event queue for E8x commands
  17. - changed the amiga fixed filter model to allow changes at runtime
  18. - three stereo separation modes now, 0=amiga, 1=65/35, 2=mono
  19. - a few bugfixes, thanks spot^uprough and esau^traktor for reporting
  20. * fixed bug in slide-to-note when 300 with no preceeding 3xy
  21. * fixed vibrato depth on ticks 1+ to match tick 0
  22. * added boolean variable for disabling A500 fixed lowpass filter
  23. * added a delay on module start, number of buffers selectable
  24. * fixed sample loop discarding pointer overflow
  25. (may 2014)
  26. - added boolean variable for the amiga led filter for ui stuff
  27. (jan 2014)
  28. - disabled ee0 filter command for tracks with over 4 channels to
  29. make mod.dope play correctly
  30. (oct 2013)
  31. - added support for firefox 24
  32. (apr 2013)
  33. - changed the logic for pattern break/jump. mod.pattern_skank now
  34. plays correctly.
  35. (feb 2013)
  36. - fixed NaN samples with mod.fractured and mod.multicolour (thanks Aegis!)
  37. (jan 2013)
  38. - fixed vibrato amplitude (was half of what it should be, apparently)
  39. - fixed to work on safari again (thanks Matt Diamond @ stackoverflow.com)
  40. (dec 2012)
  41. - replaced effect switch-statement with jumptables
  42. - fixed clicks (bad loops, empty samples)
  43. - fixed playback bug with sample-only rows
  44. - added amiga 500 lowpass filters (not 100% authentic, though)
  45. - added compressor to output
  46. - latest safari has broken web audio so chrome-only for now
  47. (aug 2012)
  48. - first version written from scratch
  49. todo:
  50. - pattern looping is way broken in mod.black_queen
  51. - properly test EEx delay pattern
  52. - implement the rest of the effects
  53. - optimize for more speed!! SPEEEED!!
  54. * switch to fixed point sample pointers, Math.floor() is _slow_ on iOS
  55. */
  56. // constructor for protracker player object
  57. // @class Phaser.Plugin.ProTracker
  58. function Protracker()
  59. {
  60. var i, t;
  61. this.initialize();
  62. this.clearsong();
  63. this.url="";
  64. this.loading=false;
  65. this.ready=false;
  66. this.playing=false;
  67. this.buffer=0;
  68. this.mixerNode=0;
  69. this.paused=false;
  70. this.repeat=false;
  71. this.filter=false;
  72. this.separation=1;
  73. this.palclock=true;
  74. this.amiga500=true;
  75. this.autostart=false;
  76. this.bufferstodelay=4; // adjust this if you get stutter after loading new song
  77. this.delayfirst=0;
  78. this.delayload=0;
  79. this.syncqueue=[];
  80. this.onReady=function(){};
  81. this.onPlay=function(){};
  82. this.onStop=function(){};
  83. this.context = null;
  84. this.samplerate=44100;
  85. this.bufferlen=2048;
  86. // paula period values
  87. this.baseperiodtable=new Array(
  88. 856,808,762,720,678,640,604,570,538,508,480,453,
  89. 428,404,381,360,339,320,302,285,269,254,240,226,
  90. 214,202,190,180,170,160,151,143,135,127,120,113);
  91. // finetune multipliers
  92. this.finetunetable=new Array();
  93. for(t=0;t<16;t++) this.finetunetable[t]=Math.pow(2, (t-8)/12/8);
  94. // calc tables for vibrato waveforms
  95. this.vibratotable=new Array();
  96. for(t=0;t<4;t++) {
  97. this.vibratotable[t]=new Array();
  98. for(i=0;i<64;i++) {
  99. switch(t) {
  100. case 0:
  101. this.vibratotable[t][i]=127*Math.sin(Math.PI*2*(i/64));
  102. break;
  103. case 1:
  104. this.vibratotable[t][i]=127-4*i;
  105. break;
  106. case 2:
  107. this.vibratotable[t][i]=(i<32)?127:-127;
  108. break;
  109. case 3:
  110. this.vibratotable[t][i]=(1-2*Math.random())*127;
  111. break;
  112. }
  113. }
  114. }
  115. // effect jumptables
  116. this.effects_t0 = new Array(
  117. this.effect_t0_0, this.effect_t0_1, this.effect_t0_2, this.effect_t0_3, this.effect_t0_4, this.effect_t0_5, this.effect_t0_6, this.effect_t0_7,
  118. this.effect_t0_8, this.effect_t0_9, this.effect_t0_a, this.effect_t0_b, this.effect_t0_c, this.effect_t0_d, this.effect_t0_e, this.effect_t0_f);
  119. this.effects_t0_e = new Array(
  120. this.effect_t0_e0, this.effect_t0_e1, this.effect_t0_e2, this.effect_t0_e3, this.effect_t0_e4, this.effect_t0_e5, this.effect_t0_e6, this.effect_t0_e7,
  121. this.effect_t0_e8, this.effect_t0_e9, this.effect_t0_ea, this.effect_t0_eb, this.effect_t0_ec, this.effect_t0_ed, this.effect_t0_ee, this.effect_t0_ef);
  122. this.effects_t1 = new Array(
  123. this.effect_t1_0, this.effect_t1_1, this.effect_t1_2, this.effect_t1_3, this.effect_t1_4, this.effect_t1_5, this.effect_t1_6, this.effect_t1_7,
  124. this.effect_t1_8, this.effect_t1_9, this.effect_t1_a, this.effect_t1_b, this.effect_t1_c, this.effect_t1_d, this.effect_t1_e, this.effect_t1_f);
  125. this.effects_t1_e = new Array(
  126. this.effect_t1_e0, this.effect_t1_e1, this.effect_t1_e2, this.effect_t1_e3, this.effect_t1_e4, this.effect_t1_e5, this.effect_t1_e6, this.effect_t1_e7,
  127. this.effect_t1_e8, this.effect_t1_e9, this.effect_t1_ea, this.effect_t1_eb, this.effect_t1_ec, this.effect_t1_ed, this.effect_t1_ee, this.effect_t1_ef);
  128. }
  129. // create the web audio context
  130. Protracker.prototype.createContext = function()
  131. {
  132. if ( typeof AudioContext !== 'undefined') {
  133. this.context = new AudioContext();
  134. } else {
  135. this.context = new webkitAudioContext();
  136. }
  137. this.samplerate=this.context.sampleRate;
  138. this.bufferlen=(this.samplerate > 44100) ? 4096 : 2048;
  139. // Amiga 500 fixed filter at 6kHz. WebAudio lowpass is 12dB/oct, whereas
  140. // older Amigas had a 6dB/oct filter at 4900Hz.
  141. this.filterNode=this.context.createBiquadFilter();
  142. if (this.amiga500) {
  143. this.filterNode.frequency.value=6000;
  144. } else {
  145. this.filterNode.frequency.value=28867;
  146. }
  147. // "LED filter" at 3275kHz - off by default
  148. this.lowpassNode=this.context.createBiquadFilter();
  149. this.lowpassNode.frequency.value=28867;
  150. this.filter=false;
  151. // mixer
  152. if ( typeof this.context.createJavaScriptNode === 'function') {
  153. this.mixerNode=this.context.createJavaScriptNode(this.bufferlen, 1, 2);
  154. } else {
  155. this.mixerNode=this.context.createScriptProcessor(this.bufferlen, 1, 2);
  156. }
  157. this.mixerNode.module=this;
  158. this.mixerNode.onaudioprocess=Protracker.prototype.mix;
  159. // compressor for a bit of volume boost, helps with multich tunes
  160. this.compressorNode=this.context.createDynamicsCompressor();
  161. // patch up some cables :)
  162. this.mixerNode.connect(this.filterNode);
  163. this.filterNode.connect(this.lowpassNode);
  164. this.lowpassNode.connect(this.compressorNode);
  165. this.compressorNode.connect(this.context.destination);
  166. }
  167. // play loaded and parsed module with webaudio context
  168. Protracker.prototype.play = function()
  169. {
  170. if (this.context==null) this.createContext();
  171. if (!this.ready) return false;
  172. if (this.paused) {
  173. this.paused=false;
  174. return true;
  175. }
  176. this.endofsong=false;
  177. this.paused=false;
  178. this.initialize();
  179. this.flags=1+2;
  180. this.playing=true;
  181. this.onPlay();
  182. this.delayfirst=this.bufferstodelay;
  183. return true;
  184. }
  185. // pause playback
  186. Protracker.prototype.pause = function()
  187. {
  188. if (!this.paused) {
  189. this.paused=true;
  190. } else {
  191. this.paused=false;
  192. }
  193. }
  194. // stop playback
  195. Protracker.prototype.stop = function()
  196. {
  197. this.playing=false;
  198. this.onStop();
  199. this.delayload=1;
  200. }
  201. // stop playing but don't call callbacks
  202. Protracker.prototype.stopaudio = function(st)
  203. {
  204. this.playing=st;
  205. }
  206. // jump positions forward/back
  207. Protracker.prototype.jump = function(step)
  208. {
  209. this.tick=0;
  210. this.row=0;
  211. this.position+=step;
  212. this.flags=1+2;
  213. if (this.position<0) this.position=0;
  214. if (this.position >= this.songlen) this.stop();
  215. }
  216. // set whether module repeats after songlen
  217. Protracker.prototype.setrepeat = function(rep)
  218. {
  219. this.repeat=rep;
  220. }
  221. // set stereo separation mode (0=paula, 1=betterpaula (60/40), 2=mono)
  222. Protracker.prototype.setseparation = function(sep)
  223. {
  224. this.separation=sep;
  225. }
  226. // set amiga video standard (false=NTSC, true=PAL)
  227. Protracker.prototype.setamigatype = function(clock)
  228. {
  229. this.palclock=clock;
  230. }
  231. // set autostart to play immediately after loading
  232. Protracker.prototype.setautostart = function(st)
  233. {
  234. this.autostart=st;
  235. }
  236. // set amiga model - changes fixed filter state
  237. Protracker.prototype.setamigamodel = function(amiga)
  238. {
  239. if (amiga=="600" || amiga=="1200" || amiga=="4000") {
  240. this.amiga500=false;
  241. if (this.filterNode) this.filterNode.frequency.value=28867;
  242. } else {
  243. this.amiga500=true;
  244. if (this.filterNode) this.filterNode.frequency.value=6000;
  245. }
  246. }
  247. // are there E8x sync events queued?
  248. Protracker.prototype.hassyncevents = function()
  249. {
  250. return (this.syncqueue.length != 0);
  251. }
  252. // pop oldest sync event nybble from the FIFO queue
  253. Protracker.prototype.popsyncevent = function()
  254. {
  255. return this.syncqueue.pop();
  256. }
  257. // clear song data
  258. Protracker.prototype.clearsong = function()
  259. {
  260. this.title="";
  261. this.signature="";
  262. this.songlen=1;
  263. this.repeatpos=0;
  264. this.patterntable=new ArrayBuffer(128);
  265. for(i=0;i<128;i++) this.patterntable[i]=0;
  266. this.channels=4;
  267. this.sample=new Array();
  268. this.samples=31;
  269. for(i=0;i<31;i++) {
  270. this.sample[i]=new Object();
  271. this.sample[i].name="";
  272. this.sample[i].length=0;
  273. this.sample[i].finetune=0;
  274. this.sample[i].volume=64;
  275. this.sample[i].loopstart=0;
  276. this.sample[i].looplength=0;
  277. this.sample[i].data=0;
  278. }
  279. this.patterns=0;
  280. this.pattern=new Array();
  281. this.note=new Array();
  282. this.looprow=0;
  283. this.loopstart=0;
  284. this.loopcount=0;
  285. this.patterndelay=0;
  286. this.patternwait=0;
  287. this.syncqueue=[];
  288. }
  289. // initialize all player variables
  290. Protracker.prototype.initialize = function()
  291. {
  292. this.syncqueue=[];
  293. this.tick=0;
  294. this.position=0;
  295. this.row=0;
  296. this.offset=0;
  297. this.flags=0;
  298. this.speed=6;
  299. this.bpm=125;
  300. this.breakrow=0;
  301. this.patternjump=0;
  302. this.patterndelay=0;
  303. this.patternwait=0;
  304. this.endofsong=false;
  305. this.channel=new Array();
  306. for(i=0;i<this.channels;i++) {
  307. this.channel[i]=new Object();
  308. this.channel[i].sample=0;
  309. this.channel[i].period=214;
  310. this.channel[i].voiceperiod=214;
  311. this.channel[i].note=24;
  312. this.channel[i].volume=64;
  313. this.channel[i].command=0;
  314. this.channel[i].data=0;
  315. this.channel[i].samplepos=0;
  316. this.channel[i].samplespeed=0;
  317. this.channel[i].flags=0;
  318. this.channel[i].noteon=0;
  319. this.channel[i].slidespeed=0;
  320. this.channel[i].slideto=214;
  321. this.channel[i].slidetospeed=0;
  322. this.channel[i].arpeggio=0;
  323. this.channel[i].semitone=12;
  324. this.channel[i].vibratospeed=0
  325. this.channel[i].vibratodepth=0
  326. this.channel[i].vibratopos=0;
  327. this.channel[i].vibratowave=0;
  328. }
  329. this.vu=new Array();
  330. }
  331. // load module from url into local buffer
  332. Protracker.prototype.load = function(url)
  333. {
  334. this.playing=false; // a precaution
  335. this.url=url;
  336. this.clearsong();
  337. var request = new XMLHttpRequest();
  338. request.open("GET", this.url, true);
  339. request.responseType = "arraybuffer";
  340. this.request = request;
  341. this.loading=true;
  342. var asset = this;
  343. request.onload = function() {
  344. asset.buffer=new Uint8Array(request.response);
  345. asset.parse();
  346. if (asset.autostart) asset.play();
  347. }
  348. request.send();
  349. }
  350. // parse the module from local buffer
  351. Protracker.prototype.parse = function()
  352. {
  353. var i,j,c;
  354. if (!this.buffer) return false;
  355. for(i=0;i<4;i++) this.signature+=String.fromCharCode(this.buffer[1080+i]);
  356. switch (this.signature) {
  357. case "M.K.":
  358. case "M!K!":
  359. case "4CHN":
  360. case "FLT4":
  361. break;
  362. case "6CHN":
  363. this.channels=6;
  364. break;
  365. case "8CHN":
  366. case "FLT8":
  367. this.channels=8;
  368. break;
  369. case "28CH":
  370. this.channels=28;
  371. break;
  372. default:
  373. return false;
  374. }
  375. this.vu=new Array();
  376. for(i=0;i<this.channels;i++) this.vu[i]=0.0;
  377. i=0;
  378. while(this.buffer[i] && i<20)
  379. this.title=this.title+String.fromCharCode(this.buffer[i++]);
  380. for(i=0;i<this.samples;i++) {
  381. var st=20+i*30;
  382. j=0;
  383. while(this.buffer[st+j] && j<22) {
  384. this.sample[i].name+=
  385. ((this.buffer[st+j]>0x1f) && (this.buffer[st+j]<0x7f)) ?
  386. (String.fromCharCode(this.buffer[st+j])) :
  387. (" ");
  388. j++;
  389. }
  390. this.sample[i].length=2*(this.buffer[st+22]*256 + this.buffer[st+23]);
  391. this.sample[i].finetune=this.buffer[st+24];
  392. if (this.sample[i].finetune > 7) this.sample[i].finetune=this.sample[i].finetune-16;
  393. this.sample[i].volume=this.buffer[st+25];
  394. this.sample[i].loopstart=2*(this.buffer[st+26]*256 + this.buffer[st+27]);
  395. this.sample[i].looplength=2*(this.buffer[st+28]*256 + this.buffer[st+29]);
  396. if (this.sample[i].looplength==2) this.sample[i].looplength=0;
  397. if (this.sample[i].loopstart>this.sample[i].length) {
  398. this.sample[i].loopstart=0;
  399. this.sample[i].looplength=0;
  400. }
  401. }
  402. this.songlen=this.buffer[950];
  403. if (this.buffer[951] != 127) this.repeatpos=this.buffer[951];
  404. for(i=0;i<128;i++) {
  405. this.patterntable[i]=this.buffer[952+i];
  406. if (this.patterntable[i] > this.patterns) this.patterns=this.patterntable[i];
  407. }
  408. this.patterns+=1;
  409. var patlen=4*64*this.channels;
  410. this.pattern=new Array();
  411. this.note=new Array();
  412. for(i=0;i<this.patterns;i++) {
  413. this.pattern[i]=new Uint8Array(patlen);
  414. this.note[i]=new Uint8Array(this.channels*64);
  415. for(j=0;j<patlen;j++) this.pattern[i][j]=this.buffer[1084+i*patlen+j];
  416. for(j=0;j<64;j++) for(c=0;c<this.channels;c++) {
  417. this.note[i][j*this.channels+c]=0;
  418. var n=(this.pattern[i][j*4*this.channels+c*4]&0x0f)<<8 | this.pattern[i][j*4*this.channels+c*4+1];
  419. for(var np=0; np<this.baseperiodtable.length; np++)
  420. if (n==this.baseperiodtable[np]) this.note[i][j*this.channels+c]=np;
  421. }
  422. }
  423. var sst=1084+this.patterns*patlen;
  424. for(i=0;i<this.samples;i++) {
  425. this.sample[i].data=new Float32Array(this.sample[i].length);
  426. for(j=0;j<this.sample[i].length;j++) {
  427. var q=this.buffer[sst+j];
  428. if (q<128) {
  429. q=q/128.0;
  430. } else {
  431. q=((q-128)/128.0)-1.0;
  432. }
  433. this.sample[i].data[j]=q;
  434. }
  435. sst+=this.sample[i].length;
  436. }
  437. if (this.context) {
  438. this.lowpassNode.frequency.value=28867;
  439. this.filter=false;
  440. }
  441. this.ready=true;
  442. this.loading=false;
  443. this.buffer=0;
  444. this.onReady();
  445. return true;
  446. }
  447. // advance player
  448. Protracker.prototype.advance=function(mod) {
  449. var spd=(((mod.samplerate*60)/mod.bpm)/4)/6;
  450. // advance player
  451. if (mod.offset>spd) { mod.tick++; mod.offset=0; mod.flags|=1; }
  452. if (mod.tick>=mod.speed) {
  453. if (mod.patterndelay) { // delay pattern
  454. if (mod.tick < ((mod.patternwait+1)*mod.speed)) {
  455. mod.patternwait++;
  456. } else {
  457. mod.row++; mod.tick=0; mod.flags|=2; mod.patterndelay=0;
  458. }
  459. }
  460. else {
  461. if (mod.flags&(16+32+64)) {
  462. if (mod.flags&64) { // loop pattern?
  463. mod.row=mod.looprow;
  464. mod.flags&=0xa1;
  465. mod.flags|=2;
  466. }
  467. else {
  468. if (mod.flags&16) { // pattern jump/break?
  469. //console.log("break to pattern " + mod.patternjump + " row "+mod.breakrow);
  470. mod.position=mod.patternjump;
  471. mod.row=mod.breakrow;
  472. mod.patternjump=0;
  473. mod.breakrow=0;
  474. mod.flags&=0xe1;
  475. mod.flags|=2;
  476. }
  477. }
  478. mod.tick=0;
  479. } else {
  480. mod.row++; mod.tick=0; mod.flags|=2;
  481. }
  482. }
  483. }
  484. if (mod.row>=64) { mod.position++; mod.row=0; mod.flags|=4; }
  485. if (mod.position>=mod.songlen) {
  486. if (mod.repeat) {
  487. mod.position=0;
  488. } else {
  489. this.endofsong=true;
  490. mod.stop();
  491. }
  492. return;
  493. }
  494. }
  495. // mix an audio buffer with data
  496. Protracker.prototype.mix = function(ape) {
  497. var f;
  498. var p, pp, n, nn;
  499. var mod;
  500. if (ape.srcElement) {
  501. mod=ape.srcElement.module;
  502. } else {
  503. mod=this.module;
  504. }
  505. outp=new Array();
  506. var bufs=new Array(ape.outputBuffer.getChannelData(0), ape.outputBuffer.getChannelData(1));
  507. var buflen=ape.outputBuffer.length;
  508. for(var s=0;s<buflen;s++)
  509. {
  510. outp[0]=0.0;
  511. outp[1]=0.0;
  512. if (!mod.paused && mod.playing && mod.delayfirst==0)
  513. {
  514. mod.advance(mod);
  515. var och=0;
  516. for(var ch=0;ch<mod.channels;ch++)
  517. {
  518. // calc playback position
  519. p=mod.patterntable[mod.position];
  520. pp=mod.row*4*mod.channels + ch*4;
  521. if (mod.flags&2) { // new row
  522. mod.channel[ch].command=mod.pattern[p][pp+2]&0x0f;
  523. mod.channel[ch].data=mod.pattern[p][pp+3];
  524. if (!(mod.channel[ch].command==0x0e && (mod.channel[ch].data&0xf0)==0xd0)) {
  525. n=(mod.pattern[p][pp]&0x0f)<<8 | mod.pattern[p][pp+1];
  526. if (n) {
  527. // noteon, except if command=3 (porta to note)
  528. if ((mod.channel[ch].command != 0x03) && (mod.channel[ch].command != 0x05)) {
  529. mod.channel[ch].period=n;
  530. mod.channel[ch].samplepos=0;
  531. if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0;
  532. mod.channel[ch].flags|=3; // recalc speed
  533. mod.channel[ch].noteon=1;
  534. }
  535. // in either case, set the slide to note target
  536. mod.channel[ch].slideto=n;
  537. }
  538. nn=mod.pattern[p][pp+0]&0xf0 | mod.pattern[p][pp+2]>>4;
  539. if (nn) {
  540. mod.channel[ch].sample=nn-1;
  541. mod.channel[ch].volume=mod.sample[nn-1].volume;
  542. if (!n && (mod.channel[ch].samplepos > mod.sample[nn-1].length)) mod.channel[ch].samplepos=0;
  543. }
  544. }
  545. }
  546. mod.channel[ch].voiceperiod=mod.channel[ch].period;
  547. // kill empty samples
  548. if (!mod.sample[mod.channel[ch].sample].length) mod.channel[ch].noteon=0;
  549. // effects
  550. if (mod.flags&1) {
  551. if (!mod.tick) {
  552. // process only on tick 0
  553. mod.effects_t0[mod.channel[ch].command](mod, ch);
  554. } else {
  555. mod.effects_t1[mod.channel[ch].command](mod, ch);
  556. }
  557. }
  558. // recalc note number from period
  559. if (mod.channel[ch].flags&2) {
  560. for(var np=0; np<mod.baseperiodtable.length; np++)
  561. if (mod.baseperiodtable[np]>=mod.channel[ch].period) mod.channel[ch].note=np;
  562. mod.channel[ch].semitone=7;
  563. if (mod.channel[ch].period>=120)
  564. mod.channel[ch].semitone=mod.baseperiodtable[mod.channel[ch].note]-mod.baseperiodtable[mod.channel[ch].note+1];
  565. }
  566. // recalc sample speed and apply finetune
  567. if ((mod.channel[ch].flags&1 || mod.flags&2) && mod.channel[ch].voiceperiod)
  568. mod.channel[ch].samplespeed=
  569. (mod.palclock ? 7093789.2 : 7159090.5)/(mod.channel[ch].voiceperiod*2) * mod.finetunetable[mod.sample[mod.channel[ch].sample].finetune+8] / mod.samplerate;
  570. // advance vibrato on each new tick
  571. if (mod.flags&1) {
  572. mod.channel[ch].vibratopos+=mod.channel[ch].vibratospeed;
  573. mod.channel[ch].vibratopos&=0x3f;
  574. }
  575. // mix channel to output
  576. och=och^(ch&1);
  577. f=0.0;
  578. if (mod.channel[ch].noteon) {
  579. if (mod.sample[mod.channel[ch].sample].length > mod.channel[ch].samplepos)
  580. f=(1.0/mod.channels) *
  581. (mod.sample[mod.channel[ch].sample].data[Math.floor(mod.channel[ch].samplepos)]*mod.channel[ch].volume)/64.0;
  582. outp[och]+=f;
  583. mod.channel[ch].samplepos+=mod.channel[ch].samplespeed;
  584. }
  585. if (s==0) mod.vu[ch]=Math.abs(f);
  586. // loop or end samples
  587. if (mod.channel[ch].noteon) {
  588. if (mod.sample[mod.channel[ch].sample].loopstart || mod.sample[mod.channel[ch].sample].looplength) {
  589. if (mod.channel[ch].samplepos >= (mod.sample[mod.channel[ch].sample].loopstart+mod.sample[mod.channel[ch].sample].looplength)) {
  590. mod.channel[ch].samplepos-=mod.sample[mod.channel[ch].sample].looplength;
  591. }
  592. } else {
  593. if (mod.channel[ch].samplepos >= mod.sample[mod.channel[ch].sample].length) {
  594. mod.channel[ch].noteon=0;
  595. }
  596. }
  597. }
  598. // clear channel flags
  599. mod.channel[ch].flags=0;
  600. }
  601. mod.offset++;
  602. mod.flags&=0x70;
  603. }
  604. // a more headphone-friendly stereo separation (aka. betterpaula)
  605. if (mod.separation) {
  606. t=outp[0];
  607. if (mod.separation==2) {
  608. outp[0]=outp[0]*0.5 + outp[1]*0.5;
  609. outp[1]=outp[1]*0.5 + t*0.5;
  610. } else {
  611. outp[0]=outp[0]*0.65 + outp[1]*0.35;
  612. outp[1]=outp[1]*0.65 + t*0.35;
  613. }
  614. }
  615. bufs[0][s]=outp[0];
  616. bufs[1][s]=outp[1];
  617. }
  618. if (mod.delayfirst>0) mod.delayfirst--; //=false;
  619. mod.delayload=0;
  620. }
  621. //
  622. // tick 0 effect functions
  623. //
  624. Protracker.prototype.effect_t0_0=function(mod, ch) { // 0 arpeggio
  625. mod.channel[ch].arpeggio=mod.channel[ch].data;
  626. }
  627. Protracker.prototype.effect_t0_1=function(mod, ch) { // 1 slide up
  628. if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data;
  629. }
  630. Protracker.prototype.effect_t0_2=function(mod, ch) { // 2 slide down
  631. if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data;
  632. }
  633. Protracker.prototype.effect_t0_3=function(mod, ch) { // 3 slide to note
  634. if (mod.channel[ch].data) mod.channel[ch].slidetospeed=mod.channel[ch].data;
  635. }
  636. Protracker.prototype.effect_t0_4=function(mod, ch) { // 4 vibrato
  637. if (mod.channel[ch].data&0x0f && mod.channel[ch].data&0xf0) {
  638. mod.channel[ch].vibratodepth=(mod.channel[ch].data&0x0f);
  639. mod.channel[ch].vibratospeed=(mod.channel[ch].data&0xf0)>>4;
  640. }
  641. mod.channel[ch].voiceperiod+=
  642. (mod.channel[ch].vibratodepth/32)*mod.channel[ch].semitone*(mod.vibratotable[mod.channel[ch].vibratowave&3][mod.channel[ch].vibratopos]/127);
  643. mod.channel[ch].flags|=1;
  644. }
  645. Protracker.prototype.effect_t0_5=function(mod, ch) { // 5
  646. }
  647. Protracker.prototype.effect_t0_6=function(mod, ch) { // 6
  648. }
  649. Protracker.prototype.effect_t0_7=function(mod, ch) { // 7
  650. }
  651. Protracker.prototype.effect_t0_8=function(mod, ch) { // 8 unused, used for syncing
  652. mod.syncqueue.unshift(mod.channel[ch].data&0x0f);
  653. }
  654. Protracker.prototype.effect_t0_9=function(mod, ch) { // 9 set sample offset
  655. mod.channel[ch].samplepos=mod.channel[ch].data*256;
  656. }
  657. Protracker.prototype.effect_t0_a=function(mod, ch) { // a
  658. }
  659. Protracker.prototype.effect_t0_b=function(mod, ch) { // b pattern jump
  660. mod.breakrow=0;
  661. mod.patternjump=mod.channel[ch].data;
  662. mod.flags|=16;
  663. }
  664. Protracker.prototype.effect_t0_c=function(mod, ch) { // c set volume
  665. mod.channel[ch].volume=mod.channel[ch].data;
  666. }
  667. Protracker.prototype.effect_t0_d=function(mod, ch) { // d pattern break
  668. mod.breakrow=((mod.channel[ch].data&0xf0)>>4)*10 + (mod.channel[ch].data&0x0f);
  669. if (!(mod.flags&16)) mod.patternjump=mod.position+1;
  670. mod.flags|=16;
  671. }
  672. Protracker.prototype.effect_t0_e=function(mod, ch) { // e
  673. var i=(mod.channel[ch].data&0xf0)>>4;
  674. mod.effects_t0_e[i](mod, ch);
  675. }
  676. Protracker.prototype.effect_t0_f=function(mod, ch) { // f set speed
  677. if (mod.channel[ch].data > 32) {
  678. mod.bpm=mod.channel[ch].data;
  679. } else {
  680. if (mod.channel[ch].data) mod.speed=mod.channel[ch].data;
  681. }
  682. }
  683. //
  684. // tick 0 effect e functions
  685. //
  686. Protracker.prototype.effect_t0_e0=function(mod, ch) { // e0 filter on/off
  687. if (mod.channels > 4) return; // use only for 4ch amiga tunes
  688. if (mod.channel[ch].data&0x0f) {
  689. mod.lowpassNode.frequency.value=3275;
  690. mod.filter=true;
  691. } else {
  692. mod.lowpassNode.frequency.value=28867;
  693. mod.filter=false;
  694. }
  695. }
  696. Protracker.prototype.effect_t0_e1=function(mod, ch) { // e1 fine slide up
  697. mod.channel[ch].period-=mod.channel[ch].data&0x0f;
  698. if (mod.channel[ch].period < 113) mod.channel[ch].period=113;
  699. }
  700. Protracker.prototype.effect_t0_e2=function(mod, ch) { // e2 fine slide down
  701. mod.channel[ch].period+=mod.channel[ch].data&0x0f;
  702. if (mod.channel[ch].period > 856) mod.channel[ch].period=856;
  703. mod.channel[ch].flags|=1;
  704. }
  705. Protracker.prototype.effect_t0_e3=function(mod, ch) { // e3 set glissando
  706. }
  707. Protracker.prototype.effect_t0_e4=function(mod, ch) { // e4 set vibrato waveform
  708. mod.channel[ch].vibratowave=mod.channel[ch].data&0x07;
  709. }
  710. Protracker.prototype.effect_t0_e5=function(mod, ch) { // e5 set finetune
  711. }
  712. Protracker.prototype.effect_t0_e6=function(mod, ch) { // e6 loop pattern
  713. if (mod.channel[ch].data&0x0f) {
  714. if (mod.loopcount) {
  715. mod.loopcount--;
  716. } else {
  717. mod.loopcount=mod.channel[ch].data&0x0f;
  718. }
  719. if (mod.loopcount) mod.flags|=64;
  720. } else {
  721. mod.looprow=mod.row;
  722. }
  723. }
  724. Protracker.prototype.effect_t0_e7=function(mod, ch) { // e7
  725. }
  726. Protracker.prototype.effect_t0_e8=function(mod, ch) { // e8, use for syncing
  727. mod.syncqueue.unshift(mod.channel[ch].data&0x0f);
  728. }
  729. Protracker.prototype.effect_t0_e9=function(mod, ch) { // e9
  730. }
  731. Protracker.prototype.effect_t0_ea=function(mod, ch) { // ea fine volslide up
  732. mod.channel[ch].volume+=mod.channel[ch].data&0x0f;
  733. if (mod.channel[ch].volume > 64) mod.channel[ch].volume=64;
  734. }
  735. Protracker.prototype.effect_t0_eb=function(mod, ch) { // eb fine volslide down
  736. mod.channel[ch].volume-=mod.channel[ch].data&0x0f;
  737. if (mod.channel[ch].volume < 0) mod.channel[ch].volume=0;
  738. }
  739. Protracker.prototype.effect_t0_ec=function(mod, ch) { // ec
  740. }
  741. Protracker.prototype.effect_t0_ed=function(mod, ch) { // ed delay sample
  742. if (mod.tick==(mod.channel[ch].data&0x0f)) {
  743. // start note
  744. var p=mod.patterntable[mod.position];
  745. var pp=mod.row*4*mod.channels + ch*4;
  746. n=(mod.pattern[p][pp]&0x0f)<<8 | mod.pattern[p][pp+1];
  747. if (n) {
  748. mod.channel[ch].period=n;
  749. mod.channel[ch].voiceperiod=mod.channel[ch].period;
  750. mod.channel[ch].samplepos=0;
  751. if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0;
  752. mod.channel[ch].flags|=3; // recalc speed
  753. mod.channel[ch].noteon=1;
  754. }
  755. n=mod.pattern[p][pp+0]&0xf0 | mod.pattern[p][pp+2]>>4;
  756. if (n) {
  757. mod.channel[ch].sample=n-1;
  758. mod.channel[ch].volume=mod.sample[n-1].volume;
  759. }
  760. }
  761. }
  762. Protracker.prototype.effect_t0_ee=function(mod, ch) { // ee delay pattern
  763. mod.patterndelay=mod.channel[ch].data&0x0f;
  764. mod.patternwait=0;
  765. }
  766. Protracker.prototype.effect_t0_ef=function(mod, ch) { // ef
  767. }
  768. //
  769. // tick 1+ effect functions
  770. //
  771. Protracker.prototype.effect_t1_0=function(mod, ch) { // 0 arpeggio
  772. if (mod.channel[ch].data) {
  773. var apn=mod.channel[ch].note;
  774. if ((mod.tick%3)==1) apn+=mod.channel[ch].arpeggio>>4;
  775. if ((mod.tick%3)==2) apn+=mod.channel[ch].arpeggio&0x0f;
  776. if (apn>=0 && apn <= mod.baseperiodtable.length)
  777. mod.channel[ch].voiceperiod = mod.baseperiodtable[apn];
  778. mod.channel[ch].flags|=1;
  779. }
  780. }
  781. Protracker.prototype.effect_t1_1=function(mod, ch) { // 1 slide up
  782. mod.channel[ch].period-=mod.channel[ch].slidespeed;
  783. if (mod.channel[ch].period<113) mod.channel[ch].period=113;
  784. mod.channel[ch].flags|=3; // recalc speed
  785. }
  786. Protracker.prototype.effect_t1_2=function(mod, ch) { // 2 slide down
  787. mod.channel[ch].period+=mod.channel[ch].slidespeed;
  788. if (mod.channel[ch].period>856) mod.channel[ch].period=856;
  789. mod.channel[ch].flags|=3; // recalc speed
  790. }
  791. Protracker.prototype.effect_t1_3=function(mod, ch) { // 3 slide to note
  792. if (mod.channel[ch].period < mod.channel[ch].slideto) {
  793. mod.channel[ch].period+=mod.channel[ch].slidetospeed;
  794. if (mod.channel[ch].period > mod.channel[ch].slideto)
  795. mod.channel[ch].period=mod.channel[ch].slideto;
  796. }
  797. if (mod.channel[ch].period > mod.channel[ch].slideto) {
  798. mod.channel[ch].period-=mod.channel[ch].slidetospeed;
  799. if (mod.channel[ch].period<mod.channel[ch].slideto)
  800. mod.channel[ch].period=mod.channel[ch].slideto;
  801. }
  802. mod.channel[ch].flags|=3; // recalc speed
  803. }
  804. Protracker.prototype.effect_t1_4=function(mod, ch) { // 4 vibrato
  805. mod.channel[ch].voiceperiod+=
  806. (mod.channel[ch].vibratodepth/32)*mod.channel[ch].semitone*(mod.vibratotable[mod.channel[ch].vibratowave&3][mod.channel[ch].vibratopos]/127);
  807. mod.channel[ch].flags|=1;
  808. }
  809. Protracker.prototype.effect_t1_5=function(mod, ch) { // 5 volslide + slide to note
  810. mod.effect_t1_3(mod, ch); // slide to note
  811. mod.effect_t1_a(mod, ch); // volslide
  812. }
  813. Protracker.prototype.effect_t1_6=function(mod, ch) { // 6 volslide + vibrato
  814. mod.effect_t1_4(mod, ch); // vibrato
  815. mod.effect_t1_a(mod, ch); // volslide
  816. }
  817. Protracker.prototype.effect_t1_7=function(mod, ch) { // 7
  818. }
  819. Protracker.prototype.effect_t1_8=function(mod, ch) { // 8 unused
  820. }
  821. Protracker.prototype.effect_t1_9=function(mod, ch) { // 9 set sample offset
  822. }
  823. Protracker.prototype.effect_t1_a=function(mod, ch) { // a volume slide
  824. if (!(mod.channel[ch].data&0x0f)) {
  825. // y is zero, slide up
  826. mod.channel[ch].volume+=(mod.channel[ch].data>>4);
  827. if (mod.channel[ch].volume>64) mod.channel[ch].volume=64;
  828. }
  829. if (!(mod.channel[ch].data&0xf0)) {
  830. // x is zero, slide down
  831. mod.channel[ch].volume-=(mod.channel[ch].data&0x0f);
  832. if (mod.channel[ch].volume<0) mod.channel[ch].volume=0;
  833. }
  834. }
  835. Protracker.prototype.effect_t1_b=function(mod, ch) { // b pattern jump
  836. }
  837. Protracker.prototype.effect_t1_c=function(mod, ch) { // c set volume
  838. }
  839. Protracker.prototype.effect_t1_d=function(mod, ch) { // d pattern break
  840. }
  841. Protracker.prototype.effect_t1_e=function(mod, ch) { // e
  842. var i=(mod.channel[ch].data&0xf0)>>4;
  843. mod.effects_t1_e[i](mod, ch);
  844. }
  845. Protracker.prototype.effect_t1_f=function(mod, ch) { // f
  846. }
  847. //
  848. // tick 1+ effect e functions
  849. //
  850. Protracker.prototype.effect_t1_e0=function(mod, ch) { // e0
  851. }
  852. Protracker.prototype.effect_t1_e1=function(mod, ch) { // e1
  853. }
  854. Protracker.prototype.effect_t1_e2=function(mod, ch) { // e2
  855. }
  856. Protracker.prototype.effect_t1_e3=function(mod, ch) { // e3
  857. }
  858. Protracker.prototype.effect_t1_e4=function(mod, ch) { // e4
  859. }
  860. Protracker.prototype.effect_t1_e5=function(mod, ch) { // e5
  861. }
  862. Protracker.prototype.effect_t1_e6=function(mod, ch) { // e6
  863. }
  864. Protracker.prototype.effect_t1_e7=function(mod, ch) { // e7
  865. }
  866. Protracker.prototype.effect_t1_e8=function(mod, ch) { // e8
  867. }
  868. Protracker.prototype.effect_t1_e9=function(mod, ch) { // e9 retrig sample
  869. if (mod.tick%(mod.channel[ch].data&0x0f)==0)
  870. mod.channel[ch].samplepos=0;
  871. }
  872. Protracker.prototype.effect_t1_ea=function(mod, ch) { // ea
  873. }
  874. Protracker.prototype.effect_t1_eb=function(mod, ch) { // eb
  875. }
  876. Protracker.prototype.effect_t1_ec=function(mod, ch) { // ec cut sample
  877. if (mod.tick==(mod.channel[ch].data&0x0f))
  878. mod.channel[ch].volume=0;
  879. }
  880. Protracker.prototype.effect_t1_ed=function(mod, ch) { // ed delay sample
  881. mod.effect_t0_ed(mod, ch);
  882. }
  883. Protracker.prototype.effect_t1_ee=function(mod, ch) { // ee
  884. }
  885. Protracker.prototype.effect_t1_ef=function(mod, ch) { // ef
  886. }