├── .gitignore ├── AUTHORS ├── topaz.ttf ├── mods ├── mod.saf ├── Necros │ └── point.s3m └── Mantronix_and_Tip │ └── mod.overload ├── screenshot.jpg ├── music_library.json ├── .editorconfig ├── example.htaccess ├── LICENSE ├── musicLibrary.php ├── js ├── utils.js ├── player.js ├── ui.js ├── pt.js ├── st3.js └── ft2.js ├── README.md ├── style.css └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | .htaccess 2 | mods 3 | *~ 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Noora Halme 2 | Google, LLC 3 | Alex Badarin 4 | Chris Johnson 5 | -------------------------------------------------------------------------------- /topaz.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electronoora/webaudio-mod-player/HEAD/topaz.ttf -------------------------------------------------------------------------------- /mods/mod.saf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electronoora/webaudio-mod-player/HEAD/mods/mod.saf -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electronoora/webaudio-mod-player/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /mods/Necros/point.s3m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electronoora/webaudio-mod-player/HEAD/mods/Necros/point.s3m -------------------------------------------------------------------------------- /mods/Mantronix_and_Tip/mod.overload: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electronoora/webaudio-mod-player/HEAD/mods/Mantronix_and_Tip/mod.overload -------------------------------------------------------------------------------- /music_library.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"composer":"Jugi","songs":[{"file":"we_go.xm","size":50830}]}, 3 | {"composer":"Mantronix_and_Tip","songs":[{"file":"mod.overload","size":199936}]}, 4 | {"composer":"Necros","songs":[{"file":"point.s3m","size":443136}]}, 5 | {"composer":"Unknown","songs":[{"file":"mod.saf","size":5656}]} 6 | ] -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # standard line/whitespace management 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | -------------------------------------------------------------------------------- /example.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | 3 | RewriteCond %{HTTP_HOST} !^mod.haxor.fi$ 4 | RewriteRule ^(.*)$ http://mod.haxor.fi/%{REQUEST_URI} [R,L] 5 | 6 | RewriteCond %{REQUEST_URI} ^/(.*)$ 7 | RewriteCond /var/www/modplay/mods/%1 -d 8 | RewriteRule ^(.*)$ /?composer=%1 [L,QSA] 9 | 10 | RewriteCond %{REQUEST_URI} ^/(.*)/mod.(.*)$ 11 | RewriteCond /var/www/modplay/mods/%1/mod.%2 -f 12 | RewriteRule ^(.*)$ /?mod=%1/mod.%2 [L,QSA] 13 | 14 | RewriteCond %{REQUEST_URI} ^/mod.(.*)$ 15 | RewriteCond /var/www/modplay/mods/mod.%1 -f 16 | RewriteRule ^(.*)$ /?mod=mod.%1 [L,QSA] 17 | 18 | RewriteCond %{REQUEST_URI} ^/(.*).mod$ 19 | RewriteCond /var/www/modplay/mods/%1.mod -f 20 | RewriteRule ^(.*)$ /?mod=%1.mod [L,QSA] 21 | 22 | RewriteCond %{REQUEST_URI} ^/(.*).s3m$ 23 | RewriteCond /var/www/modplay/mods/%1.s3m -f 24 | RewriteRule ^(.*)$ /?mod=%1.s3m [L,QSA] 25 | 26 | RewriteCond %{REQUEST_URI} ^/(.*).xm$ 27 | RewriteCond /var/www/modplay/mods/%1.xm -f 28 | RewriteRule ^(.*)$ /?mod=%1.xm [L,QSA] 29 | 30 | RewriteCond %{REQUEST_URI} ^/(.*).it$ 31 | RewriteCond /var/www/modplay/mods/%1.it -f 32 | RewriteRule ^(.*)$ /?mod=%1.it [L,QSA] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2021 Noora Halme et al. (see AUTHORS) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /musicLibrary.php: -------------------------------------------------------------------------------- 1 | $mod, 'size'=>filesize($composer."/".$mod)); 30 | } 31 | closedir($dir); 32 | 33 | $d['songs']=$songs; 34 | $library[]=$d; 35 | } 36 | 37 | // songs by an unknown composer from the root of "mods/" 38 | $d=array('composer'=>'Unknown'); 39 | $songs=Array(); 40 | $dir=opendir("mods"); 41 | $i=0; 42 | $mods=array(); 43 | while($mod=readdir($dir)) $mods[]=$mod; 44 | sort($mods); 45 | foreach($mods as $mod) { 46 | if (is_file("mods/".$mod)) 47 | $songs[]=array('file'=>$mod, 'size'=>filesize($songpath."/".$mod)); 48 | } 49 | closedir($dir); 50 | $d['songs']=$songs; 51 | $library[]=$d; 52 | 53 | echo json_encode($library); 54 | ?> 55 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Various utility functions 8 | */ 9 | 10 | // helper functions for picking up signed, unsigned, little endian, etc from an unsigned 8-bit buffer 11 | function le_word(buffer, offset) { 12 | return buffer[offset]|(buffer[offset+1]<<8); 13 | } 14 | function le_dword(buffer, offset) { 15 | return buffer[offset]|(buffer[offset+1]<<8)|(buffer[offset+2]<<16)|(buffer[offset+3]<<24); 16 | } 17 | function s_byte(buffer, offset) { 18 | return (buffer[offset]<128)?buffer[offset]:(buffer[offset]-256); 19 | } 20 | function s_le_word(buffer, offset) { 21 | return (le_word(buffer,offset)<32768)?le_word(buffer,offset):(le_word(buffer,offset)-65536); 22 | } 23 | 24 | // convert from MS-DOS extended ASCII to Unicode 25 | function dos2utf(c) { 26 | if (c<128) return String.fromCharCode(c); 27 | var cs=[ 28 | 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 29 | 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 30 | 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 31 | 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, 32 | 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, 33 | 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 34 | 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, 35 | 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0 36 | ]; 37 | return String.fromCharCode(cs[c-128]); 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | webaudio-mod-player 2 | =================== 3 | 4 | ![Screenshot](https://raw.githubusercontent.com/electronoora/webaudio-mod-player/master/screenshot.jpg) 5 | 6 | This is a MOD/S3M/XM module player implemented in Javascript using the Web Audio API and runs fully within the browser. It 7 | has been tested and confirmed to work on Chrome 14+, Firefox 24+, Safari 6+ and Edge 20+. The Javascript performance of 8 | the browsers varies significantly, so some modules may stutter on one browser while the same module can play flawlessly 9 | on other ones. YMMV. 10 | 11 | Although internally each file format is handled by a format specific player class, a front-end wrapper class is used to 12 | provide a common programming interface for the player. 13 | 14 | All player classes use 32-bit floating point arithmetic in the channel mixing code, as well as a wide dynamic range. The 15 | output is scaled down to [-1, 1] domain using a "soft clipping" algorithm to roll off any audio peaks without harsh-sounding 16 | limiting. This should - in most cases - produce a reasonably constant audio volume for all modules. 17 | 18 | Additionally, S3M and XM player classes use linear sample interpolation and volume ramping to produce a smooth Gravis 19 | Ultrasound -like sound quality. The MOD player class attempts to sound more like an Amiga by allowing audio aliasing and 20 | applying a low pass filter. 21 | 22 | None of the player classes fully implement all the features and effects in each file format, but all the major ones should 23 | be implemented. In addition, there most certainly will be some playback bugs in each player class - let me know if you run 24 | into some bad ones. 25 | 26 | You can test the player here: 27 | 28 | https://mod.haxor.fi/ 29 | 30 | 31 | To install on your own server, clone the repo to the document root and edit+rename example.htaccess to match your domain. 32 | Then create a directory 'mods' alongside index.php and structure is like this (note that both PC-style and Amiga-style filenames 33 | are supported but extension must always be in lowercase): 34 | 35 | /mods
36 | /mods/Jugi
37 | /mogs/Jugi/we_go.xm
38 | /mods/Mantronix_and_Tip
39 | /mods/Mantronix_and_Tip/mod.overload
40 | /mods/Necros
41 | /mods/Necros/point.s3m
42 | /mods/mod.saf
43 | 44 | 45 | Copyrights: 46 | - MOD/S3M/XM module player for Web Audio (c) 2012-2021 Noora Halme et al (see AUTHORS) 47 | - Topaz TTF font (c) 2009 dMG of Trueschool and Divine Stylers 48 | - "overload" (c) 1991 by Mantronix and Tip of Phenomena 49 | - "Point of Departure" (c) 1995 Necros / FM 50 | - "we go..." (c) 1996 by Jugi / Complex -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Stylesheet for the UI 8 | */ 9 | @font-face { 10 | font-family: 'Topaz'; 11 | src: url('topaz.ttf'); 12 | } 13 | 14 | body { 15 | font-family: Topaz; 16 | font-size: 16px; background-color: #000; color: #ccc; font-weight: normal; 17 | line-height: 16px; 18 | } 19 | a { color: #99c; text-decoration: none; } 20 | a:visited { color: #99c; } 21 | a:hover { color: #ddf; } 22 | a.down:hover { color: #fdd !important; } 23 | 24 | a.inactive, a.inactive:visited, a.inactive:hover { color: #445; } 25 | 26 | .tdalink { color: #113 !important; } 27 | .tdalink:hover { color: #558 !important; } 28 | .tdalink:visited { color:#113 !important; } 29 | 30 | select, input { 31 | background-color: #000; color: #8f8; 32 | font-family: Topaz; 33 | font-size: 16px; 34 | margin-bottom: 16px; 35 | border: 0px; 36 | outline: none; font-style: normal; font-weight: normal; 37 | line-height: 16px; 38 | } 39 | optgroup { font-family: Topaz; font-size: 16px; color: #88f; font-style: normal; font-weight: normal; line-height: 16px; } 40 | option { font-family: Topaz; color: #8f8; font-size: 16px; height: 16px; line-height: 16px; padding: 0; } 41 | .odd { background-color: #181818; } 42 | .even { background-color: #222222; } 43 | 44 | option:focus, 45 | option:active, 46 | option:checked { 47 | background-color: #aae; 48 | color: #fff; 49 | } 50 | 51 | ::-webkit-scrollbar { 52 | width: 16px; 53 | color: #fff; 54 | border: 1px solid #484; 55 | background-color: #242; 56 | } 57 | ::-webkit-scrollbar-thumb { 58 | color: #88f; 59 | background-color: #484; 60 | border: 1px solid #fff; 61 | } 62 | 63 | #outercontainer { 64 | width: 1176px; 65 | margin: 0 auto 0 auto; 66 | } 67 | 68 | #headercontainer { background-color:#0bc; color:#000; margin-bottom:8px; padding-top: 1px; padding-bottom: 1px; } 69 | 70 | #modtitle { white-space: pre; color: #fff; } 71 | #modinfo { color: #aaa; } 72 | #modtimer { color: #99b; } 73 | 74 | #modsamples { white-space:pre; float:right; margin-left:8px; color: #888 } 75 | 76 | #modpattern { white-space:pre; float:left; margin-top: 16px; height: 24.0em; overflow: hidden; color: #778; } 77 | 78 | #infotext { margin-top: 16px; color: #aae; } 79 | 80 | /* #load_song { float:right; } */ 81 | 82 | #modfile { float:left; width: 480px; } 83 | #fileinfo { width: 480px; float:left; margin-left: 0px; margin-top: 0px; color: #aae; } 84 | 85 | #playlist_box { width: 416px; } 86 | 87 | #musiclibrary { 88 | color: #aae; 89 | width: 500px; 90 | float: left; 91 | } 92 | 93 | #playlist { 94 | width: 420px; 95 | float: right; 96 | color: #aae; 97 | } 98 | 99 | .hl { color: #fff; } 100 | .down { color: #faa !important; } 101 | 102 | .note { color: #88f; } 103 | .sample { color: #8ff; } 104 | .command { color: #8f8; } 105 | .volume { color: #a4c; } 106 | 107 | .samplelist { background-color: #000; color: #778; } 108 | .activesample { background-color: #135; color: #fff; } 109 | 110 | .currentrow { background-color: #335; color: #ccc; } 111 | 112 | .filesize { color: #444; } 113 | 114 | .patterndata { 115 | height: 24.0em; 116 | margin-top: 0px; 117 | overflow: hidden; 118 | display: none; 119 | } 120 | 121 | .currentpattern { 122 | display: inline-block !important; 123 | } 124 | 125 | #modchannels { 126 | white-space:pre; float:left; margin-top: 16px; height: 24.0em; overflow: hidden; color: #99b; 127 | display: none; 128 | } 129 | 130 | #even-channels { 131 | width: 50%; 132 | float:right; 133 | } 134 | #odd-channels { 135 | width: 50%; 136 | float:left; 137 | } 138 | 139 | .channelnr { color:#778; } 140 | .channelsample { color:#778; } 141 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 9 | 29 | 30 | 31 | MOD/S3M/XM module player for Web Audio 32 | 33 | 34 | 35 | var mobileSafari=true;"; 40 | } else { 41 | echo " "; 42 | } 43 | ?> 44 | 45 | 46 | 47 | 48 | 49 | 50 | 66 | 67 | 68 |
69 |
70 |
MOD/S3M/XM module player for Web Audio
71 |
(c) 2012-2021 Firehawk/TDA
72 |
73 |
74 |
75 |
76 |
77 | 78 | ('    ') 79 | 80 |

81 | [|<] 82 | [<<] 83 | [play] 84 | [pause] 85 | [>>] 86 | [>|] 87 | 88 | [rept] 89 | [)oo(] 90 | [trks] 91 | [filt] 92 | 93 | [load song] 94 |
95 |
96 |
97 |
98 |
99 | The player has been tested on Chrome 14+, Firefox 24+, Safari 6+ and Edge 20+ so far. Disable AdBlock if you get cuts or stuttering! 100 | Source code available on GitHub. 101 |
102 |
103 | 104 | 136 | 137 |
138 | 139 | 140 | -------------------------------------------------------------------------------- /js/player.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Front end wrapper class for format-specific players 8 | */ 9 | 10 | function Modplayer() 11 | { 12 | this.supportedformats=new Array('mod', 's3m', 'xm'); 13 | 14 | this.url=""; 15 | this.format="s3m"; 16 | 17 | this.state="initializing.."; 18 | this.request=null; 19 | 20 | this.loading=false; 21 | this.playing=false; 22 | this.paused=false; 23 | this.repeat=false; 24 | 25 | this.separation=1; 26 | this.mixval=8.0; 27 | 28 | this.amiga500=false; 29 | 30 | this.filter=false; 31 | this.endofsong=false; 32 | 33 | this.autostart=false; 34 | this.bufferstodelay=4; // adjust this if you get stutter after loading new song 35 | this.delayfirst=0; 36 | this.delayload=0; 37 | 38 | this.onReady=function(){}; 39 | this.onPlay=function(){}; 40 | this.onStop=function(){}; 41 | 42 | this.buffer=0; 43 | this.mixerNode=0; 44 | this.context=null; 45 | this.samplerate=44100; 46 | this.bufferlen=4096; 47 | 48 | this.chvu=new Float32Array(32); 49 | 50 | // format-specific player 51 | this.player=null; 52 | 53 | // read-only data from player class 54 | this.title=""; 55 | this.signature="...."; 56 | this.songlen=0; 57 | this.channels=0; 58 | this.patterns=0; 59 | this.samplenames=new Array(); 60 | } 61 | 62 | 63 | 64 | // load module from url into local buffer 65 | Modplayer.prototype.load = function(url) 66 | { 67 | // try to identify file format from url and create a new 68 | // player class for it 69 | this.url=url; 70 | var ext=url.split('.').pop().toLowerCase().trim(); 71 | if (this.supportedformats.indexOf(ext)==-1) { 72 | // unknown extension, maybe amiga-style prefix? 73 | ext=url.split('/').pop().split('.').shift().toLowerCase().trim(); 74 | if (this.supportedformats.indexOf(ext)==-1) { 75 | // ok, give up 76 | return false; 77 | } 78 | } 79 | this.format=ext; 80 | 81 | switch (ext) { 82 | case 'mod': 83 | this.player=new Protracker(); 84 | break; 85 | case 's3m': 86 | this.player=new Screamtracker(); 87 | break; 88 | case 'xm': 89 | this.player=new Fasttracker(); 90 | break; 91 | } 92 | 93 | this.player.onReady=this.loadSuccess; 94 | 95 | this.state="loading.."; 96 | var request = new XMLHttpRequest(); 97 | request.open("GET", this.url, true); 98 | request.responseType = "arraybuffer"; 99 | this.request = request; 100 | this.loading=true; 101 | var asset = this; 102 | request.onprogress = function(oe) { 103 | asset.state="loading ("+Math.floor(100*oe.loaded/oe.total)+"%).."; 104 | }; 105 | request.onload = function() { 106 | var buffer=new Uint8Array(request.response); 107 | this.state="parsing.."; 108 | if (asset.player.parse(buffer)) { 109 | // copy static data from player 110 | asset.title=asset.player.title 111 | asset.signature=asset.player.signature; 112 | asset.songlen=asset.player.songlen; 113 | asset.channels=asset.player.channels; 114 | asset.patterns=asset.player.patterns; 115 | asset.filter=asset.player.filter; 116 | if (asset.context) asset.setfilter(asset.filter); 117 | asset.mixval=asset.player.mixval; // usually 8.0, though 118 | asset.samplenames=new Array(32) 119 | for(i=0;i<32;i++) asset.samplenames[i]=""; 120 | if (asset.format=='xm' || asset.format=='it') { 121 | for(i=0;i= this.player.songlen) this.stop(); 227 | } 228 | this.position=this.player.position; 229 | this.row=this.player.row; 230 | } 231 | 232 | 233 | 234 | // set whether module repeats after songlen 235 | Modplayer.prototype.setrepeat = function(rep) 236 | { 237 | this.repeat=rep; 238 | if (this.player) this.player.repeat=rep; 239 | } 240 | 241 | 242 | 243 | // set stereo separation mode (0=standard, 1=65/35 mix, 2=mono) 244 | Modplayer.prototype.setseparation = function(sep) 245 | { 246 | this.separation=sep; 247 | if (this.player) this.player.separation=sep; 248 | } 249 | 250 | 251 | 252 | // set autostart to play immediately after loading 253 | Modplayer.prototype.setautostart = function(st) 254 | { 255 | this.autostart=st; 256 | } 257 | 258 | 259 | 260 | // set amiga model - changes lowpass filter state 261 | Modplayer.prototype.setamigamodel = function(amiga) 262 | { 263 | if (amiga=="600" || amiga=="1200" || amiga=="4000") { 264 | this.amiga500=false; 265 | if (this.filterNode) this.filterNode.frequency.value=22050; 266 | } else { 267 | this.amiga500=true; 268 | if (this.filterNode) this.filterNode.frequency.value=6000; 269 | } 270 | } 271 | 272 | 273 | 274 | // amiga "LED" filter 275 | Modplayer.prototype.setfilter = function(f) 276 | { 277 | if (f) { 278 | this.lowpassNode.frequency.value=3275; 279 | } else { 280 | this.lowpassNode.frequency.value=28867; 281 | } 282 | this.filter=f; 283 | if (this.player) this.player.filter=f; 284 | } 285 | 286 | 287 | 288 | // are there E8x sync events queued? 289 | Modplayer.prototype.hassyncevents = function() 290 | { 291 | if (this.player) return (this.player.syncqueue.length != 0); 292 | return false; 293 | } 294 | 295 | 296 | 297 | // pop oldest sync event nybble from the FIFO queue 298 | Modplayer.prototype.popsyncevent = function() 299 | { 300 | if (this.player) return this.player.syncqueue.pop(); 301 | } 302 | 303 | 304 | 305 | // ger current pattern number 306 | Modplayer.prototype.currentpattern = function() 307 | { 308 | if (this.player) return this.player.patterntable[this.player.position]; 309 | } 310 | 311 | 312 | 313 | // get current pattern in standard unpacked format (note, sample, volume, command, data) 314 | // note: 254=noteoff, 255=no note 315 | // sample: 0=no instrument, 1..255=sample number 316 | // volume: 255=no volume set, 0..64=set volume, 65..239=ft2 volume commands 317 | // command: 0x2e=no command, 0..0x24=effect command 318 | // data: 0..255 319 | Modplayer.prototype.patterndata = function(pn) 320 | { 321 | var i, c, patt; 322 | if (this.format=='mod') { 323 | patt=new Uint8Array(this.player.pattern_unpack[pn]); 324 | for(i=0;i<64;i++) for(c=0;c=this.channels) return 0; 362 | return this.player.channel[ch].noteon; 363 | } 364 | 365 | 366 | 367 | // get currently active sample on channel 368 | Modplayer.prototype.currentsample = function(ch) 369 | { 370 | if (ch>=this.channels) return 0; 371 | if (this.format=="xm" || this.format=="it") return this.player.channel[ch].instrument; 372 | return this.player.channel[ch].sample; 373 | } 374 | 375 | 376 | 377 | // get length of currently playing pattern 378 | Modplayer.prototype.currentpattlen = function() 379 | { 380 | if (this.format=="mod" || this.format=="s3m") return 64; 381 | return this.player.patternlen[this.player.patterntable[this.player.position]]; 382 | } 383 | 384 | 385 | 386 | // create the web audio context 387 | Modplayer.prototype.createContext = function() 388 | { 389 | if ( typeof AudioContext !== 'undefined') { 390 | this.context = new AudioContext(); 391 | } else { 392 | this.context = new webkitAudioContext(); 393 | } 394 | this.samplerate=this.context.sampleRate; 395 | this.bufferlen=(this.samplerate > 44100) ? 4096 : 2048; 396 | 397 | // Amiga 500 fixed filter at 6kHz. WebAudio lowpass is 12dB/oct, whereas 398 | // older Amigas had a 6dB/oct filter at 4900Hz. 399 | this.filterNode=this.context.createBiquadFilter(); 400 | if (this.amiga500) { 401 | this.filterNode.frequency.value=6000; 402 | } else { 403 | this.filterNode.frequency.value=22050; 404 | } 405 | 406 | // "LED filter" at 3275kHz - off by default 407 | this.lowpassNode=this.context.createBiquadFilter(); 408 | this.setfilter(this.filter); 409 | 410 | // mixer 411 | if ( typeof this.context.createJavaScriptNode === 'function') { 412 | this.mixerNode=this.context.createJavaScriptNode(this.bufferlen, 1, 2); 413 | } else { 414 | this.mixerNode=this.context.createScriptProcessor(this.bufferlen, 1, 2); 415 | } 416 | this.mixerNode.module=this; 417 | this.mixerNode.onaudioprocess=Modplayer.prototype.mix; 418 | 419 | // patch up some cables :) 420 | this.mixerNode.connect(this.filterNode); 421 | this.filterNode.connect(this.lowpassNode); 422 | this.lowpassNode.connect(this.context.destination); 423 | } 424 | 425 | 426 | 427 | // scriptnode callback - pass through to player class 428 | Modplayer.prototype.mix = function(ape) { 429 | var mod; 430 | 431 | if (ape.srcElement) { 432 | mod=ape.srcElement.module; 433 | } else { 434 | mod=this.module; 435 | } 436 | 437 | if (mod.player && mod.delayfirst==0) { 438 | mod.player.repeat=mod.repeat; 439 | 440 | var bufs=new Array(ape.outputBuffer.getChannelData(0), ape.outputBuffer.getChannelData(1)); 441 | var buflen=ape.outputBuffer.length; 442 | mod.player.mix(mod.player, bufs, buflen); 443 | 444 | // apply stereo separation and soft clipping 445 | var outp=new Float32Array(2); 446 | for(var s=0;s0) mod.delayfirst--; 483 | mod.delayload=0; 484 | 485 | // update this.chvu from player channel vu 486 | for(var i=0;i"+notelist[n&0x0f]+(n>>4)+""); } 17 | function prv(v) { return (v<=0x40)?hb(v):(ft2volcmds[(v-0x50)>>4]+hn(v&0x0f)); } 18 | 19 | // 14 chars per channel (max 112) 20 | if (cc<=8) return ((n<255) ? (prn(n)+" ") : ("... "))+ 21 | (s ? (""+hb(s)+" ") : (".. "))+ 22 | ( (v!=255)?(""+prv(v)+" "):(".. "))+ 23 | ((c!=0x2e) ? (""+String.fromCharCode(c)+hb(d)+"") : "...")+ 24 | "|"; 25 | 26 | // 11 chars (max 110) 27 | if (cc<=10) return ((n<255) ? prn(n) : ("..."))+ 28 | (s ? (""+hb(s)+"") : (".."))+ 29 | ( (v!=255)?(""+prv(v)+""):(".."))+ 30 | ((c!=0x2e) ? (""+String.fromCharCode(c)+hb(d)+"") : "...")+ 31 | "|"; 32 | 33 | // 9 chars (max 108) 34 | if (cc<=12) return ((n<255) ? prn(n) : ("..."))+ 35 | (s ? (""+hb(s)+"") : 36 | ((v!=255)?(""+prv(v)+""):("..")))+ 37 | ((c!=0x2e) ? (""+String.fromCharCode(c)+hb(d)+"") : "...")+ 38 | "|"; 39 | 40 | // 7 chars (max 112) 41 | if (cc<=16) return ((n<255) ? prn(n) : ("..."))+ 42 | ((c!=0x2e) ? (""+String.fromCharCode(c)+hb(d)+"") : 43 | ( s ? (""+hb(s)+".") : ( (v!=255)?(""+prv(v)+"."):("..."))) 44 | )+"|"; 45 | 46 | // 3 chars (max 96) 47 | return ((n<255) ? prn(n) : 48 | (s ? ("."+hb(s)+"") : 49 | ((c!=0x2e) ? (""+String.fromCharCode(c)+hb(d)+"") : 50 | ((v!=255) ? (" "+prv(v)+""):("..."))))); 51 | } 52 | 53 | function hn(n) 54 | { 55 | if (typeof n == "undefined") return "0"; 56 | var s=(n&0x0f).toString(16); 57 | return s.toUpperCase(); 58 | } 59 | 60 | function hb(n) 61 | { 62 | if (typeof n == "undefined") return "00"; 63 | var s=n.toString(16); 64 | if (s.length==1) s='0'+s; 65 | return s.toUpperCase(); 66 | } 67 | 68 | function hw(n) 69 | { 70 | if (typeof n == "undefined") return "0000"; 71 | var s=n.toString(16); 72 | if (s.length==3) s='0'+s; 73 | else if (s.length==2) s='00'+s; 74 | else if (s.length==1) s='000'+s; 75 | return s.toUpperCase(); 76 | } 77 | 78 | function pad(s,l) 79 | { 80 | var ps=s; 81 | if (ps.length > l) ps=ps.substring(0,l-1); 82 | while (ps.length < l) ps+=" "; 83 | return ps; 84 | } 85 | 86 | function rpe(s) 87 | { 88 | var rs=""; 89 | for(var i=0;i') rs+=">" 91 | else if (s[i]=='<') rs+='<'; 92 | else if (s[i]=='&') rs+='&'; 93 | else rs+=s[i]; 94 | } 95 | return rs; 96 | } 97 | 98 | function vu(l) 99 | { 100 | var f=Math.round(l*20); 101 | var b=""; 102 | 103 | b=''; 104 | for(i=0;i<10;i++) b+=(i"+song+""); 134 | } 135 | return !dupe; 136 | } 137 | 138 | function refreshStoredPlaylist() 139 | { 140 | if(typeof(Storage) !== "undefined") { 141 | var playlist=[]; 142 | $("#playlist_box option").each(function(o) { 143 | playlist.push($(this).val()); 144 | }); 145 | localStorage["playlist"]=JSON.stringify(playlist); 146 | } 147 | } 148 | 149 | function playFromPlaylist(module, autostart) 150 | { 151 | module.stopaudio(); 152 | module.setautostart(autostart); 153 | oldpos=-1; 154 | var loadInterval=setInterval(function(){ 155 | if (!module.delayload) { 156 | window.currentModule=$("#playlist_box option:selected").val(); 157 | window.playlistPosition=$("#playlist_box option").index($("#playlist_box option:selected")); 158 | window.playlistActive=true; 159 | module.load(musicPath+$("#playlist_box option:selected").val()); 160 | clearInterval(loadInterval); 161 | showLoaderInfo(module); 162 | } 163 | }, 200); 164 | } 165 | 166 | function updateSelectBox(e) 167 | { 168 | var i, j, f, o=""; 169 | 170 | var filter=$("#loadfilter").val().toLowerCase(); 171 | for(i=0;i'; 175 | for(j=0;j=0) { 177 | og+=''; 180 | f++; 181 | } 182 | } 183 | og+=''; 184 | } else { 185 | og+=''; 186 | for(j=0;j=0 || 189 | window.musicLibrary[i].songs[j].file.toLowerCase().indexOf(filter)>=0) { 190 | og+=''; 193 | f++; 194 | } 195 | } 196 | og+=''; 197 | } 198 | if (f) o+=og; 199 | } 200 | $("#modfile").html(o); 201 | $("#modfile option").dblclick(function() { 202 | $("#load").click(); 203 | }); 204 | } 205 | 206 | function setVisualization(mod, v) 207 | { 208 | var visNames=["[none]", "[trks]", "[chvu]"]; 209 | switch (v) { 210 | case 0: 211 | $("#modvis").removeClass("down"); 212 | $(".currentpattern").removeClass("currentpattern"); 213 | $("#modchannels").hide(); 214 | break; 215 | 216 | case 1: 217 | $("#modvis").addClass("down"); 218 | if (mod && mod.playing) $("#pattern"+hb(mod.currentpattern())).addClass("currentpattern"); 219 | $("#modchannels").hide(); 220 | break; 221 | 222 | case 2: 223 | $("#modvis").addClass("down"); 224 | $(".currentpattern").removeClass("currentpattern"); 225 | $("#modchannels").show(); 226 | break; 227 | } 228 | $("#modvis").html(visNames[v]); 229 | window.moduleVis=v; 230 | } 231 | 232 | 233 | var oldpos=-1, oldrow=-1; 234 | 235 | var lastframe=-1; 236 | function updateUI(timestamp) 237 | { 238 | // maintain 25hz frame rate for the UI 239 | if ((timestamp-lastframe) < 40) { 240 | requestAnimationFrame(updateUI); 241 | return; 242 | } 243 | lastframe=timestamp; 244 | 245 | var i,c; 246 | var mod=window.module; 247 | 248 | if (mod.playing) { 249 | if (window.moduleVis==2) { 250 | var txt, txt0="
", txt1="
"; 251 | for(ch=0;ch ['+vu(mod.chvu[ch])+'] '+ 253 | ''+hb(mod.currentsample(ch))+':'+rpe(pad(mod.samplenames[mod.currentsample(ch)], 28))+"
"; 254 | if (ch&1) txt0+=txt; else txt1+=txt; 255 | } 256 | $("#even-channels").html(txt0); 257 | $("#odd-channels").html(txt1); 258 | } else if (window.moduleVis==1) { 259 | if (oldpos>=0 && oldrow>=0) $(".currentrow").removeClass("currentrow"); 260 | $("#pattern"+hb(mod.currentpattern())+"_row"+hb(mod.row)).addClass("currentrow"); 261 | $("#pattern"+hb(mod.currentpattern())).scrollTop(mod.row*16); 262 | if (oldpos != mod.position) { 263 | if (oldpos>=0) $(".currentpattern").removeClass("currentpattern"); 264 | $("#pattern"+hb(mod.currentpattern())).addClass("currentpattern"); 265 | } 266 | } 267 | 268 | if (oldrow != mod.row || oldpos != mod.position) { 269 | $("#modtimer").replaceWith(""+ 270 | "pos "+hb(mod.position)+"/"+hb(mod.songlen)+" "+ 271 | "row "+hb(mod.row)+"/"+hb(mod.currentpattlen()-1)+" "+ 272 | "speed "+mod.speed+" "+ 273 | "bpm "+mod.bpm+" "+ 274 | "filter "+(mod.filter ? "on" : "off")+""+ 275 | ""); 276 | 277 | $("#modsamples").children().removeClass("activesample"); 278 | for(c=0;c"+hb(i+1)+" "+rpe(pad(this.samplenames[i], 28))+"
\n"); 356 | $("#modinfo").html(""); 357 | $("#modinfo").append("('"+this.signature+"')"); 358 | var s=window.currentModule.split("/"); 359 | if (s.length > 1) { 360 | $("title").html(s[1]+" - module player for Web Audio"); 361 | window.history.pushState("object of string", "Title", "/"+s[0]+"/"+s[1]); 362 | } else { 363 | $("title").html(s[0]+" - module player for Web Audio"); 364 | window.history.pushState("object of string", "Title", "/"+s[0]); 365 | } 366 | 367 | if (window.playlistActive) { 368 | $("#prev_track").removeClass("inactive"); 369 | $("#next_track").removeClass("inactive"); 370 | } else { 371 | $("#prev_track").addClass("inactive"); 372 | $("#next_track").addClass("inactive"); 373 | } 374 | 375 | var pd=""; 376 | for(p=0;p"; 379 | for(i=0; i<12; i++) pd+="\n"; 380 | pdata=this.patterndata(p); 381 | for(i=0; i<(pdata.length/(5*this.channels)); i++) { 382 | pp=i*5*this.channels; 383 | pd+=""+hb(i)+"|"; 384 | for(c=0;c option:first").attr('selected', 'selected'); 715 | $("#load_song").click(); 716 | } else { 717 | $('#modfile option[value="'+window.currentModule+'"]').attr('selected', 'selected'); 718 | if ($("#modfile").val()!="") { 719 | var loadInterval=setInterval(function(){ 720 | if (!module.delayload) { 721 | window.currentModule=$("#modfile").val(); 722 | window.playlistActive=false; 723 | module.load(musicPath+$("#modfile").val()); 724 | clearInterval(loadInterval); 725 | showLoaderInfo(module); 726 | } 727 | }, 200); 728 | } 729 | } 730 | } 731 | request.send(); 732 | }); 733 | -------------------------------------------------------------------------------- /js/pt.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Protracker module player class 8 | 9 | todo: 10 | - pattern looping is broken (see mod.black_queen) 11 | - properly test EEx delay pattern 12 | */ 13 | 14 | // constructor for protracker player object 15 | function Protracker() 16 | { 17 | var i, t; 18 | 19 | this.clearsong(); 20 | this.initialize(); 21 | 22 | this.playing=false; 23 | this.paused=false; 24 | this.repeat=false; 25 | 26 | this.filter=false; 27 | 28 | this.mixval=4.0; 29 | 30 | this.syncqueue=[]; 31 | 32 | this.samplerate=44100; 33 | 34 | // paula period values 35 | this.baseperiodtable=new Float32Array([ 36 | 856,808,762,720,678,640,604,570,538,508,480,453, 37 | 428,404,381,360,339,320,302,285,269,254,240,226, 38 | 214,202,190,180,170,160,151,143,135,127,120,113]); 39 | 40 | // finetune multipliers 41 | this.finetunetable=new Float32Array(16); 42 | for(t=0;t<16;t++) this.finetunetable[t]=Math.pow(2, (t-8)/12/8); 43 | 44 | // calc tables for vibrato waveforms 45 | this.vibratotable=new Array(); 46 | for(t=0;t<4;t++) { 47 | this.vibratotable[t]=new Float32Array(64); 48 | for(i=0;i<64;i++) { 49 | switch(t) { 50 | case 0: 51 | this.vibratotable[t][i]=127*Math.sin(Math.PI*2*(i/64)); 52 | break; 53 | case 1: 54 | this.vibratotable[t][i]=127-4*i; 55 | break; 56 | case 2: 57 | this.vibratotable[t][i]=(i<32)?127:-127; 58 | break; 59 | case 3: 60 | this.vibratotable[t][i]=(1-2*Math.random())*127; 61 | break; 62 | } 63 | } 64 | } 65 | 66 | // effect jumptables 67 | this.effects_t0 = new Array( 68 | 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, 69 | 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); 70 | this.effects_t0_e = new Array( 71 | 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, 72 | 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); 73 | this.effects_t1 = new Array( 74 | 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, 75 | 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); 76 | this.effects_t1_e = new Array( 77 | 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, 78 | 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); 79 | } 80 | 81 | 82 | 83 | // clear song data 84 | Protracker.prototype.clearsong = function() 85 | { 86 | this.title=""; 87 | this.signature=""; 88 | 89 | this.songlen=1; 90 | this.repeatpos=0; 91 | this.patterntable=new ArrayBuffer(128); 92 | for(i=0;i<128;i++) this.patterntable[i]=0; 93 | 94 | this.channels=4; 95 | 96 | this.sample=new Array(); 97 | this.samples=31; 98 | for(i=0;i<31;i++) { 99 | this.sample[i]=new Object(); 100 | this.sample[i].name=""; 101 | this.sample[i].length=0; 102 | this.sample[i].finetune=0; 103 | this.sample[i].volume=64; 104 | this.sample[i].loopstart=0; 105 | this.sample[i].looplength=0; 106 | this.sample[i].data=0; 107 | } 108 | 109 | this.patterns=0; 110 | this.pattern=new Array(); 111 | this.note=new Array(); 112 | this.pattern_unpack=new Array(); 113 | 114 | this.looprow=0; 115 | this.loopstart=0; 116 | this.loopcount=0; 117 | 118 | this.patterndelay=0; 119 | this.patternwait=0; 120 | } 121 | 122 | 123 | // initialize all player variables 124 | Protracker.prototype.initialize = function() 125 | { 126 | this.syncqueue=[]; 127 | 128 | this.tick=0; 129 | this.position=0; 130 | this.row=0; 131 | this.offset=0; 132 | this.flags=0; 133 | 134 | this.speed=6; 135 | this.bpm=125; 136 | this.breakrow=0; 137 | this.patternjump=0; 138 | this.patterndelay=0; 139 | this.patternwait=0; 140 | this.endofsong=false; 141 | 142 | this.channel=new Array(); 143 | for(i=0;i0x1f) && (buffer[st+j]<0x7f)) ? 213 | (String.fromCharCode(buffer[st+j])) : 214 | (" "); 215 | j++; 216 | } 217 | this.sample[i].length=2*(buffer[st+22]*256 + buffer[st+23]); 218 | this.sample[i].finetune=buffer[st+24]; 219 | if (this.sample[i].finetune > 7) this.sample[i].finetune=this.sample[i].finetune-16; 220 | this.sample[i].volume=buffer[st+25]; 221 | this.sample[i].loopstart=2*(buffer[st+26]*256 + buffer[st+27]); 222 | this.sample[i].looplength=2*(buffer[st+28]*256 + buffer[st+29]); 223 | if (this.sample[i].looplength==2) this.sample[i].looplength=0; 224 | if (this.sample[i].loopstart>this.sample[i].length) { 225 | this.sample[i].loopstart=0; 226 | this.sample[i].looplength=0; 227 | } 228 | } 229 | 230 | this.songlen=buffer[950]; 231 | if (buffer[951] != 127) this.repeatpos=buffer[951]; 232 | for(i=0;i<128;i++) { 233 | this.patterntable[i]=buffer[952+i]; 234 | if (this.patterntable[i] > this.patterns) this.patterns=this.patterntable[i]; 235 | } 236 | this.patterns+=1; 237 | var patlen=4*64*this.channels; 238 | 239 | this.pattern=new Array(); 240 | this.note=new Array(); 241 | this.pattern_unpack=new Array(); 242 | for(i=0;i>4; 261 | this.pattern_unpack[i][ppu+2]=255; 262 | this.pattern_unpack[i][ppu+3]=this.pattern[i][pp+2]&0x0f; 263 | this.pattern_unpack[i][ppu+4]=this.pattern[i][pp+3]; 264 | } 265 | } 266 | } 267 | 268 | var sst=1084+this.patterns*patlen; 269 | for(i=0;ispd) { mod.tick++; mod.offset=0; mod.flags|=1; } 322 | if (mod.tick>=mod.speed) { 323 | 324 | if (mod.patterndelay) { // delay pattern 325 | if (mod.tick < ((mod.patternwait+1)*mod.speed)) { 326 | mod.patternwait++; 327 | } else { 328 | mod.row++; mod.tick=0; mod.flags|=2; mod.patterndelay=0; 329 | } 330 | } 331 | else { 332 | if (mod.flags&(16+32+64)) { 333 | if (mod.flags&64) { // loop pattern? 334 | mod.row=mod.looprow; 335 | mod.flags&=0xa1; 336 | mod.flags|=2; 337 | } 338 | else { 339 | if (mod.flags&16) { // pattern jump/break? 340 | mod.position=mod.patternjump; 341 | mod.row=mod.breakrow; 342 | mod.patternjump=0; 343 | mod.breakrow=0; 344 | mod.flags&=0xe1; 345 | mod.flags|=2; 346 | } 347 | } 348 | mod.tick=0; 349 | } else { 350 | mod.row++; mod.tick=0; mod.flags|=2; 351 | } 352 | } 353 | } 354 | if (mod.row>=64) { mod.position++; mod.row=0; mod.flags|=4; } 355 | if (mod.position>=mod.songlen) { 356 | if (mod.repeat) { 357 | mod.position=0; 358 | } else { 359 | this.endofsong=true; 360 | //mod.stop(); 361 | } 362 | return; 363 | } 364 | } 365 | 366 | 367 | 368 | // mix an audio buffer with data 369 | Protracker.prototype.mix = function(mod, bufs, buflen) { 370 | var f; 371 | var p, pp, n, nn; 372 | 373 | var outp=new Float32Array(2); 374 | for(var s=0;s3) mod.channel[ch].vibratopos=0; 402 | mod.channel[ch].flags|=3; // recalc speed 403 | mod.channel[ch].noteon=1; 404 | } 405 | // in either case, set the slide to note target 406 | mod.channel[ch].slideto=n; 407 | } 408 | nn=mod.pattern[p][pp+0]&0xf0 | mod.pattern[p][pp+2]>>4; 409 | if (nn) { 410 | mod.channel[ch].sample=nn-1; 411 | mod.channel[ch].volume=mod.sample[nn-1].volume; 412 | if (!n && (mod.channel[ch].samplepos > mod.sample[nn-1].length)) mod.channel[ch].samplepos=0; 413 | } 414 | } 415 | } 416 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 417 | 418 | // kill empty samples 419 | if (!mod.sample[mod.channel[ch].sample].length) mod.channel[ch].noteon=0; 420 | 421 | // effects 422 | if (mod.flags&1) { 423 | if (!mod.tick) { 424 | // process only on tick 0 425 | mod.effects_t0[mod.channel[ch].command](mod, ch); 426 | } else { 427 | mod.effects_t1[mod.channel[ch].command](mod, ch); 428 | } 429 | } 430 | 431 | // recalc note number from period 432 | if (mod.channel[ch].flags&2) { 433 | for(var np=0; np=mod.channel[ch].period) mod.channel[ch].note=np; 435 | mod.channel[ch].semitone=7; 436 | if (mod.channel[ch].period>=120) 437 | mod.channel[ch].semitone=mod.baseperiodtable[mod.channel[ch].note]-mod.baseperiodtable[mod.channel[ch].note+1]; 438 | } 439 | 440 | // recalc sample speed and apply finetune 441 | if ((mod.channel[ch].flags&1 || mod.flags&2) && mod.channel[ch].voiceperiod) 442 | mod.channel[ch].samplespeed= 443 | 7093789.2/(mod.channel[ch].voiceperiod*2) * mod.finetunetable[mod.sample[mod.channel[ch].sample].finetune+8] / mod.samplerate; 444 | 445 | // advance vibrato on each new tick 446 | if (mod.flags&1) { 447 | mod.channel[ch].vibratopos+=mod.channel[ch].vibratospeed; 448 | mod.channel[ch].vibratopos&=0x3f; 449 | } 450 | 451 | // mix channel to output 452 | och=och^(ch&1); 453 | f=0.0; 454 | if (mod.channel[ch].noteon) { 455 | if (mod.sample[mod.channel[ch].sample].length > mod.channel[ch].samplepos) 456 | f=(mod.sample[mod.channel[ch].sample].data[Math.floor(mod.channel[ch].samplepos)]*mod.channel[ch].volume)/64.0; 457 | outp[och]+=f; 458 | mod.channel[ch].samplepos+=mod.channel[ch].samplespeed; 459 | } 460 | mod.chvu[ch]=Math.max(mod.chvu[ch], Math.abs(f)); 461 | 462 | // loop or end samples 463 | if (mod.channel[ch].noteon) { 464 | if (mod.sample[mod.channel[ch].sample].loopstart || mod.sample[mod.channel[ch].sample].looplength) { 465 | if (mod.channel[ch].samplepos >= (mod.sample[mod.channel[ch].sample].loopstart+mod.sample[mod.channel[ch].sample].looplength)) { 466 | mod.channel[ch].samplepos-=mod.sample[mod.channel[ch].sample].looplength; 467 | } 468 | } else { 469 | if (mod.channel[ch].samplepos >= mod.sample[mod.channel[ch].sample].length) { 470 | mod.channel[ch].noteon=0; 471 | } 472 | } 473 | } 474 | 475 | // clear channel flags 476 | mod.channel[ch].flags=0; 477 | } 478 | mod.offset++; 479 | mod.flags&=0x70; 480 | } 481 | 482 | // done - store to output buffer 483 | bufs[0][s]=outp[0]; 484 | bufs[1][s]=outp[1]; 485 | } 486 | } 487 | 488 | 489 | 490 | // 491 | // tick 0 effect functions 492 | // 493 | Protracker.prototype.effect_t0_0=function(mod, ch) { // 0 arpeggio 494 | mod.channel[ch].arpeggio=mod.channel[ch].data; 495 | } 496 | Protracker.prototype.effect_t0_1=function(mod, ch) { // 1 slide up 497 | if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data; 498 | } 499 | Protracker.prototype.effect_t0_2=function(mod, ch) { // 2 slide down 500 | if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data; 501 | } 502 | Protracker.prototype.effect_t0_3=function(mod, ch) { // 3 slide to note 503 | if (mod.channel[ch].data) mod.channel[ch].slidetospeed=mod.channel[ch].data; 504 | } 505 | Protracker.prototype.effect_t0_4=function(mod, ch) { // 4 vibrato 506 | if (mod.channel[ch].data&0x0f && mod.channel[ch].data&0xf0) { 507 | mod.channel[ch].vibratodepth=(mod.channel[ch].data&0x0f); 508 | mod.channel[ch].vibratospeed=(mod.channel[ch].data&0xf0)>>4; 509 | } 510 | mod.effects_t1[4](mod, ch); 511 | } 512 | Protracker.prototype.effect_t0_5=function(mod, ch) { // 5 513 | } 514 | Protracker.prototype.effect_t0_6=function(mod, ch) { // 6 515 | } 516 | Protracker.prototype.effect_t0_7=function(mod, ch) { // 7 517 | } 518 | Protracker.prototype.effect_t0_8=function(mod, ch) { // 8 unused, used for syncing 519 | mod.syncqueue.unshift(mod.channel[ch].data&0x0f); 520 | } 521 | Protracker.prototype.effect_t0_9=function(mod, ch) { // 9 set sample offset 522 | mod.channel[ch].samplepos=mod.channel[ch].data*256; 523 | } 524 | Protracker.prototype.effect_t0_a=function(mod, ch) { // a 525 | } 526 | Protracker.prototype.effect_t0_b=function(mod, ch) { // b pattern jump 527 | mod.breakrow=0; 528 | mod.patternjump=mod.channel[ch].data; 529 | mod.flags|=16; 530 | } 531 | Protracker.prototype.effect_t0_c=function(mod, ch) { // c set volume 532 | mod.channel[ch].volume=mod.channel[ch].data; 533 | } 534 | Protracker.prototype.effect_t0_d=function(mod, ch) { // d pattern break 535 | mod.breakrow=((mod.channel[ch].data&0xf0)>>4)*10 + (mod.channel[ch].data&0x0f); 536 | if (!(mod.flags&16)) mod.patternjump=mod.position+1; 537 | mod.flags|=16; 538 | } 539 | Protracker.prototype.effect_t0_e=function(mod, ch) { // e 540 | var i=(mod.channel[ch].data&0xf0)>>4; 541 | mod.effects_t0_e[i](mod, ch); 542 | } 543 | Protracker.prototype.effect_t0_f=function(mod, ch) { // f set speed 544 | if (mod.channel[ch].data > 32) { 545 | mod.bpm=mod.channel[ch].data; 546 | } else { 547 | if (mod.channel[ch].data) mod.speed=mod.channel[ch].data; 548 | } 549 | } 550 | 551 | 552 | 553 | // 554 | // tick 0 effect e functions 555 | // 556 | Protracker.prototype.effect_t0_e0=function(mod, ch) { // e0 filter on/off 557 | if (mod.channels > 4) return; // use only for 4ch amiga tunes 558 | if (mod.channel[ch].data&0x01) { 559 | mod.filter=false; 560 | } else { 561 | mod.filter=true; 562 | } 563 | } 564 | Protracker.prototype.effect_t0_e1=function(mod, ch) { // e1 fine slide up 565 | mod.channel[ch].period-=mod.channel[ch].data&0x0f; 566 | if (mod.channel[ch].period < 113) mod.channel[ch].period=113; 567 | } 568 | Protracker.prototype.effect_t0_e2=function(mod, ch) { // e2 fine slide down 569 | mod.channel[ch].period+=mod.channel[ch].data&0x0f; 570 | if (mod.channel[ch].period > 856) mod.channel[ch].period=856; 571 | mod.channel[ch].flags|=1; 572 | } 573 | Protracker.prototype.effect_t0_e3=function(mod, ch) { // e3 set glissando 574 | } 575 | Protracker.prototype.effect_t0_e4=function(mod, ch) { // e4 set vibrato waveform 576 | mod.channel[ch].vibratowave=mod.channel[ch].data&0x07; 577 | } 578 | Protracker.prototype.effect_t0_e5=function(mod, ch) { // e5 set finetune 579 | } 580 | Protracker.prototype.effect_t0_e6=function(mod, ch) { // e6 loop pattern 581 | if (mod.channel[ch].data&0x0f) { 582 | if (mod.loopcount) { 583 | mod.loopcount--; 584 | } else { 585 | mod.loopcount=mod.channel[ch].data&0x0f; 586 | } 587 | if (mod.loopcount) mod.flags|=64; 588 | } else { 589 | mod.looprow=mod.row; 590 | } 591 | } 592 | Protracker.prototype.effect_t0_e7=function(mod, ch) { // e7 593 | } 594 | Protracker.prototype.effect_t0_e8=function(mod, ch) { // e8, use for syncing 595 | mod.syncqueue.unshift(mod.channel[ch].data&0x0f); 596 | } 597 | Protracker.prototype.effect_t0_e9=function(mod, ch) { // e9 598 | } 599 | Protracker.prototype.effect_t0_ea=function(mod, ch) { // ea fine volslide up 600 | mod.channel[ch].volume+=mod.channel[ch].data&0x0f; 601 | if (mod.channel[ch].volume > 64) mod.channel[ch].volume=64; 602 | } 603 | Protracker.prototype.effect_t0_eb=function(mod, ch) { // eb fine volslide down 604 | mod.channel[ch].volume-=mod.channel[ch].data&0x0f; 605 | if (mod.channel[ch].volume < 0) mod.channel[ch].volume=0; 606 | } 607 | Protracker.prototype.effect_t0_ec=function(mod, ch) { // ec 608 | } 609 | Protracker.prototype.effect_t0_ed=function(mod, ch) { // ed delay sample 610 | if (mod.tick==(mod.channel[ch].data&0x0f)) { 611 | // start note 612 | var p=mod.patterntable[mod.position]; 613 | var pp=mod.row*4*mod.channels + ch*4; 614 | n=(mod.pattern[p][pp]&0x0f)<<8 | mod.pattern[p][pp+1]; 615 | if (n) { 616 | mod.channel[ch].period=n; 617 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 618 | mod.channel[ch].samplepos=0; 619 | if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0; 620 | mod.channel[ch].flags|=3; // recalc speed 621 | mod.channel[ch].noteon=1; 622 | } 623 | n=mod.pattern[p][pp+0]&0xf0 | mod.pattern[p][pp+2]>>4; 624 | if (n) { 625 | mod.channel[ch].sample=n-1; 626 | mod.channel[ch].volume=mod.sample[n-1].volume; 627 | } 628 | } 629 | } 630 | Protracker.prototype.effect_t0_ee=function(mod, ch) { // ee delay pattern 631 | mod.patterndelay=mod.channel[ch].data&0x0f; 632 | mod.patternwait=0; 633 | } 634 | Protracker.prototype.effect_t0_ef=function(mod, ch) { // ef 635 | } 636 | 637 | 638 | 639 | // 640 | // tick 1+ effect functions 641 | // 642 | Protracker.prototype.effect_t1_0=function(mod, ch) { // 0 arpeggio 643 | if (mod.channel[ch].data) { 644 | var apn=mod.channel[ch].note; 645 | if ((mod.tick%3)==1) apn+=mod.channel[ch].arpeggio>>4; 646 | if ((mod.tick%3)==2) apn+=mod.channel[ch].arpeggio&0x0f; 647 | if (apn>=0 && apn <= mod.baseperiodtable.length) 648 | mod.channel[ch].voiceperiod = mod.baseperiodtable[apn]; 649 | mod.channel[ch].flags|=1; 650 | } 651 | } 652 | Protracker.prototype.effect_t1_1=function(mod, ch) { // 1 slide up 653 | mod.channel[ch].period-=mod.channel[ch].slidespeed; 654 | if (mod.channel[ch].period<113) mod.channel[ch].period=113; 655 | mod.channel[ch].flags|=3; // recalc speed 656 | } 657 | Protracker.prototype.effect_t1_2=function(mod, ch) { // 2 slide down 658 | mod.channel[ch].period+=mod.channel[ch].slidespeed; 659 | if (mod.channel[ch].period>856) mod.channel[ch].period=856; 660 | mod.channel[ch].flags|=3; // recalc speed 661 | } 662 | Protracker.prototype.effect_t1_3=function(mod, ch) { // 3 slide to note 663 | if (mod.channel[ch].period < mod.channel[ch].slideto) { 664 | mod.channel[ch].period+=mod.channel[ch].slidetospeed; 665 | if (mod.channel[ch].period > mod.channel[ch].slideto) 666 | mod.channel[ch].period=mod.channel[ch].slideto; 667 | } 668 | if (mod.channel[ch].period > mod.channel[ch].slideto) { 669 | mod.channel[ch].period-=mod.channel[ch].slidetospeed; 670 | if (mod.channel[ch].period>4); 703 | if (mod.channel[ch].volume>64) mod.channel[ch].volume=64; 704 | } 705 | if (!(mod.channel[ch].data&0xf0)) { 706 | // x is zero, slide down 707 | mod.channel[ch].volume-=(mod.channel[ch].data&0x0f); 708 | if (mod.channel[ch].volume<0) mod.channel[ch].volume=0; 709 | } 710 | } 711 | Protracker.prototype.effect_t1_b=function(mod, ch) { // b pattern jump 712 | } 713 | Protracker.prototype.effect_t1_c=function(mod, ch) { // c set volume 714 | } 715 | Protracker.prototype.effect_t1_d=function(mod, ch) { // d pattern break 716 | } 717 | Protracker.prototype.effect_t1_e=function(mod, ch) { // e 718 | var i=(mod.channel[ch].data&0xf0)>>4; 719 | mod.effects_t1_e[i](mod, ch); 720 | } 721 | Protracker.prototype.effect_t1_f=function(mod, ch) { // f 722 | } 723 | 724 | 725 | 726 | // 727 | // tick 1+ effect e functions 728 | // 729 | Protracker.prototype.effect_t1_e0=function(mod, ch) { // e0 730 | } 731 | Protracker.prototype.effect_t1_e1=function(mod, ch) { // e1 732 | } 733 | Protracker.prototype.effect_t1_e2=function(mod, ch) { // e2 734 | } 735 | Protracker.prototype.effect_t1_e3=function(mod, ch) { // e3 736 | } 737 | Protracker.prototype.effect_t1_e4=function(mod, ch) { // e4 738 | } 739 | Protracker.prototype.effect_t1_e5=function(mod, ch) { // e5 740 | } 741 | Protracker.prototype.effect_t1_e6=function(mod, ch) { // e6 742 | } 743 | Protracker.prototype.effect_t1_e7=function(mod, ch) { // e7 744 | } 745 | Protracker.prototype.effect_t1_e8=function(mod, ch) { // e8 746 | } 747 | Protracker.prototype.effect_t1_e9=function(mod, ch) { // e9 retrig sample 748 | if (mod.tick%(mod.channel[ch].data&0x0f)==0) 749 | mod.channel[ch].samplepos=0; 750 | } 751 | Protracker.prototype.effect_t1_ea=function(mod, ch) { // ea 752 | } 753 | Protracker.prototype.effect_t1_eb=function(mod, ch) { // eb 754 | } 755 | Protracker.prototype.effect_t1_ec=function(mod, ch) { // ec cut sample 756 | if (mod.tick==(mod.channel[ch].data&0x0f)) 757 | mod.channel[ch].volume=0; 758 | } 759 | Protracker.prototype.effect_t1_ed=function(mod, ch) { // ed delay sample 760 | mod.effect_t0_ed(mod, ch); 761 | } 762 | Protracker.prototype.effect_t1_ee=function(mod, ch) { // ee 763 | } 764 | Protracker.prototype.effect_t1_ef=function(mod, ch) { // ef 765 | } 766 | -------------------------------------------------------------------------------- /js/st3.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Scream Tracker 3 module player class 8 | 9 | todo: 10 | - are Exx, Fxx and Gxx supposed to share a single 11 | command data memory? 12 | */ 13 | 14 | function Screamtracker() 15 | { 16 | var i, t; 17 | 18 | this.clearsong(); 19 | this.initialize(); 20 | 21 | this.playing=false; 22 | this.paused=false; 23 | this.repeat=false; 24 | 25 | this.filter=false; 26 | 27 | this.syncqueue=[]; 28 | 29 | this.samplerate=44100; 30 | 31 | this.periodtable=new Float32Array([ 32 | 27392.0, 25856.0, 24384.0, 23040.0, 21696.0, 20480.0, 19328.0, 18240.0, 17216.0, 16256.0, 15360.0, 14496.0, 33 | 13696.0, 12928.0, 12192.0, 11520.0, 10848.0, 10240.0, 9664.0, 9120.0, 8608.0, 8128.0, 7680.0, 7248.0, 34 | 6848.0, 6464.0, 6096.0, 5760.0, 5424.0, 5120.0, 4832.0, 4560.0, 4304.0, 4064.0, 3840.0, 3624.0, 35 | 3424.0, 3232.0, 3048.0, 2880.0, 2712.0, 2560.0, 2416.0, 2280.0, 2152.0, 2032.0, 1920.0, 1812.0, 36 | 1712.0, 1616.0, 1524.0, 1440.0, 1356.0, 1280.0, 1208.0, 1140.0, 1076.0, 1016.0, 960.0, 906.0, 37 | 856.0, 808.0, 762.0, 720.0, 678.0, 640.0, 604.0, 570.0, 538.0, 508.0, 480.0, 453.0, 38 | 428.0, 404.0, 381.0, 360.0, 339.0, 320.0, 302.0, 285.0, 269.0, 254.0, 240.0, 226.0, 39 | 214.0, 202.0, 190.0, 180.0, 170.0, 160.0, 151.0, 143.0, 135.0, 127.0, 120.0, 113.0, 40 | 107.0, 101.0, 95.0, 90.0, 85.0, 80.0, 75.0, 71.0, 67.0, 63.0, 60.0, 56.0 41 | ]); 42 | 43 | this.retrigvoltab=new Float32Array([ 44 | 0, -1, -2, -4, -8, -16, 0.66, 0.5, 45 | 0, 1, 2, 4, 8, 16, 1.50, 2.0 46 | ]); 47 | 48 | this.pan_r=new Float32Array(32); 49 | this.pan_l=new Float32Array(32); 50 | for(i=0;i<32;i++) { this.pan_r[i]=0.5; this.pan_l[i]=0.5; } 51 | 52 | // calc tables for vibrato waveforms 53 | this.vibratotable=new Array(); 54 | for(t=0;t<4;t++) { 55 | this.vibratotable[t]=new Float32Array(256); 56 | for(i=0;i<256;i++) { 57 | switch(t) { 58 | case 0: 59 | this.vibratotable[t][i]=127*Math.sin(Math.PI*2*(i/256)); 60 | break; 61 | case 1: 62 | this.vibratotable[t][i]=127-i; 63 | break; 64 | case 2: 65 | this.vibratotable[t][i]=(i<128)?127:-128; 66 | break; 67 | case 3: 68 | this.vibratotable[t][i]=Math.random()*255-128; 69 | break; 70 | } 71 | } 72 | } 73 | 74 | // effect jumptables for tick 0 and tick 1+ 75 | this.effects_t0 = new Array( 76 | function(mod, ch) {}, // zero is ignored 77 | this.effect_t0_a, this.effect_t0_b, this.effect_t0_c, this.effect_t0_d, this.effect_t0_e, 78 | this.effect_t0_f, this.effect_t0_g, this.effect_t0_h, this.effect_t0_i, this.effect_t0_j, 79 | this.effect_t0_k, this.effect_t0_l, this.effect_t0_m, this.effect_t0_n, this.effect_t0_o, 80 | this.effect_t0_p, this.effect_t0_q, this.effect_t0_r, this.effect_t0_s, this.effect_t0_t, 81 | this.effect_t0_u, this.effect_t0_v, this.effect_t0_w, this.effect_t0_x, this.effect_t0_y, 82 | this.effect_t0_z 83 | ); 84 | this.effects_t0_s = new Array( 85 | this.effect_t0_s0, this.effect_t0_s1, this.effect_t0_s2, this.effect_t0_s3, this.effect_t0_s4, 86 | this.effect_t0_s5, this.effect_t0_s6, this.effect_t0_s7, this.effect_t0_s8, this.effect_t0_s9, 87 | this.effect_t0_sa, this.effect_t0_sb, this.effect_t0_sc, this.effect_t0_sd, this.effect_t0_se, 88 | this.effect_t0_sf 89 | ); 90 | this.effects_t1 = new Array( 91 | function(mod, ch) {}, // zero is ignored 92 | this.effect_t1_a, this.effect_t1_b, this.effect_t1_c, this.effect_t1_d, this.effect_t1_e, 93 | this.effect_t1_f, this.effect_t1_g, this.effect_t1_h, this.effect_t1_i, this.effect_t1_j, 94 | this.effect_t1_k, this.effect_t1_l, this.effect_t1_m, this.effect_t1_n, this.effect_t1_o, 95 | this.effect_t1_p, this.effect_t1_q, this.effect_t1_r, this.effect_t1_s, this.effect_t1_t, 96 | this.effect_t1_u, this.effect_t1_v, this.effect_t1_w, this.effect_t1_x, this.effect_t1_y, 97 | this.effect_t1_z 98 | ); 99 | this.effects_t1_s = new Array( 100 | this.effect_t1_s0, this.effect_t1_s1, this.effect_t1_s2, this.effect_t1_s3, this.effect_t1_s4, 101 | this.effect_t1_s5, this.effect_t1_s6, this.effect_t1_s7, this.effect_t1_s8, this.effect_t1_s9, 102 | this.effect_t1_sa, this.effect_t1_sb, this.effect_t1_sc, this.effect_t1_sd, this.effect_t1_se, 103 | this.effect_t1_sf 104 | ); 105 | } 106 | 107 | 108 | 109 | // clear song data 110 | Screamtracker.prototype.clearsong = function() 111 | { 112 | var i; 113 | 114 | this.title=""; 115 | this.signature=""; 116 | 117 | this.songlen=1; 118 | this.repeatpos=0; 119 | this.patterntable=new ArrayBuffer(256); 120 | for(i=0;i<256;i++) this.patterntable[i]=0; 121 | 122 | this.channels=0; 123 | this.ordNum=0; 124 | this.insNum=0; 125 | this.patNum=0; 126 | 127 | this.globalVol=64; 128 | this.initSpeed=6; 129 | this.initBPM=125; 130 | 131 | this.fastslide=0; 132 | 133 | this.mixval=8.0; 134 | 135 | this.sample=new Array(); 136 | for(i=0;i<255;i++) { 137 | this.sample[i]=new Object(); 138 | this.sample[i].length=0; 139 | this.sample[i].loopstart=0; 140 | this.sample[i].loopend=0; 141 | this.sample[i].looplength=0; 142 | this.sample[i].volume=64; 143 | this.sample[i].loop=0; 144 | this.sample[i].c2spd=8363; 145 | this.sample[i].name=""; 146 | this.sample[i].data=0; 147 | } 148 | 149 | this.pattern=new Array(); 150 | 151 | this.looprow=0; 152 | this.loopstart=0; 153 | this.loopcount=0; 154 | 155 | this.patterndelay=0; 156 | this.patternwait=0; 157 | } 158 | 159 | 160 | 161 | // initialize all player variables to defaults prior to starting playback 162 | Screamtracker.prototype.initialize = function() 163 | { 164 | this.syncqueue=[]; 165 | 166 | this.tick=-1; 167 | this.position=0; 168 | this.row=0; 169 | this.flags=0; 170 | 171 | this.volume=this.globalVol; 172 | this.speed=this.initSpeed; 173 | this.bpm=this.initBPM; 174 | this.stt=0; 175 | this.breakrow=0; 176 | this.patternjump=0; 177 | this.patterndelay=0; 178 | this.patternwait=0; 179 | this.endofsong=false; 180 | 181 | this.channel=new Array(); 182 | for(i=0;i>1; 319 | this.sample[i].bits=(buffer[offset+0x1f]&4) ? 16 : 8; 320 | this.sample[i].c2spd=buffer[offset+0x20]|buffer[offset+0x21]<<8; 321 | 322 | // sample data 323 | var smpoffset=(buffer[offset+0x0d]<<16|buffer[offset+0x0e]|buffer[offset+0x0f]<<8)*16; 324 | this.sample[i].data=new Float32Array(this.sample[i].length); 325 | for(j=0;jmax_ch) { 353 | for(j=0;j26) { 366 | this.pattern[i][row*this.channels*5 + ch*5 + 3]=255; 367 | } 368 | } 369 | } else { 370 | if (c&32) pos+=2; 371 | if (c&64) pos++; 372 | if (c&128) pos+=2; 373 | } 374 | } else row++; 375 | } 376 | } 377 | this.patterns=this.patNum; 378 | 379 | // how many channels had actually pattern data on them? trim off the extra channels 380 | var oldch=this.channels; 381 | this.channels=max_ch+1; 382 | for(i=0;i=mod.speed) { 414 | if (mod.patterndelay) { // delay pattern 415 | if (mod.tick < ((mod.patternwait+1)*mod.speed)) { 416 | mod.patternwait++; 417 | } else { 418 | mod.row++; mod.tick=0; mod.flags|=2; mod.patterndelay=0; 419 | } 420 | } 421 | else { 422 | if (mod.flags&(16+32+64)) { 423 | if (mod.flags&64) { // loop pattern? 424 | mod.row=mod.looprow; 425 | mod.flags&=0xa1; 426 | mod.flags|=2; 427 | } 428 | else { 429 | if (mod.flags&16) { // pattern jump/break? 430 | mod.position=mod.patternjump; 431 | mod.row=mod.breakrow; 432 | mod.patternjump=0; 433 | mod.breakrow=0; 434 | mod.flags&=0xe1; 435 | mod.flags|=2; 436 | } 437 | } 438 | mod.tick=0; 439 | } else { 440 | mod.row++; mod.tick=0; mod.flags|=2; 441 | } 442 | } 443 | } 444 | 445 | // step to new pattern? 446 | if (mod.row>=64) { 447 | mod.position++; 448 | mod.row=0; 449 | mod.flags|=4; 450 | while (mod.patterntable[mod.position]==254) mod.position++; // skip markers 451 | } 452 | 453 | // end of song? 454 | if (mod.position>=mod.songlen || mod.patterntable[mod.position]==255) { 455 | if (mod.repeat) { 456 | mod.position=0; 457 | } else { 458 | this.endofsong=true; 459 | } 460 | return; 461 | } 462 | } 463 | 464 | 465 | 466 | // process one channel on a row in pattern p, pp is an offset to pattern data 467 | Screamtracker.prototype.process_note = function(mod, p, ch) { 468 | var n, s, pp, pv; 469 | 470 | pp=mod.row*5*this.channels + ch*5; 471 | 472 | n=mod.pattern[p][pp]; 473 | s=mod.pattern[p][pp+1]; 474 | if (s) { 475 | mod.channel[ch].sample=s-1; 476 | mod.channel[ch].volume=mod.sample[s-1].volume; 477 | mod.channel[ch].voicevolume=mod.channel[ch].volume; 478 | if (n==255 && (mod.channel[ch].samplepos > mod.sample[s-1].length)) { 479 | mod.channel[ch].trigramp=0.0; 480 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 481 | mod.channel[ch].samplepos=0; 482 | } 483 | } 484 | 485 | if (n<254) { 486 | // calc period for note 487 | n=(n&0x0f) + (n>>4)*12; 488 | pv=(8363.0 * mod.periodtable[n]) / mod.sample[mod.channel[ch].sample].c2spd; 489 | 490 | // noteon, except if command=0x07 ('G') (porta to note) or 0x0c ('L') (porta+volslide) 491 | if ((mod.channel[ch].command != 0x07) && (mod.channel[ch].command != 0x0c)) { 492 | mod.channel[ch].note=n; 493 | mod.channel[ch].period=pv; 494 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 495 | mod.channel[ch].samplepos=0; 496 | if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0; 497 | 498 | mod.channel[ch].trigramp=0.0; 499 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 500 | 501 | mod.channel[ch].flags|=3; // force sample speed recalc 502 | mod.channel[ch].noteon=1; 503 | } 504 | // in either case, set the slide to note target to note period 505 | mod.channel[ch].slideto=pv; 506 | } else if (n==254) { 507 | mod.channel[ch].noteon=0; // sample off 508 | mod.channel[ch].voicevolume=0; 509 | } 510 | 511 | if (mod.pattern[p][pp+2]<=64) { 512 | mod.channel[ch].volume=mod.pattern[p][pp+2]; 513 | mod.channel[ch].voicevolume=mod.channel[ch].volume; 514 | } 515 | } 516 | 517 | 518 | 519 | // advance player and all channels by a tick 520 | Screamtracker.prototype.process_tick = function(mod) { 521 | 522 | // advance global player state by a tick 523 | mod.advance(mod); 524 | 525 | // advance all channels 526 | for(var ch=0;ch mod.channel[ch].samplepos) { 611 | fl=mod.channel[ch].lastsample; 612 | 613 | // interpolate towards current sample 614 | var f=mod.channel[ch].samplepos-Math.floor(mod.channel[ch].samplepos); 615 | fs=mod.sample[si].data[Math.floor(mod.channel[ch].samplepos)]; 616 | fl=f*fs + (1.0-f)*fl; 617 | 618 | // smooth out discontinuities from retrig and sample offset 619 | f=mod.channel[ch].trigramp; 620 | fl=f*fl + (1.0-f)*mod.channel[ch].trigrampfrom; 621 | f+=1.0/128.0; 622 | mod.channel[ch].trigramp=Math.min(1.0, f); 623 | mod.channel[ch].currentsample=fl; 624 | 625 | // ramp volume changes over 64 samples to avoid clicks 626 | fr=fl*(mod.channel[ch].voicevolume/64.0); 627 | f=mod.channel[ch].volramp; 628 | fl=f*fr + (1.0-f)*(fl*(mod.channel[ch].volrampfrom/64.0)); 629 | f+=(1.0/64.0); 630 | mod.channel[ch].volramp=Math.min(1.0, f); 631 | 632 | // pan samples 633 | fr=fl*mod.pan_r[ch]; 634 | fl*=mod.pan_l[ch]; 635 | } 636 | outp[0]+=fl; 637 | outp[1]+=fr; 638 | 639 | var oldpos=mod.channel[ch].samplepos; 640 | mod.channel[ch].samplepos+=mod.channel[ch].samplespeed; 641 | if (Math.floor(mod.channel[ch].samplepos) > Math.floor(oldpos)) mod.channel[ch].lastsample=fs; 642 | 643 | // loop or stop sample? 644 | if (mod.sample[mod.channel[ch].sample].loop) { 645 | if (mod.channel[ch].samplepos >= mod.sample[mod.channel[ch].sample].loopend) { 646 | mod.channel[ch].samplepos-=mod.sample[mod.channel[ch].sample].looplength; 647 | mod.channel[ch].lastsample=mod.channel[ch].currentsample; 648 | } 649 | } else if (mod.channel[ch].samplepos >= mod.sample[mod.channel[ch].sample].length) mod.channel[ch].noteon=0; 650 | } 651 | mod.chvu[ch]=Math.max(mod.chvu[ch], Math.abs(fl+fr)); 652 | } 653 | 654 | // done - store to output buffer 655 | t=mod.volume/64.0; 656 | bufs[0][s]=outp[0]*t; 657 | bufs[1][s]=outp[1]*t; 658 | mod.stt--; 659 | } 660 | } 661 | 662 | 663 | 664 | // 665 | // tick 0 effect functions 666 | // 667 | Screamtracker.prototype.effect_t0_a=function(mod, ch) { // set speed 668 | if (mod.channel[ch].data > 0) mod.speed=mod.channel[ch].data; 669 | } 670 | Screamtracker.prototype.effect_t0_b=function(mod, ch) { // pattern jump 671 | mod.breakrow=0; 672 | mod.patternjump=mod.channel[ch].data; 673 | mod.flags|=16; 674 | } 675 | Screamtracker.prototype.effect_t0_c=function(mod, ch) { // pattern break 676 | mod.breakrow=((mod.channel[ch].data&0xf0)>>4)*10 + (mod.channel[ch].data&0x0f); 677 | if (!(mod.flags&16)) mod.patternjump=mod.position+1; 678 | mod.flags|=16; 679 | } 680 | Screamtracker.prototype.effect_t0_d=function(mod, ch) { // volume slide 681 | if (mod.channel[ch].data) mod.channel[ch].volslide=mod.channel[ch].data; 682 | if ((mod.channel[ch].volslide&0x0f)==0x0f) { // DxF fine up 683 | mod.channel[ch].voicevolume+=mod.channel[ch].volslide>>4; 684 | } else if ((mod.channel[ch].volslide>>4)==0x0f) { // DFx fine down 685 | mod.channel[ch].voicevolume-=mod.channel[ch].volslide&0x0f; 686 | } else { 687 | if (mod.fastslide) mod.effect_t1_d(mod, ch); 688 | } 689 | 690 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 691 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 692 | } 693 | Screamtracker.prototype.effect_t0_e=function(mod, ch) { // slide down 694 | if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data; 695 | if ((mod.channel[ch].slidespeed&0xf0)==0xf0) { 696 | mod.channel[ch].voiceperiod+=(mod.channel[ch].slidespeed&0x0f)<<2; 697 | } 698 | if ((mod.channel[ch].slidespeed&0xf0)==0xe0) { 699 | mod.channel[ch].voiceperiod+=(mod.channel[ch].slidespeed&0x0f); 700 | } 701 | if (mod.channel[ch].voiceperiod>27392) mod.channel[ch].noteon=0; 702 | mod.channel[ch].flags|=3; // recalc speed 703 | } 704 | Screamtracker.prototype.effect_t0_f=function(mod, ch) { // slide up 705 | if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data; 706 | if ((mod.channel[ch].slidespeed&0xf0)==0xf0) { 707 | mod.channel[ch].voiceperiod-=(mod.channel[ch].slidespeed&0x0f)<<2; 708 | } 709 | if ((mod.channel[ch].slidespeed&0xf0)==0xe0) { 710 | mod.channel[ch].voiceperiod-=(mod.channel[ch].slidespeed&0x0f); 711 | } 712 | if (mod.channel[ch].voiceperiod<56) mod.channel[ch].noteon=0; 713 | mod.channel[ch].flags|=3; // recalc speed 714 | } 715 | Screamtracker.prototype.effect_t0_g=function(mod, ch) { // slide to note 716 | // if (mod.channel[ch].data) mod.channel[ch].slidetospeed=mod.channel[ch].data; 717 | if (mod.channel[ch].data) mod.channel[ch].slidespeed=mod.channel[ch].data; 718 | } 719 | Screamtracker.prototype.effect_t0_h=function(mod, ch) { // vibrato 720 | if (mod.channel[ch].data&0x0f && mod.channel[ch].data&0xf0) { 721 | mod.channel[ch].vibratodepth=(mod.channel[ch].data&0x0f); 722 | mod.channel[ch].vibratospeed=(mod.channel[ch].data&0xf0)>>4; 723 | } 724 | } 725 | Screamtracker.prototype.effect_t0_i=function(mod, ch) { // tremor 726 | } 727 | Screamtracker.prototype.effect_t0_j=function(mod, ch) { // arpeggio 728 | if (mod.channel[ch].data) mod.channel[ch].arpeggio=mod.channel[ch].data; 729 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 730 | mod.channel[ch].flags|=3; // recalc speed 731 | } 732 | Screamtracker.prototype.effect_t0_k=function(mod, ch) { // vibrato + volslide 733 | mod.effect_t0_d(mod, ch); 734 | } 735 | Screamtracker.prototype.effect_t0_l=function(mod, ch) { // slide to note + volslide 736 | mod.effect_t0_d(mod, ch); 737 | } 738 | Screamtracker.prototype.effect_t0_m=function(mod, ch) { // - 739 | } 740 | Screamtracker.prototype.effect_t0_n=function(mod, ch) { // - 741 | } 742 | Screamtracker.prototype.effect_t0_o=function(mod, ch) { // set sample offset 743 | if (mod.channel[ch].data) mod.channel[ch].lastoffset=mod.channel[ch].data; 744 | 745 | if (mod.channel[ch].lastoffset*256 < mod.sample[mod.channel[ch].sample].length) { 746 | mod.channel[ch].samplepos=mod.channel[ch].lastoffset*256; 747 | mod.channel[ch].trigramp=0.0; 748 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 749 | } 750 | } 751 | Screamtracker.prototype.effect_t0_p=function(mod, ch) { // - 752 | } 753 | Screamtracker.prototype.effect_t0_q=function(mod, ch) { // retrig note 754 | if (mod.channel[ch].data) mod.channel[ch].lastretrig=mod.channel[ch].data; 755 | mod.effect_t1_q(mod, ch); // to retrig also on lines with no note but Qxy command 756 | } 757 | Screamtracker.prototype.effect_t0_r=function(mod, ch) { // tremolo 758 | } 759 | Screamtracker.prototype.effect_t0_s=function(mod, ch) { // Sxy effects 760 | var i=(mod.channel[ch].data&0xf0)>>4; 761 | mod.effects_t0_s[i](mod, ch); 762 | } 763 | Screamtracker.prototype.effect_t0_t=function(mod, ch) { // set tempo 764 | if (mod.channel[ch].data > 32) mod.bpm=mod.channel[ch].data; 765 | } 766 | Screamtracker.prototype.effect_t0_u=function(mod, ch) { // fine vibrato 767 | } 768 | Screamtracker.prototype.effect_t0_v=function(mod, ch) { // set global volume 769 | mod.volume=mod.channel[ch].data; 770 | } 771 | Screamtracker.prototype.effect_t0_w=function(mod, ch) { // - 772 | } 773 | Screamtracker.prototype.effect_t0_x=function(mod, ch) { // - 774 | } 775 | Screamtracker.prototype.effect_t0_y=function(mod, ch) { // - 776 | } 777 | Screamtracker.prototype.effect_t0_z=function(mod, ch) { // sync for FMOD (was: unused) 778 | mod.syncqueue.unshift(mod.channel[ch].data&0x0f); 779 | } 780 | 781 | 782 | 783 | // 784 | // tick 0 special Sxy effect functions 785 | // 786 | Screamtracker.prototype.effect_t0_s0=function(mod, ch) { // set filter (not implemented) 787 | } 788 | Screamtracker.prototype.effect_t0_s1=function(mod, ch) { // set glissando control 789 | } 790 | Screamtracker.prototype.effect_t0_s2=function(mod, ch) { // sync for BASS (was: set finetune) 791 | mod.syncqueue.unshift(mod.channel[ch].data&0x0f); 792 | } 793 | Screamtracker.prototype.effect_t0_s3=function(mod, ch) { // set vibrato waveform 794 | mod.channel[ch].vibratowave=mod.channel[ch].data&0x07; 795 | } 796 | Screamtracker.prototype.effect_t0_s4=function(mod, ch) { // set tremolo waveform 797 | } 798 | Screamtracker.prototype.effect_t0_s5=function(mod, ch) { // - 799 | } 800 | Screamtracker.prototype.effect_t0_s6=function(mod, ch) { // - 801 | } 802 | Screamtracker.prototype.effect_t0_s7=function(mod, ch) { // - 803 | } 804 | Screamtracker.prototype.effect_t0_s8=function(mod, ch) { // set panning position 805 | mod.pan_r[ch]=(mod.channel[ch].data&0x0f)/15.0; 806 | mod.pan_l[ch]=1.0-mod.pan_r[ch]; 807 | } 808 | Screamtracker.prototype.effect_t0_s9=function(mod, ch) { // - 809 | } 810 | Screamtracker.prototype.effect_t0_sa=function(mod, ch) { // old stereo control (not implemented) 811 | } 812 | Screamtracker.prototype.effect_t0_sb=function(mod, ch) { // loop pattern 813 | if (mod.channel[ch].data&0x0f) { 814 | if (mod.loopcount) { 815 | mod.loopcount--; 816 | } else { 817 | mod.loopcount=mod.channel[ch].data&0x0f; 818 | } 819 | if (mod.loopcount) mod.flags|=64; 820 | } else { 821 | mod.looprow=mod.row; 822 | } 823 | } 824 | Screamtracker.prototype.effect_t0_sc=function(mod, ch) { // note cut 825 | } 826 | Screamtracker.prototype.effect_t0_sd=function(mod, ch) { // note delay 827 | if (mod.tick==(mod.channel[ch].data&0x0f)) { 828 | mod.process_note(mod, mod.patterntable[mod.position], ch); 829 | } 830 | } 831 | Screamtracker.prototype.effect_t0_se=function(mod, ch) { // pattern delay 832 | mod.patterndelay=mod.channel[ch].data&0x0f; 833 | mod.patternwait=0; 834 | } 835 | Screamtracker.prototype.effect_t0_sf=function(mod, ch) { // funkrepeat (not implemented) 836 | } 837 | 838 | 839 | 840 | // 841 | // tick 1+ effect functions 842 | // 843 | Screamtracker.prototype.effect_t1_a=function(mod, ch) { // set speed 844 | } 845 | Screamtracker.prototype.effect_t1_b=function(mod, ch) { // order jump 846 | } 847 | Screamtracker.prototype.effect_t1_c=function(mod, ch) { // jump to row 848 | } 849 | Screamtracker.prototype.effect_t1_d=function(mod, ch) { // volume slide 850 | if ((mod.channel[ch].volslide&0x0f)==0) { 851 | // slide up 852 | mod.channel[ch].voicevolume+=mod.channel[ch].volslide>>4; 853 | } else if ((mod.channel[ch].volslide>>4)==0) { 854 | // slide down 855 | mod.channel[ch].voicevolume-=mod.channel[ch].volslide&0x0f; 856 | } 857 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 858 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 859 | } 860 | Screamtracker.prototype.effect_t1_e=function(mod, ch) { // slide down 861 | if (mod.channel[ch].slidespeed<0xe0) { 862 | mod.channel[ch].voiceperiod+=mod.channel[ch].slidespeed*4; 863 | } 864 | if (mod.channel[ch].voiceperiod>27392) mod.channel[ch].noteon=0; 865 | mod.channel[ch].flags|=3; // recalc speed 866 | } 867 | Screamtracker.prototype.effect_t1_f=function(mod, ch) { // slide up 868 | if (mod.channel[ch].slidespeed<0xe0) { 869 | mod.channel[ch].voiceperiod-=mod.channel[ch].slidespeed*4; 870 | } 871 | if (mod.channel[ch].voiceperiod<56) mod.channel[ch].noteon=0; 872 | mod.channel[ch].flags|=3; // recalc speed 873 | } 874 | Screamtracker.prototype.effect_t1_g=function(mod, ch) { // slide to note 875 | if (mod.channel[ch].voiceperiod < mod.channel[ch].slideto) { 876 | // mod.channel[ch].voiceperiod+=4*mod.channel[ch].slidetospeed; 877 | mod.channel[ch].voiceperiod+=4*mod.channel[ch].slidespeed; 878 | if (mod.channel[ch].voiceperiod > mod.channel[ch].slideto) 879 | mod.channel[ch].voiceperiod=mod.channel[ch].slideto; 880 | } else 881 | if (mod.channel[ch].voiceperiod > mod.channel[ch].slideto) { 882 | // mod.channel[ch].voiceperiod-=4*mod.channel[ch].slidetospeed; 883 | mod.channel[ch].voiceperiod-=4*mod.channel[ch].slidespeed; 884 | if (mod.channel[ch].voiceperiod27392) mod.channel[ch].voiceperiod=27392; 893 | if (mod.channel[ch].voiceperiod<56) mod.channel[ch].voiceperiod=56; 894 | mod.channel[ch].flags|=1; 895 | } 896 | Screamtracker.prototype.effect_t1_i=function(mod, ch) { // tremor 897 | } 898 | Screamtracker.prototype.effect_t1_j=function(mod, ch) { // arpeggio 899 | var n=mod.channel[ch].note; 900 | if ((mod.tick&3)==1) n+=mod.channel[ch].arpeggio>>4; 901 | if ((mod.tick&3)==2) n+=mod.channel[ch].arpeggio&0x0f; 902 | mod.channel[ch].voiceperiod=(8363.0 * mod.periodtable[n]) / mod.sample[mod.channel[ch].sample].c2spd; 903 | mod.channel[ch].flags|=3; // recalc speed 904 | } 905 | Screamtracker.prototype.effect_t1_k=function(mod, ch) { // vibrato + volslide 906 | mod.effect_t1_h(mod, ch); 907 | mod.effect_t1_d(mod, ch); 908 | } 909 | Screamtracker.prototype.effect_t1_l=function(mod, ch) { // slide to note + volslide 910 | mod.effect_t1_g(mod, ch); 911 | mod.effect_t1_d(mod, ch); 912 | } 913 | Screamtracker.prototype.effect_t1_m=function(mod, ch) { // - 914 | } 915 | Screamtracker.prototype.effect_t1_n=function(mod, ch) { // - 916 | } 917 | Screamtracker.prototype.effect_t1_o=function(mod, ch) { // set sample offset 918 | } 919 | Screamtracker.prototype.effect_t1_p=function(mod, ch) { // - 920 | } 921 | Screamtracker.prototype.effect_t1_q=function(mod, ch) { // retrig note 922 | if ((mod.tick%(mod.channel[ch].lastretrig&0x0f))==0) { 923 | mod.channel[ch].samplepos=0; 924 | mod.channel[ch].trigramp=0.0; 925 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 926 | var v=mod.channel[ch].lastretrig>>4; 927 | if ((v&7) >= 6) { 928 | mod.channel[ch].voicevolume=Math.floor(mod.channel[ch].voicevolume*mod.retrigvoltab[v]); 929 | } else { 930 | mod.channel[ch].voicevolume+=mod.retrigvoltab[v]; 931 | } 932 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 933 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 934 | } 935 | } 936 | Screamtracker.prototype.effect_t1_r=function(mod, ch) { // tremolo 937 | } 938 | 939 | Screamtracker.prototype.effect_t1_s=function(mod, ch) { // special effects 940 | var i=(mod.channel[ch].data&0xf0)>>4; 941 | mod.effects_t1_s[i](mod, ch); 942 | } 943 | Screamtracker.prototype.effect_t1_t=function(mod, ch) { // set tempo 944 | } 945 | Screamtracker.prototype.effect_t1_u=function(mod, ch) { // fine vibrato 946 | } 947 | Screamtracker.prototype.effect_t1_v=function(mod, ch) { // set global volume 948 | } 949 | Screamtracker.prototype.effect_t1_w=function(mod, ch) { // - 950 | } 951 | Screamtracker.prototype.effect_t1_x=function(mod, ch) { // - 952 | } 953 | Screamtracker.prototype.effect_t1_y=function(mod, ch) { // - 954 | } 955 | Screamtracker.prototype.effect_t1_z=function(mod, ch) { // - 956 | } 957 | 958 | 959 | 960 | // 961 | // tick 1+ special Sxy effect functions 962 | // 963 | Screamtracker.prototype.effect_t1_s0=function(mod, ch) { // set filter (not implemented) 964 | } 965 | Screamtracker.prototype.effect_t1_s1=function(mod, ch) { // set glissando control 966 | } 967 | Screamtracker.prototype.effect_t1_s2=function(mod, ch) { // set finetune 968 | } 969 | Screamtracker.prototype.effect_t1_s3=function(mod, ch) { // set vibrato waveform 970 | } 971 | Screamtracker.prototype.effect_t1_s4=function(mod, ch) { // set tremolo waveform 972 | } 973 | Screamtracker.prototype.effect_t1_s5=function(mod, ch) { // - 974 | } 975 | Screamtracker.prototype.effect_t1_s6=function(mod, ch) { // - 976 | } 977 | Screamtracker.prototype.effect_t1_s7=function(mod, ch) { // - 978 | } 979 | Screamtracker.prototype.effect_t1_s8=function(mod, ch) { // set panning position 980 | } 981 | Screamtracker.prototype.effect_t1_s9=function(mod, ch) { // - 982 | } 983 | Screamtracker.prototype.effect_t1_sa=function(mod, ch) { // old stereo control (not implemented) 984 | } 985 | Screamtracker.prototype.effect_t1_sb=function(mod, ch) { // loop pattern 986 | } 987 | Screamtracker.prototype.effect_t1_sc=function(mod, ch) { // note cut 988 | if (mod.tick==(mod.channel[ch].data&0x0f)) { 989 | mod.channel[ch].volume=0; 990 | mod.channel[ch].voicevolume=0; 991 | } 992 | } 993 | Screamtracker.prototype.effect_t1_sd=function(mod, ch) { // note delay 994 | mod.effect_t0_sd(mod, ch); 995 | } 996 | Screamtracker.prototype.effect_t1_se=function(mod, ch) { // pattern delay 997 | } 998 | Screamtracker.prototype.effect_t1_sf=function(mod, ch) { // funkrepeat (not implemented) 999 | } 1000 | -------------------------------------------------------------------------------- /js/ft2.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2012-2021 Noora Halme et al. (see AUTHORS) 3 | 4 | This code is licensed under the MIT license: 5 | http://www.opensource.org/licenses/mit-license.php 6 | 7 | Fast Tracker 2 module player class 8 | 9 | Reading material: 10 | - ftp://ftp.modland.com/pub/documents/format_documentation/FastTracker%202%20v2.04%20(.xm).html 11 | - http://sid.ethz.ch/debian/milkytracker/milkytracker-0.90.85%2Bdfsg/resources/reference/xm-form.txt 12 | - ftp://ftp.modland.com/pub/documents/format_documentation/Tracker%20differences%20for%20Coders.txt 13 | - http://wiki.openmpt.org/Manual:_Compatible_Playback 14 | 15 | Greets to Guru, Alfred and CCR for their work figuring out the .xm format. :) 16 | */ 17 | 18 | function Fasttracker() 19 | { 20 | var i, t; 21 | 22 | this.clearsong(); 23 | this.initialize(); 24 | 25 | this.playing=false; 26 | this.paused=false; 27 | this.repeat=false; 28 | 29 | this.filter=false; 30 | 31 | this.syncqueue=[]; 32 | 33 | this.samplerate=44100; 34 | this.ramplen=64.0; 35 | 36 | this.mixval=8.0; 37 | 38 | // amiga period value table 39 | this.periodtable=new Float32Array([ 40 | //ft -8 -7 -6 -5 -4 -3 -2 -1 41 | // 0 1 2 3 4 5 6 7 42 | 907.0, 900.0, 894.0, 887.0, 881.0, 875.0, 868.0, 862.0, // B-3 43 | 856.0, 850.0, 844.0, 838.0, 832.0, 826.0, 820.0, 814.0, // C-4 44 | 808.0, 802.0, 796.0, 791.0, 785.0, 779.0, 774.0, 768.0, // C#4 45 | 762.0, 757.0, 752.0, 746.0, 741.0, 736.0, 730.0, 725.0, // D-4 46 | 720.0, 715.0, 709.0, 704.0, 699.0, 694.0, 689.0, 684.0, // D#4 47 | 678.0, 675.0, 670.0, 665.0, 660.0, 655.0, 651.0, 646.0, // E-4 48 | 640.0, 636.0, 632.0, 628.0, 623.0, 619.0, 614.0, 610.0, // F-4 49 | 604.0, 601.0, 597.0, 592.0, 588.0, 584.0, 580.0, 575.0, // F#4 50 | 570.0, 567.0, 563.0, 559.0, 555.0, 551.0, 547.0, 543.0, // G-4 51 | 538.0, 535.0, 532.0, 528.0, 524.0, 520.0, 516.0, 513.0, // G#4 52 | 508.0, 505.0, 502.0, 498.0, 494.0, 491.0, 487.0, 484.0, // A-4 53 | 480.0, 477.0, 474.0, 470.0, 467.0, 463.0, 460.0, 457.0, // A#4 54 | 453.0, 450.0, 447.0, 445.0, 442.0, 439.0, 436.0, 433.0, // B-4 55 | 428.0 56 | ]); 57 | 58 | this.pan=new Float32Array(32); 59 | this.finalpan=new Float32Array(32); 60 | for(i=0;i<32;i++) this.pan[i]=this.finalpan[i]=0.5; 61 | 62 | // calc tables for vibrato waveforms 63 | this.vibratotable=new Array(); 64 | for(t=0;t<4;t++) { 65 | this.vibratotable[t]=new Float32Array(64); 66 | for(i=0;i<64;i++) { 67 | switch(t) { 68 | case 0: 69 | this.vibratotable[t][i]=127*Math.sin(Math.PI*2*(i/64)); 70 | break; 71 | case 1: 72 | this.vibratotable[t][i]=127-4*i; 73 | break; 74 | case 2: 75 | this.vibratotable[t][i]=(i<32)?127:-127; 76 | break; 77 | case 3: 78 | this.vibratotable[t][i]=(1-2*Math.random())*127; 79 | break; 80 | } 81 | } 82 | } 83 | 84 | // volume column effect jumptable for 0x50..0xef 85 | this.voleffects_t0 = new Array( 86 | this.effect_vol_t0_f0, 87 | this.effect_vol_t0_60, this.effect_vol_t0_70, this.effect_vol_t0_80, this.effect_vol_t0_90, this.effect_vol_t0_a0, 88 | this.effect_vol_t0_b0, this.effect_vol_t0_c0, this.effect_vol_t0_d0, this.effect_vol_t0_e0 89 | ); 90 | this.voleffects_t1 = new Array( 91 | this.effect_vol_t1_f0, 92 | this.effect_vol_t1_60, this.effect_vol_t1_70, this.effect_vol_t1_80, this.effect_vol_t1_90, this.effect_vol_t1_a0, 93 | this.effect_vol_t1_b0, this.effect_vol_t1_c0, this.effect_vol_t1_d0, this.effect_vol_t1_e0 94 | ); 95 | 96 | // effect jumptables for tick 0 and ticks 1..f 97 | this.effects_t0 = new Array( 98 | 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, 99 | 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, 100 | this.effect_t0_g, this.effect_t0_h, this.effect_t0_i, this.effect_t0_j, this.effect_t0_k, this.effect_t0_l, this.effect_t0_m, this.effect_t0_n, 101 | this.effect_t0_o, this.effect_t0_p, this.effect_t0_q, this.effect_t0_r, this.effect_t0_s, this.effect_t0_t, this.effect_t0_u, this.effect_t0_v, 102 | this.effect_t0_w, this.effect_t0_x, this.effect_t0_y, this.effect_t0_z 103 | ); 104 | this.effects_t0_e = new Array( 105 | 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, 106 | 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 107 | ); 108 | this.effects_t1 = new Array( 109 | 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, 110 | 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, 111 | this.effect_t1_g, this.effect_t1_h, this.effect_t1_i, this.effect_t1_j, this.effect_t1_k, this.effect_t1_l, this.effect_t1_m, this.effect_t1_n, 112 | this.effect_t1_o, this.effect_t1_p, this.effect_t1_q, this.effect_t1_r, this.effect_t1_s, this.effect_t1_t, this.effect_t1_u, this.effect_t1_v, 113 | this.effect_t1_w, this.effect_t1_x, this.effect_t1_y, this.effect_t1_z 114 | ); 115 | this.effects_t1_e = new Array( 116 | 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, 117 | 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 118 | ); 119 | } 120 | 121 | 122 | 123 | // clear song data 124 | Fasttracker.prototype.clearsong = function() 125 | { 126 | var i; 127 | 128 | this.title=""; 129 | this.signature=""; 130 | this.trackerversion=0x0104; 131 | 132 | this.songlen=1; 133 | this.repeatpos=0; 134 | 135 | this.channels=0; 136 | this.patterns=0; 137 | this.instruments=32; 138 | 139 | this.amigaperiods=0; 140 | 141 | this.initSpeed=6; 142 | this.initBPM=125; 143 | 144 | this.patterntable=new ArrayBuffer(256); 145 | for(i=0;i<256;i++) this.patterntable[i]=0; 146 | 147 | this.pattern=new Array(); 148 | this.instrument=new Array(this.instruments); 149 | for(i=0;i<32;i++) { 150 | this.instrument[i]=new Object(); 151 | this.instrument[i].name=""; 152 | this.instrument[i].samples=new Array(); 153 | } 154 | 155 | this.chvu=new Float32Array(2); 156 | } 157 | 158 | 159 | 160 | // initialize all player variables to defaults prior to starting playback 161 | Fasttracker.prototype.initialize = function() 162 | { 163 | this.syncqueue=[]; 164 | 165 | this.tick=-1; 166 | this.position=0; 167 | this.row=0; 168 | this.flags=0; 169 | 170 | this.volume=64; 171 | if (this.initSpeed) this.speed=this.initSpeed; 172 | if (this.initBPM) this.bpm=this.initBPM; 173 | this.stt=0; //this.samplerate/(this.bpm*0.4); 174 | this.breakrow=0; 175 | this.patternjump=0; 176 | this.patterndelay=0; 177 | this.patternwait=0; 178 | this.endofsong=false; 179 | this.looprow=0; 180 | this.loopstart=0; 181 | this.loopcount=0; 182 | 183 | this.globalvolslide=0; 184 | 185 | this.channel=new Array(); 186 | for(i=0;imaxpatt) maxpatt=this.patterntable[i]; 278 | } 279 | maxpatt++; 280 | 281 | // allocate arrays for pattern data 282 | this.pattern=new Array(maxpatt); 283 | this.patternlen=new Array(maxpatt); 284 | 285 | for(i=0;i all columns present sequentially 328 | this.pattern[i][k+0]=c; 329 | this.pattern[i][k+1]=buffer[offset+j++]; 330 | this.pattern[i][k+2]=buffer[offset+j++]; 331 | this.pattern[i][k+3]=buffer[offset+j++]; 332 | this.pattern[i][k+4]=buffer[offset+j++]; 333 | } 334 | k+=5; 335 | } 336 | 337 | for(k=0;k<(this.patternlen[i]*this.channels*5);k+=5) { 338 | // remap note to st3-style, 255=no note, 254=note off 339 | if (this.pattern[i][k+0]>=97) { 340 | this.pattern[i][k+0]=254; 341 | } else if (this.pattern[i][k+0]==0) { 342 | this.pattern[i][k+0]=255; 343 | } else { 344 | this.pattern[i][k+0]--; 345 | } 346 | 347 | // command 255=no command 348 | if (this.pattern[i][k+3]==0 && this.pattern[i][k+4]==0) this.pattern[i][k+3]=255; 349 | 350 | // remap volume column setvol to 0x00..0x40, tone porta to 0x50..0x5f and 0xff for nop 351 | if (this.pattern[i][k+2]<0x10) { this.pattern[i][k+2]=0xff; } 352 | else if (this.pattern[i][k+2]>=0x10 && this.pattern[i][k+2]<=0x50) { this.pattern[i][k+2]-=0x10; } 353 | else if (this.pattern[i][k+2]>=0xf0) this.pattern[i][k+2]-=0xa0; 354 | } 355 | 356 | // unpack next pattern 357 | offset+=j; 358 | i++; 359 | } 360 | this.patterns=maxpatt; 361 | 362 | // instruments 363 | this.instrument=new Array(this.instruments); 364 | i=0; 365 | while(i32767) c-=65536; 508 | this.instrument[i].sample[j].data[k]=c/32768.0; 509 | } 510 | } else { 511 | for(k=0;k127) c-=256; 515 | this.instrument[i].sample[j].data[k]=c/128.0; 516 | } 517 | } 518 | offset+=this.instrument[i].sample[j].length * this.instrument[i].sample[j].bps; 519 | } 520 | } else { 521 | offset+=hdrlen; 522 | } 523 | i++; 524 | } 525 | 526 | this.mixval=4.0-2.0*(this.channels/32.0); 527 | 528 | this.chvu=new Float32Array(this.channels); 529 | for(i=0;i=mod.speed) { 563 | if (mod.patterndelay) { // delay pattern 564 | if (mod.tick < ((mod.patternwait+1)*mod.speed)) { 565 | mod.patternwait++; 566 | } else { 567 | mod.row++; mod.tick=0; mod.flags|=2; mod.patterndelay=0; 568 | } 569 | } else { 570 | if (mod.flags&(16+32+64)) { 571 | if (mod.flags&64) { // loop pattern? 572 | mod.row=mod.looprow; 573 | mod.flags&=0xa1; 574 | mod.flags|=2; 575 | } else { 576 | if (mod.flags&16) { // pattern jump/break? 577 | mod.position=mod.patternjump; 578 | mod.row=mod.breakrow; 579 | mod.patternjump=0; 580 | mod.breakrow=0; 581 | mod.flags&=0xe1; 582 | mod.flags|=2; 583 | } 584 | } 585 | mod.tick=0; 586 | } else { 587 | mod.row++; mod.tick=0; mod.flags|=2; 588 | } 589 | } 590 | } 591 | 592 | // step to new pattern? 593 | if (mod.row>=mod.patternlen[mod.patterntable[mod.position]]) { 594 | mod.position++; 595 | mod.row=0; 596 | mod.flags|=4; 597 | } 598 | 599 | // end of song? 600 | if (mod.position>=mod.songlen) { 601 | if (mod.repeat) { 602 | mod.position=0; 603 | } else { 604 | this.endofsong=true; 605 | } 606 | return; 607 | } 608 | } 609 | 610 | 611 | 612 | // process one channel on a row in pattern p, pp is an offset to pattern data 613 | Fasttracker.prototype.process_note = function(mod, p, ch) { 614 | var n, i, s, v, pp, pv; 615 | 616 | pp=mod.row*5*mod.channels + ch*5; 617 | n=mod.pattern[p][pp]; 618 | i=mod.pattern[p][pp+1]; 619 | if (i && i<=mod.instrument.length) { 620 | mod.channel[ch].instrument=i-1; 621 | 622 | if (mod.instrument[i-1].samples) { 623 | s=mod.instrument[i-1].samplemap[mod.channel[ch].note]; 624 | mod.channel[ch].sampleindex=s; 625 | mod.channel[ch].volume=mod.instrument[i-1].sample[s].volume; 626 | mod.channel[ch].playdir=1; // fixes crash in respirator.xm pos 0x12 627 | 628 | // set pan from sample 629 | mod.pan[ch]=mod.instrument[i-1].sample[s].panning/255.0; 630 | } 631 | mod.channel[ch].voicevolume=mod.channel[ch].volume; 632 | } 633 | i=mod.channel[ch].instrument; 634 | 635 | if (n<254) { 636 | // look up the sample 637 | s=mod.instrument[i].samplemap[n]; 638 | mod.channel[ch].sampleindex=s; 639 | 640 | var rn=n + mod.instrument[i].sample[s].relativenote; 641 | 642 | // calc period for note 643 | pv=mod.calcperiod(mod, rn, mod.instrument[i].sample[s].finetune); 644 | 645 | if (mod.channel[ch].noteon) { 646 | // retrig note, except if command=0x03 (porta to note) or 0x05 (porta+volslide) 647 | if ((mod.channel[ch].command != 0x03) && (mod.channel[ch].command != 0x05)) { 648 | mod.channel[ch].note=n; 649 | mod.channel[ch].period=pv; 650 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 651 | mod.channel[ch].flags|=3; // force sample speed recalc 652 | 653 | mod.channel[ch].trigramp=0.0; 654 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 655 | 656 | mod.channel[ch].samplepos=0; 657 | mod.channel[ch].playdir=1; 658 | if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0; 659 | 660 | mod.channel[ch].noteon=1; 661 | 662 | mod.channel[ch].fadeoutpos=65535; 663 | mod.channel[ch].volenvpos=0; 664 | mod.channel[ch].panenvpos=0; 665 | } 666 | } else { 667 | // note is off, restart but don't set period if slide command 668 | if (mod.pattern[p][pp+1]) { // instrument set on row? 669 | mod.channel[ch].samplepos=0; 670 | mod.channel[ch].playdir=1; 671 | if (mod.channel[ch].vibratowave>3) mod.channel[ch].vibratopos=0; 672 | mod.channel[ch].noteon=1; 673 | mod.channel[ch].fadeoutpos=65535; 674 | mod.channel[ch].volenvpos=0; 675 | mod.channel[ch].panenvpos=0; 676 | mod.channel[ch].trigramp=0.0; 677 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 678 | } 679 | if ((mod.channel[ch].command != 0x03) && (mod.channel[ch].command != 0x05)) { 680 | mod.channel[ch].note=n; 681 | mod.channel[ch].period=pv; 682 | mod.channel[ch].voiceperiod=mod.channel[ch].period; 683 | mod.channel[ch].flags|=3; // force sample speed recalc 684 | } 685 | } 686 | // in either case, set the slide to note target to note period 687 | mod.channel[ch].slideto=pv; 688 | } else if (n==254) { 689 | mod.channel[ch].noteon=0; // note off 690 | if (!(mod.instrument[i].voltype&1)) mod.channel[ch].voicevolume=0; 691 | } 692 | 693 | if (mod.pattern[p][pp+2]!=255) { 694 | v=mod.pattern[p][pp+2]; 695 | if (v<=0x40) { 696 | mod.channel[ch].volume=v; 697 | mod.channel[ch].voicevolume=mod.channel[ch].volume; 698 | } 699 | } 700 | } 701 | 702 | 703 | 704 | // advance player and all channels by a tick 705 | Fasttracker.prototype.process_tick = function(mod) { 706 | 707 | // advance global player state by a tick 708 | mod.advance(mod); 709 | 710 | // advance all channels by a tick 711 | for(var ch=0;ch=0x50 && v<0xf0) { 738 | if (!mod.tick) mod.voleffects_t0[(v>>4)-5](mod, ch, v&0x0f); 739 | else mod.voleffects_t1[(v>>4)-5](mod, ch, v&0x0f); 740 | } 741 | if (mod.channel[ch].command < 36) { 742 | if (!mod.tick) { 743 | // process only on tick 0 744 | mod.effects_t0[mod.channel[ch].command](mod, ch); 745 | } else { 746 | mod.effects_t1[mod.channel[ch].command](mod, ch); 747 | } 748 | } 749 | 750 | // recalc sample speed if voiceperiod has changed 751 | if ((mod.channel[ch].flags&1 || mod.flags&2) && mod.channel[ch].voiceperiod) 752 | { 753 | var f; 754 | if (mod.amigaperiods) { 755 | f=8287.137 * 1712.0/mod.channel[ch].voiceperiod; 756 | } else { 757 | f=8287.137 * Math.pow(2.0, (4608.0 - mod.channel[ch].voiceperiod) / 768.0); 758 | } 759 | mod.channel[ch].samplespeed=f/mod.samplerate; 760 | } 761 | 762 | // advance vibrato on each new tick 763 | mod.channel[ch].vibratopos+=mod.channel[ch].vibratospeed; 764 | mod.channel[ch].vibratopos&=0x3f; 765 | 766 | // advance volume envelope, if enabled (also fadeout) 767 | if (mod.instrument[i].voltype&1) { 768 | mod.channel[ch].volenvpos++; 769 | 770 | if (mod.channel[ch].noteon && 771 | (mod.instrument[i].voltype&2) && 772 | mod.channel[ch].volenvpos >= mod.instrument[i].volsustain) 773 | mod.channel[ch].volenvpos=mod.instrument[i].volsustain; 774 | 775 | if ((mod.instrument[i].voltype&4) && 776 | mod.channel[ch].volenvpos >= mod.instrument[i].volloopend) 777 | mod.channel[ch].volenvpos=mod.instrument[i].volloopstart; 778 | 779 | if (mod.channel[ch].volenvpos >= mod.instrument[i].volenvlen) 780 | mod.channel[ch].volenvpos=mod.instrument[i].volenvlen; 781 | 782 | if (mod.channel[ch].volenvpos>324) mod.channel[ch].volenvpos=324; 783 | 784 | // fadeout if note is off 785 | if (!mod.channel[ch].noteon && mod.channel[ch].fadeoutpos) { 786 | mod.channel[ch].fadeoutpos-=mod.instrument[i].volfadeout; 787 | if (mod.channel[ch].fadeoutpos<0) mod.channel[ch].fadeoutpos=0; 788 | } 789 | } 790 | 791 | // advance pan envelope, if enabled 792 | if (mod.instrument[i].pantype&1) { 793 | mod.channel[ch].panenvpos++; 794 | 795 | if (mod.channel[ch].noteon && 796 | mod.instrument[i].pantype&2 && 797 | mod.channel[ch].panenvpos >= mod.instrument[i].pansustain) 798 | mod.channel[ch].panenvpos=mod.instrument[i].pansustain; 799 | 800 | if (mod.instrument[i].pantype&4 && 801 | mod.channel[ch].panenvpos >= mod.instrument[i].panloopend) 802 | mod.channel[ch].panenvpos=mod.instrument[i].panloopstart; 803 | 804 | if (mod.channel[ch].panenvpos >= mod.instrument[i].panenvlen) 805 | mod.channel[ch].panenvpos=mod.instrument[i].panenvlen; 806 | 807 | if (mod.channel[ch].panenvpos>324) mod.channel[ch].panenvpos=324; 808 | } 809 | 810 | // calc final volume for channel 811 | mod.channel[ch].finalvolume=mod.channel[ch].voicevolume * mod.instrument[i].volenv[mod.channel[ch].volenvpos] * mod.channel[ch].fadeoutpos/65536.0; 812 | 813 | // calc final panning for channel 814 | mod.finalpan[ch]=mod.pan[ch]+(mod.instrument[i].panenv[mod.channel[ch].panenvpos]-0.5)*(0.5*Math.abs(mod.pan[ch]-0.5))*2.0; 815 | 816 | // setup volramp if voice volume changed 817 | if (mod.channel[ch].oldfinalvolume!=mod.channel[ch].finalvolume) { 818 | mod.channel[ch].volrampfrom=mod.channel[ch].oldfinalvolume; 819 | mod.channel[ch].volramp=0.0; 820 | } 821 | 822 | // clear channel flags 823 | mod.channel[ch].flags=0; 824 | } 825 | 826 | // clear global flags after all channels are processed 827 | mod.flags&=0x70; 828 | } 829 | 830 | 831 | 832 | // mix a buffer of audio for an audio processing event 833 | Fasttracker.prototype.mix = function(mod, bufs, buflen) { 834 | var outp=new Float32Array(2); 835 | 836 | // return a buffer of silence if not playing 837 | if (mod.paused || mod.endofsong || !mod.playing) { 838 | for(var s=0;s mod.channel[ch].samplepos) { 868 | fl=mod.channel[ch].lastsample; 869 | 870 | // interpolate towards current sample 871 | var f=Math.floor(mod.channel[ch].samplepos); 872 | fs=mod.instrument[i].sample[si].data[f]; 873 | f=mod.channel[ch].samplepos - f; 874 | f=(mod.channel[ch].playdir<0) ? (1.0-f) : f; 875 | fl=f*fs + (1.0-f)*fl; 876 | 877 | // smooth out discontinuities from retrig and sample offset 878 | f=mod.channel[ch].trigramp; 879 | fl=f*fl + (1.0-f)*mod.channel[ch].trigrampfrom; 880 | f+=1.0/128.0; 881 | mod.channel[ch].trigramp=Math.min(1.0, f); 882 | mod.channel[ch].currentsample=fl; 883 | 884 | // ramp volume changes over 64 samples to avoid clicks 885 | fr=fl*(mod.channel[ch].finalvolume/64.0); 886 | f=mod.channel[ch].volramp; 887 | fl=f*fr + (1.0-f)*(fl*(mod.channel[ch].volrampfrom/64.0)); 888 | f+=(1.0/64.0); 889 | mod.channel[ch].volramp=Math.min(1.0, f); 890 | 891 | // pan samples, if envelope is disabled panvenv is always 0.5 892 | f=mod.finalpan[ch]; 893 | fr=fl*f; 894 | fl*=1.0-f; 895 | } 896 | outp[0]+=fl; 897 | outp[1]+=fr; 898 | 899 | // advance sample position and check for loop or end 900 | var oldpos=mod.channel[ch].samplepos; 901 | mod.channel[ch].samplepos+=mod.channel[ch].playdir*mod.channel[ch].samplespeed; 902 | if (mod.channel[ch].playdir==1) { 903 | if (Math.floor(mod.channel[ch].samplepos) > Math.floor(oldpos)) mod.channel[ch].lastsample=fs; 904 | } else { 905 | if (Math.floor(mod.channel[ch].samplepos) < Math.floor(oldpos)) mod.channel[ch].lastsample=fs; 906 | } 907 | 908 | if (mod.instrument[i].sample[si].looptype) { 909 | if (mod.instrument[i].sample[si].looptype==2) { 910 | // pingpong loop 911 | if (mod.channel[ch].playdir==-1) { 912 | // bounce off from start? 913 | if (mod.channel[ch].samplepos <= mod.instrument[i].sample[si].loopstart) { 914 | mod.channel[ch].samplepos+=(mod.instrument[i].sample[si].loopstart-mod.channel[ch].samplepos); 915 | mod.channel[ch].playdir=1; 916 | mod.channel[ch].lastsample=mod.channel[ch].currentsample; 917 | } 918 | } else { 919 | // bounce off from end? 920 | if (mod.channel[ch].samplepos >= mod.instrument[i].sample[si].loopend) { 921 | mod.channel[ch].samplepos-=(mod.channel[ch].samplepos-mod.instrument[i].sample[si].loopend); 922 | mod.channel[ch].playdir=-1; 923 | mod.channel[ch].lastsample=mod.channel[ch].currentsample; 924 | } 925 | } 926 | } else { 927 | // normal loop 928 | if (mod.channel[ch].samplepos >= mod.instrument[i].sample[si].loopend) { 929 | mod.channel[ch].samplepos-=mod.instrument[i].sample[si].looplength; 930 | mod.channel[ch].lastsample=mod.channel[ch].currentsample; 931 | } 932 | } 933 | } else { 934 | if (mod.channel[ch].samplepos >= mod.instrument[i].sample[si].length) { 935 | mod.channel[ch].noteon=0; 936 | } 937 | } 938 | } else { 939 | mod.channel[ch].currentsample=0.0; // note is completely off 940 | } 941 | mod.chvu[ch]=Math.max(mod.chvu[ch], Math.abs(fl+fr)); 942 | } 943 | 944 | // done - store to output buffer 945 | t=mod.volume/64.0; 946 | bufs[0][s]=outp[0]*t; 947 | bufs[1][s]=outp[1]*t 948 | mod.stt--; 949 | } 950 | } 951 | 952 | 953 | 954 | // 955 | // volume column effect functions 956 | // 957 | Fasttracker.prototype.effect_vol_t0_60=function(mod, ch, data) { // 60-6f vol slide down 958 | } 959 | Fasttracker.prototype.effect_vol_t0_70=function(mod, ch, data) { // 70-7f vol slide up 960 | } 961 | Fasttracker.prototype.effect_vol_t0_80=function(mod, ch, data) { // 80-8f fine vol slide down 962 | mod.channel[ch].voicevolume-=data; 963 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 964 | } 965 | Fasttracker.prototype.effect_vol_t0_90=function(mod, ch, data) { // 90-9f fine vol slide up 966 | mod.channel[ch].voicevolume+=data; 967 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 968 | } 969 | Fasttracker.prototype.effect_vol_t0_a0=function(mod, ch, data) { // a0-af set vibrato speed 970 | mod.channel[ch].vibratospeed=data; 971 | } 972 | Fasttracker.prototype.effect_vol_t0_b0=function(mod, ch, data) { // b0-bf vibrato 973 | if (data) mod.channel[ch].vibratodepth=data; 974 | mod.effect_t1_4(mod, ch); 975 | } 976 | Fasttracker.prototype.effect_vol_t0_c0=function(mod, ch, data) { // c0-cf set panning 977 | mod.pan[ch]=(data&0x0f)/15.0; 978 | } 979 | Fasttracker.prototype.effect_vol_t0_d0=function(mod, ch, data) { // d0-df panning slide left 980 | } 981 | Fasttracker.prototype.effect_vol_t0_e0=function(mod, ch, data) { // e0-ef panning slide right 982 | } 983 | Fasttracker.prototype.effect_vol_t0_f0=function(mod, ch, data) { // f0-ff tone porta 984 | // if (data) mod.channel[ch].slidetospeed=data; 985 | // if (!mod.amigaperiods) mod.channel[ch].slidetospeed*=4; 986 | } 987 | ////// 988 | Fasttracker.prototype.effect_vol_t1_60=function(mod, ch, data) { // 60-6f vol slide down 989 | mod.channel[ch].voicevolume-=data; 990 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 991 | } 992 | Fasttracker.prototype.effect_vol_t1_70=function(mod, ch, data) { // 70-7f vol slide up 993 | mod.channel[ch].voicevolume+=data; 994 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 995 | } 996 | Fasttracker.prototype.effect_vol_t1_80=function(mod, ch, data) { // 80-8f fine vol slide down 997 | } 998 | Fasttracker.prototype.effect_vol_t1_90=function(mod, ch, data) { // 90-9f fine vol slide up 999 | } 1000 | Fasttracker.prototype.effect_vol_t1_a0=function(mod, ch, data) { // a0-af set vibrato speed 1001 | } 1002 | Fasttracker.prototype.effect_vol_t1_b0=function(mod, ch, data) { // b0-bf vibrato 1003 | mod.effect_t1_4(mod, ch); // same as effect column vibrato on ticks 1+ 1004 | } 1005 | Fasttracker.prototype.effect_vol_t1_c0=function(mod, ch, data) { // c0-cf set panning 1006 | } 1007 | Fasttracker.prototype.effect_vol_t1_d0=function(mod, ch, data) { // d0-df panning slide left 1008 | } 1009 | Fasttracker.prototype.effect_vol_t1_e0=function(mod, ch, data) { // e0-ef panning slide right 1010 | } 1011 | Fasttracker.prototype.effect_vol_t1_f0=function(mod, ch, data) { // f0-ff tone porta 1012 | // mod.effect_t1_3(mod, ch); 1013 | } 1014 | 1015 | 1016 | 1017 | // 1018 | // tick 0 effect functions 1019 | // 1020 | Fasttracker.prototype.effect_t0_0=function(mod, ch) { // 0 arpeggio 1021 | mod.channel[ch].arpeggio=mod.channel[ch].data; 1022 | } 1023 | Fasttracker.prototype.effect_t0_1=function(mod, ch) { // 1 slide up 1024 | if (mod.channel[ch].data) mod.channel[ch].slideupspeed=mod.channel[ch].data*4; 1025 | } 1026 | Fasttracker.prototype.effect_t0_2=function(mod, ch) { // 2 slide down 1027 | if (mod.channel[ch].data) mod.channel[ch].slidedownspeed=mod.channel[ch].data*4; 1028 | } 1029 | Fasttracker.prototype.effect_t0_3=function(mod, ch) { // 3 slide to note 1030 | if (mod.channel[ch].data) mod.channel[ch].slidetospeed=mod.channel[ch].data*4; 1031 | } 1032 | Fasttracker.prototype.effect_t0_4=function(mod, ch) { // 4 vibrato 1033 | if (mod.channel[ch].data&0x0f && mod.channel[ch].data&0xf0) { 1034 | mod.channel[ch].vibratodepth=(mod.channel[ch].data&0x0f); 1035 | mod.channel[ch].vibratospeed=(mod.channel[ch].data&0xf0)>>4; 1036 | } 1037 | mod.effect_t1_4(mod, ch); 1038 | } 1039 | Fasttracker.prototype.effect_t0_5=function(mod, ch) { // 5 1040 | mod.effect_t0_a(mod, ch); 1041 | } 1042 | Fasttracker.prototype.effect_t0_6=function(mod, ch) { // 6 1043 | mod.effect_t0_a(mod, ch); 1044 | } 1045 | Fasttracker.prototype.effect_t0_7=function(mod, ch) { // 7 1046 | } 1047 | Fasttracker.prototype.effect_t0_8=function(mod, ch) { // 8 set panning 1048 | mod.pan[ch]=mod.channel[ch].data/255.0; 1049 | } 1050 | Fasttracker.prototype.effect_t0_9=function(mod, ch) { // 9 set sample offset 1051 | mod.channel[ch].samplepos=mod.channel[ch].data*256; 1052 | mod.channel[ch].playdir=1; 1053 | 1054 | mod.channel[ch].trigramp=0.0; 1055 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 1056 | } 1057 | Fasttracker.prototype.effect_t0_a=function(mod, ch) { // a volume slide 1058 | // this behavior differs from protracker!! A00 will slide using previous non-zero parameter. 1059 | if (mod.channel[ch].data) mod.channel[ch].volslide=mod.channel[ch].data; 1060 | } 1061 | Fasttracker.prototype.effect_t0_b=function(mod, ch) { // b pattern jump 1062 | mod.breakrow=0; 1063 | mod.patternjump=mod.channel[ch].data; 1064 | mod.flags|=16; 1065 | } 1066 | Fasttracker.prototype.effect_t0_c=function(mod, ch) { // c set volume 1067 | mod.channel[ch].voicevolume=mod.channel[ch].data; 1068 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 1069 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 1070 | } 1071 | Fasttracker.prototype.effect_t0_d=function(mod, ch) { // d pattern break 1072 | mod.breakrow=((mod.channel[ch].data&0xf0)>>4)*10 + (mod.channel[ch].data&0x0f); 1073 | if (!(mod.flags&16)) mod.patternjump=mod.position+1; 1074 | mod.flags|=16; 1075 | } 1076 | Fasttracker.prototype.effect_t0_e=function(mod, ch) { // e 1077 | var i=(mod.channel[ch].data&0xf0)>>4; 1078 | mod.effects_t0_e[i](mod, ch); 1079 | } 1080 | Fasttracker.prototype.effect_t0_f=function(mod, ch) { // f set speed 1081 | if (mod.channel[ch].data > 32) { 1082 | mod.bpm=mod.channel[ch].data; 1083 | } else { 1084 | if (mod.channel[ch].data) mod.speed=mod.channel[ch].data; 1085 | } 1086 | } 1087 | Fasttracker.prototype.effect_t0_g=function(mod, ch) { // g set global volume 1088 | if (mod.channel[ch].data<=0x40) mod.volume=mod.channel[ch].data; 1089 | } 1090 | Fasttracker.prototype.effect_t0_h=function(mod, ch) { // h global volume slide 1091 | if (mod.channel[ch].data) mod.globalvolslide=mod.channel[ch].data; 1092 | } 1093 | Fasttracker.prototype.effect_t0_i=function(mod, ch) { // i 1094 | } 1095 | Fasttracker.prototype.effect_t0_j=function(mod, ch) { // j 1096 | } 1097 | Fasttracker.prototype.effect_t0_k=function(mod, ch) { // k key off 1098 | mod.channel[ch].noteon=0; 1099 | if (!(mod.instrument[mod.channel[ch].instrument].voltype&1)) mod.channel[ch].voicevolume=0; 1100 | } 1101 | Fasttracker.prototype.effect_t0_l=function(mod, ch) { // l set envelope position 1102 | mod.channel[ch].volenvpos=mod.channel[ch].data; 1103 | mod.channel[ch].panenvpos=mod.channel[ch].data; 1104 | } 1105 | Fasttracker.prototype.effect_t0_m=function(mod, ch) { // m 1106 | } 1107 | Fasttracker.prototype.effect_t0_n=function(mod, ch) { // n 1108 | } 1109 | Fasttracker.prototype.effect_t0_o=function(mod, ch) { // o 1110 | } 1111 | Fasttracker.prototype.effect_t0_p=function(mod, ch) { // p panning slide 1112 | } 1113 | Fasttracker.prototype.effect_t0_q=function(mod, ch) { // q 1114 | } 1115 | Fasttracker.prototype.effect_t0_r=function(mod, ch) { // r multi retrig note 1116 | } 1117 | Fasttracker.prototype.effect_t0_s=function(mod, ch) { // s 1118 | } 1119 | Fasttracker.prototype.effect_t0_t=function(mod, ch) { // t tremor 1120 | } 1121 | Fasttracker.prototype.effect_t0_u=function(mod, ch) { // u 1122 | } 1123 | Fasttracker.prototype.effect_t0_v=function(mod, ch) { // v 1124 | } 1125 | Fasttracker.prototype.effect_t0_w=function(mod, ch) { // w 1126 | } 1127 | Fasttracker.prototype.effect_t0_x=function(mod, ch) { // x extra fine porta up/down 1128 | } 1129 | Fasttracker.prototype.effect_t0_y=function(mod, ch) { // y 1130 | } 1131 | Fasttracker.prototype.effect_t0_z=function(mod, ch) { // z 1132 | } 1133 | 1134 | 1135 | 1136 | // 1137 | // tick 0 effect e functions 1138 | // 1139 | Fasttracker.prototype.effect_t0_e0=function(mod, ch) { // e0 filter on/off 1140 | } 1141 | Fasttracker.prototype.effect_t0_e1=function(mod, ch) { // e1 fine slide up 1142 | mod.channel[ch].period-=mod.channel[ch].data&0x0f; 1143 | if (mod.channel[ch].period < 113) mod.channel[ch].period=113; 1144 | } 1145 | Fasttracker.prototype.effect_t0_e2=function(mod, ch) { // e2 fine slide down 1146 | mod.channel[ch].period+=mod.channel[ch].data&0x0f; 1147 | if (mod.channel[ch].period > 856) mod.channel[ch].period=856; 1148 | mod.channel[ch].flags|=1; 1149 | } 1150 | Fasttracker.prototype.effect_t0_e3=function(mod, ch) { // e3 set glissando 1151 | } 1152 | Fasttracker.prototype.effect_t0_e4=function(mod, ch) { // e4 set vibrato waveform 1153 | mod.channel[ch].vibratowave=mod.channel[ch].data&0x07; 1154 | } 1155 | Fasttracker.prototype.effect_t0_e5=function(mod, ch) { // e5 set finetune 1156 | } 1157 | Fasttracker.prototype.effect_t0_e6=function(mod, ch) { // e6 loop pattern 1158 | if (mod.channel[ch].data&0x0f) { 1159 | if (mod.loopcount) { 1160 | mod.loopcount--; 1161 | } else { 1162 | mod.loopcount=mod.channel[ch].data&0x0f; 1163 | } 1164 | if (mod.loopcount) mod.flags|=64; 1165 | } else { 1166 | mod.looprow=mod.row; 1167 | } 1168 | } 1169 | Fasttracker.prototype.effect_t0_e7=function(mod, ch) { // e7 1170 | } 1171 | Fasttracker.prototype.effect_t0_e8=function(mod, ch) { // e8, use for syncing 1172 | mod.syncqueue.unshift(mod.channel[ch].data&0x0f); 1173 | } 1174 | Fasttracker.prototype.effect_t0_e9=function(mod, ch) { // e9 1175 | } 1176 | Fasttracker.prototype.effect_t0_ea=function(mod, ch) { // ea fine volslide up 1177 | mod.channel[ch].voicevolume+=mod.channel[ch].data&0x0f; 1178 | if (mod.channel[ch].voicevolume > 64) mod.channel[ch].voicevolume=64; 1179 | } 1180 | Fasttracker.prototype.effect_t0_eb=function(mod, ch) { // eb fine volslide down 1181 | mod.channel[ch].voicevolume-=mod.channel[ch].data&0x0f; 1182 | if (mod.channel[ch].voicevolume < 0) mod.channel[ch].voicevolume=0; 1183 | } 1184 | Fasttracker.prototype.effect_t0_ec=function(mod, ch) { // ec 1185 | } 1186 | Fasttracker.prototype.effect_t0_ed=function(mod, ch) { // ed delay sample 1187 | if (mod.tick==(mod.channel[ch].data&0x0f)) { 1188 | mod.process_note(mod, mod.patterntable[mod.position], ch); 1189 | } 1190 | } 1191 | Fasttracker.prototype.effect_t0_ee=function(mod, ch) { // ee delay pattern 1192 | mod.patterndelay=mod.channel[ch].data&0x0f; 1193 | mod.patternwait=0; 1194 | } 1195 | Fasttracker.prototype.effect_t0_ef=function(mod, ch) { // ef 1196 | } 1197 | 1198 | 1199 | 1200 | // 1201 | // tick 1+ effect functions 1202 | // 1203 | Fasttracker.prototype.effect_t1_0=function(mod, ch) { // 0 arpeggio 1204 | if (mod.channel[ch].data) { 1205 | var i=mod.channel[ch].instrument; 1206 | var apn=mod.channel[ch].note; 1207 | if ((mod.tick%3)==1) apn+=mod.channel[ch].arpeggio>>4; 1208 | if ((mod.tick%3)==2) apn+=mod.channel[ch].arpeggio&0x0f; 1209 | 1210 | var s=mod.channel[ch].sampleindex; 1211 | mod.channel[ch].voiceperiod=mod.calcperiod(mod, apn+mod.instrument[i].sample[s].relativenote, mod.instrument[i].sample[s].finetune); 1212 | mod.channel[ch].flags|=1; 1213 | } 1214 | } 1215 | Fasttracker.prototype.effect_t1_1=function(mod, ch) { // 1 slide up 1216 | mod.channel[ch].voiceperiod-=mod.channel[ch].slideupspeed; 1217 | if (mod.channel[ch].voiceperiod<1) mod.channel[ch].voiceperiod+=65535; // yeah, this is how it supposedly works in ft2... 1218 | mod.channel[ch].flags|=3; // recalc speed 1219 | } 1220 | Fasttracker.prototype.effect_t1_2=function(mod, ch) { // 2 slide down 1221 | mod.channel[ch].voiceperiod+=mod.channel[ch].slidedownspeed; 1222 | if (mod.channel[ch].voiceperiod>7680) mod.channel[ch].voiceperiod=7680; 1223 | mod.channel[ch].flags|=3; // recalc speed 1224 | } 1225 | Fasttracker.prototype.effect_t1_3=function(mod, ch) { // 3 slide to note 1226 | if (mod.channel[ch].voiceperiod < mod.channel[ch].slideto) { 1227 | mod.channel[ch].voiceperiod+=mod.channel[ch].slidetospeed; 1228 | if (mod.channel[ch].voiceperiod > mod.channel[ch].slideto) 1229 | mod.channel[ch].voiceperiod=mod.channel[ch].slideto; 1230 | } 1231 | if (mod.channel[ch].voiceperiod > mod.channel[ch].slideto) { 1232 | mod.channel[ch].voiceperiod-=mod.channel[ch].slidetospeed; 1233 | if (mod.channel[ch].voiceperiod>4); 1262 | if (mod.channel[ch].voicevolume>64) mod.channel[ch].voicevolume=64; 1263 | } 1264 | if (!(mod.channel[ch].volslide&0xf0)) { 1265 | // x is zero, slide down 1266 | mod.channel[ch].voicevolume-=(mod.channel[ch].volslide&0x0f); 1267 | if (mod.channel[ch].voicevolume<0) mod.channel[ch].voicevolume=0; 1268 | } 1269 | } 1270 | Fasttracker.prototype.effect_t1_b=function(mod, ch) { // b pattern jump 1271 | } 1272 | Fasttracker.prototype.effect_t1_c=function(mod, ch) { // c set volume 1273 | } 1274 | Fasttracker.prototype.effect_t1_d=function(mod, ch) { // d pattern break 1275 | } 1276 | Fasttracker.prototype.effect_t1_e=function(mod, ch) { // e 1277 | var i=(mod.channel[ch].data&0xf0)>>4; 1278 | mod.effects_t1_e[i](mod, ch); 1279 | } 1280 | Fasttracker.prototype.effect_t1_f=function(mod, ch) { // f 1281 | } 1282 | Fasttracker.prototype.effect_t1_g=function(mod, ch) { // g set global volume 1283 | } 1284 | Fasttracker.prototype.effect_t1_h=function(mod, ch) { // h global volume slude 1285 | if (!(mod.globalvolslide&0x0f)) { 1286 | // y is zero, slide up 1287 | mod.volume+=(mod.globalvolslide>>4); 1288 | if (mod.volume>64) mod.volume=64; 1289 | } 1290 | if (!(mod.globalvolslide&0xf0)) { 1291 | // x is zero, slide down 1292 | mod.volume-=(mod.globalvolslide&0x0f); 1293 | if (mod.volume<0) mod.volume=0; 1294 | } 1295 | } 1296 | Fasttracker.prototype.effect_t1_i=function(mod, ch) { // i 1297 | } 1298 | Fasttracker.prototype.effect_t1_j=function(mod, ch) { // j 1299 | } 1300 | Fasttracker.prototype.effect_t1_k=function(mod, ch) { // k key off 1301 | } 1302 | Fasttracker.prototype.effect_t1_l=function(mod, ch) { // l set envelope position 1303 | } 1304 | Fasttracker.prototype.effect_t1_m=function(mod, ch) { // m 1305 | } 1306 | Fasttracker.prototype.effect_t1_n=function(mod, ch) { // n 1307 | } 1308 | Fasttracker.prototype.effect_t1_o=function(mod, ch) { // o 1309 | } 1310 | Fasttracker.prototype.effect_t1_p=function(mod, ch) { // p panning slide 1311 | } 1312 | Fasttracker.prototype.effect_t1_q=function(mod, ch) { // q 1313 | } 1314 | Fasttracker.prototype.effect_t1_r=function(mod, ch) { // r multi retrig note 1315 | } 1316 | Fasttracker.prototype.effect_t1_s=function(mod, ch) { // s 1317 | } 1318 | Fasttracker.prototype.effect_t1_t=function(mod, ch) { // t tremor 1319 | } 1320 | Fasttracker.prototype.effect_t1_u=function(mod, ch) { // u 1321 | } 1322 | Fasttracker.prototype.effect_t1_v=function(mod, ch) { // v 1323 | } 1324 | Fasttracker.prototype.effect_t1_w=function(mod, ch) { // w 1325 | } 1326 | Fasttracker.prototype.effect_t1_x=function(mod, ch) { // x extra fine porta up/down 1327 | } 1328 | Fasttracker.prototype.effect_t1_y=function(mod, ch) { // y 1329 | } 1330 | Fasttracker.prototype.effect_t1_z=function(mod, ch) { // z 1331 | } 1332 | 1333 | 1334 | 1335 | // 1336 | // tick 1+ effect e functions 1337 | // 1338 | Fasttracker.prototype.effect_t1_e0=function(mod, ch) { // e0 1339 | } 1340 | Fasttracker.prototype.effect_t1_e1=function(mod, ch) { // e1 1341 | } 1342 | Fasttracker.prototype.effect_t1_e2=function(mod, ch) { // e2 1343 | } 1344 | Fasttracker.prototype.effect_t1_e3=function(mod, ch) { // e3 1345 | } 1346 | Fasttracker.prototype.effect_t1_e4=function(mod, ch) { // e4 1347 | } 1348 | Fasttracker.prototype.effect_t1_e5=function(mod, ch) { // e5 1349 | } 1350 | Fasttracker.prototype.effect_t1_e6=function(mod, ch) { // e6 1351 | } 1352 | Fasttracker.prototype.effect_t1_e7=function(mod, ch) { // e7 1353 | } 1354 | Fasttracker.prototype.effect_t1_e8=function(mod, ch) { // e8 1355 | } 1356 | Fasttracker.prototype.effect_t1_e9=function(mod, ch) { // e9 retrig sample 1357 | if (mod.tick%(mod.channel[ch].data&0x0f)==0) { 1358 | mod.channel[ch].samplepos=0; 1359 | mod.channel[ch].playdir=1; 1360 | 1361 | mod.channel[ch].trigramp=0.0; 1362 | mod.channel[ch].trigrampfrom=mod.channel[ch].currentsample; 1363 | 1364 | mod.channel[ch].fadeoutpos=65535; 1365 | mod.channel[ch].volenvpos=0; 1366 | mod.channel[ch].panenvpos=0; 1367 | } 1368 | } 1369 | Fasttracker.prototype.effect_t1_ea=function(mod, ch) { // ea 1370 | } 1371 | Fasttracker.prototype.effect_t1_eb=function(mod, ch) { // eb 1372 | } 1373 | Fasttracker.prototype.effect_t1_ec=function(mod, ch) { // ec cut sample 1374 | if (mod.tick==(mod.channel[ch].data&0x0f)) 1375 | mod.channel[ch].voicevolume=0; 1376 | } 1377 | Fasttracker.prototype.effect_t1_ed=function(mod, ch) { // ed delay sample 1378 | mod.effect_t0_ed(mod, ch); 1379 | } 1380 | Fasttracker.prototype.effect_t1_ee=function(mod, ch) { // ee 1381 | } 1382 | Fasttracker.prototype.effect_t1_ef=function(mod, ch) { // ef 1383 | } 1384 | --------------------------------------------------------------------------------