├── .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 | 
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 |
74 |
75 |
76 |
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 |
105 |
106 |
dh0:music_library>
107 |
108 |
109 |
110 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
ram:playlist>
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
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';
106 | for(;i<16;i++) b+=(i';
108 | for(;i<20;i++) b+=(i';
110 |
111 | return b;
112 | }
113 |
114 | function showLoaderInfo(module)
115 | {
116 | window.loaderInterval=setInterval(function(){
117 | if (module.loading) {
118 | $("#modtimer").html(module.state);
119 | } else {
120 | clearInterval(window.loaderInterval);
121 | }
122 | }, 20);
123 | }
124 |
125 | function addToPlaylist(song)
126 | {
127 | var dupe=false;
128 | $("#playlist_box option").each(function(o) {
129 | if ($(this).val() == song) dupe=true;
130 | });
131 | if (!dupe) {
132 | var optclass=($("#playlist_box option").length & 1) ? "odd" : "even";
133 | $("#playlist_box").append(""+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+=''+window.musicLibrary[i].songs[j].file+' '+
179 | '('+window.musicLibrary[i].songs[j].size+' bytes) ';
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+=''+window.musicLibrary[i].songs[j].file+' '+
192 | '('+window.musicLibrary[i].songs[j].size+' bytes) ';
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\n";
388 | }
389 | for(i=0; i<24; i++) pd+="\n";
390 | pd+="";
391 | }
392 | $("#modpattern").html(pd);
393 | $("#modtimer").html("ready.");
394 | };
395 |
396 | module.onPlay=function() {
397 | $("#play").html("[stop]");
398 | if (!this.paused) $("#pause").removeClass("down");
399 | requestAnimationFrame(updateUI);
400 | };
401 |
402 | module.onStop=function() {
403 | $("#modsamples").children().removeClass("activesample");
404 | $("#even-channels").html("");
405 | $("#odd-channels").html("");
406 | $(".currentpattern").removeClass("currentpattern");
407 | $("#modtimer").html("stopped");
408 | $("#play").html("[play]");
409 |
410 | // if in playlist mode, load next song
411 | if (window.playlistActive && module.endofsong) {
412 | var opt=$("#playlist_box option:selected");
413 | if (opt.length) {
414 | var n=$(opt).next("option");
415 | if (n.length) {
416 | // load next track
417 | } else {
418 | // jump to first
419 | n=$("#playlist_box option:first");
420 | }
421 | $("#playlist_box").val($(n).val()).change();
422 | playFromPlaylist(module, true);
423 | }
424 | }
425 | };
426 |
427 | $("#play").click(function(){
428 | if (module.playing) {
429 | module.stop();
430 | $("#pause").removeClass("down");
431 | return false;
432 | }
433 | module.play();
434 | return false;
435 | });
436 |
437 | $("#pause").click(function(){
438 | $("#pause").toggleClass("down");
439 | module.pause();
440 | return false;
441 | });
442 |
443 | $("#go_back").click(function(){
444 | module.jump(-1);
445 | return false;
446 | });
447 |
448 | $("#go_fwd").click(function(){
449 | module.jump(1);
450 | return false;
451 | });
452 |
453 | $("#modrepeat").click(function(){
454 | $("#modrepeat").toggleClass("down");
455 | module.setrepeat($("#modrepeat").hasClass("down"));
456 | if(typeof(Storage) !== "undefined") localStorage.setItem("modrepeat", $("#modrepeat").hasClass("down"));
457 | return false;
458 | });
459 |
460 | $("#modpaula").click(function() {
461 | if ($("#modpaula").hasClass("down")) {
462 | if ($("#modpaula").hasClass("stereo")) {
463 | $("#modpaula").toggleClass("stereo");
464 | $("#modpaula").toggleClass("down");
465 | // mono
466 | $("#modpaula").html("[mono]");
467 | module.setseparation(2);
468 | if(typeof(Storage) !== "undefined") localStorage.setItem("modpaula", 2);
469 | } else {
470 | $("#modpaula").toggleClass("stereo");
471 | // normal stereo
472 | $("#modpaula").html("[))((]");
473 | module.setseparation(0);
474 | if(typeof(Storage) !== "undefined") localStorage.setItem("modpaula", 0);
475 | }
476 | } else {
477 | $("#modpaula").toggleClass("down");
478 | // narrow stereo
479 | $("#modpaula").html("[)oo(]");
480 | module.setseparation(1);
481 | if(typeof(Storage) !== "undefined") localStorage.setItem("modpaula", 1);
482 | }
483 | return false;
484 | });
485 |
486 | $("#modvis").click(function() {
487 | var v=(window.moduleVis+1)%3;
488 | setVisualization(module, v);
489 | if(typeof(Storage) !== "undefined") localStorage.setItem("modvis", v);
490 | return false;
491 | });
492 |
493 | $("#modamiga").click(function() {
494 | $("#modamiga").toggleClass("down");
495 | if ($("#modamiga").hasClass("down")) {
496 | module.setamigamodel("500");
497 | if(typeof(Storage) !== "undefined") localStorage.setItem("modamiga", "500");
498 | } else {
499 | module.setamigamodel("1200");
500 | if(typeof(Storage) !== "undefined") localStorage.setItem("modamiga", "1200");
501 | }
502 | });
503 |
504 | $("#load_song").click(function(){
505 | $("#loadercontainer").show();
506 | $("#innercontainer").hide();
507 | $("#modfile").focus();
508 | var s=document.getElementById("modfile");
509 | var i=s.selectedIndex;
510 | s[i].selected=false;
511 | s[(i<(s.length-12))?(i+12):(s.length-1)].selected=true;
512 | s[i].selected=true;
513 | return false;
514 | });
515 |
516 | $("#loadercontainer").click(function(){
517 | return false;
518 | });
519 |
520 | $("#load").click(function(){
521 | if (module.playing) {
522 | module.stop();
523 | module.setautostart(true);
524 | } else {
525 | module.setautostart(false);
526 | }
527 | oldpos=-1;
528 | $("#loadercontainer").hide();
529 | $("#innercontainer").show();
530 | var loadInterval=setInterval(function(){
531 | if (!module.delayload) {
532 | window.currentModule=$("#modfile").val();
533 | window.playlistActive=false;
534 | module.load(musicPath+$("#modfile").val());
535 | clearInterval(loadInterval);
536 | showLoaderInfo(module);
537 | }
538 | }, 200);
539 | return false;
540 | });
541 |
542 | $("#load_cancel").click(function(){
543 | $("#loadercontainer").hide();
544 | $("#innercontainer").show();
545 | return false;
546 | });
547 |
548 | $("#add_playlist").click(function(){
549 | var song=$("#modfile").val();
550 | if (addToPlaylist(song)) refreshStoredPlaylist();
551 | return false;
552 | });
553 |
554 | $("#modfile").keypress(function(event) {
555 | if (event.keyCode==13) $("#load").click();
556 | });
557 |
558 | $("#playlist_remove").click(function(){
559 | var opt=$("#playlist_box option:selected");
560 | if (opt.length) {
561 | var song=opt.val();
562 | opt.remove();
563 | refreshStoredPlaylist();
564 | }
565 | return false;
566 | });
567 |
568 | $("#playlist_clear").click(function(){
569 | $("#playlist_box").html("");
570 | refreshStoredPlaylist();
571 | return false;
572 | });
573 |
574 | $("#playlist_jumpto").click(function(){
575 | var opt=$("#playlist_box option:selected");
576 | if (opt.length) {
577 | if (module.playing) module.stop();
578 | oldpos=-1;
579 | module.setautostart(true);
580 | $("#loadercontainer").hide();
581 | $("#innercontainer").show();
582 | var loadInterval=setInterval(function(){
583 | if (!module.delayload) {
584 | window.currentModule=$("#playlist_box option:selected").val();
585 | window.playlistPosition=$("#playlist_box option").index($("#playlist_box option:selected"));
586 | window.playlistActive=true;
587 | module.load(musicPath+$("#playlist_box option:selected").val());
588 | clearInterval(loadInterval);
589 | }
590 | }, 200);
591 | }
592 | return false;
593 | });
594 |
595 | $("#playlist_box option").dblclick(function() {
596 | $("#playlist_jumpto").click();
597 | });
598 |
599 | $("#playlist_up").click(function(){
600 | var opt=$("#playlist_box option:selected");
601 | if (opt.length) {
602 | var p=$(opt).prev("option");
603 | if (p.length) {
604 | var v=$(p).val();
605 | var t=$(p).html();
606 | $(p).html($(opt).html());
607 | $(p).val($(opt).val());
608 | $(opt).html(t);
609 | $(opt).val(v);
610 | $("#playlist_box").val($(p).val()).change();
611 | }
612 | refreshStoredPlaylist();
613 | }
614 | return false;
615 | });
616 |
617 | $("#playlist_dn").click(function(){
618 | var opt=$("#playlist_box option:selected");
619 | if (opt.length) {
620 | var n=$(opt).next("option");
621 | if (n.length) {
622 | var v=$(n).val();
623 | var t=$(n).html();
624 | $(n).html($(opt).html());
625 | $(n).val($(opt).val());
626 | $(opt).html(t);
627 | $(opt).val(v);
628 | $("#playlist_box").val($(n).val()).change();
629 | }
630 | refreshStoredPlaylist();
631 | }
632 | return false;
633 | });
634 |
635 | $("#next_track").click(function(){
636 | var opt=$("#playlist_box option:selected");
637 | if (opt.length) {
638 | var n=$(opt).next("option");
639 | if (n.length) {
640 | // load next track
641 | } else {
642 | // jump to first
643 | n=$("#playlist_box option:first");
644 | }
645 | $("#playlist_box").val($(n).val()).change();
646 | playFromPlaylist(module, module.playing);
647 | }
648 | return false;
649 | });
650 |
651 | $("#prev_track").click(function(){
652 | var opt=$("#playlist_box option:selected");
653 | if (opt.length) {
654 | var p=$(opt).prev("option");
655 | if (p.length) {
656 | // load previous track
657 | } else {
658 | // jump to last
659 | p=$("#playlist_box option:last");
660 | }
661 | $("#playlist_box").val($(p).val()).change();
662 | playFromPlaylist(module, module.playing);
663 | }
664 | return false;
665 | });
666 |
667 | $("#loadfilter").on("input", updateSelectBox);
668 |
669 | $(document).keyup(function(ev){
670 | // keyboard shortcuts for main player screen
671 | if ($("#innercontainer").is(":visible")) {
672 | if (ev.keyCode==32) { // start/pause playback with space
673 | if (module.playing) {
674 | $("#pause").click();
675 | } else {
676 | $("#play").click();
677 | }
678 | event.preventDefault(); return false;
679 | }
680 | if (ev.keyCode==76) { // 'L' to open loading screen
681 | $("#load_song").click();
682 | event.preventDefault(); return false;
683 | }
684 | if (ev.keyCode==37) { // left to jump to previous order
685 | $("#go_back").click();
686 | event.preventDefault(); return false;
687 | }
688 | if (ev.keyCode==39) { // right to jump to next order
689 | $("#go_fwd").click();
690 | event.preventDefault(); return false;
691 | }
692 | }
693 |
694 | // keyboard shortcuts for load/playlist screen
695 | if ($("#loadercontainer").is(":visible")) {
696 | if (ev.keyCode==27) {
697 | $("#load_cancel").click();
698 | event.preventDefault(); return false;
699 | }
700 | }
701 | });
702 |
703 | // all done, load the song library and default module
704 | var request = new XMLHttpRequest();
705 | request.open("GET", "/music_library.json", true);
706 | request.responseType = "json";
707 | request.onload = function() {
708 | window.musicLibrary=eval(request.response);
709 | updateSelectBox(null);
710 |
711 | if (window.defaultComposer != "") {
712 | $("#loadfilter").val(window.defaultComposer);
713 | updateSelectBox(null);
714 | $("#modfile optgroup[label='"+window.defaultComposer+"'] > 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 |
--------------------------------------------------------------------------------