├── LICENSE ├── dynamicaudio-min.js ├── dynamicaudio.swf ├── index.html ├── modfile.js ├── modplayer.js └── mods ├── ambpower.mod ├── dope.mod ├── frust.mod ├── mindkick.mod └── sundance.mod /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010 by Matt Westcott 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /dynamicaudio-min.js: -------------------------------------------------------------------------------- 1 | var swfobject=function(){var m="undefined",t="object",W="Shockwave Flash",bh="ShockwaveFlash.ShockwaveFlash",F="application/x-shockwave-flash",X="SWFObjectExprInst",Y="onreadystatechange",r=window,i=document,w=navigator,Z=false,G=[bi],x=[],H=[],B=[],D,I,O,ba,z=false,J=false,u,P,bb=true,f=function(){var a=typeof i.getElementById!=m&&typeof i.getElementsByTagName!=m&&typeof i.createElement!=m,c=w.userAgent.toLowerCase(),b=w.platform.toLowerCase(),d=b?/win/.test(b):/win/.test(c),g=b?/mac/.test(b):/mac/.test(c),j=/webkit/.test(c)?parseFloat(c.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,h=!+"\v1",o=[0,0,0],l=null;if(typeof w.plugins!=m&&typeof w.plugins[W]==t){l=w.plugins[W].description;if(l&&!(typeof w.mimeTypes!=m&&w.mimeTypes[F]&&!w.mimeTypes[F].enabledPlugin)){Z=true;h=false;l=l.replace(/^.*\s+(\S+\s+\S+$)/,"$1");o[0]=parseInt(l.replace(/^(.*)\..*$/,"$1"),10);o[1]=parseInt(l.replace(/^.*\.(.*)\s.*$/,"$1"),10);o[2]=/[a-zA-Z]/.test(l)?parseInt(l.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else if(typeof r.ActiveXObject!=m){try{var q=new ActiveXObject(bh);if(q){l=q.GetVariable("$version");if(l){h=true;l=l.split(" ")[1].split(",");o=[parseInt(l[0],10),parseInt(l[1],10),parseInt(l[2],10)]}}}catch(e){}}return{w3:a,pv:o,wk:j,ie:h,win:d,mac:g}}(),bo=function(){if(!f.w3){return}if((typeof i.readyState!=m&&i.readyState=="complete")||(typeof i.readyState==m&&(i.getElementsByTagName("body")[0]||i.body))){C()}if(!z){if(typeof i.addEventListener!=m){i.addEventListener("DOMContentLoaded",C,false)}if(f.ie&&f.win){i.attachEvent(Y,function(){if(i.readyState=="complete"){i.detachEvent(Y,arguments.callee);C()}});if(r==top){(function(){if(z){return}try{i.documentElement.doScroll("left")}catch(e){setTimeout(arguments.callee,0);return}C()})()}}if(f.wk){(function(){if(z){return}if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}C()})()}bc(C)}}();function C(){if(z){return}try{var a=i.getElementsByTagName("body")[0].appendChild(y("span"));a.parentNode.removeChild(a)}catch(e){return}z=true;var c=G.length;for(var b=0;b0){for(var c=0;c0){var j=s(b);if(j){if(K(x[c].swfVersion)&&!(f.wk&&f.wk<312)){A(b,true);if(d){g.success=true;g.ref=R(b);d(g)}}else if(x[c].expressInstall&&S()){var h={};h.data=x[c].expressInstall;h.width=j.getAttribute("width")||"0";h.height=j.getAttribute("height")||"0";if(j.getAttribute("class")){h.styleclass=j.getAttribute("class")}if(j.getAttribute("align")){h.align=j.getAttribute("align")}var o={};var l=j.getElementsByTagName("param");var q=l.length;for(var p=0;p'}}g.outerHTML=''+o+'';H[H.length]=a.id;d=s(a.id)}else{var q=y(t);q.setAttribute("type",F);for(var p in a){if(a[p]!=Object.prototype[p]){if(p.toLowerCase()=="styleclass"){q.setAttribute("class",a[p])}else if(p.toLowerCase()!="classid"){q.setAttribute(p,a[p])}}}for(var n in c){if(c[n]!=Object.prototype[n]&&n.toLowerCase()!="movie"){bm(q,n,c[n])}}g.parentNode.replaceChild(q,g);d=q}}return d}function bm(a,c,b){var d=y("param");d.setAttribute("name",c);d.setAttribute("value",b);a.appendChild(d)}function be(a){var c=s(a);if(c&&c.nodeName=="OBJECT"){if(f.ie&&f.win){c.style.display="none";(function(){if(c.readyState==4){bn(a)}else{setTimeout(arguments.callee,10)}})()}else{c.parentNode.removeChild(c)}}}function bn(a){var c=s(a);if(c){for(var b in c){if(typeof c[b]=="function"){c[b]=null}}c.parentNode.removeChild(c)}}function s(a){var c=null;try{c=i.getElementById(a)}catch(e){}return c}function y(a){return i.createElement(a)}function bj(a,c,b){a.attachEvent(c,b);B[B.length]=[a,c,b]}function K(a){var c=f.pv,b=a.split(".");b[0]=parseInt(b[0],10);b[1]=parseInt(b[1],10)||0;b[2]=parseInt(b[2],10)||0;return(c[0]>b[0]||(c[0]==b[0]&&c[1]>b[1])||(c[0]==b[0]&&c[1]==b[1]&&c[2]>=b[2]))?true:false}function bf(a,c,b,d){if(f.ie&&f.mac){return}var g=i.getElementsByTagName("head")[0];if(!g){return}var j=(b&&typeof b=="string")?b:"screen";if(d){u=null;P=null}if(!u||P!=j){var h=y("style");h.setAttribute("type","text/css");h.setAttribute("media",j);u=g.appendChild(h);if(f.ie&&f.win&&typeof i.styleSheets!=m&&i.styleSheets.length>0){u=i.styleSheets[i.styleSheets.length-1]}P=j}if(f.ie&&f.win){if(u&&typeof u.addRule==t){u.addRule(a,c)}}else{if(u&&typeof i.createTextNode!=m){u.appendChild(i.createTextNode(a+" {"+c+"}"))}}}function A(a,c){if(!bb){return}var b=c?"visible":"hidden";if(z&&s(a)){s(a).style.visibility=b}else{bf("#"+a,"visibility:"+b)}}function bg(a){var c=/[\\\"<>\.;]/;var b=c.exec(a)!=null;return b&&typeof encodeURIComponent!=m?encodeURIComponent(a):a}var bp=function(){if(f.ie&&f.win){window.attachEvent("onunload",function(){var a=B.length;for(var c=0;c 2 | 3 | 4 | 5 | 6 | 7 | 8 | 63 | 64 | 65 |

JSModPlayer

66 |

the imaginatively named Javascript .mod player by Gasman

67 | 74 |

(refresh the page to stop audio - no proper playback controls yet...)

75 |
76 |

This is a work in progress - not all effects are implemented yet. Sound output is done with Ben Firshman's dynamicaudio.js (using Firefox's audio data API, falling back on Flash if not available).

77 |

source code, on Github

78 |

@westdotcodottt - matt.west.co.tt - gasman@raww.org - 22 May 2010

79 | 80 | 81 | -------------------------------------------------------------------------------- /modfile.js: -------------------------------------------------------------------------------- 1 | function ModFile(mod) { 2 | function trimNulls(str) { 3 | return str.replace(/\x00+$/, ''); 4 | } 5 | function getWord(str, pos) { 6 | return (str.charCodeAt(pos) << 8) + str.charCodeAt(pos+1) 7 | } 8 | 9 | this.data = mod; 10 | this.samples = []; 11 | this.positions = []; 12 | this.patternCount = 0; 13 | this.patterns = []; 14 | 15 | this.title = trimNulls(mod.substr(0, 20)) 16 | 17 | /* TODO: distinguish 15-sample and 31-sample mods. Currently assuming 31-sample. 18 | "Check the bytes at location 471 in the file. If there is text there (ASCII 19 | $20-$7E (32-126)), then you can probably assume it's a 31-instrument file. 20 | Otherwise, it's an older 15 instrument file." */ 21 | 22 | this.sampleCount = 31; 23 | 24 | for (var i = 0; i < this.sampleCount; i++) { 25 | var sampleInfo = mod.substr(20 + i*30, 30); 26 | var sampleName = trimNulls(sampleInfo.substr(0, 22)); 27 | this.samples[i] = { 28 | length: getWord(sampleInfo, 22) * 2, 29 | finetune: sampleInfo.charCodeAt(24), 30 | volume: sampleInfo.charCodeAt(25), 31 | repeatOffset: getWord(sampleInfo, 26) * 2, 32 | repeatLength: getWord(sampleInfo, 28) * 2, 33 | } 34 | } 35 | 36 | this.positionCount = mod.charCodeAt(950); 37 | this.positionLoopPoint = mod.charCodeAt(951); 38 | for (var i = 0; i < 128; i++) { 39 | this.positions[i] = mod.charCodeAt(952+i); 40 | if (this.positions[i] >= this.patternCount) { 41 | this.patternCount = this.positions[i]+1; 42 | } 43 | } 44 | 45 | var identifier = mod.substr(1080, 4); 46 | 47 | var channelCountByIdentifier = { 48 | 'TDZ1': 1, '1CHN': 1, 'TDZ2': 2, '2CHN': 2, 'TDZ3': 3, '3CHN': 3, 49 | 'M.K.': 4, 'FLT4': 4, 'M!K!': 4, '4CHN': 4, 'TDZ4': 4, '5CHN': 5, 'TDZ5': 5, 50 | '6CHN': 6, 'TDZ6': 6, '7CHN': 7, 'TDZ7': 7, '8CHN': 8, 'TDZ8': 8, 'OCTA': 8, 'CD81': 8, 51 | '9CHN': 9, 'TDZ9': 9, 52 | '10CH': 10, '11CH': 11, '12CH': 12, '13CH': 13, '14CH': 14, '15CH': 15, '16CH': 16, '17CH': 17, 53 | '18CH': 18, '19CH': 19, '20CH': 20, '21CH': 21, '22CH': 22, '23CH': 23, '24CH': 24, '25CH': 25, 54 | '26CH': 26, '27CH': 27, '28CH': 28, '29CH': 29, '30CH': 30, '31CH': 31, '32CH': 32 55 | } 56 | 57 | this.channelCount = channelCountByIdentifier[identifier]; 58 | if (!this.channelCount) { 59 | //console.log("Warning: unknown identifier " + identifier + ". Assuming 4 channels."); 60 | this.channelCount = 4; 61 | } 62 | 63 | var patternOffset = 1084; 64 | for (var pat = 0; pat < this.patternCount; pat++) { 65 | this.patterns[pat] = []; 66 | for (var row = 0; row < 64; row++) { 67 | this.patterns[pat][row] = []; 68 | for (var chan = 0; chan < this.channelCount; chan++) { 69 | b0 = mod.charCodeAt(patternOffset); 70 | b1 = mod.charCodeAt(patternOffset + 1); 71 | b2 = mod.charCodeAt(patternOffset + 2); 72 | b3 = mod.charCodeAt(patternOffset + 3); 73 | this.patterns[pat][row][chan] = { 74 | sample: (b0 & 0xf0) | (b2 >> 4), 75 | period: ((b0 & 0x0f) << 8) | b1, 76 | effect: b2 & 0x0f, 77 | effectParameter: b3 78 | }; 79 | patternOffset += 4; 80 | } 81 | } 82 | } 83 | 84 | var sampleOffset = patternOffset; 85 | for (var s = 0; s < this.sampleCount; s++) { 86 | this.samples[s].startOffset = sampleOffset; 87 | sampleOffset += this.samples[s].length; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /modplayer.js: -------------------------------------------------------------------------------- 1 | /* 2 | ModPeriodTable[ft][n] = the period to use for note number n at finetune value ft. 3 | Finetune values are in twos-complement, i.e. [0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1] 4 | The first table is used to generate a reverse lookup table, to find out the note number 5 | for a period given in the MOD file. 6 | */ 7 | var ModPeriodTable = [ 8 | [1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960 , 906, 9 | 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480 , 453, 10 | 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240 , 226, 11 | 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120 , 113, 12 | 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 , 56 ], 13 | [1700, 1604, 1514, 1430, 1348, 1274, 1202, 1134, 1070, 1010, 954 , 900, 14 | 850 , 802 , 757 , 715 , 674 , 637 , 601 , 567 , 535 , 505 , 477 , 450, 15 | 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 239 , 225, 16 | 213 , 201 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119 , 113, 17 | 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 , 56 ], 18 | [1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948 , 894, 19 | 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474 , 447, 20 | 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237 , 224, 21 | 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118 , 112, 22 | 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 , 56 ], 23 | [1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940 , 888, 24 | 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470 , 444, 25 | 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235 , 222, 26 | 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118 , 111, 27 | 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 , 55 ], 28 | [1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 990 , 934 , 882, 29 | 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 495 , 467 , 441, 30 | 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233 , 220, 31 | 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 124 , 117 , 110, 32 | 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 62 , 58 , 55 ], 33 | [1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926 , 874, 34 | 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463 , 437, 35 | 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232 , 219, 36 | 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116 , 109, 37 | 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 , 54 ], 38 | [1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920 , 868, 39 | 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460 , 434, 40 | 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230 , 217, 41 | 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115 , 109, 42 | 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 , 54 ], 43 | [1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914 , 862, 44 | 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457 , 431, 45 | 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228 , 216, 46 | 204 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114 , 108, 47 | 102 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 , 54 ], 48 | [1814, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 49 | 907 , 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480, 50 | 453 , 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240, 51 | 226 , 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120, 52 | 113 , 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 ], 53 | [1800, 1700, 1604, 1514, 1430, 1350, 1272, 1202, 1134, 1070, 1010, 954, 54 | 900 , 850 , 802 , 757 , 715 , 675 , 636 , 601 , 567 , 535 , 505 , 477, 55 | 450 , 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 238, 56 | 225 , 212 , 200 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119, 57 | 112 , 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 ], 58 | [1788, 1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948, 59 | 894 , 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474, 60 | 447 , 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237, 61 | 223 , 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118, 62 | 111 , 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 ], 63 | [1774, 1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940, 64 | 887 , 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470, 65 | 444 , 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235, 66 | 222 , 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118, 67 | 111 , 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 ], 68 | [1762, 1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 988 , 934, 69 | 881 , 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 494 , 467, 70 | 441 , 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233, 71 | 220 , 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 123 , 117, 72 | 110 , 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 61 , 58 ], 73 | [1750, 1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926, 74 | 875 , 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463, 75 | 437 , 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232, 76 | 219 , 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116, 77 | 109 , 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 ], 78 | [1736, 1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920, 79 | 868 , 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460, 80 | 434 , 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230, 81 | 217 , 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115, 82 | 108 , 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 ], 83 | [1724, 1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914, 84 | 862 , 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457, 85 | 431 , 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228, 86 | 216 , 203 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114, 87 | 108 , 101 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 ]]; 88 | 89 | var ModPeriodToNoteNumber = {}; 90 | for (var i = 0; i < ModPeriodTable[0].length; i++) { 91 | ModPeriodToNoteNumber[ModPeriodTable[0][i]] = i; 92 | } 93 | 94 | function ModPlayer(mod, rate) { 95 | /* timing calculations */ 96 | var ticksPerSecond = 7093789.2; /* PAL frequency */ 97 | var ticksPerFrame; /* calculated by setBpm */ 98 | var ticksPerOutputSample = Math.round(ticksPerSecond / rate); 99 | var ticksSinceStartOfFrame = 0; 100 | 101 | function setBpm(bpm) { 102 | /* x beats per minute => x*4 rows per minute */ 103 | ticksPerFrame = Math.round(ticksPerSecond * 2.5/bpm); 104 | } 105 | setBpm(125); 106 | 107 | /* initial player state */ 108 | var framesPerRow = 6; 109 | var currentFrame = 0; 110 | var currentPattern; 111 | var currentPosition; 112 | var currentRow; 113 | 114 | var channels = []; 115 | for (var chan = 0; chan < mod.channelCount; chan++) { 116 | channels[chan] = { 117 | playing: false, 118 | sample: mod.samples[0], 119 | finetune: 0, 120 | volume: 0, 121 | volumeDelta: 0, 122 | periodDelta: 0, 123 | arpeggioActive: false 124 | }; 125 | } 126 | 127 | function loadRow(rowNumber) { 128 | currentRow = rowNumber; 129 | currentFrame = 0; 130 | for (var chan = 0; chan < mod.channelCount; chan++) { 131 | var note = currentPattern[currentRow][chan]; 132 | if (note.period != 0 || note.sample != 0) { 133 | channels[chan].playing = true; 134 | channels[chan].samplePosition = 0; 135 | channels[chan].ticksSinceStartOfSample = 0; /* that's 'sample' as in 'individual volume reading' */ 136 | if (note.sample != 0) { 137 | channels[chan].sample = mod.samples[note.sample - 1]; 138 | channels[chan].volume = channels[chan].sample.volume; 139 | channels[chan].finetune = channels[chan].sample.finetune; 140 | } 141 | if (note.period != 0) { 142 | channels[chan].noteNumber = ModPeriodToNoteNumber[note.period]; 143 | channels[chan].ticksPerSample = ModPeriodTable[channels[chan].finetune][channels[chan].noteNumber] * 2; 144 | } 145 | } 146 | if (note.effect != 0 || note.effectParameter != 0) { 147 | channels[chan].volumeDelta = 0; /* new effects cancel volumeDelta */ 148 | channels[chan].periodDelta = 0; /* new effects cancel periodDelta */ 149 | channels[chan].arpeggioActive = false; 150 | switch (note.effect) { 151 | case 0x00: /* arpeggio: 0xy */ 152 | channels[chan].arpeggioActive = true; 153 | channels[chan].arpeggioNotes = [ 154 | channels[chan].noteNumber, 155 | channels[chan].noteNumber + (note.effectParameter >> 4), 156 | channels[chan].noteNumber + (note.effectParameter & 0x0f) 157 | ] 158 | channels[chan].arpeggioCounter = 0; 159 | break; 160 | case 0x01: /* pitch slide up - 1xx */ 161 | channels[chan].periodDelta = -note.effectParameter; 162 | break; 163 | case 0x02: /* pitch slide down - 2xx */ 164 | channels[chan].periodDelta = note.effectParameter; 165 | break; 166 | case 0x0a: /* volume slide - Axy */ 167 | if (note.effectParameter & 0xf0) { 168 | /* volume increase by x */ 169 | channels[chan].volumeDelta = note.effectParameter >> 4; 170 | } else { 171 | /* volume decrease by y */ 172 | channels[chan].volumeDelta = -note.effectParameter; 173 | } 174 | break; 175 | case 0x0c: /* volume */ 176 | if (note.effectParameter > 64) { 177 | channels[chan].volume = 64; 178 | } else { 179 | channels[chan].volume = note.effectParameter; 180 | } 181 | break; 182 | case 0x0f: /* tempo change */ 183 | if (note.effectParameter == 0) { 184 | } else if (note.effectParameter <= 32) { 185 | framesPerRow = note.effectParameter; 186 | } else { 187 | setBpm(note.effectParameter); 188 | } 189 | break; 190 | } 191 | } 192 | } 193 | } 194 | 195 | function loadPattern(patternNumber) { 196 | currentPattern = mod.patterns[patternNumber]; 197 | loadRow(0); 198 | } 199 | 200 | function loadPosition(positionNumber) { 201 | currentPosition = positionNumber; 202 | loadPattern(mod.positions[currentPosition]); 203 | } 204 | 205 | loadPosition(0); 206 | 207 | function getNextPosition() { 208 | if (currentPosition + 1 == mod.positionCount) { 209 | loadPosition(mod.positionLoopPoint); 210 | } else { 211 | loadPosition(currentPosition + 1); 212 | } 213 | } 214 | 215 | function getNextRow() { 216 | if (currentRow == 63) { 217 | getNextPosition(); 218 | } else { 219 | loadRow(currentRow + 1); 220 | } 221 | } 222 | 223 | function doFrame() { 224 | currentFrame++; 225 | /* apply volume/pitch slide before fetching row, because the first frame of a row does NOT 226 | have the slide applied */ 227 | for (var chan = 0; chan < mod.channelCount; chan++) { 228 | channels[chan].volume += channels[chan].volumeDelta; 229 | if (channels[chan].volume > 64) { 230 | channels[chan].volume = 64; 231 | } else if (channels[chan].volume < 0) { 232 | channels[chan].volume = 0; 233 | } 234 | channels[chan].ticksPerSample += channels[chan].periodDelta * 2; 235 | if (channels[chan].ticksPerSample > 4096) { 236 | channels[chan].ticksPerSample = 4096; 237 | } else if (channels[chan].ticksPerSample < 96) { /* equivalent to period 48, a bit higher than the highest note */ 238 | channels[chan].ticksPerSample = 96; 239 | } 240 | if (channels[chan].arpeggioActive) { 241 | channels[chan].arpeggioCounter++; 242 | var noteNumber = channels[chan].arpeggioNotes[channels[chan].arpeggioCounter % 3]; 243 | channels[chan].ticksPerSample = ModPeriodTable[channels[chan].finetune][noteNumber] * 2; 244 | } 245 | } 246 | 247 | if (currentFrame == framesPerRow) { 248 | getNextRow(); 249 | } 250 | } 251 | 252 | this.getSamples = function(sampleCount) { 253 | samples = []; 254 | var i = 0; 255 | while (i < sampleCount) { 256 | ticksSinceStartOfFrame += ticksPerOutputSample; 257 | while (ticksSinceStartOfFrame >= ticksPerFrame) { 258 | doFrame(); 259 | ticksSinceStartOfFrame -= ticksPerFrame; 260 | } 261 | 262 | leftOutputLevel = 0; 263 | rightOutputLevel = 0; 264 | for (var chan = 0; chan < mod.channelCount; chan++) { 265 | if (channels[chan].playing) { 266 | channels[chan].ticksSinceStartOfSample += ticksPerOutputSample; 267 | while (channels[chan].ticksSinceStartOfSample >= channels[chan].ticksPerSample) { 268 | channels[chan].samplePosition++; 269 | if (channels[chan].sample.repeatLength > 2 && channels[chan].samplePosition >= channels[chan].sample.repeatOffset + channels[chan].sample.repeatLength) { 270 | channels[chan].samplePosition = channels[chan].sample.repeatOffset; 271 | } else if (channels[chan].samplePosition >= channels[chan].sample.length) { 272 | channels[chan].playing = false; 273 | break; 274 | } else 275 | channels[chan].ticksSinceStartOfSample -= channels[chan].ticksPerSample; 276 | } 277 | if (channels[chan].playing) { 278 | var rawVol = mod.data.charCodeAt(channels[chan].sample.startOffset + channels[chan].samplePosition); 279 | var vol = (((rawVol + 128) & 0xff) - 128) * channels[chan].volume; /* range (-128*64)..(127*64) */ 280 | if (chan & 3 == 0 || chan & 3 == 3) { 281 | leftOutputLevel += vol * 3 282 | rightOutputLevel += vol 283 | } else { 284 | leftOutputLevel += vol 285 | rightOutputLevel += vol * 3 286 | } 287 | /* range of outputlevels is 128*64*2*channelCount */ 288 | /* (well, it could be more for odd channel counts) */ 289 | } 290 | } 291 | } 292 | 293 | samples[i] = leftOutputLevel / (128 * 128 * mod.channelCount); 294 | samples[i+1] = rightOutputLevel / (128 * 128 * mod.channelCount); 295 | i += 2; 296 | } 297 | 298 | return samples; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /mods/ambpower.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gasman/jsmodplayer/27742674506e5f1dcec8864f8b9873906d999592/mods/ambpower.mod -------------------------------------------------------------------------------- /mods/dope.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gasman/jsmodplayer/27742674506e5f1dcec8864f8b9873906d999592/mods/dope.mod -------------------------------------------------------------------------------- /mods/frust.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gasman/jsmodplayer/27742674506e5f1dcec8864f8b9873906d999592/mods/frust.mod -------------------------------------------------------------------------------- /mods/mindkick.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gasman/jsmodplayer/27742674506e5f1dcec8864f8b9873906d999592/mods/mindkick.mod -------------------------------------------------------------------------------- /mods/sundance.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gasman/jsmodplayer/27742674506e5f1dcec8864f8b9873906d999592/mods/sundance.mod --------------------------------------------------------------------------------