// https://github.com/horsicq/Detect-It-Easy signature file
// Authors:
// Kaens TG@kaens (for all the https://en.wikipedia.org/wiki/Music_tracker stuff)
// hypn0 <hypn0@mail.ru>

// Due to the extreme script file size, many good code practices were sacrificed to ensure it loads
/* beautify ignore:start */

init("audio", "");
includeScript("read");
debug = 0; //verbosity up to 3, where every note/command is shown

function detect(bShowType,bShowVersion,bShowOptions) {

	if(X.c("'[1tracker module]'0D0A")) {
		sName = "Shiru's 1tracker module (.1TM)"; bDetected = 1;
		l = X.fStr(1,0x40,"Engine="); if(l >= 0) {
			r = X.fStr(l+7,0x40,".");
			sVersion = "for "+X.SA(l+7,r-l-7)
		}
		if(X.isVerbose()) {
			l = X.fStr(r,0x80,"Title="); if(l >= 0) {
				r = X.fSig(l+6,0x40,"0D0A");
				sOption(X.SA(l+6,r-l-6)) }
			l = X.fStr(r,0x80,"Author="); if(l >= 0) {
				r = X.fSig(l+7,0x40,"0D0A");
				sOption(X.SA(l+7,r-l-7),"by: ") }
			l = X.fStr(r,0x80,"Speed="); if(l >=0 ) {
				r = X.fSig(l+7,0x40,"0D0A");
				sOption(X.SA(l+7,r-l-7),"spd:") }
		}
	}
	else if(X.c("'_A2module_'") && isWithin(nV = X.U8(0xE),1,14)) {
		bDetected = 1; bad = false;
		sName = "AdLib Tracker II module (.A2M)";
		sVersion = "/┤DLiB TR/┤CK3R ][ v"+nV;
		ptn = X.U8(0xF); if(!ptn || ptn>64) bad = "!badptn";
		if(bad) sVersion += "/malformed"+bad;
		else if(X.isVerbose()) sOption('ptn:'+ptn); //TODO extract more, ver.-dependent
	}
	else if(X.c("'_A2tiny_module_'") && isWithin(nV = X.U8(0x13),1,14)) {
		sName = "AdLib Tracker II module (.A2T)"; bDetected = 1; bad = false;
		sVersion = "/┤DLiB TR/┤CK3R ][ v"+nV+' tiny';
		ptn = X.U8(0x14); if(!ptn || ptn>64) bad = "!badptn";
		tmp = X.U8(0x15); spd = X.U8(0x16); rows = X.U16(0x18); trk = X.U8(0x1A);
		if(bad) sVersion += "/malformed"+bad;
		else if(X.isVerbose()) sOption('tempo:'+tmp+' spd:'+tmp+' trk:'+trk+' ptn:'+ptn); //TODO extract more
	}
	else if(X.c("'EXITgB'2018201820182F18201024C94AA80004660A558006A80000000200042200D28924C1D0A8000424C0D1DF2018D1D82018D1D82018201812D804800000000166F660B4610000'H|'017E01610000'Z|'027E02610000'R|'037E03610000'JL'DF7FFF'NuH'E7FFFE7E01610000AC7E02610000A67E03610000A04CDF7FFF'Nu`'0A602260000092600000'~A'FA....4BF900DFF000'p0;|'000000AA0440001064F4'NuK'F900DFF0003007C0FC",0x1A) //check D0h bytes
		) {
		bDetected = 1; sName = "Art and Magic module (.AAM)";
		if(X.isVerbose()) {
			t = X.fSig(0x8000,Math.min(0xFFFF,X.Sz()),"'EXIT'");
			if(t >= 0) sOption(outSz(t+4),"sz:");
			else sVersion = "malformed!short"
		}
	}
	else if(X.c("'ADLIB'01")) {
		sName = "Martin Fernandez's Adlib module (.ADLIB)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SA(6,0x100);
			g = X.SA(6+t.length+1,0x100);
			sOption(t); sOption(g,"for: ")
		}
	}
	else if(X.c("'AERO'")) {
		sName = "Aero Tracker module (.AERO)"; bDetected = 1;
		sVersion = "v"+X.U8(0x0E);
	}
	else if(X.fSig(20,0x100,"'<aks:song xmlns:aks=\"http://www.julien-nevo.com/ArkosTrackerSong\"") > 0) {
		sName = 'Arkos Tracker 2 module (.AKS)'; sVersion = 'unpacked'; bDetected = 1;
	}
	else if(X.c("'AM01'000000") && X.c("'ASD1'00", 0x38)) {
		sName = "Ace Tracker module (.AM)"; bDetected = 1;
	}
	else if(X.c("'ZXAYAMAD'") && X.U8(9) <= 3) {
		//the underlying format is the same as FXM
		//ref https://vgmrips.net/wiki/AY_File_Format
		bDetected = true;
		authp = 0x0C+X.I16(0x0C,_BE); if(authp < X.Sz()) auth = authp?X.SA(authp,0x100):''; else bDetected = false;
		miscp = 0x0E+X.I16(0x0E,_BE); if(miscp < X.Sz()) misc = miscp?X.SA(miscp,0x100):''; else bDetected = false;
		x = X.U8(0x10)+1; p = 0x12+X.I16(0x12,_BE); if(p > X.Sz()) bDetected = false;
		titles = []; for(i=0; i < x; i++,p+=4) if(t = p+X.I16(p,_BE))
			if(t < X.Sz()) titles.push(X.SA(t,0x100).trim()); else { bDetected = false; break }
		if(bDetected) {
			sName = "Fuxoft's AY Amadeus module (.AMAD)";
			sVersion = [X.U8(8)?'Rel '+X.U8(8):'', X.U8(9)?'Plr '+X.U8(9):''].join(' ').trim();
		 	if(X.isVerbose()) {
				sOptionT(titles.join(',')); if(x > 1) sOption(x,'×');
				if(authp) sOptionT(auth,'by: '); if(miscp) sOptionT(misc);
			}
		}
	}
	if(!bDetected)
	if(X.c("'AMC V'......' REPLAY!'")) {
		sName = "A.M. Composer module (.AMC)"; bDetected = 1;
		sVersion = "v"+X.SA(5,3);
	}
	else if(X.c("'<o'EF'QU'EE'RoR'", 0x426) || X.c("'MaDoKaN96'", 0x426)) {
		//ref http://modland.com/pub/documents/format_documentation/AMusic%20(.amd).txt
		sName = "AMusic Adlib-MOD module (.AMD)"; bDetected = 1;
		if(X.SA(0x426,1) != "<") sVersion = "xms rip-off";
		if(X.U8(0x42F) == 0x11) sVersion = sVersion.append("packed");
		if(X.isVerbose()) {
			sOptionT(X.SA(0,0x18));
			sOptionT(X.SA(0x18,0x18),"by: ");
			sOption("ord:"+X.U8(0x3A4)+" ptn:"+(X.U8(0x3A5)+1));
		}
	}
	else if(X.c("'ASYLUM Music Format V1.0'00000000 00000000") && X.U8(0x22) <= 0x40
	  && X.Sz() >= 0xA66+0x800*X.U8(0x23)) {
		sName = "ASYLUM Music Format module (.AMF)"; bDetected = 1;
		sVersion = "v"+X.SA(0x15,3);
		if(X.isVerbose()) {
			spd0 = X.U8(0x20); bpm0 = X.U8(0x21);
			smp = X.U8(0x22); ptn = X.U8(0x23); ord = X.U8(0x24);
			p = 0x126;
			ssz = 0; smps = [];
			for(i=0; i < smp; i++) {
				smpn = readBytes(0x126+0x25*i,0x16,true);
				smpn = decEncoding(smpn,CP437,false,Chars0to1F).trim();
				if(smpn != "") smps.push(smpn);
				slen = X.U32(0x126+0x25*i+0x19); ssz += slen;
			}
			sz = 0xA66+0x800*ptn+ssz;
			sOption(addEllipsis(smps.join(" "),0x100,0xA0), 'smp/msg:"','"')
			sOption("spd0:"+spd0+" bpm0:"+bpm0+" ord:"+ord+" ptn:"+ptn+" smp:"+smp+" smpsz:"+Hex(ssz)+" sz:"+outSz(sz))
		}
	}
	else if(X.c("'AMF'") && (X.U8(3) == 1 ||
	  (X.U8(3) >= 8 && X.U8(3) <= 0x0E)) && X.U8(4) && X.U8(5)
	  && X.U16(6) && (X.U8(3) < 9 || /[\x01-\x20]/.test(X.SA(8,1)))) {
		sName = "Digital Sound and Music Interface Advanced Music Format module (.AMF)";
		sVersion = "v"+Hex(X.U8(3)); bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(4,32));
	}
	else if(X.c("'Extreme0'01") && X.U8(0xF) <= 0x20) {
		//ref http://modland.com/pub/documents/format_documentation/Extreme's%20Tracker%20v1.3%20(.ams).txt
		sName = "Extreme's Tracker Advanced Module System track (.AMS)"; bDetected = 1;
		//song tech info
		if(X.isVerbose()) {
			cmd = X.U8(9)>>5; sch = (X.U8(9)&0x1F)+1;
			smp = X.U8(10); ptn = X.U16(11); ord = X.U16(13);
			vmch = X.U8(15); xtra = X.U16(16);
			p = xtra + 18; S = []; allsmpsz = 0;
			for(i=0; i < smp; i++,p+=17) {
				ssz = X.U32(p); allsmpsz += ssz;
				co = X.U8(p+16)&3; b16 = (X.U8(p+16)&0x80)?2:1; S.push([co,ssz,b16]);
			}
			sOptionT(X.SC(p+1,X.U8(p),'CP437')); p += 1+X.U8(p); //skip title
			smps = []; for(i=0; i < smp; i++) { //store smp names
				smps.push(X.SC(p+1,X.U8(p),'CP437')); p += 1+X.U8(p) }
			schs = []; for(i=0; i < sch; i++) { //store "channel" names
				schs.push(X.SC(p+1,X.U8(p),'CP437')); p += 1+X.U8(p) }
			for(i=0; i < ptn; i++) p += 1+X.U8(p); //skip ptn names
			msg = readBytes(p+2,X.U16(p),true/*\0 is a space*/); var msg_ = []; p += 2+X.U16(p);
			for(i=0; i < msg.length; i++) { //RLE
				if(msg[i] < 0x80) msg_.push(msg[i]);
				else if(msg[i] <= 0xA0) /*for(j=0; j < msg[i]-0x80; j++)*/ msg_.push(0x20); //too long
				else msg_.push(0x0A); //this RLE uses too_many_spaces as a line break; let's maybe not
			}
			msg = decEncoding(msg_,'CP437'); delete msg_;
			mptn = -1;
			for(i=0; i < ord; i++) { t = X.U16(p+2*i); if(t > mptn) mptn = t }
			mptn++; p += 2*ord;
			for(i=0; i < ptn; i++) p += 4+X.U32(p);
			allsmpcsz = 0;
			for(i=0; i < smp; i++) if(!S[i][0]) p += 8+S[i][1]*S[i][2]; //non-compressed
			else if(S[i][1]) { //compressed & smp size > 0
				p += 4; scosz = X.U32(p); cc = X.U8(p+4); p += 5+scosz; allsmpcsz += scosz
			}
			delete S;
			sOption(addEllipsis(schs.join(' '),0xA0,0x80),'chns:"','"');
			sOption(addEllipsis(msg.trim(),0x100,0x80),'msg:"','"');
			sOption('ord:'+ord+' ptn:'+ptn+(mptn!=ptn?'/'+mptn:'')+' smp:'+smp+' cmd:'+cmd
				+' strk:'+sch+(vmch?' mtrk:'+vmch:'')+' co.smpsz:'+(allsmpcsz*100/allsmpsz).toFixed(1)+'% sz:'+outSz(p))
		}
	}
	else if(X.c("'AMShdr'1A") && X.U8(7) <= 30 && X.U8(9+X.U8(7)) == 2 && X.U8(8+X.U8(7)) <= 2) {
		//ref http://modland.com/pub/documents/format_documentation/Velvet%20Studio%20v2.2,%20Advanced%20Module%20System%20(.ams).txt
		bDetected = 1; p = 8+X.U8(7); nv = X.U8(p);
		sName = "Velvet Studio Advanced Module System track (.AMS)";
		sVersion = "v"+X.U8(p+1)+'.'+nv;
		bad = '';
		ptn = X.U16(p+3); if(ptn > 1024) bad = bad.addIfNone('!badptn');
		ord = X.U16(p+5);
		if(X.isVerbose()) {
			title = X.SC(8,p-8,'CP437');
			ins = X.U8(p+2);
			p += 7; if(X.Sz() < 36+11+ins*2+ord*2+ptn*4) bad = bad.addIfNone('!short');
			if(nv == 2) {
				bpm0 = Math.max(32<<8, X.U16(p)); p += 2; bpm0 = (bpm0>>8)+'.'+(bpm0&0xFF)
				spd0 = Math.max(1,X.U8(p++)); p += 3; flg = X.U16(p); p += 2
			} else {
				bpm0 = Math.max(32,X.U8(p++)); spd0 = Math.max(1,X.U8(p++)); flg = X.U8(p++)
			}
			ch = (flg >> 6)&1?'2':'1';
			linfreqtbl = (flg & 0x40) ? " lnr.freq.tbl.":"";
			midiused = (flg & 0x80) ? " MIDI used":"";
			sOptionT(title);
			inss = []; smps = []; S = []; allsmpsz = allsmpcsz = smp = shd = 0;
			for(i = 0; i < ins; i++) {
				t_ = X.U8(p++); if(t_ > 30) bad = bad.addIfNone('!badins');
				t = X.SC(p,t_,'CP437').trim(); p += t_; if(t != "") inss.push(t);
				inssmp = X.U8(p++);
				if(inssmp) {
					if(nv == 0) p += 100; else p += 124;
					t = X.U8(p++); if(t > 63) bad = bad.addIfNone('!badenv');
					p += t*3+4; t = X.U8(p++); if(t > 63) bad = bad.addIfNone('!badpan');
					p += t*3+4; t = X.U8(p++); if(t > 63) bad = bad.addIfNone('!badvibenv');
					p += t*3; t = X.U8(p);
					shdins = t > 0; if(shdins) shd++; p += 5;
					for(s = 0; s < inssmp; s++) {
						t_ = X.U8(p++); if(t_ > 22) bad = bad.addIfNone('!badsmp');
						t = X.SC(p,t_,'CP437').trim(); p += t_; //if(t != "") smps.push(t);
						ssz = X.U32(p); p += 4; if(!shdins) smp++;
						if(ssz) {
							if(!shdins) { allsmpsz += ssz; sfl = X.U8(p+15);
								co = sfl&3; b16 = (sfl&4)?2:1; S.push([co,ssz,b16]) }
							p += 16
						}
					}
				}
			}
			t_ = X.U8(p++); sOptionT(X.SC(p,t_,'CP437'),'by:'); p += t_; 
			schs = []; for(i=0; i < 32; i++) { //store "channel" names
				schs.push(X.SC(p+1,X.U8(p),'CP437')); p += 1+X.U8(p) }
			// read the description
			msg = readBytes(p+11,X.U32(p)-11); msg_ = []; co = X.U8(p+10); p += X.U32(p);
			if(co) { for(i=0; i < msg.length;) { //RLE
					c = msg[i++];
					if(c == 0xFF && msg.length-i >= 2) {
						c = msg[i++]; n = msg[i++];
						if(c != 0x20) for(j=0; j < n; j++) msg_.push(c);
						else if(n <= 0x20) msg_.push(0x20); //not too many spaces in one
						else msg_.push(0x0A); //too many spaces.
					}
					else msg_.push(c);
				}
				msg = decEncoding(msg_,'CP437');
			} else msg = decEncoding(msg,'CP437');
			delete msg_;
			p += ord<<1; //skip the orderlist
			for(i=0; i < ptn; i++) p += 4+X.U32(p); //skip the patterns
			if(p > X.Sz()) bad = bad.addIfNone('!short');
			for(i=0; i < smp; i++)
				if(!S[i][0]) {
			 		p += S[i][1]*S[i][2]; //non-compressed
				} else if(S[i][1]) { //compressed & smp size > 0
					scosz = X.U32(p+4); cc = X.U8(p+9);
					p += 9+scosz; allsmpcsz += scosz
				}
			sz = p;
			sOption(addEllipsis(schs.join(' '),0xA0,0x80),'chns:"','"');
			sOption(addEllipsis(inss.join(' '),0xA0,0x80),'insts/msg:"','"');
			//sOption(addEllipsis(smps.join(' '),0xA0,0x80),'smps/msg:"','"');
			sOption(addEllipsis(msg.trim(),0xA0),'msg:"','"');
			sOption('ch:'+ch+' ins:'+ins+(shd?'+'+shd+'sh':'')+' ord:'+ord+' ptn:'+ptn+
				' bpm0:'+bpm0+' spd0:'+spd0+linfreqtbl+midiused+
				' co.smpsz:'+(allsmpcsz*100/allsmpsz).toFixed(1)+'% sz:'+outSz(sz))
		}
		if(bad) sVersion = sVersion.appendS('malformed'+bad,'/');
	}
	else if(X.c("'AMX ")) {
		sName = "AND Xynth module (.AMX)"; bDetected = 1
	}
	else if(X.c("'AON4'") || X.c("'AON8'")) {
		sName = "ArtOfNoise/Chorus module (.AON)"; bDetected = 1;
		sVersion = X.SA(3,1)+"ch";
		if(X.isVerbose()) {
			id = X.SA(4,0x2A);
			p = 0x2E; //header skipped
			t = ''; a = ''; d = ''; c = ''; ord = 0;
			while (p < X.Sz()) {
				hkhd = X.SA(p,4);
				hksz = X.U32(p+4,_BE);
				p+=0x08; //chunk header = 4xfourcc + 4xsize
				switch (hkhd) {
					case "NAME": t = X.SC(p,hksz,'CP1252'); break;
					case "AUTH": a = X.SC(p,hksz,'CP1252'); break;
					case "DATE": d = X.SC(p,hksz,'CP1252'); break;
					case "RMRK": c = X.SC(p,hksz,'CP1252'); break; //apparently intended to be 78ch per line, maybe TODO auto-wrap (without left trimming!)
					case "PLST": ord = hksz; break;
				}
				p += hksz;
			}
			sOptionT(t); sOptionT(a,"by: "); sOptionT(d,"on: ");
			sOption(c); sOption(id,"with: "); sOption(ord,"ord:");
		}
	}
	else if(X.c("'ADRVPACK'")) {
		sName = "AProSys module (.APS)"; bDetected = 1
	}
	else if(X.c("'ARP.'")) {
		sName = "Arpeggiator module (.ARP)"; bDetected = 1
	}
	else if(X.c("'ACTIONAMICS SOUND TOOL'",0x3E)) {
		sName = "Actionamics Sound Tool module (.AST)"; bDetected = 1;
		sVersion = "v"+X.SA(0x56,3);
	}
	else if(X.c("08'AST '")) {
		sName = "All Sound Tracker module (.AST)"; bDetected = 1;
		sVersion = "v" + X.SA(5,4);
		if(X.isVerbose()) {
			L = X.U16(0x0A,_BE);
			info = X.SC(12,L+1,'CP850').trim();
			var s = ""; var q=0;
			for (var i = 0; q<2; i+=0x26) {
				a = info.slice(i,i+0x26);
				if(a.trim() != "") {q++; s += " "+a.trim();} //skipping the empty lines
			}
			sOption(s);
		}
	}
	else if(X.c("'AudioSculpture10'00180018")) {
		sName = "Synchron Assembly's Audio Sculpture module (.AS)"; bDetected = 1
	}
	else if(X.c("'##synth'0D0A")) {
		sName = "Athtune module (.ATHTUNE)"; bDetected = 1
	}
	else if(X.c("'FORM'.... ....'AXSFUSER'") && X.c("'SHDR'", t=X.U32(0x10,_BE)+0x14)
	  && X.c("'SONG'",X.U32(t+4,_BE)+t+8)) {
	  	//ref https://web.archive.org/web/20141129231503/http://www.resolutionaudio.nl/
		sName = "Resolution's AXS module (.AXS)"; bDetected = 1;
		if(X.isVerbose())
			sOption(outSz(X.U32(4,_BE)+8),'sz:')
	}
	else if(X.c("'CBMF'")) {
		sName = "Bob's Adlib Music module (.BAM)"; bDetected = 1;
		if(X.isVerbose() && X.isDeepScan()) {
			//ref https://github.com/adplug/adplug/blob/master/src/bam.cpp
			p = 4; notes = ins = 0; songend = false;
			while(!songend && p < X.Sz()) {
				while(!songend && X.U8(p) < 0x80) {
					cmd = X.U8(p) >> 4;
					switch(cmd) {
					case 0: songend = true; p++; break;
					case 1: p += 2; notes++; break; //start note
					case 2: p++; break; //stop note
					case 3: p += 12; ins++; break; //define ins
					case 5: p++; break; //set label
					case 6: p += 2; break; //jump
					case 7: //end of chorus
					default: p++; break;
					}
				}
				if(X.U8(p) >= 0x80) p++;
			}
			if(!songend || !ins || notes < 0x10) bDetected = 0; else sOption('insdef:'+ins+' notes:'+notes+' sz:'+outSz(p))
		}
	}
	if(!bDetected)
	if(X.c("'BBSONG'00'0001'00")) {
		//ref https://github.com/atsidaev/beepola/blob/master/STSong.pas etc.
		sName = "Shiru's Beepola module (.BBSONG)"; bDetected = 1;
		p = 0xC; var layouttag = false;
		ord = orn = ptn = svgptn = svgwarp = phains = 0; bad = title = auth = engine = '';
		while(p < X.Sz()) {
			if(p >= X.Sz()) break;
			if(X.U8(p++) != 0x3A) { p--; break }
			t = X.SA(p,254); p += t.length+1;
//_log(t+' @'+Hex(p));
			switch(t) {
			case 'INFO':
				for(; p < X.Sz();) {
					t = X.SC(p,TOEOF,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'Title': title = xx[1]; break;
					case 'Author': auth = xx[1]; break;
					case 'Engine': engine = xx[1]; break;
					}
				}
				break;
			case 'LAYOUT':
				layouttag = true;
				while(p < X.Sz()) {
					t = X.SC(p,TOEOF,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'LoopStart': lp = +xx[1]; break;
					case 'Length': ord = +xx[1]; ptn = 0;
						for(i=0; i < ord; i++) if(ptn <= (t=X.U8(p++))) ptn = t+1; break;
					}
				}
				break;
			case 'PATTERNDATA':
				while(p < X.Sz()) {
					t = X.SC(p,0x400,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'PatternCount': ptns = +xx[1];
						for(i = 0; i < ptns && p < X.Sz(); i++) {
							t = X.SC(p,0x500,'CP1251'); p += t.length + 1; if(t === ':END') break;
							xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
							if(xx[0] === 'PatternName') p += 8+5*X.U32(p);
						}
						break;
					}
				}
				break;
			case 'SVGPATTERNDATA':
				while(p < X.Sz()) {
					t = X.SC(p,0x400,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'PatternCount': svgptn = +xx[1];
						for(i = 0; i < svgptn && p < X.Sz(); i++) p += 4+16*X.U32(p);
						break;
					}
				}
				break;
			case 'SVGWARPDATA':
				while(p < X.Sz()) {
					t = X.SC(p,0x400,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'PatternCount': svgwarp = +xx[1];
						for(i = 0; i < svgwarp && p < X.Sz(); i++) p += 4+2*X.U32(p);
						break;
					}
				}
				break;
			case 'P1INSTR':
				while(p < X.Sz()) {
					t = X.SC(p,0x400,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'Length': phains = +xx[1]; p += phains; break;
					}
				}
				break;
			case 'SVGORNAMENTS':
				while(p < X.Sz()) {
					t = X.SC(p,0x400,'CP1251'); p += t.length + 1; if(t === ':END') break;
					xx = t.split('='); if(xx.length > 2) xx = [xx[0], xx.slice(1,xx.length).join('=')];
					switch(xx[0]) {
					case 'OrnamentCount': orn = +xx[1];
						for(i = 0; i < orn && p < X.Sz(); i++) p += 4+X.U32(p);
						break;
					}
				}
				break;
			default: bad = bad.addIfNone('!badchunk')
			}
//_log('   ...ends @'+Hex(p));
		}
		if(!layouttag) bad = bad.addIfNone('!corrupt');
		sVersion = 'engine:'+engine; if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
		if(X.isVerbose()) {
			sOptionT(title); sOptionT(auth,"by: ");
			sOption('ord:'+ord+(lp?' lp:'+lp:'')+' ptn:'+ptn+(svgptn?'+svg':'')
				+(svgwarp?' warp':'')+(orn?' orn':'')+(phains?' phasers':'')+' sz:'+outSz(p))
		}
	}
	else if(X.c("000003F3") && X.U8(0x14)
	  && X.c("70FF4E75'DAGLISH!'",0x20)
	  && X.U32(0x2C,_BE) && X.U32(0x30,_BE)
	  && X.U32(0x34,_BE) && X.U32(0x38,_BE)) {
		bDetected = 1;
		sName = "Ben Daglish's SID (.BDS)"; sVersion = "v1.1";
		if(X.isVerbose()) {
			title = X.SA(0x20+X.U32(0x3C,_BE),0x100);
			auth = X.SA(0x20+X.U32(0x40,_BE),0x100);
			misc = X.SA(0x20+X.U32(0x44,_BE),0x100);
			sOptionT(title);
			x = X.U32(0x38,_BE); if(x > 1) sOption(x,"×");
			sOptionT(auth,"by: ");
			sOptionT(misc);
		}
	}
	else if(/BMF1\.[012]/.test(X.SA(0,6)) && X.Sz() > 8 && X.fSig(6,42,"00") > 6) {
		//ref https://github.com/adplug/adplug/blob/master/src/bmf.cpp
		sName = "The Brain's Easy Adlib module (.BMF)"; sV = X.SA(5,1);
		sVersion = "v1."+sV; bDetected = 1;
		if(X.isVerbose() && sV > '0') {
			p = 6; bad = title = author = "";
			f = X.fSig(p,-1,"00");
			title = X.SA(p,f-p); p += title.length+1; //read title
			f = X.fSig(p,-1,"00"); if(f > 0) {
			author = X.SA(p,f-p); p += author.length+1; //read author
			spd = X.U8(p++);
			if(X.isDeepScan()) {
				fl = X.U32(p); p += 4; ins = 0;
				for(i = 0; i < 32; i++) {
					if(fl&1) { p += 24; if(p > X.Sz()) bad = bad.addIfNone('!short') } fl >>= 1; ins++
				}
				fl = X.U32(p); p += 4; const last = 1023;
				trk = 0; trkend = false;
				for(i = 0; i < 32; i++) {
					if(fl&1) { trk++; trkend = false;
						for(q = 0; p < X.Sz() && !trkend && q <= last; q < last && q++) {
							t = X.U8(p++);
							switch(t) {
							case 0xFE: trkend = true; break; //end of reader
							case 0xFC: case 0x7D: break;  //set marker, loop to marker
							default:
								evnote = t & 0x7F; if(!(t & 0x80)) break;
								t = X.U8(p);
								if(t & 0x80) {
									evdly = t & 0x3F; p++; if(!(t & 0x40)) break;
								}
								t = X.U8(p++);
								if(0x40 <= t) { evvol = t+1-0x40; }
								else if(0x20 <= t && t <= 0x3F) { evins = t+1-0x20; }
								else {
									if(sV == '2') if(1 <= t && t <= 6) p++
								}
							}
						}
					}
					fl >>= 1
				}
				if(!trkend) { bad = bad.addIfNone('!short'); p = -1 }
			}//if deepscan
			}//if author is null-terminated
			else bad = bad.addIfNone('!badtags');
			if(bad != "") sVersion = sVersion.appendS('malformed'+bad,'/');
			sOptionT(title); sOptionT(author,'by: ');
			sOption('spd:'+spd+(X.isDeepScan()?' trk:'+trk+' ins:'+ins+' sz:'+outSz(p):''));
		}
	}
	else if(X.Sz() > 0x180 && X.c("'Buzz'") && isWithin(X.U32(4),1,0xC) && X.c("7C010000",0xC) && X.fSig(8,0x40,"'MACH'") > 0) {
		sName = "Jeskola Buzz module (.BMX)";  bDetected = 1;
		// Do NOT waste time trying to extract anything else...
		// You'll find the format dox lie and formats differ
		// For example, the no. of machines overlaps with some beta's version string
		// For example, that beta has no float x/y coordinates
		// And there is no signature difference.
	}
	else if(X.Sz() >= 0x6E5C && X.c("0100'ADLIB'9D02A0021C000000") && X.adler32(0,0x6E5C) == 0x1DF2E9B9) {
		sName = 'Instrument bank for Adlib Visual Composer (STANDARD.BNK)'; bDetected = 1;
		if(X.isVerbose()) sOption(outSz(0x6E5C),'sz:')
	}
	else if(X.c("'BRTF'")) {
		sName = "BeRoTracker module (.BRT)"; bDetected = 1;
		if(X.isVerbose()) {
			p = 8; t = ''; c = ''; q = ''; instmsg = []; sainmsg = [];
			ins = 0; smp = 0; ord = 0; //TODO figure out how to get ptn
			bpm0 = 0; spd0 = 0; st0 = 0; mvol = 0; rowsperbeat = 0; hltu = 0; hltd = 0;
			while (p < X.Sz()) {
				hkhd = X.SA(p,4);
				hksz = X.U32(p+4,_LE);
				p += 8;
				switch (hkhd) {
					case "NAME": t = X.UCSD(p); break;
					case "MESS": c = X.SC(p+2,X.U32(p,_LE),'CP1252'); break;
					case "BPMI": rpb = X.U8(p); break;
					case "INFO":
						spd0 = X.U8(p+4); bpm0 = X.U8(p+5);
						st0 = X.U8(p+6); mvol = X.U8(p+10);
						break;
					case "PORD": ord = X.U8(p); break;
					case "PAIN": hltu = X.U8(p+1); hltd = X.U8(p+2); break;
					case "SAIN":
						smp++;
						q = X.UCSD(p+2); if(q.trim().length) sainmsg.push(q);
						break;
					case "INST":
						if(X.U8(p)) ins++;
						q = X.UCSD(p+1); if(q.trim().length) instmsg.push(q);
						break;
					case "DONE": p = X.Sz(); break;
				}
				p += hksz;
			}
			sOption(t); sOption(c);
			sOption("ord:"+ord+" ins:"+ins+" smp:"+smp+" hlt:"+hltu+"/"+hltd+
				" RPB:"+rpb+" spd0:"+spd0+" bpm0:"+bpm0+" st.sep0:"+st0+" mixvol:"+mvol);
			sOption(instmsg.join("\n"),"ins.text:\n"); sOption(sainmsg.join("\n"),"smp.text:\n");
		}
	}
	else if(X.c("FFFFFFFF",0x34) && X.c("1027",0x15C)
	  && X.c("1027",0x164)) {
		sName = "BoyScout module (.BSF)"; bDetected = 1
	}
	else if(X.c("'FUCO'") && X.c("'DIGI'",0x4404) && X.c("'DIGP'",0x47F8)) {
		sName = "Anthony J. 'Slates' Bybell's BSI Future Composer module (.BSI)"; bDetected = 1;
		bad = '';
		for(smp=smpsz=i=0; i < 63; i++) {
			if(t=X.U32(0x440C+i*16,_BE)) smp++; smpsz += t;
			if(t > 0x20000) bad = bad.addIfNone('!badsmpsz')
		} if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
		if(X.isVerbose()) {
			sOption('smp:'+smp+' sz:'+outSz(0x47FC+smpsz))
		}
	}
	else if(X.c("'NuBEATHOVEN'",0x22)) {
		bDetected = 1;
		sName = "Beathoven Synthesizer (.BSS)";
		sVersion = "v"+X.SA(0x2D,3);
		if(X.isVerbose()) {
			title = X.SA(0x6C,0x100);
			auth = X.SA(0x6C+title.length+1,0x100);
			misc = X.SA(0x6C+title.length+auth.length+2,0x100);
			sOptionT(title);
			sOptionT(auth,"by: ");
			sOptionT(misc);
		}
	}
	else if(X.c("'CBA'F9") && X.U8(0x24) == 0x1A && 0x14C+X.U16(0x25)+X.U8(0x29)*0x30 <= X.Sz()
	  && isWithin(X.U8(0x27),1,32) && X.U8(0x2B) && X.U8(0x2C) >= 32) {
		sName = "Chuck Biscuits / Black Artist module (.CBA)"; bDetected = 1;
		if(X.isVerbose()) {
			msglen = X.U16(0x25); ch = X.U8(0x27); ptn = X.U8(0x28)+1; ord = X.U8(0x29);
			smp = X.U8(0x2A); spd0 = X.U8(0x2B); tmp0 = X.U8(0x2C);
			for(i=smpsz=0; i < smp; i++) smpsz += X.U32(0x14C+i*0x30+0x24);
			sz = 0x14C + smp*0x30 + ptn*64*5*ch + smpsz;
			msg = X.SC(sz,msglen,"CP437").trim(); sz += msglen;
			sOptionT(X.SA(4,0x20)); //some sample names have user messages but few, no good way to tell
			sOption(addEllipsis(msg,0xC0,0xA0),'msg:"','"');
			sOption('spd:'+spd0+' tempo:'+tmp0+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'<CUD-FM-File>'1ADEE0") && (!X.U8(0x13) && X.c("'CUD-FM-File - SEND A POSTCARD -'",0x601)
	  || X.U8(0x13) && X.c("'YsComp'07'CUD1997'1A04",0x20))) {
		//ref https://github.com/adplug/adplug/blob/master/src/cff.cpp
		sName = "BoomTracker 4 module (.CFF)"; sVersion = 'v'+X.U8(0x10); bDetected = 1; 
		if(X.U8(0x13)) sVersion += '/LZW-packed';
		if(X.isVerbose()) sOption(outSz(0x20+X.U16(0x11)),'sz:')
	}
	else if(X.c("'CHIPv'3.")) {
		//ref "chip" from the release
		sName = "Chip Tracker module (.CHI)"; bDetected = 1;
		sVersion = "v"+X.SA(5,1)+".x"; if(X.Sz() <= 0x100) sVersion += "/malformed";
		if(X.isVerbose()) {
			sOptionT(X.SA(8,0x20));
			tempo = X.U8(0x28); ord = X.U8(0x29); loop =  X.U8(0x30);
			ptn=0; for(i=0; i < ord; i++) { p =X.U8(0x100+i); if(p > ptn) ptn = p }
			sOption("tempo:"+tempo+" ord:"+ord+" ptn:"+ptn+" loop:"+loop)
		}
	}
	else if(X.c("'CTMF'") && isWithin(X.U16(4),0x100,0x101) && isWithin(X.U16(8),0x25,X.Sz()-1)) {
		sName = "Creative Music File (.CMF)"; bDetected = 1;
		//ref https://github.com/adplug/adplug/blob/master/src/cmf.cpp
		// & https://moddingwiki.shikadi.net/wiki/CMF_Format
		nV = X.U16(4,_LE); ic = 0 /*invalid values for heuristics*/; bad = '';
		sVersion = "v"+(nV>>8)+"."+(nV&0xFF);
		pins = X.U16(6,_LE); pmus = X.U16(8,_LE); //instruments, music ptr
		if(pmus <= pins) {ic++; bad = bad.addIfNone('!badptr') }
		if(nV >= 0x101) { p = 0x28; ins = X.U16(0x24) } else { p = 0x25; ins = X.U8(0x24) }
		if((t=Util.divu64(pmus-pins,0x10)) != ins) { ic++; bad = bad.addIfNone('!inconsistentinscnt'+t) }
		tp = X.U16(0x0E,_LE); if(tp && !isWithin(tp,p,pins-1)) { tp = 0; ic++; bad = bad.addIfNone('!badptr') }
		ap = X.U16(0x10,_LE); if(ap && !isWithin(ap,p,pins-1)) { ap = 0; ic++; bad = bad.addIfNone('!badptr') }
		cp = X.U16(0x12,_LE); if(cp && !isWithin(cp,p,pins-1)) { cp = 0; ic++; bad = bad.addIfNone('!badptr') }
		ch = 0; for(i=0x14; i < 0x24; i++) { if((t=X.U8(i)) == 1) ch++; else if(t > 1) ic++} if(!ch) ic++;
		if(ic > 5) bDetected = 0; else if(bad != '') sVersion += "/malformed"+bad;
		if(bDetected && X.isVerbose()) {
			if(tp) sOptionT(X.SC(tp,0x100,'CP437'));
			if(ap) sOptionT(X.SC(ap,0x100,'CP437'),"by: ");
			if(cp) sOptionT(X.SC(cp,0x100,'CP437'));
			sz = X.fSig(pmus,Math.min(0x10000,X.Sz()),"00FF2F00"); if(sz > 0) sz += 4;
			if(nV >= 0x101 && X.U8(sz) == 0xFF) sz++; //a lot of v1.1 CMFs end in FF, enough to be significant?
			sOption('ch:'+ch+' ins:'+ins+' sz:'+outSz(sz))
		}
	}
	if(!bDetected)
	if(X.c("000003F3") && X.c("70FF4E75'S.PHIPPS'",0x20) &&
	  0 < X.I32(0x40,_BE) < X.Sz() && 0 < X.I32(0x44,_BE) < X.Sz() ) {
		sName = "Core Design module (.CORE)"; bDetected = 1;
		if(X.isVerbose()) {
			title = X.SA(0x68,0x100);
			auth = X.SA(0x68+title.length+1,0x100);
			misc = X.SA(0x68+title.length+auth.length+2,0x100);
			sOptionT(title);
			sOptionT(auth,"by: ");
			sOptionT(misc);
		}
	}
	else if(X.c("'CAT '................'FORM'")) {
		sName = "Cybertracker (not C64) module (.CT)"; bDetected = 1;
	}
	else if(X.c("0004'NNTRKMZX'")) {
		sName = "Cybertracker C64 module (.CT)"; bDetected = 1;
		//ref https://noname.c64.org/download.php/ctmisc/ctfileformat-1_01.html
		sVersion = "v"+X.U8(0x0B)+"."+X.U8(0x0A);
		if(X.isVerbose()) {
			for(sng=0; sng < X.U16(0x4B9,_LE); sng++)
				if(X.U8(0x4BB+sng-1) == 0) break;
			if(sng) {
				sng0ord = X.U8(0x4BB);
				loop0 = X.U8(0x6BD);
				if(sng > 1) sOption("×"+sng+" ord0:"+sng0ord+" loop0:"+loop0);
				else sOption(" ord:"+sng0ord+" loop:"+loop0)
			}
			else sOption("empty");
		}
	}
	else if(X.c("0004'NNTRKINS'")) {
		sName = "Cybertracker C64 instrument (.CI)"; bDetected = 1;
		sVersion = "v"+X.U8(0x0B)+"."+X.U8(0x0A);
		if(X.isVerbose())
			sOptionT(X.SA(0x1A,0x10))
	}
	else if(X.c("000003F3") && (X.fStr(0x20,0x100,"NuDELIRIUM") > 0)) {
		sName = "DeliTracker player addon or Amiga Custom Module (.CUST)"; bDetected = 1;
		sVersion = "CustomPlay";
		if(X.isVerbose())
			sOptionT(X.SA(X.fStr(0,0x100,"NuDELIRIUM")+20,0x100))
	}

	else if(X.c("'JCH'26026601") && X.c("'Creative Voice File'1A1A000A01291101......C300",0x40)) {
		sName = "Edlib Tracker module samples (.S01)"; bDetected = 1
	}
	else if(X.Sz() < 0xFFFF && (X.c("'JCH'26 026600") || X.c("'JCH'26 026680"))
	  && (X.c("FFFF",X.U16(0x71,_LE)-2) || X.c("FFFF",X.U16(0x73,_LE)-2))) {
		//ref http://ftp.modland.com/pub/documents/format_documentation/Edlib%20Tracker%20(.edl,%20.d00,%20.d01).txt
		msgp = 0; i = 0; //bruteforce-find the message at the end of the file (version-dependent)
		while (i < 4) { t = X.U16(0x71+i,_LE); if(t != 0xFFFF && t > msgp) msgp = t; i += 2 }
		sz = X.fSig(msgp,TOEOF,"FFFF")+2;
		bDetected = 1; bad = 0;
		sName = "Edlib Tracker module (.D00,.D01)";
		sVersion = "v"+X.U8(7);
		//if(X.U8(6) == 4) bad = 1;
		if(X.U8(0xA) > 1) bad = 1;
		if(bad) sVersion += "/malformed"+bad;
		if(X.isVerbose()) {
			x = X.U8(9);
			sOptionT(X.SC(0x0B,0x20,'CP850'));
			if(x > 1) sOption(x,"×");
			sOptionT(X.SC(0x2B,0x20,'CP850'),"by: ");
			sOptionT(X.SC(msgp,sz-msgp-2,'CP850'));
			sOption(outSz(sz),"sz:")
		}
	}
	else if(X.c("'DBM0'........'NAME'0000002C") && X.U8(4) < 4
	  && X.c("'INFO'0000000A",0x3C) && X.c("'SONG'",0x4E)
	  && X.U16(0x44,_BE) <= 255 && X.U16(0x46,_BE) <= 255
	  && X.U16(0x4A,_BE) <= 1024 && X.U16(0x4C,_BE) >=4
	  && X.U16(0x4C,_BE) <= 254 && !(X.U16(0x4C,_BE)%1)) {
		// ref http://www.digibooster.de/en/format.php
		// & https://www.amigafuture.de/app.php/dlext/details?df_id=5071
		sName = "DigiBooster Pro module (.DBM)"; bDetected = 1;
		sVersion = "v"+X.U8(4)+"."+X.U8(5).padStart(2,'0');
		p = 8; done = gotinfo = false; bad = ""; if(X.isVerbose()) ord = []; insts = []; titles = [];
		while(!done && p < X.Sz()) {
			hkhd = X.SA(p,4); hksz = X.U32(p+4,_BE); p += 8;
			switch(hkhd) {
			case "INFO": x = X.U16(p+4,_BE); ins = X.U16(p,_BE);
				info = "ch:"+X.U16(p+8,_BE)+" ptn:"+X.U16(p+6,_BE)
				+" ins:"+ins+" smp:"+X.U16(p+2,_BE);
				gotinfo = true;
				break;
			case "SONG": if(!gotinfo) { bad = bad.addIfNone("!badchunkorder"); break }
				if(!X.isVerbose()) break; q = 0;
				while(q+44 < hksz) { t = X.SC(p+q,44,'CP1250').trim(); titles.push(t);
					q += 44; ord.push(X.U16(p+q,_BE)); q += 2+ord[ord.length-1]*2 }
				break;
			case "INST": if(!gotinfo) { bad = bad.addIfNone("!badchunkorder"); break }
				for(i = 0; i < ins; i++) {
					t = X.SA(p+i*50,34).trim(); if(t != "") insts.push(t);
					c3freq = X.U32(p+i*50+34,_BE);
					if(c3freq < 2000 || c3freq > 192000) bad = bad.addIfNone("!badc3freq");
				} break;
			case "SMPL": if(!gotinfo) bad = bad.addIfNone("!badchunkorder"); done = true
			}
			p += hksz;
		}
		if(!done || p > X.Sz()) bad = bad.addIfNone("!short");
		if(bad != "") sVersion = sVersion.appendS("malformed"+bad,"/");
		if(X.isVerbose()) {
			title = X.SA(0x10,0x2C).trim(); sOption(title);
			sOption(titles.join("; "),(!title && title.length == 1 ? "songs:" : ""));
			if(title == "" && !titles.length) sOptionT(X.SA(0xD8,28));
			if(x > 1) sOption(x,"×");
			sOptionT(addEllipsis(insts.join(" "),0x100,0xA0),'smp/msg: "','"');
			info = "ord:"+ord.join("+")+" "+info; sOption(info+" sz:"+outSz(p))
		}
	}
	else if(X.c("'DFM'1A")) {
		sName = "Digital FM module (.DFM)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(7,32))
	}
	else if(X.c("'DSNGSEQU'00")) {
		sName = "David Hanney's module (.DH)"; bDetected = 1
	}
	else if(X.c("'DIGI Booster module'00") && X.Sz() >= 0x624 && X.U8(0x19)
	  && X.U8(0x19) <= 8 && X.U8(0x2F) <= 0x7F) {
		sName = "DigiBooster module (.DIGI)"; bDetected = 1;
		nV = X.U8(0x18); sVersion = "v"+(nV>>4)+"."+(nV&0xF); co = X.U8(0x1A);
		if(sVersion != X.SA(0x14,4).toLowerCase()) sVersion += '/"'+X.SA(0x14,4)+'"';
		if(co) sVersion = sVersion.appendS("co.ptn","/");
		if(X.isVerbose()) {
			sOptionT(decAnsi(0x262,0x20,CPAmiga,true)); msg = []; ch = X.U8(0x19);
			ord = X.U8(0x2F)+1; ptn = X.U8(0x2E)+1;
			smp = ssz = 0;
			for(i=0; i < 31; i++) {
				slen = X.U32(0xB0+4*i,_BE);
				if(slen) { smp++; ssz += slen }
			}
			for(i=0; i < 31; i++) {
				sname = decAnsi(0x282+0x1E*i,0x1E,CPAmiga,true).trim();
				if(sname != "") msg.push(sname);
			}
			sz = 0x624;
			if(co) for(i=0; i < ptn && sz <= X.Sz(); i++) sz += 2+X.U16(sz,_BE);
			else sz += 0x100*ptn*ch;
			sz += ssz;
			if(sz > X.Sz()) sVersion = sVersion.appendS("malformed!short","/");
			sOptionT(addEllipsis(msg.join(" "),0x100,0xA0),'by/msg: "','"');
			sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
		}
	}
	else if(X.c("000003F3") && X.c("70FF'NuUNCLEART'",0x20)
	  && X.U8(0x14) && X.U32(0x2C,_BE)
	  && X.U32(0x30,_BE) && X.U32(0x38,_BE)) {
		sName = "Dave 'Uncle Tom' Lowe module (.DL)"; bDetected = 1;
		if(X.isVerbose()) {
			smpip = 0x20+X.U32(0x3C,_BE);
			smpiep = 0x20+X.U32(0x40,_BE);
			if(!smpiep) smp = 0;
			else smp = Math.floor((smpiep-smpip)/14);
			title = X.SA(0x20+X.U32(0x50,_BE),0x100);
			auth = X.SA(0x20+X.U32(0x54,_BE),0x100);
			cmt = X.SA(0x20+X.U32(0x58,_BE),0x100);
			loadsz = 0x20+X.U32(0x5C,_BE);
			sz = 0x20+X.U32(0x60,_BE);
			sza = Hex(X.Sz()-sz); //TODO value different, why? Where does it start?
			smpsz = 0x20+X.U32(0x64,_BE);
			songsz = 0x20+X.U32(0x68,_BE);
			sfx = 0x20+X.U32(0x6C,_BE);
			x = 1;
			d1 = 0x20+X.U32(0x4C,_BE);
			if(d1) {
				a1 = d1;
				while(1) {
					a1 += 16;
					d1 = X.U32(a1,_BE);
//_log("read(0x20+a1:"+Hex(a1)+") = "+Hex(d1));
					if(!d1) break;
					d1 -= X.U8(a1+3);
					if(!d1) break; else x++
				}
			}
			sOptionT(title);
			if(x > 1) sOption(x,"×");
			sOptionT(auth,"by: ");
			sOptionT(cmt);
			sOption("smp:"+smp+" sfx:"+sfx)//+" sz:"+outSz(sz)+" a:"+sza+" smpsz:"+smpsz+" songsz:"+songsz+" loadsz:"+loadsz);
		}
	}
	else if(X.c("'ALL '00")) {
		sName = "Delta Music module (.DM)"; bDetected = 1
	}
	else if(X.c("670003B40C000001670001EC0C",6)
	  && (X.fStr(0x146,181,"DELTA MUSIC") > 0)) {
		sName = "Delta Music 2 module (.DM2)"; bDetected = 1
	}
	else if(X.c("'DMF'0E")) {
		sName = "Digital Sound and Music Interface Advanced Music Format hack (.DMF)";
		sOption("delta samples & no text"); bDetected = 1
	}
	else if(X.c("'DDMF'") && X.U8(4) && X.U8(4) <= 10
		&& X.U16(0x3F) && X.c("'CMSG'", 0x42)) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_dmf.cpp
		sName = "X-Tracker module (.DMF)"; bDetected = 1;
		nV = X.U8(4); sVersion = "v"+nV;
		p = 0x42; hkhd = hksz = end = lpst = ch = ord = ptn = smp = sz = ptntotallen = 0; lped = 0xFFFF;
		while(hkhd != "ENDE" && p < X.Sz()) {
			hkhd = X.SA(p,4); hksz = X.U32(p+4); p += 8;
//_log("@"+Hex(p-4)+" "+hkhd+" ["+Hex(p)+" -> "+Hex(p+hksz)+"]");
			switch(hkhd) {
			case "SEQU": ord = 0;
				if(nV == 3) p += 2; else if(nV == 4) p += 4;
				u = p;
				if(nV >= 3) { lpst = X.U16(u); u += 2 }
				if(nV >= 4) { lpst = X.U16(u); u += 2 }
				if(nV === 4 & !lped) lped = 0xFFFF;
				ord = (p+hksz-u)>>1; break;
			case "PATT":
				var ptnhdsz = nV < 3 ? 9 : 8; u = p;
				ptn = X.U16(u); trk = X.U8(u+2); u += 3;
				ch = trk; if(ch < 1) ch = 1; if(ch > 32) ch = 32;
				for(i = 0; i < ptn; i++) {
					u += ptnhdsz-4;
					var ptnlen = X.U32(u); ptntotallen += ptnlen; u += ptnlen;
				}
				break;
			case "SMPI": u = p; smp = X.U8(u++); var smptotallen = 0;
				for(i = 0; i < smp; i++) {
					var snamelen = nV < 2 ? 30 : X.U8(u++);
					var sname = X.SC(u,snamelen,'CP437').trim(); u += snamelen;
					var slen = X.U32(u); smptotallen += slen;
//_log("slen @"+Hex(u)+" = "+Hex(slen));
					u += 4+4+4+2;
					var svol = X.U8(u++), sf = X.U8(u++);
//_log("smp["+i+"] "+sname+" length:"+Hex(slen)+" total:"+Hex(smptotallen)+(svol?"":" ignored"));
					if(nV >= 8) { var libname = X.SC(u,8,'CP437'); u += 8 }
					u += nV > 1 ? 6 : 2;
				}
				break;
			case "SMPD":
				if(!X.c("'ENDE'",p+hksz)) {
					// Simply search for it. It's unique enough.
					// The multiplier of 3 is chosen for stereo + white noise "compression"
					t = X.fSig(p,Math.min(X.Sz()-p, smptotallen*3),"'ENDE'");
					if(t < 0) { sVersion += "/malformed!noeof"; hkhd = "ENDE"; break }
					else {
						hksz = t-p;
//_log("SMPD length should be "+Hex(hksz));
					}
				}
				break;
			case "ENDE": p -= 4+hksz; break;
			}
			sz = p;
			p += hksz;
		}
		if(hkhd != "ENDE") sVersion += "/malformed!short"; else sz = p;
		if(X.isVerbose()) {
			sOptionT(X.SC(0xD,30,'CP437'));
			sOptionT(X.SC(0x2B,20,'CP437'),"by: ");
			yy = X.U8(0x41);
			if(yy < 80) yyyy = "20"; else yyyy = "19"; yyyy += yy.padStart(2,'0');
			sOptionT(yyyy+"-"+X.U8(0x40).padStart(2,'0')+
				"-"+X.U8(0x3F).padStart(2,'0'),"on: ");
			sOptionT(X.SA(5,8),"in: ");
			cmt = ""; cnt = X.U32(0x46)-1; var linelen = 40, readpos = 0x4B;
			while(cnt) {
				var readlen = Math.min(cnt,linelen,0x200);
				var curline = X.SC(readpos,readlen,'CP437');
				if(curline != "") cmt = cmt.appendS(curline,'\n');
				cnt -= readlen; readpos += readlen;
			}
			sOption(addEllipsis(cmt,0x100,0x80));
			sOption("ch:"+ch+"+1 ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
		}
	}
	else if(X.c("'.DelekDefleMask.'")) {
		//ref https://web.archive.org/web/20150226185703if_/http://www.delek.com.ar:80/soft/deflemask/DMF_SPECS.txt
		// It's a pretty damn old document though...
		sName = "DeFleMask module (.DMF)"; bDetected = 1;
		sVersion = "v"+X.U8(0x10);
		if(X.isVerbose()) {
			switch(X.U8(0x11)) { //TODO add more new chip versions
			case 0x01: sVersion += "#YMU759"; ch = 17; break;
			case 0x02: sVersion += "#Genesis (10ch)"; ch = 10; break;
			case 0x03: sVersion += "#SMS (4ch)"; ch = 4; break;
			case 0x04: sVersion += "#GameBoy (4ch)"; ch = 4; break;
			case 0x05: sVersion += "#PCEngine (6ch)"; ch = 6; break;
			case 0x06: sVersion += "#NES (5ch)"; ch = 5; break;
			case 0x07: case 0x47: sVersion += "#C64 (3ch)"; ch = 3; break;
			case 0x08: sVersion += "#YM2151 (13ch)"; ch = 13; break;
			default: ch = 4 //just 'cause
			}
			sOption(X.UCSD(0x12));
			l = X.U8(0x12); p = 0x12+l+1;
			sOption(X.UCSD(p),"by: ");
			l = X.U8(p); p += l+1+2;
			tbase = X.U8(p++);
			tick1 = X.U8(p++); tick2 = X.U8(p++);
			Hz = X.U8(p++)?"60(NTSC)":"50(PAL)";
			if(X.U8(p++)) Hz = X.U8(p++)+":"+X.U8(p++)+":"+X.U8(p++);
			else p+=3;
			//These have changed over the years it seems, TODO: RE it
			//p++; _ripm = X.U8(p++);
			//arptick = X.U8(p++); p += ch*_ripm;
			//ins = X.U8(p++);
			sOption("tbase:"+tbase+" tck:"+tick1+":"+tick2+" freq:"+Hz); //+" ins:"+ins+" arp.tick:");
		}
	}
	else if(X.c("' M'........'IAN'") && X.c("0000....0000....0000....0000",0x1C) && (smp=X.U32(0x44,_BE)) <= 255
		&& ( !((smpsz=X.U32(0x48,_BE)) && !smp) || (smp && smpsz) ) && isWithin(ins=X.U32(0x3C,_BE),1,255)) {
		sName = "Reinier 'Rhino' van Vliet's Digital Mugician module (.DMU,.MUG)";
		if((sv=X.SA(9,1)) == '/') sVersion = 'v1'; else if(sv == '2') sVersion = 'v2'; bDetected = 1;
		if(X.isVerbose()) {
			x = ord = 0; ptn = X.U16(0x1A,_BE); wf = X.U32(0x40,_BE);
			for (i=0x4C, p = 0x1C, a2 = 0xCC; i < 0xCC; i += 0x10, p += 4) {
				if(!X.U32(i,_BE)) break; if(X.U32(p,_BE) != 1 || X.U32(a2,_BE) || X.U32(a2+4,_BE)) x++;
				a2 += X.U32(p,_BE) << 3;
				t = X.SA(i+4,12); while(t[t.length-1] < ' ') t = t.slice(0,t.length-1); sOptionT(t)
			}
			for(i=0x1C; i < 0x3C; i += 4) ord += X.U32(i,_BE);
			sz = smp*0x20+ins*0x10+smpsz+0x1CC+ptn*0x100+wf*0x80+ord*8;
			if(x > 1) sOption(x,'x');
			sOption('ord:'+ord+' ptn:'+ptn+' ins:'+ins+' smp/syn:'+smp+'+'+wf+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'DBRAWOPL'")) {
		//ref https://github.com/ValleyBell/libvgm/blob/master/player/droplayer.cpp
		if((t = X.U32(8)) & 0xFF00FF00) { sVersion = 'v0'; mV = MV = 0 }
		else if(!(t & 0x0000FFFF)) { mV = X.U16(8); MV = X.U16(0xA) }
		else { MV = X.U16(8); mV = X.U16(0xA) }
		if(MV <= 3) { //we only know about v2 but it may happen one day
			sName = "DOSBox Raw OPL chiptune (.DRO)"; bDetected = 1;
			if(MV) sVersion = 'v'+MV+'.'+mV.padStart(2,'0'); db = '';
			switch(MV) {
			case 0:
				db = 'DOSBox 0.62'; lenMS = X.U32(8); dtsz = X.U32(0xC);
				hw = X.U8(0x10); dtofs = 0x11; //fallthrough
			case 1:
				if(MV == 1) {
					db = 'DOSBox 0.63'; lenMS = X.U32(0xC); dtsz = X.U32(0x10);
					hw = (t=X.U32(0x14)) <= 0xFF ? t : 0xFF; dtofs = 0x18;
				}
				if(hw == 1) hw = 2; else if(hw == 2) hw = 1;
				fmt = 0; co = 0; break;
			case 2:
				db = 'DOSBox 0.73'; dtsz = X.U32(0xC) << 1; lenMS = X.U32(0x10); hw = X.U8(0x14);
				fmt = X.U8(0x15); co = X.U8(0x16); ds = X.U8(0x17); dl = X.U8(0x18); regcmdcnt = X.U8(0x19);
				dtofs = 0x1A+regcmdcnt; if(regcmdcnt > 0x80) regcmdcnt = 0x80; regmap = readBytes(0x1A,regcmdcnt);
				//scan init block
				if(hw == 1) {
					p = dtofs; opl3on = 0; reginit = []; for(i=0; i < 0x200; i++) reginit[i] = false;
					while(p < X.Sz()) {
						t = X.U8(p); if(t == ds || t == dl) break; if((t & 0x7F) >= regcmdcnt) break;
						creg = ((t & 0x80) << 1) | regmap[t & 0x7F]; reginit[creg] = true; if(creg == 0x105) opl3en = X.U8(p+1);
						p += 2
					}
					if(reginit[0x105] && (opl3en & 1)) hw = 2;
				}
			}
			hw = ['YM3812 (OPL2)', 'YM3812 (Dual OPL2)', 'YMF262 (OPL3)'].indexOf(hw);
			if(hw === -1) hw = 'YMF262(portshift)';
			sVersion = sVersion.appendS(hw,'#');
			if(X.isVerbose()) {
				sOption('len: '+secondsToTimeStr(Util.divu64(lenMS+500,1000))+' via: '+db+' packed:'+co
					+' sz:'+outSz(dtofs+dtsz))
			}
		}
	}
	if(!bDetected)
	if(X.c("'DSM'10")) {
		sName = "Digital Audio Sound Interface Kit module (.DSM)"; bDetected = 1;
		sOptionT(X.SA(4,32));
	}
	else if(X.c("'RIFF'........'DSMFSONG'") && X.U16(0x36) <= 128 && X.U16(0x38) <= 128
	  && X.U16(0x3C) <= 256 && X.U16(0x3E) <= 16) {
		sName = "Digital Sound Interface Kit module (.DSM)"; bDetected = 1;
		sVersion = "RIFF";
		if(X.isVerbose()) {
			sOptionT(X.SC(0x14,28,'CP437'));
			sz = X.U32(4)+8; p = 0xC; smp = 0;
			while(p < sz) {
				hkhd = X.SA(p,4); hksz = X.U32(p+4); p += 8;
				if(hkhd == "INST") smp++
				p += hksz;
			}
			if(p > X.Sz()) sVersion += "/malformed!badchunk";//+hkhd+" @"+Hex(p-hksz-8)+"="+Hex(hksz);
			sOption("ch:"+Math.max(1,X.U16(0x3E))+" spd0:"+X.U8(0x42)+" bpm0:"+X.U8(0x43)
				+" ord:"+X.U16(0x38)+"("+X.U16(0x34)+"-"+X.U16(0x36)
				+") ptn:"+X.U16(0x3C)+" smp:"+X.U16(0x3A)+"/"+smp
				+" sz:"+outSz(sz))
		}
	}
	else if(X.c("02011313 1412010B 0.")) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_dsym.cpp
		// & https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/sym_load.c
		sName = "Digital Symphony module (.DSYM)"; bDetected = 1;
		sVersion = "v"+X.U8(8);
		if(X.isVerbose()) {
			var infolenlo = X.U16(0x0E,_LE); var infolenhi = X.U8(0x10);
			infolen = (infolenhi<<16) + infolenlo;
			p = 0x11;
			for(i=0; i < 63; i++)
				if((X.U8(p) & 0x80)) p++;
				else p += 3;
			sOption(X.UCSD(p));
			sOption("ch:"+X.U8(9)+" ord:"+X.U16(0x0A,_LE)
			  +" trk:"+X.U16(0x0C,_LE));
		}
	}
	else if(X.c("'DSm'1A20")) {
		sName = "Dynamic Studio Pro module (.DSM)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(5,20));
			sOptionT(X.SA(25,20),"by: ");
		}
	}
	else if(X.c("'DSFmt1'0D0A")) {
		sName = "DreamStation module (.DSS)"; bDetected = 1;
		sVersion = "v1.0";
		if(X.isVerbose()) {
			pt = X.fSig(0,TOEOF,"F0E40001")+4; //the 00/01 at the end is "show info at track load"
			if(pt < 4) pt = X.fSig(0,TOEOF,"F0E40000")+4;
			if(pt >= 4) {
				pa = X.fSig(pt,TOEOF,"0D0A"); t = X.SA(pt,pa-pt); pa += 2;
				pc = X.fSig(pa,TOEOF,"0D0A"); a = X.SA(pa,pc-pa); pc += 2;
				c = X.SA(pc,X.Sz()-pc);
				sOption(t); sOption(a,"by: "); sOption(c)
			}
		}
	}
	else if(X.c("'DS2F0'....'Default'")) {
		sName = "DreamStation II module (.DS2)"; bDetected = 1;
		sVersion = "v2";
		if(X.isVerbose()) {
			ts=X.U8(0x62); t=X.SA(0x63,ts);
			pa=0x62+ts+1; as=X.U8(pa); a=X.SA(pa+1,as);
			pc=pa+as+1;  cs=X.U8(pc); c=X.SA(pc+1,cs);
			sOption(t);
			sOption(a,"by: ");
			sOption(c);
		}
	}
	else if(X.c("'MMU2'00")) {
		sName = "Digital Sound Studio module (.DSS)"; bDetected = 1;
		sVersion = "v1-3.0";
		if(X.isVerbose()) {
			sOptionT(X.SA(10,32));
			sOption("ord:"+X.I16(0x59C,_BE));
		}
	}
	else if(X.c("'DTL'00")) {
		sName = "Drum Traker module (.DTL)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(4,20))
	}
	else if(X.c("'D.T.'00")) {
		sv = ["S.Q.","VERS"].indexOf(X.SA(0x2A,4));
		if(sv >= 0) {
			sName = "Digital Tracker module (.DTM)"; bDetected = 1;
			if(X.isVerbose()) {
				if(sv==0) sOptionT(X.SA(0x16,20))
				//let's play along with Bummtschak here
				else sOptionT(X.SA(0x16,24))
			}
		}
	}
	else if(X.c("'DeFy DTM'")) {
		sName = "DeFy AdLib Tracker module (.DTM)"; bDetected = 1;
		sVersion = "v"+X.SA(9,3);
		if(X.isVerbose()) {
			sOptionT(X.SA(0xD,20));
			sOptionT(X.SA(0x21,20));
		}
	}
	else if(X.c("'SONG'........'NAME'")) {
		sName = "DigiTrekker module (.DTM)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(0x10,20));
	}
	else if(X.c("48E7F1FE610000964CDF7F8F'NuH'E70010610000'$L'DF0800'NuH'E7F1FE610001844CDF7F8F'NuH'E70010610000'NL'DF0800'NuG'FAFFC651EB05',Q'EB05'PQ'EB05'tQ'EB059833FC000F00DFF09633FC00FF00DFF09E33FC000000DFF0A833FC000000DFF0B833FC000000DFF0C833FC000000DFF0D8'NuG'FAFF80177C00010524177C00010548177C0001056C177C00010590'NuG'FAFF'bJ+'00BA670E'S+'00B96608177C000600B9'Nu`'0003180006")) {
		sName = "David Whittaker's SFX module (.DW)"; bDetected = 1
	}
	else if(X.c("' PWD'03") && X.c("'Master'",0x0E)) {
		sName = "DarkWave Studio module (.DWP)"; bDetected = 1
	}
	else if(X.c("'EASO'")) {
		sName = "EarAche module (.EA,.EAS)"; bDetected = 1;
	}
	else if(X.c("'FORM'.... ....'EMODEMIC'")
	  && X.c("'PATT'",pt=0x14+X.U32(0x10,_BE)) && X.c("'8SMP'",q=pt+8+X.U32(pt+4,_BE))) {
		//ref https://github.com/libxmp/libxmp/blob/master/src/loaders/emod_load.c
		sName = "Bo Lincoln's Quadra Composer module (.EMOD)"; bDetected = 1; sVersion = 'v'+X.U16(0x14,_BE);
		smp = X.U8(0x3F); if(smp) smps = []; sz = X.U32(4,_BE)+8;
		for(i=ssz=0,p=0x40; i < smp; i++,p+=0x22) {
			if((t=X.SC(p+4,0x14,'CP1252').trim()) != '') smps.push(t);
			ssz += X.U16(p+2,_BE) << 1;
		}
		bad = ''; if(ssz != X.U32(q+4,_BE)) bad = bad.addIfNone('!badsmplen:'+X.U32(q+4,_BE)+' vs '+ssz)
		ptn = X.U8(++p); p++;  p += 0x1A*ptn;
		// for (i=0; i < ptn; i++,p+=0x1A) if((t=X.SC(p+2,0x14,'CP1252').trim()) != '') smps.push(t);
		if(X.c("'MDIN'",q1=q+8+X.U32(q+4,_BE))) q = q1;
		if(Math.abs(sz-(sz1=q+8+X.U32(q+4,_BE))) > 1) bad = bad.addIfNone('!badlen:'+sz+' vs '+sz1);
		if(sz < sz1) sz = sz1; ord = X.U8(p++); if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
		if(X.isVerbose()) {
			sOptionT((t=X.SA(0x16,0x14)) === '<sans titre>'? '': t);
			if(smp) sOptionT(addEllipsis(smps.join(' '),0xC0,0xA0),'smp/msg: "','"'); delete smps;
			sOption('bpm:'+X.U8(0x3E)+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'E.M.S. V6.0'..00010000") && isWithin(X.U8(0xB),0x31,0x36)) {
		sName = "Sean Connolly's Electronic Music System module (.EMS)"; bDetected = 1;
		sVersion = "v6."+X.SA(10,2);
		if(X.isVerbose()) {
			//ref ems_player_dis.asm
			a1 = X.U32(0xE,_BE); a2 = X.U32(0x12,_BE); a3 = X.U32(0x16,_BE); p = lb0 = 0x22;
			p += a1; lb4 = p; p += a2; lb8 = p; p += a3; insp = p; sz = p+X.U32(0x1E,_BE);
//_log(Hex(lb0)+'; [4]:'+Hex(lb4)+'; [8]:'+Hex(lb8)+'; insp:'+Hex(insp))
			// p = a2 = lb4; lbdata = a5 = 0x13740;
			// ssp = ( 0 & 0x1F)*0x84; ss1 = X.U16(p+ssp,_BE); ss2 = X.U16(p+ssp+2,_BE);
//_log('subsong @'+Hex(p+ssp)+' has '+Hex(ss1)+' and '+Hex(ss2))
			// In the player, the following bit was copying the modified offsets (of something) to the data buffer:
			// p += ssp+4; p1 = p;
			// for(d1 = 0, ptrs=[]; d1 < 0x20; d1++) { t = X.U32(p,_BE)+p1; p += 4; ptrs.push(t) }
			sOption('sz:'+outSz(sz))
		}
	}
	else if(X.c("'ZXAYEMUL'") && X.U8(9) <= 3) {
		// ref https://vgmrips.net/wiki/AY_File_Format
		bDetected = true;
		authp = 0x0C+X.I16(0x0C,_BE);
		if(authp < X.Sz()) auth = authp?X.SC(authp,0x100,'CP1250'):''; else bDetected = false;
		miscp = 0x0E+X.I16(0x0E,_BE);
		if(miscp < X.Sz()) misc = miscp?X.SC(miscp,0x100,'CP1250'):''; else bDetected = false;
		x = X.U8(0x10)+1; p = 0x12+X.I16(0x12,_BE); if(p > X.Sz()) bDetected = false;
		titles = []; for(i=0; i < x; i++,p+=4) if(t = p+X.I16(p,_BE))
			if(t < X.Sz()) titles.push(X.SC(t,0x100,'CP1250').trim()); else { bDetected = false; break }
		if(bDetected) {
			sName = "AY Emul chiptune (.EMUL)"; bDetected = 1;
			sVersion = [X.U8(8)?'Rel '+X.U8(8):'', X.U8(9)?'Plr '+X.U8(9):''].join(' ').trim();
		 	if(X.isVerbose()) {
				sOptionT(titles.join(',')); if(x > 1) sOption(x,'×');
				if(authp) sOptionT(auth,'by: '); if(miscp) sOptionT(misc);
			}
		}
	}
	if(!bDetected)
	if(X.c("'ENF '....'scor'")) {
		sName = "Extended Notation Format sheet music (.ENF)"; bDetected = 1;
		p = 6; staf = brln = 0; hkhd = "dumm";
		while(p < X.Sz()) {
			hkhd = X.SA(p,4);
			if(!/[a-z\[\]\-0-9]{4}/.test(hkhd)) break;
			hksz = X.U16(p+4,_BE);
			if(hkhd === "staf") staf++;
			else if(hkhd === "brln") brln++;
			p += hksz //this format includes signatures into the chunk size
		}
		sOption("staves:"+staf+" bars:"+brln+" sz:"+outSz(p))
	}
	else if(X.Sz() > 0x24 && X.c("'ETracker (C) BY ESI.'",10) ||
	  (X.Sz() > 0x4D4 && (X.c("21B384") ||X.c("21B304")) && X.c("'ETracker (C) BY ESI.'",0x4BD))) {
		//kudos to horsicq for helping narrow down the bad samples in modland files
		sName = "SAM Coupe E-Tracker file (.ETC,.SAA)"; bDetected = 1;
		if(X.c("21B384") || X.c("21B304")) { sVersion = "&player"; ofs = 0x4B3 } else ofs = 0;
		p = ofs; x = mp = sz = 0; ords = []; ptns = [];
		while(p < X.Sz()-0x24 && X.c("'ETracker (C) BY ESI.'",p+10)) {
			x++; //TODO not too good for a ripper but there're multitrack files like this, iono
			ordp = p+X.U16(p); if(ordp > mp) mp = ordp;
			ptnp = p+X.U16(p+2); if(ptnp > mp) mp = ptnp;
			smpp = p+X.U16(p+4); if(smpp > mp) mp = smpp;
			ornp = p+X.U16(p+6); if(ornp > mp) mp = ornp; svd = ofs+X.U16(p+6); if(svd > mp) mp = svd;
			if(sz < mp) sz = mp;
			if(x == 1 && sz > X.Sz()) return false;
//_log('Track at '+Hex(p)+': ordp:'+Hex(ordp)+' ptnp:'+Hex(ptnp)+' smpp:'+Hex(smpp)+' ornp:'+Hex(ornp)+' svd:'+Hex(svd))
			ord = lp = xpos = ptn = 0; var ordl = [];
			for(p = ordp; p < X.Sz(); p++) {
				o = X.U8(p);
				if(o == 0xFF) { p++; break }
				else if(o == 0xFE) lp = ord;
				else if(o >= 0x60) xpos = o-0x60;
				else {
					if(x == 1 && o%3) { _log('SAAFault @'+Hex(p)+': trk '+x+' pos not divisible by 3'); return false }
					o = Util.div64(o,3);
					if(x == 1 && o > 0x1F) { _log('SAAFault: trk '+x+' pos over 1Fh'); return false }
					if(o > ptn) ptn = o; ord++
				}
				if(ord > 254) { _log('SAAFault: trk '+x+' pos not divisible by 3'); return false }
			}
			if(!ord) return false; ords.push(++ord); ptns.push(++ptn);
			if(sz < p) sz = p;
			p = ptnp+12*ptn; //a pattern record contains uint16_t pattern offsets for 6 channels
			for(i=ptnp; i < p; i += 2) { //find if the a pattern isn't after its record (doesn't seem to ever happen)
				if((t=X.U16(i)) > mp-ofs) mp = ofs+t;
				if(t < 0x1E) return false;
			}
			if(x == 1 && mp > X.Sz()) return false;
			notes = -1;
			if(t > p) { // the hard way
				p = t+64;
				//parse the last pattern
				p = ptnp; notes = 0; chncnt = [64,64,64,64,64,64];
				for(l=0; l < 64; l++) { //lines
					for(c=0; c < 6; c++) { //channels
						if(chncnt[c]) { chncnt[c]--; continue }
						while(p < X.Sz()) { //ParseChannel
							cmd = X.U8(p++);
							if(cmd >= 0xD2) { chncnt[c] -= cmd-0xD2; break }
							else if(cmd >= 0x72) notes++; //set note cmd-0x72
							else if(cmd >= 0x52) {} //set sample cmd-0x52
							else if(cmd >= 0x51) break; //isn't this just if cmd == 0x51?
							//else if(cmd >= 0x50) {} //set ornament cmd-0x30
							//else if(cmd >= 0x2E) {} //set swap sample channels with "cmd > 0x2E"
							//else if(cmd >= 0x21) {} //set envelope cmd-0x21
							//else if(cmd >= 0x11) {} //set attenuation cmd-0x11
							//else if(cmd >= 0x0F) {} //set noise to "cmd != 0xF? 3: cmd-0xF" (so either 3 or 0!)
							//else {} //set tempo cmd+1
						}
					}
				}
			}
			if(sz < p) sz = p;
			if(ornp == mp)
				if(sz < mp) sz = mp //never happens so no further parsing
			if(smpp == mp)
				if(sz < mp) sz = mp; //ditto
			if(svd == mp) if(sz < mp) sz = mp //ditto
		}
		if(X.isVerbose()) {
			if(x > 1) sOption(x,'×');
			sOption('ord:'+ords.join('+')+(lp?' looped':'')+' ptn:'+ptns.join('+')+(xpos?' xpos:'+xpos:'')
				+(notes>-1?' notes:'+notes:'')+' sz:'+outSz(sz))
		}
	}
	else if((X.Sz() > 2048+6+6)
	  && X.c("00000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x3E1)
	  && X.c("......00 ......00 ......00 ......00 ......00 ......00 ......00",0x4D4)
		) {
		sName = "Euphony module (.EUP)"; bDetected = 1;
		if(X.isVerbose()) {
			sOption(X.SC(0,0x20,'CP932'))
		}
	}
	else if(X.c("'FAR'FE") && X.c("0D0A1A",0x2C)) {
		//ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
		bDetected = 1; nV = X.U8(0x31);
		sName = "Farandole Composer module (.FAR)";
		sVersion = "v"+ (nV>>4) +"."+ (nV&0x0F);
		if(X.isVerbose()) {
			sOptionT(X.SC(4,0x28,'CP850'));
			var stlen = X.U16(0x60,_LE);
			sOption(X.SC(0x62,stlen,'CP850').trim().slice(0,0x100).trim()+"...");
			sOption("ord:"+X.U8(stlen+0x163)
				+" ptn:"+X.U8(stlen+0x162)
				+" lp:"+X.U8(stlen+0x164))
		}
	}
	else if(X.c("'FPT'FE") && X.c("0D0A1A",0x24)) {
		//ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
		sName = "Farandole Composer pattern (.FPT)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SC(4,0x20,'CP850'));
	}
	else if(X.c("'FSM'FE") && X.c("0A0D1A",0x24)) {
		//ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
		sName = "Farandole Composer sample (.FSM)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SC(4,0x20,'CP850'));
			if(X.U8(0x35)) type = "16bit"; else type = "8bit";
			if(X.U8(0x36)&4) looped = " looped"; else looped = "";
			sVersion = type+looped;
			sOption(outSz(X.U32(0x27)),'sz:');
		}
	}
	else if(X.c("'SMOD'") && X.Sz() > 0x70 && isWithin(X.U32(8,_BE),0x70,X.Sz()) && isWithin(X.U32(0x10,_BE),0x70,X.Sz())
	  && isWithin(X.U32(0x18,_BE),0x70,X.Sz()) && isWithin(X.U32(0x20,_BE),0x70,X.Sz())) {
		//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FutureComposer13.c
		// & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/ModuleConverters/ModuleConverter/Formats/FutureComposer13Format.cs
		sName = "Superions' Future Composer module (.FC,.FC13,.SMOD)"; sVersion = "v1.0~3"; bDetected = 1;
		if(X.isVerbose()) {
			ordsz = X.U32(4,_BE); ordsz += ordsz%2; ord = Util.divu64(ordsz,13); if(!ord) ord = 1;
			ptnp = X.U32(8,_BE); ptnsz = X.U32(0xC,_BE); ptn = ptnsz >> 6;
			smpp = X.U32(0x20,_BE); smpsz = X.U32(0x24,_BE); smp = wf = 0;
			sszs = []; for(p=0x28,i=0; i < 10; i++,p+=6) { if(t=X.U16(p,_BE)<<1) smp++; sszs.push(t) }
			for(i=10; i < 90; i++) sszs.push(X.U8(p++)<<1);
			for(i=89; i >= 10; i--) if(sszs[i]) break; wf = i-9; delete sszs;
			sOption('ord:'+ord+' ptn:'+ptn+' smp:'+smp+' wf:'+wf+' sz:'+outSz(smpp+smpsz))
		}
	}
	else if(X.c("'FC14'") && X.Sz() > 0xC0 && isWithin(X.U32(8,_BE),0xC0,X.Sz()) && isWithin(X.U32(0x10,_BE),0xC0,X.Sz())
	  && isWithin(X.U32(0x20,_BE),0xC0,X.Sz()) && isWithin(X.U32(0x24,_BE),0xC0,X.Sz())) {
		//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FutureComposer14.c
		// & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/FutureComposer/FutureComposerWorker.cs
		sName = "Superions' Future Composer module (.FC,.FC14)"; sVersion = "v1.4"; bDetected = 1;
		if(X.isVerbose()) {
			ordsz = X.U32(4,_BE); ord = Util.divu64(ordsz,13); if(!ord) ord = 1;
			ptnp = X.U32(8,_BE); ptnsz = X.U32(0xC,_BE); ptn = ptnsz >> 6;
			smpp = X.U32(0x20,_BE); wfp = X.U32(0x24,_BE); smp = 10; rsmp = msmp = wf = 0;
			sszs = []; for(p=0x28,i=0; i < 10; i++,p+=6) sszs.push(X.U16(p,_BE)<<1);
			for(sz=0,i=10; i < 90; i++) { sz += t=X.U8(p++)<<1; sszs.push(t) }
			for(i=89; i >= 10; i--) if(sszs[i]) break; wf = i-9; sz += X.U32(0x24,_BE);
			for(p=smpp,i=0; i < 10; i++) {
				rsmp++; if(sszs[i]) {
					if(X.c("'SSMP'")) { mszs = [];
						p += 4; msmp++; smp--; rsmp--; for(j=0; j < 20; j++, p+=20) mszs.push(X.U16(p+4,_BE)<<1);
						for(j=0; j < 20; j++) if(mszs[j]) rsmp++; smp++
					}
				}
			} delete sszs; delete mszs;
			sOption('ord:'+ord+' ptn:'+ptn+' smp:'+rsmp+(smp!=rsmp?'('+smp+')':'')
				+(msmp?' ssmp:'+msmp:'')+' wf:'+wf+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'FMK!'")) {
		sName = "FM-Kingtracker module (.FMK)"; bDetected = 1;
		if(X.isVerbose()) {
			text = true; for(i=0; i < 0x38; i++) if(0x20 > X.U8(i+4)) { text = false; break }
			if(text) {
				sOptionT(X.SA(0x04,0x1C));
				sOptionT(X.SA(0x20,0x1C),"by: ");
			}
		}
	}
	else if(X.c("'FMTracker'....'The FM Tracker!'")) {
		sName = "FM Tracker module (.FMT)"; bDetected = 1;
		sVersion = "v"+X.U8(9)+"."+X.U8(10);
		if(X.isVerbose())
			sOptionT(X.SA(0x1F,0x20));
	}
	else if(X.c("'FMTRK'1A")) {
		sName = "FM Tracker (Adlib) module (.FMT)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(0x10,0x20));
	}
	else if(X.c("'Funk'") && X.Sz() >= 0x8FF) {
		//ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/docs/formats/FORMAT.FunktrackerGOLD
		sName = "Funktracker module (.FNK,.Funk)"; bDetected = 1;
		sV = X.SA(0x0C,4); t = X.U32(4,_LE);
		if(/F2\d\d/.test(sV))
			sVersion = "R2 GOLD "+(1980+((t>>9)&0x7F))+"-"
			  +((t>>5)&0xF).padStart(2,'0')+"-"+(t&0x1F).padStart(2,'0')+" ";
		else sVersion = "R1";
		if(/F[2vk]\d\d/.test(sV)) ch = X.SA(0x0E,2);
		else sVersion += "b";
		switch((t>>20)&0xF) {
		case 1: case 2: t = "IBM"; break; case 3: t = "Intel 386"; break;
		case 4: t = "Intel 486"; break; case 5: t = "Pentium"; break; case 6: t = "Linux"; break;
		case 7: t = "FreeBSD"; break; case 8: t = "N/A"; break; default: t = "unk.system" }
		sVersion += "#"+t;
		switch((t>>16)&0xF) {
		case 0: t = "SB 2.0"; break; case 1: t = "SB Pro"; break;
		case 2: t = "GUS+ch.pan"; break; case 3: t = "SB compatible"; break;
		case 4: t = "SB 16"; break; case 5: t = "GUS"; break; case 6: t = "conversion"; break;
		case 7: t = "Pro Audio Spectrum"; break; case 8: t = "Voxware /dev/dsp 8 bit"; break;
		case 9: t = "Voxware /dev/dsp 16 bit"; break; case 15: t = "unk.soundcard"; break;
		default: t = "soundcard N/A" }
		sVersion += ":"+t;
		sz = X.U32(8,_LE); bad = "";
		if(X.Sz() < sz) bad += "!short";
		ord = 0; ptn = -1; for(i = 0; i < 256; i++) {
			t = X.U8(0x11+i); if(t == 0xFF) break; ord++;
			if(t > 0x79)
				if(X.isHeuristicScan()) { bad = bad.addIfNone("!badord") }
				else { bDetected = 0; break }
			else if(t > ptn) ptn = t;
		} ptn++;
		t = X.U8(0x10);
		if(t == 0xFF) looped = " non-looped";
		else looped = " looped@"+t;
		if(t > ord) if(X.isHeuristicScan()) bad += "!badloop"; else bDetected = 0;
		if(bDetected && X.isVerbose()) {
			bpm = t>>24; bits = bpm&1 ? 16 : 8; bpm >>= 1; var sine = bpm >> 6;
			bpm &= (1<<6)-1; if(sine) bpm = 125-bpm; else bpm = 125+bpm;
			smp = 0; for(i = 0x1A8; i < 0x8BF; i += 0x20) if(X.U32(i,_LE)) smp++; //length
			sOptionT(X.SA(0x191,0x13));
			sOption("ch:"+ch+" "+bits+"bit ord:"+ord+" ptn:"+ptn+" smp:"+smp+" bpm:"+bpm+looped+" sz:"+outSz(sz));
		}
		if(bad != "") sVersion += "/malformed"+bad
	}
	else if(X.c("000003F3") && X.U8(0x14)
	  && X.c("70FF4E75'F.PLAYER'",0x20) && X.I32(0x40,_BE)) {
		sName = "Future Player module (.FP)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(X.I32(0x2C,_BE)+0x20,0x100));
			x = 1; p = 0x48;
			do { t = X.U32(p,_BE); if(t) x++; p += 8 } while(t);
			if(x > 1) sOption(x,"×");
			sOptionT(X.SA(X.I32(0x30,_BE)+0x20,0x100),"by:");
			sOptionT(outSz(X.I32(0x38,_BE)+0x20),"sz:");
		}
	}
	else if(X.c("'Fred Editor '0000") && X.U16(0xE,_BE) <= 10) {
	//ref https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/ModuleConverters/ModuleConverter/Formats/FredEditorFinalFormat.cs
		p = 0x10+0x401*(x=X.U16(0xE,_BE));
		spds = []; for(i=0x10; i < 0x10+x; i++) if(spds.indexOf(t=X.U8(i)) < 0 ) spds.push(t); 
		if(p < X.Sz()) for(i=0; i < 128 && p < X.Sz(); i++) { trksz = X.U32(p,_BE); p += trksz+4 }
		if(p < X.Sz()) {
			ins = X.U16(p,_BE); p += 2;
			for(i = syn = un = 0; i < ins && p < X.Sz(); i++) {
				switch(X.U8(p+0x53)) {
				case 0xFF: un++; break;
				default: syn++
				}
				p += 0x60; //name & parameters
			}
		}
		smp = X.U16(p,_BE); syn -= smp; p += 2; for(i=0; i < smp && p < X.Sz(); i++) { p += 4+X.U16(p+2,_BE) }
		if(X.c("12345678",p)) { bDetected = true; sz = p+4 }
		if(bDetected) {
			sName = "Fred & Julien Clermonte's Fred Editor module (.FRED,.MOD)"; sVersion = 'project';
			if(X.isVerbose()) {
				if(x > 1) sOption(x,'x');
				sOption('tempos:'+spds.sort().join('-')+' ins:'+(ins-un)+(un?'('+ins+')':'')
					+(smp?' smp:'+smp:'')+(syn?' syn:'+syn:'')+' sz:'+outSz(sz))
			}
		}
	}
	if(!bDetected) if(X.c("'Module: '") && X.c("';Fast Tracker v1.00'", 0x32)) {
		sName = "Fast Tracker module (.FTС)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(8,0x2A));
	}
	else if(X.c("'FTMN'03") && X.U8(5) < 64 && X.U16(8,_BE) >= 0x1000 && X.U16(8,_BE) < 0x5000
	  && X.U8(0xA) < 12 && X.U8(0xC) < 64 && !(X.U8(0xD) & 0xFC) && X.U8(0xE) && X.U8(0xE) <= 24
	  && X.U8(0xF) >= 4 && X.U8(0xF) <= 96 && X.U8(0xF) == Util.div64(96,X.U8(0xE))
	  && X.U8(0x50) <= 64 && !X.U8(0x51) && X.Sz() >= 0x52+X.U8(5)*32+X.U8(0x50)*4) {
	  	//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_ftm.cpp
		sName = "Face the Music module (.FTM)"; sVersion = 'v'+X.U8(4); bDetected = 1;
		if(X.isVerbose()) {
			smp = X.U8(5); msr = X.U16(6,_BE); bpm0 = (1766278.163/X.U16(8,_BE)).toFixed(0);
			fl = X.U8(0xD); fx = X.U8(0x50); gvol0 = X.U8(0xC);
			p = 0x52+smp*32; realsmp = 0;
			for(i=0; i < smp; i++) if(X.U8(0x52+i*32)) realsmp++;
			fxln = 0; ticksper = X.U8(0xE); rowsper = X.U8(0xF);
			for(i=0; i < fx; i++) { t = X.U16(p,_BE); fxln += t; p+=4+4*t }
			if(msr) for(i=0; i < 8; i++) p += 6+X.U32(p+2,_BE);
			if(fl&1) for(i=0; i < realsmp; i++) p += 4+2*(X.U16(p,_BE)+X.U16(p+2,_BE));
			sOptionT(decAnsi(0x10,0x20,CPAmiga));
			sOptionT(decAnsi(0x30,0x20,CPAmiga),'by: ');
			sOption('bpm0:'+bpm0+' ptn:'+msr+' smp:'+realsmp+'/'+smp+' fx:'+fx+'/'+fxln
				+' msr:'+ticksper+'/'+rowsper+(!(fl&1)?' ext.smps':'')+' gvol0:'+gvol0+' sz:'+outSz(p))
		}
	}
	else if(X.c("'FMS!'..000000")) {
		sName = "FamiStudio module (.FMS)"; bDetected = 1;
		sVersion = "v"+X.U8(4)
	}
	else if(X.c("'FamiTracker Module'")) {
		sName = "FamiTracker module (.FTM)"; bDetected = 1;
		if(X.isVerbose()) {
			itag = X.fStr(0x14,0x100,"INFO\x00");
			sOptionT(X.SA(itag+0x18,0x20));
			sOptionT(X.SA(itag+0x38,0x20),"by: ");
			sOptionT(X.SA(itag+0x58,0x20))
		}
	}
	else if(X.c("'-Furnace '")) {
		// from https://github.com/tildearrow/furnace/blob/master/papers/format.md
		switch(X.SA(9,7)) {
			case 'module-':
				cV = 'm'; bDetected = 1; sName = "Furnace Tracker module (.FUR)"; break;
			case 'instr.-':
				cV = 'i'; bDetected = 1; sName = "Furnace Tracker instrument (.FUI)"; break;
			case 'waveta-':
				cV = 'w'; bDetected = 1; sName = "Furnace Tracker wavetable (.FUW)"; break
			default:
				cv = "?"; bDetected = 1; sName = "unknown Furnace Tracker file"
		}
		nV = X.U16(0x10,_LE);
		//ref https://github.com/tildearrow/furnace/blob/master/papers/format.md
		//they update it every 0.1 femtosecond, why not help out since you're reading this?
		if(nV < 12) sVersion = "["+nV+"]"; else
		if(nV <= 14) sVersion = "v0.2.x"; else
		if(nV <= 16) sVersion = "v0.3.x"; else
		if(nV <= 27) sVersion = "v0.4.x"; else
		if(nV < 35) sVersion = "["+nV+"]"; else
		if(nV <= 54) sVersion = "v0.5.x"; else
		if(nV < 57) sVersion = "["+nV+"]"; else
		if(nV == 75) sVersion = "v.dev75/April Fools' 0.6pre0"; else
		if(nV <= 99) sVersion = "v.dev"+nV; else
		if(nV == 100) sVersion = "v0.6pre1"; else
		if(nV == 101) sVersion = "v0.6pre1 (dev101)"; else
		if(nV == 102) sVersion = "v0.6pre1 (dev102)"; else
		if(nV <= 115) sVersion = "v.dev"+nV; else
		if(nV == 116) sVersion = "v.0.6pre1.5"; else
		if(nV <= 131) sVersion = "v.dev"+nV; else
		if(nV == 132) sVersion = "v.0.6pre2"; else
		if(nV == 133) sVersion = "v.0.6pre3"; else
		if(nV <= 140) sVersion = "v.dev"+nV; else
		if(nV == 141) sVersion = "Tournament Edition"; else
		if(nV == 142) sVersion = "v.dev"+nV; else
		if(nV == 143) sVersion = "v.0.6pre4"; else
		if(nV <= 145) sVersion = "v.dev"+nV; else
		if(nV == 146) sVersion = "v.Pro (joke edition)"; else
		if(nV <= 157) sVersion = "v.dev"+nV; else
		if(nV == 158) sVersion = "v.0.6pre5"; else
		if(nV <= 160) sVersion = "v.dev"+nV; else
		if(nV == 161) sVersion = "v.0.6pre6"; else
		if(nV == 162) sVersion = "v.0.6pre7";
		else sVersion = "["+nV+"]";
		if(cV === 'i') {
			p = X.U32(0x14,_LE);
			if(p>0 && X.c("'INST'",p)) {
				ity = X.U16(0x10,_LE);
				switch(ity) {
					case 0: sVersion += "/std"; break;
					case 1: sVersion += "/FM (OPM/OPN)"; break;
					case 2: sVersion += "/Game Boy"; break;
					case 3: sVersion += "/C64"; break;
					case 4: sVersion += "/Amiga|smp"; break;
					case 5: sVersion += "/PC Engine"; break;
					case 6: sVersion += "/AY-3-8910"; break;
					case 7: sVersion += "/AY8930"; break;
					case 8: sVersion += "/TIA"; break;
					case 9: sVersion += "/SAA1099"; break;
					case 10: sVersion += "/VIC"; break;
					case 11: sVersion += "/PET"; break;
					case 12: sVersion += "/VRC6"; break;
					case 13: sVersion += "/OPLL"; break;
					case 14: sVersion += "/OPL"; break;
					case 15: sVersion += "/FDS"; break;
					case 16: sVersion += "/Vritual Boy"; break;
					case 17: sVersion += "/Namco 163"; break;
					case 18: sVersion += "/SCC"; break;
					case 19: sVersion += "/OPZ"; break;
					case 20: sVersion += "/POKEY"; break;
					case 21: sVersion += "/PC Speaker"; break;
					case 22: sVersion += "/WonderSwan"; break;
					case 23: sVersion += "/Lynx"; break;
					case 24: sVersion += "/VERA"; break;
					case 25: sVersion += "/X1-010"; break;
					case 26: sVersion += "/VRC6(saw)"; break;
					case 27: sVersion += "/ESS5506"; break;
					case 28: sVersion += "/MultiPCM"; break;
					case 29: sVersion += "/SNES"; break;
					case 30: sVersion += "/Sound Unit"; break;
					case 31: sVersion += "/Namco WSG"; break;
					default: sVersion += "/unk"
				}
				if(X.isVerbose()) {
					wvt = X.U16(p+0x18,_LE);
					smp = X.U16(p+0x1A,_LE);
					sOptionT(X.SC(p+0x0C,0x200,"UTF8"));
					sOption(" wvt:"+wvt+" smp:"+smp);
				}
			} //verbose inst
		} //inst
		if(cV === 'm' && X.isVerbose()) {
			p = X.U32(0x14,_LE);
			if(p>0 && X.c("'INFO'",p)) {
				t = X.SC(p+0x100,0x200,"UTF8");
				p1 = X.fSig(p+0x100,0x200,"00")+1;
				a = X.SC(p1,0x200,"UTF8");
				p1 = X.fSig(p1,0x200,"00")+1;
				ins = X.U16(p+0x16,_LE);
				wvt = X.U16(p+0x18,_LE);
				smp = X.U16(p+0x1A,_LE);
				ptng = X.U32(p+0x1C,_LE);
				freq = X.F32(p+0x0C,_LE);
				A4freq = X.F32(p1,_LE);
				s1spd = X.U8(p+9)+"/"+X.U8(p+0x0A);
				s1ptn = X.U16(p+0x10);
				s1ord = X.U16(p+0x12);
				sOptionT(t);
				sOptionT(a,"by: ");
				chips = []; el = false; i = 0;
				while(!el && (i<32)) {
					switch(X.U8(p+0x20+i)) {
					case 0: el = true; break;
					case 0x01: chips[i] = "YMU759 (17ch)"; break;
					case 0x02: chips[i] = "Genesis (10ch comp.)"; break;
					case 0x03: chips[i] = "SMS (SN76489) (4ch)"; break;
					case 0x04: chips[i] = "Game Boy (4ch)"; break;
					case 0x05: chips[i] = "PC Engine (6ch)"; break;
					case 0x06: chips[i] = "NES (5ch)"; break;
					case 0x07: chips[i] = "C64 (8580) (3ch)"; break;
					case 0x08: chips[i] = "Arcade (YM2151+SegaPCM) (13ch)"; break;
					case 0x09: chips[i] = "Neo Geo CD (YM2610) (13ch)"; break;
					case 0x42: chips[i] = "Genesis extended (13ch)"; break;
					case 0x43: chips[i] = "SMS (SN76489) + OPLL (YM2413) (13ch comp.)"; break;
					case 0x46: chips[i] = "NES+VRC7 (11ch)"; break;
					case 0x47: chips[i] = "C64 (6581) (3ch)"; break;
					case 0x49: chips[i] = "Neo Geo CD extended (16ch)"; break;
					case 0x80: chips[i] = "AY-3-8910 (3ch)"; break;
					case 0x81: chips[i] = "Amiga (4ch)"; break;
					case 0x82: chips[i] = "YM2151 (8ch)"; break;
					case 0x83: chips[i] = "YM2612 (6ch)"; break;
					case 0x84: chips[i] = "TIA (2ch)"; break;
					case 0x85: chips[i] = "VIC-20 (4ch)"; break;
					case 0x86: chips[i] = "PET (1ch)"; break;
					case 0x87: chips[i] = "SNES (8ch)"; break;
					case 0x88: chips[i] = "VRC6 (3ch)"; break;
					case 0x89: chips[i] = "OPLL (YM2413) (9ch)"; break;
					case 0x8A: chips[i] = "FDS (1ch)"; break;
					case 0x8B: chips[i] = "MMC5 (3ch)"; break;
					case 0x8C: chips[i] = "Namco 163 (8ch)"; break;
					case 0x8D: chips[i] = "YM2203 (6ch)"; break;
					case 0x8E: chips[i] = "YM2608 (16ch)"; break;
					case 0x8F: chips[i] = "OPL (YM3526) (9ch)"; break;
					case 0x90: chips[i] = "OPL2 (YM3812) (9ch)"; break;
					case 0x91: chips[i] = "OPL3 (YMF262) (18ch)"; break;
					case 0x92: chips[i] = "MultiPCM (28ch)"; break;
					case 0x93: chips[i] = "Intel 8253 (beeper) (1ch)"; break;
					case 0x94: chips[i] = "POKEY (4ch)"; break;
					case 0x95: chips[i] = "RF5C68 (8ch)"; break;
					case 0x96: chips[i] = "WonderSwan (4ch)"; break;
					case 0x97: chips[i] = "Philips SAA1099 (6ch)"; break;
					case 0x98: chips[i] = "OPZ (YM2414) (8ch)"; break;
					case 0x99: chips[i] = "Pokémon Mini (1ch)"; break;
					case 0x9A: chips[i] = "AY8930 (3ch)"; break;
					case 0x9B: chips[i] = "SegaPCM (16ch)"; break;
					case 0x9C: chips[i] = "Virtual Boy (6ch)"; break;
					case 0x9D: chips[i] = "VRC7 (6ch)"; break;
					case 0x9E: chips[i] = "YM2610B (16ch)"; break;
					case 0x9F: chips[i] = "ZX Spectrum (beeper) (6ch)"; break;
					case 0xA0: chips[i] = "YM2612 extended (9ch)"; break;
					case 0xA1: chips[i] = "Konami SCC (5ch)"; break;
					case 0xA2: chips[i] = "OPL drums (YM3526) (11ch)"; break;
					case 0xA3: chips[i] = "OPL2 drums (YM3812) (11ch)"; break;
					case 0xA4: chips[i] = "OPL3 drums (YMF262) (20ch)"; break;
					case 0xA5: chips[i] = "Neo Geo (YM2610) (14ch)"; break;
					case 0xA6: chips[i] = "Neo Geo extended (YM2610) (17ch)"; break;
					case 0xA7: chips[i] = "OPLL drums (YM2413) (11ch)"; break;
					case 0xA8: chips[i] = "Atari Lynx (4ch)"; break;
					case 0xA9: chips[i] = "SegaPCM (DefleMask compat.) (5ch)"; break;
					case 0xAA: chips[i] = "MSM6295 (4ch)"; break;
					case 0xAB: chips[i] = "MSM6258 (1ch)"; break;
					case 0xAC: chips[i] = "Commander X16 (VERA) (17ch)"; break;
					case 0xAD: chips[i] = "Bubble System WSG (2ch)"; break;
					case 0xAE: chips[i] = "OPL4 (YMF278B) (42ch)"; break;
					case 0xAF: chips[i] = "OPL4 drums (YMF278B) (44ch)"; break;
					case 0xB0: chips[i] = "Seta/Allumer X1-010 (16ch)"; break;
					case 0xB1: chips[i] = "Ensoniq ES5506 (32ch)"; break;
					case 0xB2: chips[i] = "Yamaha Y8950 (10ch)"; break;
					case 0xB3: chips[i] = "Yamaha Y8950 drums (12ch)"; break;
					case 0xB4: chips[i] = "Konami SCC+ (5ch)"; break;
					case 0xB5: chips[i] = "tildearrow Sound Unit (8ch)"; break;
					case 0xB6: chips[i] = "YM2203 extended (9ch)"; break;
					case 0xB7: chips[i] = "YM2608 extended (19ch)"; break;
					case 0xB8: chips[i] = "YMZ280B (8ch)"; break;
					case 0xB9: chips[i] = "Namco WSG (3ch)"; break;
					case 0xBA: chips[i] = "Namco 15xx (8ch)"; break;
					case 0xBB: chips[i] = "Namco CUS30 (8ch)"; break;
					case 0xBC: chips[i] = "MSM5232 (8ch)"; break;
					case 0xBD: chips[i] = "YM2612 extra features extended (11ch)"; break;
					case 0xBE: chips[i] = "YM2612 extra features (7ch)"; break;
					case 0xBF: chips[i] = "T6W28 (4ch)"; break;
					case 0xC0: chips[i] = "PCM DAC (1ch)"; break;
					case 0xC1: chips[i] = "YM2612 CSM (10ch)"; break;
					case 0xC2: chips[i] = "Neo Geo CSM (YM2610) (18ch)"; break;
					case 0xC3: chips[i] = "YM2203 CSM (10ch)"; break;
					case 0xC4: chips[i] = "YM2608 CSM (20ch)"; break;
					case 0xC5: chips[i] = "YM2610B CSM (20ch)"; break;
					case 0xC6: chips[i] = "K007232 (2ch)"; break;
					case 0xC7: chips[i] = "GA20 (4ch)"; break;
					case 0xDE: chips[i] = "YM2610B extended (19ch)"; break;
					case 0xE0: chips[i] = "QSound (19ch)"; break;
					case 0xFC: chips[i] = "Pong (1ch)"; break;
					case 0xFD: chips[i] = "Dummy System (8ch)"; break;
					case 0xFE: case 0xFF: chips[i] = "reserved for development"; break;
					default: chips[i] = "unk.";
					}
					if(!el) i++;
				}
				sOption("chips: "+chips.join("+"));
				sOption("ticks="+freq.toFixed(2)+"Hz, A4="+A4freq+"Hz");
				sOption("ins:"+ins+" wvt:"+wvt+" smp:"+smp+" glob.ptn:"+ptng);
				sOption("1st song's spd:"+s1spd+" ptn:"+s1ptn+" ord:"+s1ord);
			} //verbose mod
		} //mod
	}
	else if(X.c("'FWMP'00")) {
		sName = "Forgotten Worlds BGM module (.FW)"; bDetected = 1;
	}
	else if(X.c("'FXSM'")) { //same as AY Amadeus!
		sName = "Fuxoft AY Language module (.FXM)"; bDetected = 1;
	}
	else if(X.c("'GBRF'")) {
		sName = "Gameboy Ripped Format module (.GBR)"; bDetected = 1;
		t = X.SA(0x154,0x13);
		if(t != "") if(/^([a-zA-Z0-9_ -]{4,})/.test(t)) sOptions = sOptions.append(t);
	}
	else if(X.c("'GBS'01")) {
		bDetected = 1;
		sName = "Gameboy Sound System module (.GBS)";
		if(X.isVerbose())
			sOptionT(X.SA(0x10,0x20));
		tc = X.U8(4); if(tc > 1) sOption(tc,"×");
		if(X.isVerbose()) {
			sOptionT(X.SA(0x30,0x20),"by: ");
			sOptionT(X.SA(0x50,0x20));
		}
	}
	else if(X.c("'GDM'FE") && X.c("0D0A1A'GMFS'",0x44)) {
		//ref http://modland.com/pub/documents/format_documentation/General%20Digital%20Music%20(.gdm).txt
		sName = "General Digital Music module (.GDM)"; bDetected = 1;
		trkr = "";
		if(X.U16(0x4D,_BE)==0) trkr = "2gdm";
		sVersion = "v"+ X.U8(0x4B) +"."+ X.U8(0x4C);
		if(trkr != "") sVersion += "/"+trkr+" v."+X.U8(0x4F)+"."+X.U8(0x50);
		if(X.isVerbose()) {
			sOptionT(X.SA(0x04,0x20));
			sOptionT(X.SA(0x24,0x20),"by: ");
			tof = X.U16(0x74,_BE);
			switch(tof) {
				case 1: sOptionT("orig: MOD"); break;
				case 2: sOptionT("orig: MTM"); break;
				case 3: sOptionT("orig: S3M"); break;
				case 4: sOptionT("orig: 669"); break;
				case 5: sOptionT("orig: FAR"); break;
				case 6: sOptionT("orig: ULT"); break;
				case 7: sOptionT("orig: STM"); break;
				case 8: sOptionT("orig: MED")
			}
			sOptionT(X.SA(X.U32(0x8A,_BE),X.U32(0x8D,_BE)))
		}
	}
	else if(X.c("'GLUE'B8B3AABA")) {
		//TODO reverse & find calcsize
		sName = "GlueMon module (.GLUE)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(8,8));
			ins = X.U8(0x7F);
			ord = X.U8(0x9E); ptn = 0;
			for(i=0; i < ord; i++) { t = X.U8(0x9F+i); if(t != 0xFF && ptn < t) ptn = t }
			ptn++;
			sOption("ord:"+ord+" ptn:"+ptn+" ins:"+ins)
		}
	}
	else if(X.c("'NuFREDGRAY'",0x22)) {
		sName = "Fred Gray's module (.GRAY)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SA(0x50,0x100); pn = 0x50+t.length+1;
			a = X.SA(pn,0x100); pn += a.length+1;
			c = X.SA(pn,0x100);
			sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
		}
	}
	else if(X.c("'+SNT'")) {
		bDetected = 1;
		sName = "Beaver Sweeper module (.GTK)";
	}
	else if(X.c("'GTK'") && isWithin(X.U8(3),1,4) && X.U16(0xC4,_BE) <= 255 && isWithin(X.U16(0xC6,_BE),1,256)
	  && isWithin(X.U16(0xC8,_BE),1,32) && (ord = X.U16(0xCA,_BE)) <= 256 && X.U16(0xCC,_BE) <= ord
	  || X.c("'GT2'") && (X.U8(3) > 5 && X.U32(4,_BE) == 0xE4 || X.U32(4,_BE) == 0xEC+2*X.U16(0xEA,_BE))
	  && isWithin(X.U16(0xCA,_BE),1994,9999)) {
		//GTK from http://ldesoras.free.fr/src/gt2/dev_gtk.zip / formats.txt
		//GT2 from mod_gt2.cpp
		//+ input from the author Laurent "Dumbo" de Soras (http://ldesoras.free.fr)
		bDetected = 1;
		sigv = 0; bad = '';
		switch(X.U8(2)) {
			case 0x4B: sName = "Graoumf Tracker module (.GTK)"; sVersion = "Amiga "; sigv = 1; break;
			case 0x32: sName = "Graoumf Tracker 2 module (.GT2)"; sVersion = "PC " ; sigv = 2;
		}
		v = X.U8(3); sVersion += "v"+v;
		if(sigv == 1  && v < 6) switch(v) {
			case 1: sVersion += "/GT v0.7"; break;
			case 2: sVersion += "/GT v0.726"; break;
			case 3: sVersion += "/GT v0.731"; break;
			//lost in time
		}
		else {
			 if(v == 9) sVersion += "/GT r27"; //TODO maybe track down some other values
		}
		tracker = statln = '';
		if(sigv == 1) { //m68k GTK
			t = X.SC(4,0x20,'ISO8859-1').trim(); d = xc = "";
			c = X.SC(0x24,0xA0,'ISO8859-1').trim(); //the author says he never implemented this
			trk = X.U16(0xC8,_BE); ord = X.U16(0xCA,_BE); smp = 0; smprecs = X.U16(0xC4,_BE);
			rows = X.U16(0xC6,_BE); lp = X.U16(0xCC,_BE); smpinfosz = v <= 2 ? 0x30 : 0x40;
			sszofs = v == 1 ? 32 : 28; if(v >= 3) sszofs += 16; if(v >= 2) sszofs += 4;
			smp = smpsz = 0; p = 0xCE; for(i=0; i < smprecs; i++) {
				if(ssz = X.U32(p+sszofs,_BE)) { smpsz += ssz; smp++ }; p += smpinfosz;
				if(ssz%2) bad = bad.addIfNone('!oddsmpsz') }
			ptn = 0; for(i=0; i < ord; i++) { if((pt=X.U16(p,_BE)) > ptn) ptn = pt; p += 2 } ptn++;
			nn = v == 4 ? 5 : 4;
			songsz = 0x2CE + smprecs*smpinfosz + ptn*rows*trk*nn; //hdr, smpinfo, order, ptns
			sz = songsz+smpsz;
			statln = 'trk:'+trk+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+(lp?' lp:'+lp:'')+' sz:'+outSz(sz)
		}
		else { //x86 GT2
			t = X.SA(8,0x20).trim();
			d = X.U16(0xCA,_BE)+"-"+X.U8(0xC9).padStart(2,'0')+"-"+X.U8(0xC8).padStart(2,'0');
			c = X.SA(0x28,0xA0).trim(); tracker = X.SA(0xCC,0x18);
			pn = songhk = spd = bpm = ord = lp = mptn = ptn = ins = smp = trk = 0; sz = -1; xc = [];
			if(v < 6) { spd = X.U16(0xE4,_BE); bpm = X.U16(0xE6,_BE) }
			while(pn < X.Sz()) {
				hkhd = X.SA(pn,4); hksz = X.U32(pn+4,_BE); pn += 8;
				switch (hkhd) {
				case 'SONG': ord = X.U16(pn,_BE); lp = X.U16(pn+2,_BE);
					mptn = 0; p = pn+4; for(i=0; i<ord; i++,p+=2) mptn = Math.max(mptn,X.U16(p,_BE));
					mptn++; songhk++; break;
				case 'PATD': case 'PAIN': case 'PAFX': case 'PAMI': ptn++;
					trk = Math.max(trk, X.U16(pn+0x16,_BE)); break;
				case 'INST': ins++; break;
				case 'SAMP': case 'SAM1': case 'SAM2': smp++; break;
				case 'XCOM': xcc = X.U16(pn,_BE);
					xc = xc.push(X.SA(pn+2,xcc).trim());
					break;
				case 'TCN2':
					bpm = X.U16(pn+2,_BE)+(X.U16(pn+4,_BE) ? '.'+X.U16(pn+4,_BE) : '');
				case 'ENDC': sz = pn+hksz-8;
					break
				}
				pn += (hksz-8 > 0)? hksz - 8 : 0;
				if(sz >= 0) break; //or one could see a mod with no ENDC. gimmekuh!.gt2 (v==1)? Any modern TCN2 thing?
				if(charStat(next = X.SA(pn,4)).indexOf('allasc') < 0 //check for all 4ccs being ok
				  || next.toUpperCase() != next) break;
			}
			if(!songhk) bad = bad.addIfNone('!noSONG');
			if(sz <= 0) { sz = pn; bad = bad.addIfNone('!noendtag') }
			if(ptn != mptn) ptn = mptn+'/'+ptn;
			statln = statln.appendS((bpm?'bpm0:'+bpm+' ':'')+(spd?'spd0:'+spd+' ':'')+'trk:'+trk
				+' ord:'+ord+(lp?' lp:'+lp:'')+' ptn:'+ptn+' ins:'+ins+' smp:'+smp+' sz:'+outSz(sz),' ')
		}
		if(X.isVerbose()) {
			sOption(t); sOption(d,'on:'); sOption(tracker,'in:'); sOption(c);
			if(xc.length) sOption(addEllipsis(xc.join('\n'),0x100,0xA0),'msg: "','"');
			sOption(statln)
		}
		if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/')
	}
	else if(X.c("'NuH.DAVIES'",0x22)) {
		sName = "Howie Davies's module (.HD)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SA(0x64,0x100); pn = 0x64+t.length+1;
			a = X.SA(pn,0x100); pn += a.length+1;
			c = X.SA(pn,0x100);
			sOptionT(t);
			sOptionT(a,"by: ");
			sOptionT(c);
		}
	}
	else if(X.c("'HESM'00") && isWithin(X.U8(7),0x7D,0xFE) && X.c("FFF8", 8)
	  && isWithin(X.I8(0xA),-8,0x20) && isWithin(X.I8(0xB),-8,0x20) && isWithin(X.I8(0xC),-8,0x20)
	  && isWithin(X.I8(0xD),-8,0x20) && isWithin(X.I8(0xE),-8,0x20) && X.c("'DATA'", 0x10)
	  && [0,0xE0].indexOf(X.U8(0x14)) >= 0 && X.c("002000000000000000", 0x17) && X.Sz() >= 0x100) {
	 	//ref https://github.com/libgme/game-music-emu/blob/master/gme/Hes_Emu.cpp
		sName = "Hudson Entertainment System multitrack tune (.HES)"; bDetected = 1;
		sVersion = "v"+X.U8(4); startsong = X.U8(5); reqaddr = X.U16(6);
		dtsz = X.U32(0x14); dtaddr = X.U32(0x18);
		if(X.isVerbose()) {
			t = a = c = ""; p = 0x40;
			function getText() { if(X.U8(p)) {
				var len = 0x20; if(X.U8(p+0x1F) && !X.U8(p+0x2F)) len = 0x30; //some are longer than t'others
				var s = readBytes(p,len), z = s.indexOf(0); if(z < 0) return '';
				if(charStat(s.slice(0,z),1).indexOf('asc') < 0) return '';
				p += len; s = decEncoding(s,true,Chars0to1F); if(s == '<?>') return ''; else return s
			} else return '' }
			if(X.U8(p) >= 0x20) { sOptionT(getText()); sOptionT(getText(),"by:"); sOptionT(getText()) }
			sOption('from:'+startsong+' sz:'+outSz(dtaddr+dtsz))
		}
	}
	else if(X.c("'IM10'",0x3C) && X.U8(0x30)
		&& X.U8(0x31) >= 0x20 && X.U8(0x32) <= 0x40
		&& X.U8(0x33) >= 4 && X.U8(0x33) < 0x80) {
		//ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/imf_load.c
		sName = "Imago Orpheus module (.IMF)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0,0x20));
			sOption("tempo0:"+X.U8(0x30)+" bpm0:"+X.U8(0x31)
				+" ord:"+X.U16(0x20,_LE)+" ptn:"+X.U16(0x22,_LE)
				+" ins:"+X.U16(0x24,_LE));
		}
	}
	else if(X.c("'GTI5'")) {
		sName = "GoatTracker 2 Instrument (.INS)"; bDetected = 1
	}
	else if(X.c("'ISM!V1.2'")) {
		sName = "Sound Invasion Music System/In Stereo! module (.IS)"; bDetected = 1;
		sVersion = "v"+X.SA(5,3);
		if(X.isVerbose())
			sOptionT(X.SA(0x24,25))
	}
	else if(X.c("'IS20DF10STBL'")) {
		sName = "Sound Invasion Music System/In Stereo! module (.IS20)"; bDetected = 1;
		sVersion = "v2.0";
	}
	else if(X.c("'IXS!'")) {
		sName = "iXalance module (.IXS)"; bDetected = 1; sVersion = "compressed";
		if(X.isVerbose()) {
			sOptionT(X.SA(0x18,0x20)); sOption(outSz(0x38+X.U32(0x10)),'sz:')
		}
	}
	else if(X.c("'MUSE'DEADBEAF") || X.c("'MUSE'DEADBABE")) {
		sName = "Jazz Jackrabbit 2 container (.J2B)/Galaxy Sound System module";
		sVersion = "compressed"; bDetected = 1;
		//TODO: confirm if zlib extraction available; if so, derive the module info too
		//Unpacked module detection is in the sanity checks
	}
	else if(X.c("'NuJ.FLOGEL'",0x22)) {
		sName = "Janko Mrsic-Flogel's module (.JMF)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SA(0x54,0x100); pn = 0x54+t.length+1;
			a = X.SA(pn,0x100); pn += a.length+1;
			c = X.SA(pn,0x100);
			sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
		}
	}
	else if( X.c("2B7C.... ........ 2B7C.... ........ 2B7C.... ........ 2B7C.... ........ 303C00FF 32004EB9 ........ 4E75")
	  && X.I32(2,_BE) >= 0x2E) {
		sName = "Steve Turner's module (.JPO)"; bDetected = 1;
		if(X.isVerbose()) {
			//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SteveTurner/src/Steve%20Turner_v4.asm
			ofs = X.U32(2,_BE);
			p1 = p = X.U32(0x12,_BE)-ofs+0x2E;  d1 = 10;
			while(p <= Math.min(X.Sz(),0x100000)) {
				t = X.U16(p,_BE); if(t == 0xF0FF) break; p += 2;
				d0 = t; if(d0 > d1) d1 = d0;
			}
			sz = X.fSig(p1+d1,TOEOF,"FF"); if(sz > 0) sz++;
			p = X.U32(0xA,_BE)-ofs+0x2E; x = 0; // e4ec~e4fe
			while(p < X.Sz()) { d0 = X.U16(p,_BE)&0xFFF0; if(d0) break; p += 0x0C; x++ }
			if(X.c("2b7c0004449c0faa2b7c000479840fa22b7c00047ab40fa62b7c000458980fae"))
				x = 5; // paradroid90.jpo
			d0 = 0x14+X.U32(0x1A,_BE)-ofs;
			smp = (X.U32(d0,_BE) >> 2)-1;
			smpsz = X.U32(d0+4,_BE);
			if(x > 1) sOption(x,"×"); sOption("smp:"+smp+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz))
		}
	}
	else if(X.c("0040....0040....0040....0040....0040....0040",0x19D1) ) {
		//unreliable for potential new modules (but there's not gonna be even one)
		sName = "JayTrax module (.JXS)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(0xA6,0x40));
	}
	else if(X.Sz() > 4500 && X.c("000003F300000000000000030000000000000002000002")
	  && X.c("00000001000003E9000002", 0x1C) && X.c("B07C0000 6700???? B07C0001670009?? B07C0002670009", 0x30)
	  && X.c("4E754A79 0000028E 66D84A79 000002",0xAA) && X.c("4E7548E7 FFFFB1FC 00000000 67",0xD0)) {
		sName = "Kris Hatlelid's module player (SONGPLAY+.KH)"; bDetected = 1
	}
	else if((X.c("'KSCC'") && !X.U8(0xE) && !(X.U8(0xF) & 0xE0)) || //&meant to be 0xF0 but majutsushi*.kss have 0x10 there
	  (X.c("'KSSX'") && !X.U32(0x14))) {
		// ref https://sourceforge.net/p/nezplug/code/HEAD/tree/nezplug/trunk/src/nes/m_kss.c & trunk/in_nez.txt
		sName = "KSS chiptune (.KSS)"; bDetected = 1;
		if(X.c("'KSSX'")) { nv = 1; sVersion = "extended" } else nv = 0;
		bnk = X.U8(0xD) & 0x7F; bnk16 = X.U8(0xD) & 0x80 ? 1 : 2;
		if(bnk) sVersion = sVersion.appendS('banks'+(bnk16 == 1?'8k':'16k'), ',');
		chip = X.U8(0xF);
		if(X.isVerbose()) if(X.c("'MBM'",0x20)) sOptionT(X.SA(0x24,0x34));
		sz = 0x10+X.U8(0xE)+X.U16(6)+0x2000*bnk*bnk16;
		hnmmode = rammode = false; scc = true; ch = 1;
		if(chip & 2) {
			sVersion = sVersion+'#SEGA-MkIII(SMS)';
			if(chip & 4) { sVersion = sVersion+'/GameGear-Stereo'; ch = 2 }
			else sVersion = sVersion+'/SMS-SNG(SN76489)';
			if(chip & 1) sVersion = sVersion+'/FM-UNIT(YM2413)';
			if(chip & 0x88) rammode = true;
			scc = false
		} else {
			if(chip & 0x10) {
				if(chip & 8) { sVersion = sVersion+'#MSX-AUDIO-STEREO'; ch = 2 }
				else { sVersion = sVersion+'#MSX-AUDIO'; hnmmode = true }
			} else sVersion = sVersion+'#MSX-AUDIO';
			if(chip & 1) sVersion = sVersion+'/MSX-MUSIC';
			if(chip & 0x80) { rammode = true; scc = false }
			else {
				rammode = (chip & 4) != 0; scc = !rammode
			}
		} 
		if(rammode) sVersion = sVersion+'/RAM'; if(scc) sVersion = sVersion+'/SCC';
		if(hnmmode) sVersion = sVersion+'/牌の魔術師DAC';
		if(nv) if(X.U8(0xE) >= 0xB) if(X.isVerbose()) {
			x1 = X.U16(0x18); x2 = X.U16(0x1A);
			if(x2-x1 > 1) sOption('subsongs '+x1+'-'+x2);
		}
		if(X.isVerbose()) sOption('ch:'+ch+(bnk?' ex.bnk:'+bnk:'')+' sz:'+outSz(sz))
	}
	else if(X.c("'cyd!song'") ) {
		//ref https://github.com/kometbomb/klystron/tree/master/src/snd/music.c , .h : mus_load_song_RW
		sName = "Klystrack module (.KT)"; bDetected = 1;
		v = X.U8(8); sVersion = "v"+v;
		if(X.isVerbose()) {
			pn = 9;
			if(v >= 6) trk = X.U8(pn++); else if(v > 3) trk = 4; else trk = 3;
			timesig = X.U8(pn++)+"/"+X.U8(pn++);
			if(v >= 17) pn+=2; //seqstep;
			instc = X.U8(pn++);  patc = X.U16(pn,_LE); pn += 2;
			pn += trk*2; //seq cnt per track, ignore
			songlen = X.U16(pn,_LE); pn += 2; /*loopat = X.U16(pn,_LE);*/ pn += 2;
			if(v >= 12) pn++; //master_volume
			spd = X.U8(pn++); spd2 = X.U8(pn++);
			rate = X.U8(pn++); //bpm?...
			if(v > 2) pn += 4; if(v >= 9) pn++; if(v >= 16) pn++; //flags, mulpd, ptcin

			tlen = 17; if(v >= 11) tlen = X.U8(pn++);
			if(v >= 5) tlen = Math.min(tlen,65);
			sOptionT(X.SA(pn,tlen));
			sOption("trk:"+trk+" tsig:"+timesig+" bpm:"+rate+" ins:"+instc+
				" len:"+Hex(songlen)+" ptn:"+Hex(patc)+" spd:"+spd+"-"+spd2)
		}
	}
	else if(X.c("'cyd!inst'") ) {
		sName = "Klystrack instrument (.KI)"; bDetected = 1
	}
	else if(X.c("'cyd!efex'") ) {
		sName = "Klystrack effects (.KF?)"; bDetected = 1
	}
	else if( X.c("'ADL '0000....'MDhd'00000008000080..00000080'MThd'00000006000200..01E0'MTrk'0000....00FF03")
	  || ( X.U32(0,_LE)==X.Sz() && X.c("'AD'",4) ) ) {
		sName = "Lucas Arts Adlib chiptune (.LAA)"; bDetected = 1;
		if(X.SA(0,4)==="ADL ") sVersion = "new"; else sVersion = "old"
	}
	else if( X.c("'Liquid Module:'") ||
	  ( (X.isHeuristicScan() || X.c("'NO'"))
		 && extIs("liq"))
	  ||  X.c("21",0x01D6) && X.c("21",0x0AD2) && X.c("21",0x0BE6)
	  && X.c("FF",0x0ED4) && X.c("FF",0x0EE8) ) {
		sName = "Liquid Tracker module (.LIQ)"; bDetected = 1;
		sVersion = X.SA(0x41,0x14).trim();
		if(X.isVerbose()) {
			if(X.SA(0,2) == "NO")
				sOptionT(X.SA(5,0x14))
			else if(X.SA(0,3) == "Liq") {
				sOptionT(X.SA(0x0E,0x32));
				sOptionT(X.SA(0x0E,0x0F),"by: ");
			}
		}
	}
	else if(X.c("'LME'00'(c)1990 Leggless/Silents UK     ") ) {
		sName = "Leggless module (.LME)"; bDetected = 1
	}
	else if(X.c("0C'Nusa-team 89a'10610A6108610678006000",0x169)
	  && X.c("'dynamite89'",0x234)) {
		sName = "Music Assembler module (.MA)"; bDetected = 1
	}
	else if(X.c("'MAD+'")) {
		sName = "Mlat Adlib Tracker module (.MAD)"; bDetected = 1
	}
	else if(X.c("'MADG'") ) {
		sName = "B. Birney's PlayerPro module (.MAD)"; bDetected = 1;
		if(X.isVerbose())
			sOption(X.SA(4,0x12))
	}
	else if(X.c("D040D0404EFB")) {
		sName = "Mark Cooksey's module (.MC)"; sVersion = "new"; bDetected = 1
	}
	else if(X.c("'TFMX-SONG'")) {
		//TODO deal with the tags, because sometimes the field is "by blahblah",
		// and other times it's like a bitfield
		sName = "TFMX or TFMX ST module (.MDAT,.MDST)"; bDetected = 1
	}
	else if(X.c("'DMDL'..'IN'")) {
		sName = "Digitrakker module (.MDL)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0x0B,32));
			sOptionT(X.SA(0x2B,20),"by: ");
		}
	}
	else if(X.c("'MMDC'")) { //before the more generic compare("'MMD'")
		sName = "Tony Crowther's packed MED module (.MMDC)"; bDetected = 1;
		if(X.isVerbose()) sOption(outSz(X.U32(4,_BE)),"sz:")
	}
	else if(X.c("'GameBoy Music Module'",0x0C)) {
		sName = "Gameboy Tracker module (.MGB)"; bDetected = 1
	}
	else if(X.c("'MGT'11BD'MCS'")) {
		sName = "Megatracker module (.MGT)"; bDetected = 1;
		sOptionT(X.SA(0x3A,0x20))
	}
	else if(X.c("'MThd'") && X.c("'MTrk'",8+X.U32(4,_BE))
	  && X.U16(8,_BE) <= 2) {
		//ref. spec. from midi.org: RP-001_v1-0_Standard_MIDI_Files_Specification_96-1-4.pdf
		sName = "Standard MIDI File (.MID)"; sVersion = "v1.0";
		nV = X.U16(8,_BE); bad = ""; bDetected = 1;
		switch(nV) {
		case 0: sVersion += " t.0:one track"; break;
		case 1: sVersion += " t.1:tracks"; break;
		case 2: sVersion += " t.2:tracks+tempo"
		}
		bDetected = 1;
		trk = X.U16(0x0A,_BE); if(trk > 1) sOption(trk,"trk:");
		if(nV == 0 && trk != 1) bad += "!badvertrk";
		sz = p = 0x0E; txt = by = title = lyr = "";
		for(i=0; i < trk; i++) {
			var readmore = 20; //mostly enough to catch the info messages; set to 0xFFFFFFFF for all of 'em
			if(!X.c("'MTrk'",p) || (i && !X.c("FF2F00",p-3))) { bad += "!badtrk"; break }
			len = X.U32(p+4,_BE); p += 8; sz = p;
			while(readmore && p < sz+len && p < X.Sz()) {
				readmore--; dt = readVarUInt(p);
//logpart1="@"+Hex(p)+" on "+dt+": ";
				p += dt[0];
				switch(X.U8(p++)) {
				case 0xF0: case 0xF7: t = readVarUInt(p); p += t[0]+t[1]; break;
				case 0xFF: a = X.U8(p++);
//_log(logpart1+"["+readVarUInt(p)+"] meta event "+Hex(a));
					switch(a) {
						case 0: if(X.U8(p++) != 2) readmore = 0; else p += 4; break;
						case 3: t = readVarUInt(p); p += t[0];
							title = title.append(X.SC(p,t[1],'CP1252')); p += t[1];
							break;
						case 2: t = readVarUInt(p); p += t[0];
							by = by.append(X.SC(p,t[1],'CP1252')); p += t[1]; break;
						case 1: t = readVarUInt(p); p += t[0];
							txt = txt.append(X.SC(p,t[1],'CP1252')); p += t[1]; break;
						case 5: t = readVarUInt(p); p += t[0];
							lyr = lyr.append(X.SC(p,t[1],'CP1252',"-")); p += t[1];
							break;
						case 4: case 6: case 7: t = readVarUInt(p); p += t[0]+t[1]; break; //ins., lyrics, markers, cues
						case 0x20: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 1) {
							readmore = 0; bad += "!badprefix@"+(p-t[0]-t[1]) } break
						case 0x2F: t = readVarUInt(p); p += t[0]+t[1]; if(t[1])
							bad += "!badEoTtag@"+(p-t[0]-t[1]); readmore = 0; break;
						case 0x51: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 3) {
							readmore = 0; bad += "!badtempo@"+(p-t[0]-t[1]) } break;
						case 0x54: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 5) {
							readmore = 0; bad += "!badSMPTE@"+(p-t[0]-t[1]) } break;
						case 0x58: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] < 2 || t[1] > 4) {
							readmore = 0; bad += "!badtime@"+(p-t[0]-t[1]) } break;
						case 0x59: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 2) {
							readmore = 0; bad += "!badkey@"+(p-t[0]-t[1]) } break;
						default: t = readVarUInt(p); p += t[0]+t[1]
					} break;
				default: //p += readVarUInt(p)[0];
//_log(logpart1+"default...");
				}
				if((txt != "" && by != "" && title != "") || p-sz > 0x200) readmore = 0;
			}
			sz += len; p = sz; if(p>X.Sz()) { bad += "!short"; break }
		} //for..trk
		if(bad) sVersion += "/malformed"+bad;
		if(X.isVerbose()) {
			sOptionT(title); sOptionT(by,"by: "); sOptionT(txt);
			sOption(outSz(sz),"sz:")
		}
	} else if(X.c("'SMF2CLIP'")) {
		//ref. spec. from midi.org: M2-104-UM, MA06
		sName = "MIDI Clip File (.midi2)"; sVersion = "v2.0"; bDetected = 1;
	}
	else if(X.c("'MKJamz'")) {
		sName = "MK-Jamz module (.MKJ)"; bDetected = 1
	}
	else if(X.c("'MLEDMODL'000000")
	  && X.c("'VERS'",X.U32(8,_BE)+12)) {
		sName = "Musicline Editor module (.ML)"; bDetected = 1;
		p = 4; //header skipped
		t = ""; c = ""; x = 0; ch = smp = ins = ptn = 0;
		while (p < X.Sz()) {
			hkhd = X.SA(p,4); hksz = X.U32(p+4,_BE);
//_log(hkhd+': ['+Hex(p)+'..'+Hex(p+hksz)+']')
			if(charStat(hkhd).indexOf('allasc') < 0) break; //break off if it doesn't look like a 4cc
			p += 8;
			switch (hkhd) {
			case "VERS":
				v = X.U16(p,_BE); //skips MODL and VERS 4cc+4sz
				sVersion = "v"+(v>>8)+"."+((v&0xF0)>>4)+(v&0x0F);
				break;
			case "TUNE":
				x ++; t = t.appendS(X.SC(p,hksz,'CP1252').trim(),'; ');
				tmp0 = X.U16(p+0x20,_BE); spd0 = X.U8(p+0x22,_BE);
				groove = X.U8(p+0x23,_BE); ch = X.U8(p+0x27);
				//v0.79 through v1.06 = hksz broken, v1.10+ good, no samples of 1.07~1.09 :( We'll calc
				//if(v <= 0x106) {
				chsz = 0; hksz = 0x28; //we'll recalc the chunk size instead
				for(i=0; i < ch; i++) { chsz += X.U32(p+hksz,_BE); hksz += 4 }
				hksz += chsz; //fetch chdata sizes, skip data
				//}
				break;
			case "INFO":
				q = 0;
				while(q < hksz) {
					z = X.fSig(p+q,hksz-q,"00");
					if(z > -1) l = z-p-q;
					else l = hksz;
					c += X.SC(p+q,l,'CP1252')+"\n";
					q += l+1;
				} break;
			case "PART": ptn++; break
			case "INST": ins++; break
			case "SMPL":
				smp++; p += 6; break;
			}
			p += hksz;
		}
		if(X.isVerbose()) {
			if(x > 1) sOption(x,'×');
			sOptionT(t); sOptionT(addEllipsis(c,0xA0,0x80),'msg: "','"');
			sOption('ch:'+ch+' ptn:'+ptn+' ins:'+ins+' smp:'+smp+' sz:'+outSz(p));
		}
	}
	else if(X.c("'FORM'........'MMV8SDAT'................'SE'")) {
		sName = "Music Maker EXT module (.MM8,.MM4)"; bDetected = 1;
		sVersion = "v8";
		if(X.isVerbose()) {
			sOptionT(X.SA(0x1A,0x14));
			sOptionT(X.SA(0x36,0x29));
			sOption(outSz(X.I32(4,_BE)+8),"sz:")
		}
	}
	//TODO insert the SDATA format of Music Maker
	else if(X.c("'SEI1XX'00")) { //TODO find more than 2 files to test on :)
		sName = "Music Maker STD instrument (.IP)"; bDetected = 1;
		sVersion = "v8 old";
	}
	else if(X.c("'MO3'")) {
		sName = "MO3 MOD module (.MO3)"; bDetected = 1
	}
	else if(X.c("'RASP'",0x438)) {
		// TODO find out what format it was...
		sName = "Generic module (.MOD)"; bDetected = 1;
		sVersion = X.SA(0x438,4);
		if(X.isVerbose()) {
			sOptionT(X.SC(0,0x14,"IBM850"));
			sOptionT(X.SC(0x14,0x16,"IBM850"),"by/inst: ")
		}
	}
	else if(X.c("08'MONOTONE'") && X.Sz() >= 0x15F) {
		//ref https://github.com/MobyGamer/MONOTONE/blob/master/MTSRC/MT_SONG.PAS
		sName = "MONOTONE module (.MON)"; bDetected = 1;
		sVersion = "v"+X.U8(0x5B);
		if(X.isVerbose()) {
			sOptionT(X.UCSD(0x09));
			sOptionT(X.UCSD(0x32));
			ptn = X.U8(0x5C); trk = X.U8(0x5D); cellsz = X.U8(0x5E);
			ord = 0; i = 0x5F; do { t = X.U8(i++); if(t != 0xFF) ord++ } while(i < 0x15F && i != 0xFF)
			sOption('trk:'+trk+' ord:'+ord+' ptn:'+ptn+' sz:'+outSz(0x15F+0x40*ptn*trk*cellsz));
		}
	}
	else if(/SONG[0-9.]{4}(COMP|NORM)/.test(X.SA(0,12))) {
		sName = "Megastation track (.MS)"; bDetected = 1;
		sVersion = "v"+X.SA(4,5).toLowerCase()
	}
	else if(/SNGM[0-9.]{4}(COMP|NORM)/.test(X.SA(0,12))) {
		sName = "Megastation MOD module (.MSM)"; bDetected = 1;
		sVersion = "v"+X.SA(4,5).toLowerCase()
	}
	else if(X.c("'MSOB'00")) {
		sName = "Medley module (.MSO)"; bDetected = 1
	}
	else if(X.c("'MTC1'00") && X.U8(5) < 0x10 && X.Sz() >= (sz=X.U32(4,_BE)+8)) {
		sName = "Multi-track Container module (.MTC)"; bDetected = 1;
		if(X.isVerbose()) {
			name = auth = c = ''; p = 8; x = 0; //ignoring PROP--it's never useful
			while(p < sz) {
				hkhd = X.SA(p,4); hksz = X.U32(p+4,_BE); p += 8;
				switch(hkhd) {
				case "NAME": name = name.addIfNone(decAnsi(p,hksz,CPSpeccy),'/'); break;
				case "AUTH": auth = auth.addIfNone(decAnsi(p,hksz,CPSpeccy),'/'); break;
				case "ANNO": c = c.addIfNone(decAnsi(p,hksz,CPSpeccy),'/'); break;
				case "TRCK": x++; break
				}
				p += hksz; if(p%2) p++
			}
			sOptionT(name); sOptionT(auth,'by:'); sOptionT(addEllipsis(c,0xA0));
			sOption('trk:'+x+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'mpu401tr'92'kk'EE'r'")) {
		sName = "MPU-401 Trakker Adlib module (.MTK)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.UCSD(0x18); sOptionT(t);
			sOptionT(X.SA(0x18+t.length+2),"by: ")
		}
	}
	else if(X.c("'MTM'")) {
		//ref https://github.com/libxmp/libxmp/blob/master/docs/formats/Mtm-form.txt
		sName = "Multitracker module (.MTM)"; bDetected = 1;
		v=X.U8(3); sVersion = "v"+(v>>4)+"."+(v & 0x0F);
		if(X.isVerbose()) {
			sOptionT(X.SA(4,20));
			trk = X.U16(24); ptn = X.U8(26);
			ord = X.U8(27)+1; csz = X.U16(28);
			nos = X.U8(30); bpt = X.U8(32); smpsz = 0;
			for(i=66+22; i < 66+22+nos*37; i += 37) smpsz += X.U32(i);
			pxc = 194+nos*37+trk*192+(ptn+1)*32*2;
			c = X.SA(pxc,csz); sz = pxc+csz+smpsz;
			if(c.length < csz) c = c.trim()+' <...>';
			sOptionT(c);
			sOption('trk:'+(trk+1)+' ord:'+(ord+1)+' ptn:'+(ptn+1)+' smp:'+nos+' sz:'+outSz(sz))
		}
	}

	else if(X.c("'MT20'") && X.Sz() >= 0x184 && X.U8(9) == 2
	  && X.U16(0x70) < 64 && X.U16(0x6A) <= 0x100) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mt2.cpp
		sName = "MadTracker 2 module (.MT2)"; nV = X.U8(8); bDetected = 1;
		sVersion = "v"+X.U8(9)+"."+nV.toString(16).padStart(2,'0');
		bad = "";
		ord = X.U16(0x6A); loop = X.U16(0x6C);
		ptn = X.U16(0x6E); trk = X.U16(0x70);
		flags = X.U32(0x76);
		ins = X.U16(0x7A); smp = X.U16(0x7C);
		hasdrums = X.U16(0x17E) != 0; //:477
		if(hasdrums) dptn = X.U16(0x180); else dptn = 0; // this one line's :789
		p = 0x184+(hasdrums?0x112:0); //180 = filehdr+hasdrums+orders
		addp = p+X.U32(p-4); msg = ""; vst2 = 0; igskip = 0;
//_log("addp @"+Hex(addp));
		smpszs = []; insszs = []; inss = []; smps = []; extsmp = [];
		while(p < addp) { //:578
			hkhd = X.SA(p,4); hksz = X.U32(p+4); p += 8;
			switch(hkhd) {
				case "MSG": showmsg = X.U8(p);
					msg = X.SC(p+1,hksz-1,'CP1252')
					  .replace("\r","\n").replace("\n\n","\n"); break;
				case "SUM": artist = X.SC(p+6,hksz-6,'CP1252');
					if(artist == "Unregistered") artist == ""; break;
				case "VST2": vst2 = X.U32(p); break;
			}
			p += hksz
		}
//_log("ptns @"+Hex(p));
		if(p > addp) bad = bad.addIfNone("!badaddsz");
		else if(p > X.Sz()) bad = bad.addIfNone("!short");
		else {
			for(i=0; i < ptn && p < X.Sz(); i++) p += 6+((X.U32(p+2)+1)&~1)
//_log("drums? @"+Hex(p));
			if(hasdrums) { //:786
				for(i=0; i < dptn && p < X.Sz(); i++) { //:811
					p += 2+X.U16(p)*32 }
			}
//_log("atmt @"+Hex(p));
			if(flags & 2) { //automation; 8 is drum autn., 0x10 is master autn.
				env = trk + (flags & 8) + ((nV >= 0x50) ? vst2 : 0) + ((flags & 0x10) ? 1 : 0);
				for(q = 0; q < ptn; q++)
					for(e = 0; e < env && p+4 <= X.Sz(); e++) {
						if(nV >= 3) { fl = X.U32(p); p += 8 }
						else { fl = X.U16(p); p += 4 }
						while(fl) {
							if(fl&1) p += 0x104; fl >>= 1;
							if(fl<0)fl=-fl //fixes something suspiciously like a JS glitch
						}
					}
			}
		}
//_log("ins @"+Hex(p));
		if(p > X.Sz()) bad = bad.addIfNone("!short");
		else {
			for(i=0; i < 255; i++) {
				if(!msg && inss.length < 3) {
					t = X.SC(p,0x20,'CP1252'); if(t.trim() != "") inss.push(t) }
				dtlen = X.U32(p+0x20); if(dtlen == 0x20) dtlen += 0x18C;
				if(nV > 1 && dtlen) dtlen += 4;
				if(dtlen) igskip += X.U16(p+0x24)<<3; p += 0x24+dtlen;
			}
		}
//_log("smp @"+Hex(p));
		if(p > X.Sz()) bad = bad.addIfNone("!short");
		else {
			for(i=0; i < 256; i++) {
				if(!msg) {
					t = X.SA(p,0x20); if(i < smp && t.trim() != "") smps.push(t) }
				dtlen = X.U32(p+32); p += 0x24;
				if(dtlen && i < smp) {
					slen = X.U32(p); sfl = X.U8(p+10);
					if(sfl&5) smpszs.push({"ext":1,"slen":0}); else if(slen) smpszs.push({"ext":0,"slen":slen});
					p += dtlen;
				}
			}
			p += igskip;
			if(p > X.Sz()) bad = bad.addIfNone("!short");
			else for(i=0; i < smp && p < X.Sz(); i++) {
				if(smpszs[i]["ext"]) {
					slen = X.U32(p); p += 0x10;
					iextsmp.push(X.SA(p,slen));
					p += slen;
				} else p += smpszs[i]["slen"];
			}
		}
		if(bad != "") sVersion += "/malformed"+bad
		if(X.isVerbose()) {
			sOptionT(X.SA(0x2A,0x40));
			if(msg != "")
				if(showmsg) sOption(addEllipsis(msg,0x80),"msg: ");
				else sOption("("+addEllipsis(msg,0x80)+")","msg: ");
			else if(inss.length) sOption("\""+addEllipsis(inss.join(" "),200)+"\"","ins/msg:");
			else if(smps.length) sOption("\""+addEllipsis(smps.join(" "),200)+"\"","smp/msg:");
			sOption("ord:"+ord+" loop:"+loop+" ptn:"+ptn+(hasdrums?"+"+dptn:"")+" ins:"+ins+
			  " smp:"+smp+(extsmp.length > 0 ? " ext.smp: ["+extsmp.join(",")+"]" : "")+
			  " trk:"+trk+(hasdrums?"+8":"")+(vst2?("vst2:"+vst2):"")+" sz:"+outSz(p))
		}
	}

	else if(X.c("'MTRAC'")) {
		sName = "Master Tracker Adlib module (.MTR)"; bDetected = 1;
		//TODO
		// x = readBytes(0,X.fSig(0,TOEOF,"1A"));
		// tt = X.SA(5,20).trim();

		// if(v!=null) sVersion = "v"+v;
		// if(X.isVerbose())
		//     sOptionT(X.SA(0x2A,0x40))
	}
	else if(X.c("'MVSM1'")) {
		sName = "MVS Tracker module (.MUS)"; bDetected = 1
	}
	else if(X.c("'MUS'1A") && (X.U16(4) >= X.U16(6))) {
		sName = "DOOM music module (.MUS)"; bDetected = 1;
		if(X.isVerbose())
			sOption(outSz(X.U16(4)),'sz:')
	}
	else if(X.c("'MVM'00")) {
		sName = "MVX Module (.MVM)"; bDetected = 1
	}
	else if(X.c("'MXM'00")) {
		sName = "Cubic Tiny XM module (.MXM)"; bDetected = 1
	}
	else if(X.c("'MXTX'00")) {
		sName = "MaxTrax module (.MXTX)"; bDetected = 1
	}
	else if(X.c("'TWNNSNG1'00")) { // looks like the first ProTrekkr was called this
		sName = "NoiseTrekker module (.NTK)"; bDetected = 1;
		sVersion = "v1.6b";
		if(X.isVerbose())
			sOption(X.SA(9,20))
	}
	else if(X.c("'TWNNSNG2'00")) { // looks like the first ProTrekkr was called this
		sName = "NoiseTrekker module (.NTK)"; bDetected = 1;
		sVersion = "v2.0";
		if(X.isVerbose())
			sOption(X.SA(9,20))
	}
	else if((X.c("'TWNNSNG'..00") && ["6","G","I"].indexOf(X.SA(7,1) >= 0))
	  || X.c("'PROTREK'")) {
		sName = "ProTrekkr module (.PTK)"; bDetected = 1;
		sVersion = "v"+X.SA(0x7,1);
		if(X.isVerbose()) // this stuff has compression in most versions, TODO?
			if(sVersion == "v6") sOption(X.SA(9,20))
	}
	else if(X.c("'NESM'1A") || X.c("'NSFE'")) {
		//ref http://www.nesdev.org/wiiki/NSF
		sName = "Nintendo Sound Format audio (."; bDetected = 1;
		sig = X.SA(0,4);
		if(sig === "NSFE") filever = "NSFe";
		else if(X.U8(5) == 2) filever = "NSF2";
		else filever = "NSF";
		sName += filever+")";
		INFOready = false; NEND = false; hkhd = ""; pn = 4;
		nsf2jump = palntsc = sz = -1; spd = ""; vrc7repl = 0;
		playtime = Number(0); g=""; t=""; a=""; c=""; taut=[]; tlbl=[]; xc="";
		if(filever === "NSFe") {
			if(X.isVerbose()) //no base header, find INFO first
			  while(pn < X.Sz()){
				hksz = X.U32(pn,_BE); hkhd = X.SA(pn+4,4);
				if(hkhd === "INFO") break;
				else pn += hksz+8;
			}
			nsf2jump = 0;
		}
		else { //NSF v1/2 have a hardplaced header, let's read that
			bDetected = 1;
			sVersion = "";
			if(X.isVerbose()) {
				palntsc = X.U8(0x7A);
				chip = X.U8(0x7B);
				tc = X.U8(6); t = X.SA(0x0E,0x20);
				a = X.SA(0x2E,0x20); c = X.SA(0x4E,0x20);
			}
			INFOready = true;
			nsf2jump = X.U24(0x7D,_LE);
			pn += nsf2jump; //NSF v1 files ALSO can have this jump... orz
		}
		// at this point we're standing at the first hunk unless it's a pure NSF, and at "INFO" for NSFe
		if(nsf2jump >= 0 && filever === "NSFe" && X.isVerbose())
			while(pn < X.Sz() && !NEND) {
				hksz = X.U32(pn,_LE); hkhd = X.SA(pn+4,4); pn+=8;
				switch (hkhd) {
					case "NEND": //end of hunks
						NEND = true; break;
					case "INFO": //really shouldn't happen with nsf1/2
						if(INFOready) break;
						palntsc = X.U8(pn+6);
						chip = X.U8(pn+7);
						tc = X.U8(pn+7);
						INFOready = true; break;
					case "RATE":
						spd = "rate :: NTSC: "+X.U16(pn,_LE)+" ticks";
						palspd = X.U16(pn+2,_LE);
						if(palspd > 0) spd += ", PAL: "+palspd+" ticks";
						dendyspd = X.U16(pn+4,_LE);
						if(dendyspd > 0) spd += ", Dendy: "+dendyspd+" ticks";
						break;
					case "VRC7":
						vrc7repl = X.U8(pn); break;
					case "time":
						for(i=0; i*4 < hksz; i++) {
							if(i*4 >= hksz) curtime=-1
							else curtime = X.I32(pn+i*4,_LE);
							if(curtime < 0) curtime = 2*60000; //default time: 2 minutes :V
							playtime += curtime/1000;
						} break;
					case "auth":
						apn = 0;
						g = X.SC(pn,0x100,"UTF8"); apn+=g.length+1;
						a = X.SC(pn+apn,0x100,"UTF8"); apn+=a.length+1;
						c = X.SC(pn+apn,0x100,"UTF8"); apn+=c.length+1;
						c += ", rip: "+X.SC(pn+apn,0x100,"UTF8");
						break;
					case "taut":
						if(X.isDeepScan()) {
							apn = 0;
							while(apn < hksz) {
								trkauth = X.SC(pn+apn,hksz-apn,"UTF8"); apn += trkauth.length+1;
								taut.push(trkauth)
							}
						} break;
					case "tlbl":
						if(X.isDeepScan()) {
							apn = 0;
							while(apn < hksz) {
								trklbl = X.SC(pn+apn,hksz-apn,"UTF8"); apn += trklbl.length+1;
								tlbl.push(trklbl)
							}
						} break;
					case "text":
						if(X.isDeepScan())
							xc = X.SC(pn,hksz,"UTF8");
				}
				pn += hksz; sz = pn;
			}
		if(X.isVerbose() && INFOready) {
			switch(palntsc) {
				case 0: sVersion += " NTSC"; break;
				case 1: sVersion += " PAL"; break;
				case 2: sVersion += " NTSC/PAL"
			}
			if(chip&0x01) sVersion += "#VRC6";
			if(chip&0x02) switch(vrc7repl) {
				case 1: sVersion += "#YM2413"; break;
				default: sVersion += "#VRC7" }
			if(chip&0x04) sVersion += "#FDS";
			if(chip&0x08) sVersion += "#MMC5";
			if(chip&0x10) sVersion += "#Namco163";
			if(chip&0x20) sVersion += "#Sunsoft5B";
			sOptionT(t);
			if(tc > 1) sOption(tc,"×");
			sOptionT(a,"by: ");
			sOptionT(c);
		}
		if(X.isVerbose()) {
			if(playtime > 1)
			  sOption(new Date(Math.round(playtime*1000)).toISOString().substr(11, 8),"Playtime: ");
			if(Math.max(tlbl.length,taut.length)>0) sOption("[Tracks]");
			for (i=0; i < Math.max(tlbl.length,taut.length); i++) {
				if(i < tlbl.length) ttlbl = tlbl[i];
				else ttlbl = "#"+(i+1);
				if(i < taut.length) ttaut = " by: "+taut[i];
				else ttaut = "";
				sOption(ttlbl+ttaut)
			}
			if(xc != "") sOption(xc,"[Commentary]: ");
			if(sz > -1) sOption(outSz(sz),"sz:")
		}
	}
	else if(X.c("'OKTASONG'")) {
		sName = "Oktalyzer module (.OKTA)"; bDetected = 1;
		if(X.isVerbose()) {
			sOption("[Sample names]");
			for(i=0x20; i <= 0x100; i+=0x20)
				sOptionT(X.SA(i,0x14))
		}
	}
	else if(X.c("'Onyx Music File'1A")) {
		// ref ftp://http.modland.com/pub/documents/format_documentation/Onyx%20Music%20File%20(.omf).txt
		sName = "Onyx Music File (.OMF)"; bDetected = 1;
		if(X.isVerbose()) {
			ch = X.U8(0x192)+1;
			ptn = X.U8(0x193)+1;
			ord = X.U8(0x194)/2+1;
			t = X.SA(0x195,0x1F);
			if(t == "") {
				sn=[];
				for(i=0; i < 31; i++) {
					s = X.SA(0x1B4+i*28,21);
					if(s != "") sn.push(s)
				}
				t = sn.join("\n")
			}
			sOptionT(t);
		}
	}
	else if((X.c("'Org-02'") || X.c("'Org-03'")) && X.Sz() >= 114) {
		sName = "Organya module (.ORG)"; bDetected = 1;
		if(X.U8(5) === 0x32) sVersion == "v1"; else sVersion = "v2";
		if(X.isVerbose()) {
			c = ins = 0; for(i=0; i < 16; i++) {
				t = X.U16(0x16+6*i); if(t) { ins++; c += t }
			}
			sOption("ins:"+ins+" tempo:"+X.U16(6)+" rhythm:"+X.U8(8)
			  +"/"+X.U8(9)+" notes:"+c+" sz:"+outSz(114+(c<<3)))
		}
	}
	else if(X.c("'OBISYNTHPACK'") && X.Sz() > 0x510 && ( X.c("0100",0x50C) || !X.U32(0x50C,_BE) )) {
		sName = "Karsten 'Obi' Obarski's Synth Pack module (.OSP+SMP.set)"; bDetected = 1;
		if(X.isVerbose()) { //from initplayer RE
			p = a2 = 0xC; // @650
			for(x = ord = 0, d1 = 0x40; p < X.Sz() && d1--;) {
				d2 = X.U32(p,_BE); p += 4; if(!d2) break; ord++; d2 &= 0xFFFF;
				if(d2 == 0xF0) x++; // @656~664
			}
			for(d3 = d6 = 0; a2 < X.Sz() && d6 < 0x40;) { // @668~698
				d4 = X.U32(a2,_BE); a2 += 4;
				if(!d4) { if(X.U32(a2,_BE)) d6++;
				} else {
					d4 &= 0xFF00;
					if(d4 == 0xFE00 || d4 == 0xFC00) d3++;
					d6++;
				}
				if(d6 >= 0x40) break;
			}
			if(d3 > x) { x = d3; ord = d6 } // @69a~6a0
			if(X.c("2000",0xC) || X.c("FE",0xE)) x = 7; // @6a2~6b8
			d1 = 0x100;
			if(p < X.Sz()) p = 0x10C; ptn = 0; // @6bc~6c4
			for(; p < X.Sz() && d1--;) { if((t = X.U8(p)) > ptn) ptn = t; p += 4 } // @6c6~6d2
			if(p < X.Sz()) p = 0x50C; // @6d6...
			if(!X.U32(0x50C,_BE)) // to @6fe
				p += (ptn+1)*0x180; //
			else {
				for(; p < X.Sz(); p += 2) if(!X.U16(p,_BE)) break;
				for(p += X.U16(p-2,_BE); p < X.Sz(); p += 2) if(X.c("FFFFFFFF5FFF",p)) break; // @6e8~f2
				p += 6;
			}
			if(x > 1) sOption(x,'x');
			sOption('ord:'+ord+' ptn:'+ptn+' sz:'+outSz(p))
		}
	}
	else if(X.c("'OBISYNTHPACK'")) { //this must be the sample set
		for(p=0xC,i=smp=0; p < X.Sz() && i < 0x40; i++) {
			t = X.U32(p+4,_BE)-X.U32(p,_BE); p += 4; if(t) smp++;
		} p -= 4; sz = X.U32(p,_BE);
		if(sz <= X.Sz()) {
			sName = "Synth Pack's sample set (SMP.SET)"; bDetected = 1;
			if(X.isVerbose()) sOption('smp:'+smp+' sz:'+outSz(sz))
		}
	}
	else if(X.c("00800404 1214191C 1FCE26D6 328E35EA 3CF23CF2") && X.c("0C000384000000000000",0x80)) {
		// I'm specifically detecting the one for Dyter-07 here
		for(p=0,i=smp=0; p < X.Sz() && i < 0x40; i++) {
			t = X.U16(p+2,_BE)-X.U16(p,_BE); p += 2; if(t) smp++;
		} p -= 2; sz = X.U16(p,_BE);
		if(sz <= X.Sz()) {
			sName = "Synth Pack's sample set (SMP.SET)"; sVersion = "headerless"; bDetected = 1;
			if(X.isVerbose()) sOption('smp:'+smp+' sz:'+outSz(sz))
		}		
	}
	else if(X.c("'PACG'........'PAIN'") && (t=X.fSig(0xC,0x100,"'SOIN'")+8) > 0x10 && X.U16(t+2)
	  && X.U8(t+4) && X.c("4005",t+5) && X.U32(4)+8 <= X.Sz()) {
	  	//ref https://web.archive.org/web/20151027142155/http://hackipedia.org/File%20formats/Music/Sample%20based/text/SBStudio%20II%20PAC%20format.cp437.txt.utf-8.txt
		sName = "SBStudio module (.PAC)"; bDetected = 1;
		p = 8; sz = p+X.U32(4);
		title = tracker = ''; end = trk = ord = ptn = spd0 = bpm0 = smp = 0;
		while (p < X.Sz()) {
			hkhd = X.SA(p,4); if(charStat(hkhd).indexOf('allasc') < 0) break;
			hksz = X.U32(p+4); p+=0x08; //chunk header = 4xfourcc + 4xsize
			switch (hkhd) {
			case "SND ": smp++; break;
			case "SOIN": spd0 = X.U8(p); bpm0 = X.U8(p+1); ptn = X.U16(p+2); trk = X.U8(p+4); break;
			case "SONA": title = X.SC(p,hksz,'CP850'); break;
			case "SOOR": ord = hksz>>1; break;
			case "PAOR": tracker = X.SC(p,hksz,'CP850'); break;
			case "END ": end = 1; break
			}
			p += hksz; if(end) break
		}
		if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
		if(X.isVerbose()) {
			sOptionT(title); sOption(tracker,'in: ');
			sOption('trk:'+trk+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(X.U32(4)+8))
		}
	}
	else if(X.c("'PLM'1A6010")) {
		sName = "Disorder Tracker 2 module (.PLM)"; bDetected = 1;
		sVersion = "v2";
		smp = X.U8(0x5C); ptn = X.U8(0x5D); ord = X.U16(0x5E,_LE);
		sz = 0x61; ofs = 0;
		for(i=0; i < ord+ptn+smp; i++) { z = X.U32(i*4+sz,_LE); if(z > ofs) ofs = z }
		sz += 4*ord+4*ptn+4*smp;
		if(X.isVerbose()) {
			sOptionT(X.SA(6,0x30));
			sOption("ch:"+X.U8(0x36)+" bpm0:"+X.U8(0x3A)
				+" spd0:"+X.U8(0x3B)+" ord:"+ord+" ptn:"+ptn+" smp:"+smp);
		}
	}
	else if(X.c("'PLX'00")) {
		sName = "Palladix Adlib module (.PLX)"; bDetected = 1
	}
	else if(X.c("FFFFE002E102")) {
		sName = "POKEYNoise chiptune (.PN)"; bDetected = 1
	}
	else if(X.c("01080B08 E1079E32 30363100 000078D8 A2FF9A20 1B082000")) {
		sName = "Polyanna module (.PRG)"; sVersion = "v1.0&player"; bDetected = 1;
		if(X.isVerbose()) sOption('sz:'+outSz(0x5801))
		//TODO add info
	}
	else if(X.c("'PRT'")) {
		sName = "PreTracker module (.PRT)"; bDetected = 1;
		nV = X.U8(3);
		if(nV < 25) sVersion = "v<0.3"; else
		if(nV == 25) sVersion = "v0.3~0.866"; else
		if(nV == 26) sVersion = "v0.87~0.92"; else
		if(27<=nV && nV<30) sVersion = "v.[0.93~1.5)"; else
		if(nV == 30) sVersion = "v1.5+"; else
		  sVersion = "v.TODO";
		if(X.isVerbose()) {
			sOptionT(X.SA(0x14,0x14));
			sOptionT(X.SA(0x28,0x14),"by: ");
			trks = X.U8(0x5A); if(trks>31) sVersion += "/malformed";
			sOption("trks:"+X.U8(0x5A)+
				" ord:"+X.U8(0x5F)+
				" ptn:"+X.U8(0x5D)+"("+X.U8(0x5E)+")"+
				" restart:"+X.U8(0x5C))
		}
	}
	else if(X.c("'PSA'00") && 0x34 < X.U32(0x28,_BE) < X.Sz()
	  && 0x34 < X.U32(0x2C,_BE) < X.Sz()
	  && 0x34 < X.U32(0x30,_BE) < X.Sz()) {
		sName = "Professional Sound Artists module (.PSA)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0x4,0x14));
			//ref https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/PSA/PSA_v2.asm
			d2 = X.U32(0x28,_BE);
			x = (d2-56) >> 3; if(x > 1) sOption(x,"×");
			d3 = X.U32(0x2C,_BE); d0 = X.U32(0x30,_BE);
			ins = ((d3-d2) >> 6); d3 = ins;
			synsmp = 0; smp = 0; allsmpsz = 0; d7 = 3;
			while(d3) { //hop
				ts = X.U32(d2,_BE); //ins size?
				if(ts) {
					if(ts < d7) {
						d7 = ts;
						smpsz = (X.U16(d2,_BE)+4) << 1;
						allsmpsz += smpsz;
						smp++
					}
				} else synsmp++; //synth
				d2 += 64; d3--
			}
			sz = X.U32(36,_BE); songsz = sz-allsmpsz; ptn = (songsz-d0) >> 10;
			sOption("ptn:"+ptn+" ins:"+ins+" smp:"+smp+" syn.smp:"+synsmp+" songsz:"+songsz+" sz:"+outSz(sz))
		}
	}
	else if(X.c("'PSC V'........' COMPILATION OF '")) {
		sName = "Pro Sound Creator module (.PSC)";
		sVersion = "v"+X.SA(5,4); bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0x19,0x14));
			sOptionT(X.SA(0x31,0x14),"by: ")
		}
	}
	else if(X.c("'PSF'")) {
		//ref https://gist.github.com/SaxxonPike/a0b47f8579aad703b842001b24d40c00
		nV = X.U8(3);
		if(nV > 0) {
			bDetected = 1;
			sName = "Portable Sound Format module (.";
			switch(nV) {
				case 0x01: sName += "PSF,.PSF1,.MINIPSF,.MINIPSF1)"; sVersion = "Playstation"; break;
				case 0x02: case 0x03: sName += "PSF2,.MINIPSF2)"; sVersion = "Playstation 2"; break;
				case 0x11: sName += "SSF,.MINISSF)"; sVersion = "Saturn"; break;
				case 0x12: sName += "DSF,.MINIDSF)"; sVersion = "Dreamcast"; break;
				case 0x21: sName += "USF,.MINIUSF)"; sVersion = 'Ultra64'; break;
				case 0x22: sName += "GSF,.MINIGSF)"; sVersion = 'Gameboy'; break;
				case 0x23: sName += "SNSF,.MINISNSF)"; sVersion = 'Super Nintendo'; break;
				case 0x24: sName += "2SF,.MINI2SF)"; sVersion = 'Nintendo DS'; break;
				case 0x25: sName += "NCSF,.MININCSF)"; sVersion = 'Nintendo DS Nitro Sound'; break;
				case 0x41: sName += "QSF,.MINIQSF)"; sVersion = 'Capcom Q-Sound'; break;
				//case ??:  sName += "DCSF,.MINIDCSF)"; sVersion = '?'; break;
				default: sName += "*SF,.MINI*SF)"; sVersion = "unk.console";
			}
			sVersion = "v"+Hex(nV)+" "+sVersion;
			if(X.isVerbose()) {
				//fs1 = Math.max(X.Sz()-0x200,0); fs2 = Math.min(X.Sz(),0x200);
				//ptags = X.fSig(fs1,fs2,"'[TAG]'")+5;
				ptags = X.U32(8,_LE)+0x15; sig2 = false;
				if(0x15 < ptags && ptags < X.Sz()) sig2 = X.SA(ptags-5,5) === "[TAG]";
				if(!sig2) { //coincides with pv==2
					ptags = X.U32(4,_LE)+0x15;
					if(0x15 < ptags && ptags < X.Sz()) sig2 = X.SA(ptags-5,5) === "[TAG]";
				}
				if(sig2) {
					tags = X.SC(ptags,X.Sz()-ptags,"UTF8").trim();
					a=""; g=""; t=""; y=""; l="";
					tagl = tags.split('\n');
					  for (i=0; i<tagl.length;i++) {
						tag = tagl[i].split("=");
						switch (tag[0]) {
							case "length": l = tag[1]; break;
							case "title": t = tag[1]; break;
							case "game": g = tag[1]; break;
							case "artist": a = tag[1]; break;
							case "copyright": if(a=="") a = tag[1]; break;
							case "year": y = tag[1]; break;
							case "dsfby","psfby","ssfby": if(a=="") a = tag[1]; break;
						}
					  }
					sOption(t);
					sOption(a,"by: ")
					sOption(y,"'");
					sOption(g,"for: ");
					sOption(l,"len ");
				}
				else sVersion += " library";
			}
			if(X.Sz() < 0x300) sVersion += " header"
		}
	}
	else if(X.c("'PSG'1A")) { sName = "fMSX/x128 PSG chiptune (.PSG)"; bDetected = 1 }
	else if(X.c("'EPSG'1A")) {
		//ref https://web.archive.org/web/20070919070829/http://maxheadroom.no-ip.com/ay/formats_epsg.php
		sName = "Z80 Stealth Extended PSG chiptune (.EPSG)"; bDetected = 1; hw = X.U8(5);
		switch(hw) {
		case 0: sVersion = "ZX Spectrum 128k"; break;
		case 1: "Pentagon"; break;
		}
		if(hw<2 && !X.c("00000000 000000000000",6)) sVersion += "/malformed"
	}
	else if(X.c("'PSG2'")) {
		//ref https://web.archive.org/web/20070919070829/http://maxheadroom.no-ip.com/ay/formats_epsg.php
		sName = "PSG2 chiptune (.PSG2)"; bDetected = 1;
	}
	else if((X.c("'PSM '") || X.c("'PSM'FE")) && X.fStr(8,10,'FILE') >= 0 && X.fStr(16,50,'MAINSONG') >= 0) {
		sName = "Epic Megagames MASI module (.PSM)"; bDetected = 1;
		if(X.isVerbose()) {
			pt = X.fStr(16,50,"MAINSONGTITL");
			if(pt>0) {
				ts = X.U32(pt+12);
				sOptionT(X.SA(pt+16,ts));
			}
			sOption(outSz(X.U32(4)+12),'sz:')
		}
	}
	else if(X.c("'psm1'00", 0x08)) {
		sName = "Pro Sound Maker module (.PSM)"; sVersion = "compiled"; bDetected = 1; bad = 0;
		posp = X.U16(0,_LE); if(posp<0x0D || posp>X.Sz()) bad++;
		smpp = X.U16(2,_LE); if(smpp<0x0D || smpp>X.Sz()) bad++;
		ornp = X.U16(0,_LE); if(ornp<0x0D || ornp>X.Sz()) bad++;
		ptnp = X.U16(0,_LE); if(ptnp<0x0D || ptnp>X.Sz()) bad++;
		if(bad > 1) bDetected = 0;
		if(X.isVerbose()) {
			t = posp; if(smpp < t) t = smpp; if(ornp < t) t = ornp; if(ptnp < t) t = ptnp;
			if(t-0x0D) sOptionT(X.SA(0x0D,Math.min(t-0x0D,128)),"msg: ");
			ord = 0; t = posp;
			while(X.U8(t)<0xFF && t<X.Sz()) {
				ord++; t += 2;
			}
			loop = 0xFF; if(X.U8(t) == 0xFF) loop = X.U8(t+1); else bad = 5;
			if(loop == 0xFF) loop = "none";
			sOption("ord:"+ord+" loop:"+loop)
		}
		if(bad) sVersion += "/malformed"+bad;
	}
	if(!bDetected) if(X.c("'PSY'..'SONG")) {
		sName = "Psycle module (.PSY)"; bDetected = 1;
		sV = X.SA(3,1); sVersion = "v"+sV;
		if(X.isVerbose()) {
			switch(sV) {
				case "0": case "1":
					sOptionT(X.SC(0x08,0x20,'CP1252'));
					sOptionT(X.SC(0x28,0x20,'CP1252'),"by: ");
					sOptionT(X.SC(0x48,0x80,'CP1252')); break;
				case "2":
				//ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy2filter.cpp
					sOptionT(X.SC(0x08,0x20,'CP1252'));
					sOptionT(X.SC(0x28,0x20,'CP1252'),"by: ");
					sOptionT(X.SC(0x48,0x80,'CP1252'));
					bpm = X.I32(0xC8); ord = X.I32(0x191);
					trk = X.I32(0x195); ptn = X.I32(0x199);
					p = 0x19D;
					if(X.isDeepScan()) {
						inss = []; vsts = []; macs = []; waves = mach = 0; bad = "";
						for(i=0; i < ptn; i++) { rows = X.I32(p); p += 0x24+0xA0*rows }
						p += 4; inss = [];
						for(i=0; i < 0xFF; i++) { t = X.SC(p,0x20,'CP1252').trim();
							if(t != "" && t != "empty") inss.push(t); p += 0x20 }
						p += 0x37CC; //_log("waves @"+Hex(p))
						if(p > X.Sz()) bad = bad.addIfNone("!short");
						else for(i=0; i < 0xFF && p < X.Sz(); i++)
							for(w=0; w < 0x10 && p < X.Sz(); w++) { t = X.U32(p); p += 4;
//if(t || p+t*2 > X.Sz()) _log("wave["+i+","+w+"] @"+Hex(p)+" ["+Hex(t)+"]: "+X.SC(p,0x20,'CP1252').trim());
								if(t) { if(!w) waves++; p += 0x2D; var st = X.U8(p++)+1; p += t<<st }
							}
//_log("vsts @"+Hex(p));
						if(p > X.Sz()) bad = bad.addIfNone("!short");
						else for(i=0; i < 0x100; i++) {
							t = X.U8(p++); if(t) {
//_log("vst["+i+"] @"+Hex(p));
								t = X.SC(p,0x80,'CP1252').trim(); if(t != "") vsts.push(t);
								p += 0x80; t = X.I32(p); p += 4+t*4;
							}
						}
//_log("mach @"+Hex(p));
						m = []; for(i = 0; i < 128; i++) { t = X.U8(p++); if(t) mach++; m[i] = t }
						t = oldt = 0;
						const mtMa=0,mtRM=1,mtDi=2,mtSa=3,mtDe=4,mtF2=5,mtGa=6,mtFl=7,mtPl=8,
						  mtVI=9,mtVF=10,mtSc=11,mtAB=12,mtA1=13,mtA2=14,mtA21=15,mtDu=255;
						for(i=0; i < 128; i++) if(m[i]) {
							t = X.I32(p+8); p += 12;
							tn = X.SA(t===mtPl?p+0x100:p, 0x10);
							tpn = t === mtPl ? X.SA(p,0x100) : ""; ts = tn+tpn;
//_log("ts: "+ts+", oldt = "+oldt);
							nonascii = false; for(q=0; q < ts.length; q++)
								if(ts[q] < " " || ts[q] > "~" || /[\"\+\?\*]/.test(ts[q])) {nonascii = true; break }
							if(((15 < t && t < 255) || ts.length < 2 || nonascii) && oldt === mtPl) {
								bad = bad.addIfNone("!pluginDefinedDataSize"); break
							}
							else if(15 < t && t < 255) { bad = bad.addIfNone("!badMachineType"); break }
							oldt = t;
							switch(t) {
							case mtPl: macs.push(tn);
								t = X.I32(p+0x110); p += 0x114+4*t;
//_log("   ["+macs[macs.length-1]+"] post-"+Hex(t)+" vals @"+Hex(p));
								p += 0x181; break;
							case mtVI: case mtVF: vsts.push(tn); p += 0x197; break;
							default: macs.push(X.SA(p,0x10)); p += 0x191
							}
						}
						if(p > X.Sz()) bad = bad.addIfNone("!short");
//_log("patch 0 @"+Hex(p));
						p += 5*0xFF; //patch 0
						if(p > X.Sz()) bad = bad.addIfNone("!noP0");
//_log("patch 1 @"+Hex(p));
						p += 0x40;
						if(p > X.Sz()) bad = bad.addIfNone("!noP1");
//_log("patch 2 @"+Hex(p));
						if(p < X.Sz() && X.U8(p++))
							p += 4+X.U32(p);
						if(bad != "") sVersion += "/malformed"+bad;
						if(inss.length) sOption("inss: ["+inss.join(",")+"]");
						if(vsts.length) sOption("vsts: ["+vsts.join(",")+"]");
						if(macs.length) sOption("mcn: ["+macs.join(",")+"]");
						sOption("ord:"+ord+" ptn:"+ptn+" trk:"+trk+" bpm:"+bpm+" mach:"+mach+" smp:"+waves+" sz:"+outSz(p));
					} //isdeep
					else sOption("ord:"+ord+" ptn:"+ptn+" trk:"+trk+" bpm:"+bpm);
					break;
				case "3":
				//ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy3filter.cpp
					nV = X.I32(8);
					sVersion+="/"+nV;
					p = 0x10+X.U32(0x0C); //chunk header = 4xfourcc + 4xver + 4xsize
					hkn = X.U32(0x10,_LE);
					t=''; a=''; c=''; trk=0; bpm=0; ptnlns=0; ptn=0; mac=0; ins=0; eins=0;
					while (hkn > 0) {
						hkhd = X.SA(p,4);
						hkn--;
						cV = X.U32(p+4,_LE);
						hksz = X.U32(p+8,_LE);
						p+=0x0C;
						switch (hkhd) {
							case "INFO":
								p1 = p;
								if((cV & 0xFF00)==0) {
									t = X.SC(p1,128,'CP1252'); p1+=t.length+1;
									a = X.SC(p1,64,'CP1252'); p1+=a.length+1;
									c = X.SC(p1,0xFFFF,'CP1252'); p1+=c.length+1;
									if(cV == 0) hksz=t.length+a.length+c.length+3 //bug workaround
								}
								break;
							case "SNGI":
								if((cV & 0xFF00) == 0) {
									trk=X.I32(p,_LE);
									if(cV==0) hksz = 11*4+trk*2; //bug workaround
									bpm=X.I16(p+4,_LE) + X.I16(p+6,_LE)/100.0;
								}
								break;
							case "SEQD":
								if((cV & 0xFF00) == 0)
									seqlen=X.I32(p+4,_LE);
								break;
							case "PATD":
								if((cV & 0xFF00) == 0) {
									_idx = X.I32(p,_LE);
									ptnlns = X.I32(p+4,_LE);
									ptnn = X.SA(p+0x0C,0x20);
									p1=p+0x0C+ptnn.length+1;
									ptnsz = X.I32(p1,_LE);
									ptn++; p1+=4;
									if((cV == 0) && (p1+ptnsz == p+hksz+4)) hksz += 4; //bug workaround
								}
								break;
							case "MACD":
								mac++;
								//bugfix impossible here, let's pretend it won't happen and move on
								break;
							case "INSD":
								ins++;
								break;
							case "EINS":
								if((cV & 0xFFFF0000) == 0x10000) {
									eins = X.U32(p,_LE)
									//and again, pretending there's no bug (and no samples)
								}
								break;
						}
						p += hksz;
					}
					sOptionT(t); sOptionT(a,"by: "); sOptionT(c);
					sOption("bpm:"+bpm+" trk:"+trk+" ptnlns:"+ptnlns+
						" mac:"+mac+" ptn:"+ptn+"/idx:"+_idx+" ins:"+ins+" eins:"+eins+" sz:"+outSz(p));
					break;
				case "4":
				//ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy4filter.cpp
					sVersion+="/future";
					//yet another zipped-xmls format
			}
		}
	}
	else if(X.c("'PSMP'") && [0,0x10].indexOf(X.U8(4)) >= 0) {
		sName = 'Sega MegaDrive Pre-SMP chiptune (.PSZ)'; bDetected = 1;
		sVersion = X.U8(4)? 'BE': 'LE';
		if(X.isVerbose()) sOption('tempo:'+X.U8(5))
	}
	else if(X.c("'PTCOLLAGE-'") || X.c("'PTTUNE--20'")) {
		//ref source code / pxtnService.cpp, pxtnDescriptor.cpp
		bDetected = 1;
		v1 = X.SA(2,1);
		if(v1 === "T") { var rough = 10; //used for clocking
			sName = "pxtone tune (.PTTUNE)" }
		else {  var rough = 1;
			sName = "pxtone project (.PTCOP)" }
		dt = X.SA(10,6);
		if(dt <= "050227") { sV = "v.x1x"; nV = 1 }
		else if(dt <= "050608") { sV = "v.x2x"; nV = 2 }
		else if(dt <= "060115") { sV = "v.x3x"; nV = 3 }
		else if(dt <= "060930") { sV = "v.x4x"; nV = 4 }
		else if(dt <= "071119") { sV = "v5"; nV = 5 }
		d = dt.substr(0,2)+"-"+dt.substr(2,2)+"-"+dt.substr(4,2);
		sVersion = sV+"/20"+d;
		if(X.isVerbose()) {
			switch(nV) {
				case 1: case 2: p = 0x10; break;
				case 3: case 4: p = 0x14; break;
				default: p = 0x14
			}
			t = ""; c = ""; bclock = 0; bnum = 0; btempo = 0; bps = 0; bEnd = false;
			while(!bEnd && p < X.Sz()) {
				hkhd = X.SA(p,8); hksz = X.U32(p+8,_LE);
				switch (hkhd) {
					case "PROJECT=":
						t = X.SC(p+12,0x10,"Shift_JIS");
						btempo = X.F32(p+0x1C,_LE).toFixed(0);
						bclock = X.I16(p+0x20,_LE);
						bnum = X.I16(p+0x22,_LE);
						break;
					case "evenMAST":
						if(X.U16(p+12,_LE) != 3)
							sVersion += "/unk";
						else {
							var p_ = X.fSig(p+3,0x100,"'textNAME'")-12;
							if(p_ > -12) p = p_-hksz;
							else {
								p_ = X.fSig(p+3,0x100,"'textCOMM'")-12;
								if(p_ > -12) p = p_-hksz;
							}
						}
						break; //not gonna parse what vari-read gives me
					case "MasterV5":
						bclock = X.I16(p+12,_LE)*rough;
						bnum = X.I8(p+14);
						btempo = X.F32(p+15,_LE).toFixed(0);
						break;
					case "Event V5":
						var evtn = X.U32(p+12,_LE);
						hksz = 4;
						for(e=0; e < evtn; e++) { //hksz is broken in this chunk so it's like this
							for(i=0; i < 5; i++) { hksz++; if(X.U8(p+11+hksz) < 0x80) break }
							hksz += 2;
							for(i=0; i < 5; i++) { hksz++; if(X.U8(p+11+hksz) < 0x80) break }
						}
						break;
					case "textNAME":
						t = X.SC(p+12,hksz,"Shift_JIS"); break;
					case "textCOMM":
						c = addEllipsis(X.SC(p+12,hksz,"Shift_JIS"),0xA0); break;
					case "END=====": case "pxtoneND":
						bEnd = true; break
				}
				p += 12+hksz
			}
			if(t != "no name") sOption(t);  sOptionT(c);
			if(bclock+btempo+bnum > 0)
				sOption("btempo:"+btempo+" bclock:"+bclock+" bnum:"+bnum);
			sOption(outSz(p),"sz:")
		}
	}
	else if(X.c("'PTNOISE-'")) {
		sName = "pxtone Noise instrument (.PTNOISE)"; bDetected = 1
	}
	else if(X.c("'PTVOICE-'")) {
		sName = "pxtone Voice instrument (.PTVOICE)"; bDetected = 1;
		if(X.isVerbose()) sOption(outSz(X.U32(0x0C)+0x10),"sz:")
	}
	else if(X.c("'PTMF'",0x2C) && X.c("1A",0x1C) && isWithin(X.U16(0x26),1,32)
	  && X.U16(0x20)<256 && isWithin(X.U16(0x22),1,255) && isWithin(X.U16(0x24),1,128)) {
		// ref https://web.archive.org/web/20151027135201if_/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Poly%20Tracker%20module%20format.cp437.txt.utf-8.txt
		sName = "Poly Tracker module (.PTM)"; bDetected = 1;
		sV = Hex(X.U16(0x1D,_LE));
		sVersion = "v"+sV.substr(0,1)+"."+sV.substr(1,2);
		if(X.isVerbose()) {
			sOptionT(X.SA(0,0x1C));
			bad = "";
			ch = X.U16(0x26,_LE); if(!ch || ch > 32) bad = bad.addIfNone("!badchn");
			ord = X.U16(0x20,_LE); if(ord > 256) bad = bad.addIfNone("!badord");
			ins = X.U16(0x22,_LE); if(!ins || ins > 255) bad = bad.addIfNone("!badins");
			ptn = X.U16(0x24,_LE); if(!ptn || ptn > 128) bad = bad.addIfNone("!badptn");
			if(bad) sVersion += "/malformed"+bad;
			sOption("ch:"+ch+" ord:"+ord+" ptn:"+ptn+" ins:"+ins)
		}
	}
	else if(X.c("'FORM'.. .. .. ..'MODLVERS'")){
		sName = "ProTracker IFF module (.PTM)"; bDetected = 1;
		sVersion = X.SA(0x18,X.U32(0x10,_BE)-0x10);
		if(X.isVerbose()) {
			p = X.fSig(0x1E,0x200,"'INFO'");
			if(p > 0) sOptionT(X.SA(p+8,0x20));
			p = X.fSig(p+X.U32(p+4,_BE),0x200,"'CMNT'");
			if(p > 0) sOptionT(X.SA(p+8,X.U32(p+4,_BE)))
		}
	}
	else if(X.c("'RAD by REALiTY!!'")) {
		//partial ref https://www.3eality.com/Public/Releases/RAD/radv2_0a.zip source/validate20.cpp
		// & https://www.3eality.com/Public/Releases/RAD/radv1_1a.zip player.asm
		sName = "Reality Adlib Tracker module (.RAD)"; bDetected = 1;
		sV = X.U8(0x10).toString(16);
		sVersion = "v"+sV[0]+"."+sV[1]; bad = "";
		if(X.isVerbose()) {
			al = X.U8(0x11);
			fbpm = (sV > "20") && (al&0x20); // in v2.1, if the bpm flag is set, bpm != 125 and is the next byte
			if((sV=="10" && (al&0x80)) || (sV >= "20")) { //desc present
				l = 0x12; if(fbpm) l+=2;
				desc = ""; maxdesc = 250; cutoff = false;
				r = X.fSig(l,X.Sz()-32-l,"00"); p = r+1;
				if(r < 0) { maxdesc = 0; bad = bad.addIfNone("!baddesc") }
				else if(r > l+maxdesc) { r = l+maxdesc; cutoff = true }
				tmp = readBytes(l,r-l);
				for(i=0; desc.length < maxdesc; i++)
					switch(tmp[i]) {
					case 0: i = r-l; break;
					case 1: desc += "\n"; break;
					default:
						if(tmp[i] < 0x20) for(j=0; j<tmp[i] && desc.length<=maxdesc; j++) desc += " ";
						else desc += String.fromCharCode(tmp[i])
					}
				if(cutoff) { desc+="..." } sOption(desc);
			} else p = 0x12;
			spd0 = al&0x1F;
			if(fbpm) bpm = X.U16(0x12,_LE); else bpm = 125;
			ins = 0;
			while(1) {
				insn = X.U8(p++); if(!insn || p>X.Sz()) break;
				ins++;
				if(sV == "10") p += 11;
				else if(sV == "21") {
					nmlen = X.U8(p);
					p += nmlen+1;
					rm = X.U8(p); riff = rm&0x80; midi = (rm&7)==7;
					if(midi) p += 7; else p += 24;
					if(riff) p += X.U16(p,_LE)+2;
				} else bad = bad.addIfNone("!badver");
			}
			ord = X.U8(p++); p += ord;
			ptn = 0; for(i=0; i < 32; i++) if(X.U16(p+i*2,_LE)) ptn++; p += 64;
			if(!ord || ord>128) bad = bad.addIfNone("!badord");
			if(!ptn) bad = 5; if(!ins) bad = bad.addIfNone("!badptn");
			if(sV > "20") { riffs = 0;
				while(1) {
					riffn = X.U8(p++);
					if(riffn==0xFF || p>X.Sz()) break;
					riffs++; p += X.U16(p)
				}
				sOption("spd0:"+spd0+" bpm:"+bpm+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" riffs:"+riffs)
			}
			else sOption("spd0:"+spd0+" bpm:"+bpm+" ord:"+ord+" ptn:"+ptn+" ins:"+ins)

			if(bad) sVersion += "/malformed"+bad
		}
	}
	else if(X.c("'RAWADATA'") && X.Sz()>10) {
		sName = "Raw OPL Capture chiptune (.RAW)"; bDetected = 1;
		if(X.isVerbose() && (X.isDeepScan() || X.Sz()<0xFFFF)) { //may take a sec
			//taken from https://github.com/adplug/adplug/blob/master/src/raw.cpp
			p = 10; len = (X.Sz()-10) >> 1;
			tagdata = false; t = ""; a = ""; d = ""; next = 0;
			for(i=0; i < len && p < X.Sz(); i++) {
				x = (tagdata ? 0xFFFF : X.U16(p));
				p += 2;
				if(!tagdata && x==0xFFFF) { //trying to stop at the RAW EOF data marker
					tagcode = X.U8(p++);
					if(tagcode == 0x1A) tagdata = true; //tag marker found
					else if(!tagcode) { //old comment (music archive 2004)
						d = X.SA(p,1023); p += 1023; tagdata = true }
					else p--;
				}
			}
//_log("after-for "+Hex(p)+", tagdata? "+tagdata);
			if(tagdata) { //searching for tags
				t = X.SA(p,40); p += 40;
				if(X.U8(p++) != 0x1B) { //if not, skip "author"
					p--;
					if(X.U8(p) >= 0x20) { //older version tag, eg. stunts.raw
						a = X.SA(p,60); p += 60;
						d = X.SA(p,1023); p += 1023;
						next = 2; //end_section
					} else p--;
					next = 1; //desc_section
				}
				if(!next) {
					a = X.SA(p,40); p += 40
				}
				if(next == 1) { next = 0;
					if(X.U8(p++) != 0x1C) next = 2;
					if(!next) {
						d = X.SA(p,1023); p += 1023
					}
				}
			}
			sOption(t); sOption(a,"by: "); sOption(d)
		} //isDeepVerbose
		if(X.isVerbose()) sOption(X.U16(8,_LE),"clkspd:")
	}
	else if(X.c("00407F40 00C081C0") && X.c("41FAFFEE",0x38)) {
		//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/RobHubbard_ST/src/Rob%20Hubbard%20ST_v2.asm
		sName = "Rob Hubbard ST module (.RHO)"; sVersion = "v1.1"; bDetected = 1;
		if(X.isVerbose()) {
			a2 = 180; bad = false;
			do { //findplay
				t = X.U16(a2,_BE); a2 += 2;
			} while(a2 < X.Sz() && t != 0x4E75);
			if(a2 > X.Sz()) { bad = true; break }
			playp = a2;
			do { //findend
				t = X.U16(a2,_BE); a2 += 2;
			} while(a2 < X.Sz() && t != 0xDFFC);
			if(a2 > X.Sz()) { bad = true; break }
			a2 += 4; endp = a2;
			do { //findinit
				t = X.U16(a2,_BE); a2 += 2;
			} while(a2 < X.Sz() && t != 0x4E75);
			if(a2 > X.Sz()) { bad = true; break }
			do { //findIt1
				t = X.U16(a2,_BE); a2 += 2;
			} while(a2 < X.Sz() && t != 0x7E02);
			if(a2 > X.Sz()) { bad = true; break }
			songp = a2+X.U16(a2+2,_BE)+2;
			d0 = X.U32(songp,_BE)/12;
			if(d0 > 1) sOption(d0,"×");
			d0 = d0*3-1; d5 = 0;
			a1 = a3 = songp;
			while(d0 >= 0) {
				a1 = songp;
				a1 += X.U32(a3,_BE); a3 += 4;
				while(a1 < X.Sz()) {
					d1 = X.U32(a1,_BE); a1 += 4;
					if(!d1) break;
					if(d1 > d5) d5 = d1
				}
				d0--
			}
			a1 = songp+d5;
			do {
				t = X.U8(a1); a1++
			} while(a1 <= X.Sz() && t != 0x87)
			sz = a1; // - moduleptr, 0 here
			if(a1 > X.Sz()) { bad = true; break }
			sOption(outSz(sz),"sz:")
			if(bad) sVersion += "/malformed";
		}
	}
	else if(X.c("'RNS0'") && X.c("'>>> Chunk Start <<<'00", 9)) {
		sName = "Renoise module (.RNS)"; bDetected = 1;
		sV = X.SA(3,4); sVersion = "v"+sV;
		if(sV < "05") sVersion += "/RN<1.1.1";
		else if(sV === "05") sVersion += "/RN1.1.1";
		else if(sV < "015") sVersion += "/RN<1.2.7";
		else if(sV === "015") sVersion += "/RN1.2.7";
		else if(sV < "018") sVersion += "/RN<1.5.2";
		else if(sV === "018") sVersion += "/RN1.5.2";
		else sVersion += "/RN>1.5.2"
		if(X.isVerbose()) {
			p = 9;
			while (p < X.Sz()) {
				if(!X.c("'>>> Chunk Start <<<'00",p)) break;
				p += 20;
				if(X.c("'Header V00         '00",p)) {
					sOptionT(X.SC(p+20,20,'CP1252'));
					sOptionT(X.SC(p+40,20,'CP1252'),"by: ");
					sOptionT(X.SC(p+60,20,'CP1252'),"style: ");
					break;
				}
				else if(X.c("'Header V01         '00",p)) {  p += 20;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252')); p+=sz+4;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252'),"by: "); p+=sz+4;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252'),"style: ");
					break;
				}
				else if(X.c("Header V02         '00",p)) {  p += 20;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252')); p+=sz+4;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252'),"by: "); p+=sz+4;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252'),"style: "); p+=sz+4+5;
					sz = X.U32(p); sOptionT(X.SC(p+4,sz,'CP1252'));
					break;
				}
				p = X.fSig(p+1,TOEOF,"''>>>  Chunk End  <<<'00");
				if(p == -1) break;
				p += 20
			}
		}
	}
	else if(extIs("xrns") && X.c("'PK'0304")) {
		//TODO when ZIP parsing is available
		sName = "Renoise module (.XRNS)"; sVersion = "xml"; bDetected = 1
		//versions vs doc_version:
		// 10: 1.9.1    14: 2.0.0    15: 2.1.0    21: 2.5.1    22: 2.6.1    30: 2.7.0, 2.7.1, 2.7.2
		// 37: 2.8.2    54: 3.0.1    63: 3.1.0    65: 3.3.2    66: 3.4.2
	}
	else if(extIs("xrdp") && X.fStr(0,0x100,"<FilterDevicePreset")>=0) {
		sName = "Renoise filter device preset (.XRDP)"; bDetected = 1;
		p = X.fStr(20,0x100,"doc_version=");
		if(p < 0) sVersion = "malformed";
		else {
			sVp = X.SA(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
		}
	}
	else if(extIs("xrni") && X.c("'PK'0304")) {
		//TODO when ZIP parsing is available
		sName = "Renoise instrument (.XRNI)"; sVersion = "xml"; bDetected = 1
	}
	else if(extIs("xrno") && (X.fStr(0,0x100,"<SampleModulationSet")>=0)) {
		sName = "Renoise sample modulation set (.XRNO)"; bDetected = 1;
		p = X.fStr(15,0x100,"doc_version=");
		if(p < 0) sVersion = "malformed";
		else {
			sVp = X.SA(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
		}
	}
	else if(extIs("xrnt") && (X.fStr(0,0x100,"<RenoiseDeviceChain")>=0)) {
		sName = "Renoise effect chain (.XRNT)"; bDetected = 1;
		p = X.fStr(15,0x100,"doc_version=");
		if(p < 0) sVersion = "malformed";
		else {
			sVp = X.SA(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
		}
	}
	else if(extIs("xrnt") && X.c("'PK'0304")) {
		sName = "Renoise effect chain (.XRNT)"; sVersion = "v<3.0"; bDetected = 1
	}
	else if(extIs("xrnz") && (X.fStr(0,0x100,"<InstrumentPhrase")>=0)) {
		sName = "Renoise instrument phrase (.XRNZ)"; bDetected = 1;
		p = X.fStr(15,0x100,"doc_version=");
		if(p < 0) sVersion = "malformed";
		else {
			sVp = X.SA(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
		}
	}
	else if(X.c("'NuRIFFRAFF'",0x22)) {
		sName = "Riff Raff module (.RIFF)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SC(0x64,0x100,'CP1252');
			a = X.SC(0x64+t.length+1,0x100,'CP1252');
			c = X.SC(0x64+t.length+a.length+2,0x100,'CP1252');
			sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
		}
	}
	else if(X.c("'RON_KLAREN_SOUNDMODULE!'00", 0x28)) {
		sName = "Ron Klaren module (.RK)"; bDetected = 1
	}
	else if(X.c("'RTMM '")) {
		sName = "Real Tracker module (.RTM)"; bDetected = 1;
		sVersion = "v"+X.SA(0x37,7);
		if(X.isVerbose()) {
			sOptionT(X.SC(5,0x20,'CP1252'));
			sOptionT(X.SC(0x3E,0x20,'CP1252'),"by: ");
			sOptionT(X.SC(0x8C,0x20,'CP1252'),"orig.: ")
		}
	}
	if(X.c("00000000 00000200",0x0C) && X.c("00")
	  && X.U16(0x18,_LE) === X.U16(0x40,_LE)
	  && X.c("00'ST-Module.'", 0x4B7)) {
		sName = "Amstrad CPC Soundtrakker module (.STK)"; bDetected = 1;
		datasz = X.U16(0x18,_LE); sz = datasz + 0x80;
		if(sz < X.Sz()) sVersion = "malformed";
		crc = 0; for(i=0; i < 67; i++) crc += X.U8(i); crc &= 0xFFFF;
		if(crc != X.U16(0x43)) sVersion += sVersion != "" ? "!badCRC" : "/malformed!badCRC";
		if(X.isVerbose())
			sOptionT(X.SA(1,8));
			sOption(outSz(sz),"sz:")
	}
	else if(X.c("00000000 00000200",0x0C) && X.c("00")
	  && X.U16(0x18,_LE) === X.U16(0x40,_LE)
	  && X.SA(1,8) === X.SA(0xB10,8)) {
		sName = "Amstrad CPC Soundtrakker 128K module (.128)"; bDetected = 1;
		datasz = X.U16(0x18,_LE); sz = datasz + 0x80;
		if(sz < X.Sz()) sVersion = "malformed";
		crc = 0; for(i=0; i < 67; i++) crc += X.U8(i); crc &= 0xFFFF;
		if(crc != X.U16(0x43)) sVersion += sVersion != "" ? "!badCRC" : "/malformed!badCRC";
		if(X.isVerbose())
			sOptionT(X.SA(1,8));
			inst = []; ins = 0;
			for(i=0; i < 16; i++) if(![0,0x20].indexOf(X.U8(0xB18+8*i))) {
				ins++; inst.push(X.SA(0xB18+8*i,8).trim())
			} //TODO confirm that that's how to count the instruments
			sOption(inst);
			sOption("ins:"+ins+" sz:"+outSz(sz))
	}
	else if(X.c("00'ST-Module.'",0x437)) {
		sName = "Amstrad CPC Soundtrakker module (.STK)"; bDetected = 1;
		sVersion = "headerless"
	}
	else if(X.c("'SCRM'", 0x2C) && X.c("10",0x1D)
	  && [1,2].indexOf(X.U8(0x2A)) >= 0 && X.Sz() > 0x60) {
		//ref https://moddingwiki.shikadi.net/wiki/S3M_Format
		//from https://github.com/OpenMPT/openmpt/blob/master/soundlib/S3MTools.h & Load_s3m.cpp
		// & http://asle.free.fr/prowiz/prowiz.src.zip / r/S3M.c
		//TODO perform deep version detection
		bDetected = 1; sName = "ScreamTracker 3 module (.S3M)"
		_V = Hex(X.U16(0x28,_LE)); tracker = '';
		sVersion = "v"+_V.substr(1,1)+"."+_V.substr(2,2);
		if(_V === "1300") sVersion = "v3.00"; else //because they're weird duh
		if(_V === "1320") sVersion = "v3.20"; else
		if(_V === "1301") sVersion = "v3.01";
		switch(_V.substr(0,1)) {
			case "1": tracker = "ScreamTracker 3"; break;
			case "2": tracker = "Imago Orpheus"; break;
			case "3": tracker = "Impulse Tracker"; break;
			case "4":
				if(_V === "4100") { tracker = "BeRoTracker v.2004-2012"; sVersion = ''}
				else tracker = "Schism Tracker";
				break;
			case "5":
				if(_V=== "4754") { tracker = "GraoumfTracker"; sVersion = '' }
				else tracker = "OpenMPT"; break;
			case "6": tracker = "BeRoTracker"; break;
			case "7": tracker = "CreamTracker"; break;
			case "C":
				if(_V === "CA00") {
					tracker = "Camoto/libgamemusic"; sVersion = ""; break }
			default: tracker = "ScreamTracker-compatible";
		}
		bad = ""; sus = 0;
		if(!X.c("1A",0x1C)) {sus++; bad = bad.addIfNone("!badsig1a") }
		z1 = X.U16(0x1E); if(z1) { sus++; bad = bad.addIfNone("!badsig0") }
		ord = X.U16(0x20); if(ord&1) { sus++; bad = bad.addIfNone("!oddord") }
		if(sus > 1) bDetected = 0;
		else if(X.isVerbose()) {
			smp = X.U16(0x22); ptn = X.U16(0x24);
			gvol = X.U8(0x30); spd = X.U8(0x31);
			tmp = X.U8(0x32); mvol = X.U8(0x33);
			p = 0x60+ord+smp*2;
			max = sz = 0;
			for (i=0; i < ptn; i++) {
				var p1 = X.U16(p+i*2) << 4;
				if(!p1) continue;
				if (p1 > max) { max = p1; sz = max + X.U16(p1) }
			}
			if(sz%16) sz += 16-(sz%16);
			p = 0x60+ord;
			for(i=0; i < smp; i++) {
				si = X.U16(p+i*2) << 4; if(!si) continue;
				if(X.U8(si) === 1) {
					sofs = X.U16(si+14) << 4;
					if (sofs > max) {
						ssz = X.U32(si+16);
						if (X.U8(si+31)&4) ssz *= 2;
						if(ssz%16) ssz += 16-ssz%16; //the padding might be important
						max = sofs; if(sz < max+ssz) sz = max+ssz;
					}
//_log("sofs:"+Hex(sofs)+" ["+Hex(ssz)+"] -> "+Hex(sofs+ssz)+", max="+Hex(max))
				}
			}

			if(bad != "") sVersion += "/malformed"+bad;
			sOptionT(X.SC(0,0x1C,'CP1252')); sOption(tracker,'in:');
			ch = 1 + (mvol >> 7); mvol &= 0x7F;
			sOption("ch:"+ch+" ord:"+ord+" ptn:"+ptn+" smp:"+smp
				+" gvol:"+gvol+" mvol:"+mvol+" r/s:"+(tmp/spd).toFixed(1)+" sz:"+outSz(sz));
		}
	}
	if(!bDetected)
	if(X.Sz() > 0x20 && /S98[1-3]/.test(X.SA(0,4)) && X.U32(4) <= 0x20 && !X.U32(8) && X.U8(0x13) < 0x10 && X.U16(0x16) <= 2) {
		//ref https://www.purose.net/befis/download/lib/t98/ins98131s.zip / s98spec1.txt,
		//    http://www.purose.net/befis/download/kmp/old/s98spec2.txt,
		//    https://github.com/rururutan/s98spec3/blob/master/s98spec3-ja.txt
		sName = "S98 tune (.S98)"; bDetected = 1;
		sV = X.SA(3,1); sVersion = "v"+sV;
		if(X.isVerbose()) {
			a=""; c="";s=""; g=""; t=""; y="";
			switch(sV) {
				case "0": case "1": case "2":
					p = X.U32(0x10,_LE);
					if(p > 0)
						sOptionT(X.SA(p,0x40));
					pk = X.U32(0x0C,_LE);
					if(pk) sVersion += " compressed";
					break;
				case "3":
					ptags = X.U32(0x10,_LE);
					if(ptags > 6 && X.SA(ptags,5)=="[S98]") {
						ptags+=5;
						bUTF8 = X.c("EFBBBF",ptags); //if BOM's there
						if(bUTF8) tags = X.SC(ptags+3,0x200,"UTF8"); //test whether this parses BOM too
						else tags = X.SC(ptags,0x200,"Shift_JIS");
						tagl = tags.split('\x0A');
						for (i=0; i < tagl.length; i++) {
							tag = tagl[i].split("=");
							switch (tag[0]) {
								case "title": case "ｔｉｔｌｅ": t = tag[1]; break;
								case "game": case "ｇａｍｅ": g = tag[1]; break;
								case "system": case "ｓｙｓｔｅｍ": s = tag[1]; break;
								case "artist": case "ａｒｔｉｓｔ": a = tag[1]; break;
								case "year": case "ｙｅａｒ": y = tag[1]; break;
								case"copyright": case "ｃｏｐｙｒｉｇｈｔ": if(a=="") a = tag[1]; break;
								case"s98by": case "ｓ９８ｂｙ": if(a=="") a = tag[1]; break;
								case"comment": case "ｃｏｍｍｅｎｔ": c = tag[1]; break;
							}
						}
						sOption(t); sOption(a,'by: '); sOption(y,"'");
						sOption(g,'for: '); sOption(s,'on: '); sOption(c);
						dev = X.U32(0x1C,_LE); if(!dev) dev = 1;
						if(dev > 64) sVersion += '/malformed!devnum';
						sOption(dev,'devices: ');
						if(ptags > 6 && X.c("'[S98]'",ptags-5) && (t=X.fSig("00",ptags) > -1))
							sOption(outSz(ptags+Math.min(0x200,t)),'sz:')
						break
					} //if correct tag
			}//switch sV
		}//isVerbose
	}
	else if(X.c("'SAP'0D0A")) {
		sName = "Slight Atari Player module (.SAP)"; bDetected = 1;
		if(X.isVerbose()) {
			bad = false; t=""; a=""; dt=""; tp=""; tm="";
			p = X.fSig(3,TOEOF,"0D0AFFFF");
			if(p == 0) bad=true;
			else {
				taghunk = X.SA(5,p);
				if(taghunk.length > 5) {
					tags = taghunk.split("\x0D\x0A");
					for(i=0; i < tags.length; i++) {
						tagdiv = tags[i].indexOf(" ");
						tagl = tags[i].substr(0,tagdiv);
						tagr = tags[i].substr(tagdiv+1,tags[i].length);
						switch(tagl) {
							case "NAME": if(tagr != '"<?>"') t = tagr.substr(1,tagr.length-2); break;
							case "AUTHOR": if(tagr != '"<?>"') a = tagr.substr(1,tagr.length-2); break;
							case "DATE": if(tagr != '"<?>"') dt = tagr.substr(1,tagr.length-2); break;
							case "TYPE": sVersion = "v."+tagr; break;
							case "TIME": tm = tagr; break
						}
					}
				}
			}
			if(bad) sVersion += "/malformed";
			sOption(t); sOption(a,"by: "); sOption(dt,"'"); sOption(tm,"len: ")
		}
	}
	else if(X.c("'SAdT'")) {
		//ref http://www.adlibtracker.net/files/at2_sourcecode_09-09-2019.zip
		// in /adt2play/iloaders.inc -> sat_file_loader
		bDetected = 1; nV = X.U8(4); sVersion = "v"+nV;
		if(nV < 8)  sName = "Surprise! AdLib Tracker module (.SAT)";
		else sName = "Surprise! AdLib Tracker 2 module (.SA2)";
		if(X.isVerbose()) {
			if(nV < 5) {
				instnp = 0x15A;
				ptn = X.U16(0x448,_LE);
				ord = X.U8(0x44A);
				ticks = X.U8(0x44C)
			} else { //v5,6, 8,9 are the same
				instnp = 0x1D6;
				ptn = X.U16(0x446,_LE);
				ord = X.U8(0x448);
				ticks = X.U8(0x44A)
			}
			inst=[];
			for(i=0; i < 3 /*31*/; i++) //that's enough title info
				inst.push(X.UCSD(instnp+i*17).trim());
			t = inst.join(" ").trim(); sOption(addEllipsis(t)); sOption("rate:"+ticks+"Hz ord:"+ord+" ptn:"+ptn)
		}
	}
	else if(X.c("'SC68 Music-file / (c) (BeN)jamin Gerard / SasHipA-Dev  '00'SC68'........'SCFN'")) {
		sName = "SC 68000 programmatic chiptune (.SC68)"; bDetected = 1;
		if(X.isVerbose()) {
			hdrl = X.SA(0,0x100).length+1;
			p = hdrl; //header skipped
			t=''; a=''; cp=''; st=''; df=-1; x=0; ef=false; mn = [];
			while (p < X.Sz()) {
				hkhd = X.SA(p,4);
				hksz = X.U32(p+4,_LE);
				p += 0x08; //chunk header = 4xfourcc + 4xsize
				switch (hkhd) {
					case "SC68": if(hksz+hdrl != X.Sz()) sVersion = "malformed"; hksz = 0; break;
					case "SCFN": t = X.SC(p,hksz,'CP1252'); break;
					case "SCDF": df = X.U32(p,_LE)+1; break;
					case "SCMN":
						mn.push(X.SC('CP1252',p,hksz));
						if(st=="" || df==mn.length)
							st = mn[mn.length-1];
						break;
					case "SCAN": a = X.SC(p,hksz,'CP1252'); break;
					case "SCCN": cp = X.SC(p,hksz,'CP1252'); break;
					case "SCEF": ef = true; p = X.Sz(); break
				}
				p += hksz;
			}
			sOptionT(t); if(mn.length > 1) sOption(mn.length,"×");
			sOptionT(mn.join(";"));
			sOptionT(a,"by: "); sOptionT(cp,"(c)"); sOption(outSz(p),"sz:")
			if(!ef) sVersion = "malformed!short"
		}
	}
	else if(X.c("'shro'020000")) {
		sName = "Mario Paint's Shroom module (.SHO)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(7,0x20));
			sOptionT(X.SA(0x27,0x20),"by: ")
		}
	}
	else if(X.c("'PSID'") || X.c("'RSID'")) {
		v1 = X.SA(0,1); bDetected = 1;
		if(v1 == "P")  sName = "PlaySID programmatic chiptune (.SID, .PSID)";
		else sName = "RealSID programmatic chiptune (.SID, .RSID)";
		v2 = X.U16(4,_BE);
		sVersion = "v"+Hex(v2);
		bad = 0;
		x = X.U16(0x0E,_BE);
		if(x<1 || x>256) bad = 1;
		else if(x > 1) sOption(x,"×");
		startSong = X.U16(0x10,_BE);
		if(startSong > x) bad = 2;
		dataOfs = X.U16(0x06,_BE);
		if((v2==1 && dataOfs!=0x0076) || (v2==2 && dataOfs!=0x007C))
			bad = 3;
		loadAddr = X.U16(0x08,_BE);
		if(v1=="R" && loadAddr>0 && (loadAddr<0x07E8)) bad = 4;
		initAddr = X.U16(0x0A,_BE);
		if(v1=="R" &&
		   ( initAddr<0x07E8 || (0xA000<=initAddr && initAddr<0xC000) || 0xD000<=initAddr))
			bad = 5;
		flags = X.U16(0x76,_BE);
		if(v1=="R" && ((flags&2) >> 1) && initAddr>0)
			bad = 6;
		switch((flags&0x30)>>4) {
			case 1: sVersion += "/6581"; break;
			case 2: sVersion += "/8580"; break;
			case 3: sVersion += "/6581&8580"; break;
			default: sVersion += "/unk.chip"
		}
		switch((flags&0x0C)>>2) {
			case 1: sVersion += "/PAL"; break;
			case 2: sVersion += "/NTSC"; break;
			case 3: sVersion += "/PAL&NTSC"; break;
		}
		sidn = (dataOfs-0x7C)/2+1;
		if(sidn > 1) sVersion += "/"+sidn+"SID";
		if(bad > 0) sVersion += "/malformed"+bad;
		if(X.isVerbose()) {
			t = X.SC(0x16,0x20,'CP1252'); if(t == "<?>") t = ""; sOptionT(t);
			a = X.SC(0x36,0x20,'CP1252'); if(a == "<?>") a = ""; sOptionT(a,"by: ");
			c = X.SC(0x56,0x20,'CP1252'); if(c == "<?>") c = ""; sOptionT(c);
		}
	}
	else if(X.c("00 FF00FF00 9100FF00 FF008000 92..00967F 01", 0x07)
	  && X.c("9908", 0x1542)) {
		sName = "Sound Images Generation 2 module (.SIG)"; bDetected = 1
	}
	else if(X.c("0100FEFF09000000'ALIM3'")) {
		sName = "Skale Tracker module (.SKM)"; bDetected = 1;
		if(X.isVerbose()) sOptionT(X.SA(0x19));
	}
	else if(X.c("'<track'") && (X.fStr(6,0x100,"rowhighliohtingminor=")>0)) {
		sName = "Picatune module v1 (.SMUFI) or v2 (.PT2)"; bDetected = 1;
		if(X.isVerbose()) {
			s = X.SC(0,0x100,"UTF8");
			t = /.*name="([^"]*)".*/.exec(s);
			if(t != null) sOptionT(t[1]);
			a = /.*author="([^"]*)".*/.exec(s);
			if(a != null) sOptionT(a[1],"by: ");
			spd = /.*speed="([^"]*)".*/.exec(s);
			bpm = /.*bpm="([^"]*)".*/.exec(s);
			if(spd != null) sVersion+=" spd: "+spd[1];
			if(bpm != null) sVersion+=" bpm: "+bpm[1];
		}
	}
	else if(X.c("'SNGs'") && X.Sz() > 0x10 && X.U8(0xA) <= X.U8(7) && X.U8(0xB) <= 0x1E && isWithin(X.U8(0xC),0,0x78)
	  && isWithin(X.U8(0xD),1,30) && isWithin(X.U8(0xE),1,30)) {
	  	//TODO RE reading additional data
		sName = "BlueMoon's Sound Club for DOS module (.SN)"; bDetected = 1;
		if(!(ord=X.U8(7))) sVersion = 'malformed!0ord';
		if(X.isVerbose()) {
			sOptionT(X.SA(0x0F,0x1A));
			lp = X.U8(0xA); gvol = X.U8(0xB)*5; tx = X.U8(0xC); tpb = X.U8(0xD); bpms = X.U8(0xE);
			sOption('rhythm:'+tpb+'/'+bpms+' ord:'+X.U8(7)+' ins:'+X.U8(4)+' gvol:'+gvol+'%')
		}
	}
	else if(X.c("'SN2'") && isWithin(X.U32(0x13),0,0x78) && isWithin(X.U32(0x17),1,30) && isWithin(X.U32(0x1B),1,30)
	  && X.c("'NAM'",0x1F) && X.c("'SEQ'",0x26+X.U32(0x22))) {
		sName = "Bluemoon's Sound Club for Windows module (.SN2)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(addEllipsis(X.SA(0x26,X.U32(0x22)))); sz = X.U32(3)+7;
			lp = X.U32(0x0F); tx = X.U32(0x13); tpb = X.U32(0x17); bpms = X.U32(0x1B);
			//TODO instead of a quartic approximated curve, find out the actual calculation lol
			tempop = Math.round(200 - tx*(4.27246 - tx*(0.0603477 + tx*(-0.000453202 + 1.33871*tx/1000000))));
			p = 0x26+X.U32(0x22); ord = ptn = ins = 0;
			end = false; while(!end && p < X.Sz()) {
				hkhd = X.SA(p,3); hksz = X.U32(p+3); p += 7;
				switch(hkhd) {
				case 'PAT': ptn++; break;
				case 'SEQ': ord = Util.divu64(hksz,4); break;
				case 'INS': ins++; break;
				}
				p += hksz
			}
			sOption('rhythm:'+tpb+'/'+bpms+' tempo: '+tempop+'% ord:'+ord+(lp?' lp:'+lp:'')
				+' ptn:'+ptn+' ins:'+ins+' sz:'+outSz(sz))
		}
	}
	else if(X.c("'SNDH'",0x0C) && (X.isHeuristicScan() || X.c("6000............6000"))) {
		//ref https://sndh.atari.org/files/sndhv21.txt
		sName = "Atari ST Sound Header module (.SND,.SNDH)"; sVersion = "uncompressed"; bDetected = 1;
		if(X.isVerbose()) {
			p = 0x10; title=artist=yr=""; totaltime = i = 0; x = 1;
			while(i < 10 && p < X.Sz()) {
				t = X.SA(p,4); if(t === "TIME") {
					for(j=0; j < x; j++) totaltime += X.U16(p+4+j*2,_BE);
					tlen = 4+x*2-1; if((p+tlen+1)%2) tlen++;
				}
				else { t = X.SA(p,Math.min(X.Sz()-p),0x100); tlen = t.length }
				if(tlen >= 4) {
					hd = t.substr(0,4); tag = t.substr(4,tlen);
					switch(hd) {
						case "TITL": if(tag.substr(0,7) != "Unknown") title = tag; break;
						case "COMM": if(tag.substr(0,7) != "Unknown") artist = tag; break;
						case "YEAR": yr = tag; break;
						case "HDNS": i = 8; break;
						default: if(/##\d\d/.test(hd)) x = hd.slice(2,4);
							else if(/#!\d\d/.test(hd)) if((p+tlen+1)%1) tlen++;
							break;
					}
				}
				p += tlen+1; i++
			}
			sOptionT(title); if(x > "01") sOption(x,"×"); sOptionT(artist,"by: "); sOptionT(yr,"'");
			if(totaltime) sOption(secondsToTimeStr(totaltime,"time:"));
		}
	}
	else if(X.c("'FMC!'")) {
		sName = "Faust Music Creator module (.SNG)"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(4,20));
	}
	else if(/GTS[25!]/.test(X.SA(0,4))) {
		sName = "GoatTracker module (.SNG)"; bDetected = 1;
		if([0x32,0x35].indexOf(X.U8(3)) >= 0) sVersion = "v2"
		else sVersion = "v1";
		if(X.isVerbose()) {
			sOptionT(X.SC(4,0x20,'CP1250'));
			tc = X.U8(0x64);
			if(tc > 1) sOption(tc,"×");
			sOptionT(X.SC(0x24,0x20,'CP1250'),"by: ");
			sOptionT(X.SC(0x44,0x20,'CP1250'))
		}
	}
	else if(X.c("'ObsM'")) {
		sName = "Jonne Valtonen's SNG Player module (.SNG)"; bDetected = 1;
		if(X.U8(0x0B)) sVersion += "compressed";
		len = X.U16(4,_LE); if(len>X.Sz()) sVersion += "malformed";
		if(X.isVerbose()) {
			//ref https://github.com/adplug/adplug/blob/master/src/sng.cpp
			start = X.U16(6,_LE);
			loop = X.U16(8,_LE); delay = X.U16(0x0A,_LE);
			sOption("len:"+Hex(len)+" start:"+Hex(start)+" loop:"+Hex(loop)+" delay:"+delay)
		}
	}
	else if(X.c("'RJP'3.'SMOD'")) {
		sName = "Richard Joseph's module (.SNG)"; bDetected = 1;
		sVersion = "v"+X.SA(3,1)
	}
	else if(X.c("'SYNC'") || X.c("'SYNB'")) {
		sName = "Synder SNG-player module (.SNG)"; bDetected = 1;
		sVersion = "ver."+X.SA(3,1);
		if(X.isVerbose()) sOption(X.SA(0x10,0x200))
	}
	else if(X.c("'SYND'....'S0'")) {
		sName = "Synder Tracker module (.SNG)";  bDetected = 1; //TODO unpack & read tags
		sVersion = "ver."+X.SA(3,1)
		//if(X.isVerbose()) sOption(X.SA(0x10,0x200))
	}
	else if((t = ((X.U8(0)+1) << 4) + ((X.U8(1)+1) << 7) + 0x365) >= 0x365
	  && (X.c("'df'..':'",t) || X.c("'amp'",t+1))) {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/ZoundMonitor/src/ZoundMonitor_v1.asm
	sName = 'ZoundMonitor module (.SNG + Samples/)'; bDetected = 1;
	if(X.isVerbose()) {
		sOption('ord:'+(X.U8(3)+1)+' ptn:'+(X.U8(1)+1)+' sz:'+outSz(t+101))
	}
}

	else if(X.c("'SYND'") || X.c("'SYNF'") || X.c("'SYNH'")) {
		sName = "Synder SNG-player Stereo module (.SNG)"; bDetected = 1;
		sVersion = "ver."+X.SA(3,1);
		if(X.isVerbose()) sOption(X.SA(0x10,0x200))
	}
	else if(X.c("'RJP'3. 0000 0000")) {
		sName = "Richard Joseph's module instruments (.INS)"; bDetected = 1;
		sVersion = "v"+X.SA(3,1)
	}
	else if(X.c("'SNES-SPC700 Sound File Data'")) {
		// ref http://snesmusic.org/files/spc_file_format.txt   and snippets from
		// http://www.alpha-ii.com/Source/SAmp310s.rar/snes/id666.cpp, .h
		sName = "Nintendo SNES SPC module (.SPC)"; bDetected = 1;
		sVersion = "v0."+X.U8(0x24);
		if(X.isVerbose()) {
			id666inhdr = X.U8(0x23)==0x1A;
			t=""; a=""; c=""; g=""; dumper=""; emu="";
			preferBin = false;
			if(id666inhdr){
				t = X.SA(0x2E,0x20);
				g = X.SA(0x4E,0x20);
				dumper = X.SA(0x6E,0x10);
				dumpdate = X.SA(0x9E,11);
				emu = X.U8(0xD2);
				if(emu>=0x30 && emu<=0x39)
					emu -= 0x30;
				switch(emu) {
					case 1: emu = "ZSNES"; break;
					case 2: emu = "Snes9x"; break;
					case 3: emu = "ZST2SPC"; break;
					case 4: emu = "ETC"; break;
					case 5: emu = "SNEShout"; break;
					case 6: emu = "ZSNESW"; break;
					default: emu = ""
				}
				c = X.SA(0x7E,0x20);
				slen = X.SA(0xA9,3); lp=X.SA(0xAC,4);
				if((slen+lp+dumpdate).length < 5) {
					chnDis = X.U8(0xD1);
					if(chnDis == 1 && emu == "") bin = true; else bin = preferBin;
				} else if( /[0-9/]*/.test(slen+lp+dumpdate) ) { //id666 text format
					songlen = Number(slen); //in seconds
					a = X.SA(0xB1,0x20);
				} else {
					bin = true;
					//check songlen bitness
					songlen = X.U8(0xA9)<<16+X.U8(0xAA)<<8+X.U8(0xAB); //in seconds
					a = X.SA(0xB0,0x20);
				}
			}
			else
				t = X.SA(0x30,0x14);
			sOption(t); sOption(g,"for: "); sOption(a,"by: "); sOption(c);
			if(emu != "") sVersion += " "+emu;
		}
	}

	else if(X.c("'STK1.0SONG'") && [1,2,3].indexOf(X.U8(0x34)) >= 0
	  && X.U8(0x35) <= X.U8(0x3A) && X.U8(0x36) <= X.U8(0x35)
	  && X.U8(0x38) <= 0x3F && X.U8(0x39) <= 5) {
		// format details here: https://modland.com/pub/documents/format_documentation/STarKos%20(.sks).md
		sName = "STarKos module (.SKS)"; bDetected = 1;
		bad = "";
		switch(X.U8(0x39)) {
		case 0: hz = 13; break;  case 1: hz = 25; break;  case 2: hz = 50; break;
		case 3: hz = 100; break;  case 4: hz = 150; break;  case 5: hz = 300; break;
		default: hz = 0; bad = bad.addIfNone("!badRepFreq")
		}
		xpos = X.I8(0x37).toString(); if(xpos[0] != "-") xpos = "+"+xpos;
		spd0 = X.U8(0x38); ord = 1+X.U16(0x3A);
		ptn = -1; rows = ptnxpos = 0; p = 0x3C;
		for(i=0; i < ord*4; i++) {
			if((i%4) == 3) rows += X.U8(p)+1;
			else { t = X.U8(p); if(ptn < t) ptn = t; if(X.I8(p+1)>>1) ptnxpos++ }
			p += 2
		} ptn++;
		insns = [];
		const ifHard = 0x80, ifPitch = 0x40, ifArp = 0x20, ifNoiseEtc = 0x10;
		for(ins = 0; p < X.Sz() && ins < 0x100; ins++) {
			insn = X.U16(p); p += 2;
			if(insn == 0xFFFF) break;
			ip = p; isz = X.U16(ip); p += 4;
			iend = 1+X.U8(p+2);
			p += 5;
			iname = X.SA(p,8).trim(); p += 8; if(iname != "") insns.push(iname);
			if(X.isDeepScan())
			for(l = 0; l < iend; l++) {
				x = X.U8(p++);
				if(!x) continue;
				if((x & ifHard)) {
					y = X.U8(p++); if(x & 8) p++; if(y & 0x40) p++; if(x & 2) p++;
					if(x & 4) p += 2; if(x & 0x10) p += 2; if(x & 0x20) p += 2
				} else {
					if(x & ifNoiseEtc) { y = X.U8(p++);
						if(y & 0x40) { p += 2;
							if(x & (ifArp | ifPitch)) { bad = bad.addIfNone("!badinsflags"); ins=l=0xFFFE; break }
						}
					}
					if(x & ifArp) p++; if(x & ifPitch) p += 2
				}
			} else p = ip+isz;
		}
		for(sptn = 0; p < X.Sz(); sptn++) {
			curptn = X.U16(p); p += 2; if(curptn == 0xFFFF) break;
			p += X.U8(p)
		}
		if(X.isDeepScan()) notecnt = 0;
		if(p < X.Sz() && ins < 0xFFFE) for(i = 0; i <= ptn; i++) {
			curptn = X.U16(p); p += 2;
			if(curptn != 0xFF && curptn != 0xFFFF && curptn > 0x200) { bad = bad.addIfNone("!badnptn"); break }
			if(curptn == 0xFFFF) break;
			pp = p; psz = X.U16(pp);
			if(X.isDeepScan()) {
				p += 2; var pvol = pins = -1, lc = 0;
				while(p < Math.min(X.Sz(), pp+psz)) {
					var ppitch = 0, lnp = p,
						pfnote = pfvol = pfpitch = false;
					x = X.U8(p++);
					if(x == 0xFF) break;
					else if(x & 0x80) lc += x & 0x7F;
					else if(x >= 0x60)
						switch(x & 0xF) {
						case 0: pfvol = true; pfpitch = false; pvol = 0xF - X.U8(p++);
							if(pvol < 0) { bad = bad.addIfNone("!badptnvol0"); i = ptn; p = pp+psz; break; }
						case 1: pfvol = false; pfpitch = true; ppitch = - X.U8(p++); break;
						case 2: pfvol = pfpitch = true;  pvol = 0xF - X.U8(p++);
							if(pvol < 0) { bad = bad.addIfNone("!badptnvol2"); i = ptn; p = pp+psz }
							ppitch = - X.U8(p++); break;
						case 3: pfnote = true; pnote = "rst"; break;
						case 4: pfnote = true; pnote = "spl"; pins = X.U8(p++); break;
						}
					else {
						pfnote = true;
						y = X.U8(p++);
						pfvol = !(y & 0x40);
						if(pfvol) pvol = 0xF - (y & 0xF);
						if(pins < 0 || !(y & 0x20)) pins = X.U8(p++);
						pfpitch = y & 0x10;
						if(pfpitch) ppitch = - X.I8(p++);
					}
					if(pfnote) notecnt++;
					lc++
				} //single pattern cycle
			} else p = pp+psz;
		} //patterns cycle
		if(p > X.Sz()) bad = bad.addIfNone("!short");
		if(bad != "") sVersion = sVersion.appendS("malformed"+bad,"/");
		if(X.isVerbose()) {
			sOptionT(X.SA(0x14,0x20));
			sOptionT(X.SA(0x0A,0x0A),"by: ");
			sOption("spd0:"+spd0+(xpos!="+0"?" xpos:"+xpos:"")+" ord:"+ord+" ptn:"+ptn+"+"+sptn+" ins:"+ins
				+(ptnxpos?" ptn.xpos:"+ptnxpos:"")+" smp.ch:"+X.U8(0x34)+" rep.freq:"+hz+"Hz"
				+" loop:"+(X.U8(0x36) ? Hex(X.U8(0x36))+"-" : "")
				+Hex(X.U8(0x35))+(X.isDeepScan()?" notes:"+notecnt:" rows:"+rows)+" sz:"+p)
		}
	}//.SKS
	else if(!X.U8(0) && X.c("'SK10'",0x80) && [1,2,3].indexOf(X.U8(0x86)) >= 0
	  && [13,25,50,100,150,300].indexOf(X.U16(0x87)) >= 0 && X.U16(0x18) == X.U16(0x40)) {
		//from https://web.archive.org/web/20240331033458_nf/https://www.grimware.org/doku.php/documentations/software/starkos/start#tweaking.the.song
		sName = "STarKos module (.BIN)"; sVersion = "compiled/ofs:80h"; bDetected = 1;
		crc = 0; for(i=0;i<67;i++) crc += X.U8(i); crc &= 0xFFFF;
		if(crc != X.U16(0x43)) sVersion += "/malformed!badCRC";
		if(X.isVerbose()) {
			sOption(X.SA(1,8).trim()+"."+X.SA(9,3).trim());
			sOption(X.U16(0x87)+"Hz");
			sOption(Hex(X.U16(0x84)),"base:");
			sOption(outSz(X.U24(0x40)+0xBC),"sz:");
		}
	}
	else if(X.c("'Nu!SOPROL!'", 0x22)) {
		sName = "Sound Programming Language module (.SPL)"; bDetected = 1;
		if(X.isVerbose()) {
			t = X.SA(0x58,0x100);
			a = X.SA(0x58+t.length+1,0x100);
			c = X.SA(0x58+t.length+a.length+2,0x100);
			sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
		}
	}
	if(X.c("'SPM'0.")) {
		//ref http://aminet.net/mus/edit/stonefree1.lha
		sName = "Stonetracker tune file (.SPM)"; sVersion = "v"+X.U8(3); bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(4,0x20));
			//TODO find pointer and read the extra stuff
		}
	}
	if(X.c("'SPS'0.0.")) {
		sName = "Stonetracker samples file (.SPS)"; sVersion = "v"+X.U8(3); bDetected = 1;
		if(X.isVerbose()) {
			smp=X.U8(5);
			sOption(smp,"smp:")
			for(i=0; i < Math.min(10,smp); i++)
				sOptionT(X.SA(0x20*i+6,8))
		}
	}
	else if(X.c("'SPU'00") || X.c("'SPU1'") ||
		(X.fSig(X.Sz()-6,TOEOF,"1D80FF"))>-1) {
		bDetected = 1; sName = "Eternal SPU chiptune (.SPU)";
		if(X.c("'SPU'")) {
			if(!X.U8(3)) sVersion = "v0"; else sVersion = "v1"; }
		else sVersion = "headerless";
		if(X.isVerbose() && (sVersion != "headerless")) {
			game = X.SC(4,0x40,"Shift_JIS");
			title = X.SC(0x44,0x40,"Shift_JIS");
			sOptionT(title); sOptionT(game, "game: ");
			artist = X.SC(0x84,0x20,"Shift_JIS"); sOptionT(artist,"by: ");
			cmt = X.SC(0xA4,0xF00,"Shift_JIS"); sOptionT(cmt);
		}
	}
	else if(X.c("'SPEEDY-SYSTEM'")) {
		bDetected = 1; sName = "Speedy System module (.SS)"; sVersion = "v1"
	}
	else if(X.c("'ZXAYST11'")) {
		sName = "Sound Tracker module (.ST1, .ST11)"; bDetected = 1;
		sVersion = "v1.1/uncompiled";
		if(X.isVerbose()) {
			t_= X.U8(0x17); t = X.SA(0x18,t_-1).trim();
			if(t != "Some SoundTracker Song") sOption(t);
		}
	}
	else if(X.c("'KSA SOFTWARE COMPILATION OF '",0x0A) && (X.Sz()<0x2800)) {
		sName = "Sound Tracker Pro module (.STP)"; sVersion = "compiled"; bDetected = 1;
		if(X.isVerbose())
			sOptionT(X.SA(0x26,0x19)); sOption("tempo: "+X.U8(0))
	}
	else if(X.c("'STP3'")) {
		sName = "Soundtracker Pro II module (.STP)"; bDetected = 1;
	}
	else if(X.c("48E7FFFE 4DFA.... 4A2E") && isWithin(X.I8(6),-1,6) && [0x61,0x66].indexOf(X.U8(0xC)) >= 0) {
		sName = "SUNtronic module (.SUN)"; bDetected = 1
	}
	else if(X.c("'SVOX'00000000")) {
		sName = "SunVox module (.SUNVOX)"; bDetected = 1;
		if(X.isVerbose()) {
			p=8;
			t=''; bpm=0; spd=0; tme=0; ptn=0; blk=0;
			while (p < X.Sz()) {
				hkhd = X.SA(p,4); if(charStat(hkhd).indexOf('allasc') < 0) break;
				hksz = X.U32(p+4,_LE); p += 8;
				switch(hkhd) {
					case "VERS":
						nV = X.U32(p,_LE); aV=[];
						for(i=0; i < 4; i++) aV[3-i] = (nV>>(8*i))&0xFF;
						sVersion += "v"+aV.join("."); break;
					case "BVER":
						nB = X.U32(p,_LE);
						if(nB != nV) { aV = [];
							for(i=0; i < 4; i++) aV[3-i] = (nB>>(8*i))&0xFF;
							sVersion += "/v"+aV.join(".");
						}
						break;
					case "NAME": t = X.SA(p,hksz); break;
					case "BPM ": bpm = X.U32(p,_LE); break;
					case "SPED": spd = X.U32(p,_LE); break;
					case "TIME": tme = X.U32(p,_LE); break;
					case "SNAM": blk++; break;
					case "PDTA": ptn++; break;
				}
				p += hksz;
			}
			sOptionT(t);
			if(bpm > 0) sOption(bpm,"bpm:"); if(spd > 0) sOption(spd,"spd:");
			if(tme > 0) sOption(tme,"time:"); if(ptn > 0) sOption(ptn,"ptn:"); if(blk > 0) sOption(blk,"blk:");
			sOption(outSz(p),'sz:')
		}
	}
	else if(X.c("'SymM'")) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_symmod.cpp
		sName = "Symphonie module (.SYMMOD)"; bDetected = 1;
		sVersion = "v"+X.U32(4,_BE);
		if(X.isVerbose()) {
			p=8;
			t=''; done=false; ch=0; len=0; extsmp=false; pro=false;
			while (p < X.Sz()) {
				if(done || charStat(hkhd).indexOf('allasc') < 0) break;
				hkhd = X.I32(p,_BE); hkhx = Hex(X.U32(p,_BE)); hksz=4;
				p += 0x04; //chunk header = 4xtype (the following will change by chunk type)
				switch (hkhd) {
					case -16: pklen=X.U32(p,_BE); hksz+=pklen; o=4; //InfoText RLE-COMPRESSED YO
					  if(pklen>=10 && X.c(p+o,"'PACK'FFFF")) if(!X.isDeepScan()) { p = 0; break } else {
						o+=6; unplen = X.U32(p+o,_BE); o+=4;
						maxlen = pklen-10; if(0xFFFFFFFF/170 >= maxlen) maxlen *= 170; else maxlen = 0xFFFFFFFF;
						if(unplen > maxlen) unplen = maxlen;
						done = false; ofs = 0; left = unplen;
						while(!done && o < hksz) {
							tp = X.I8(p+o); o++;
							switch(tp) {
							case 0:
								l = X.U8(p+o); o++;
								if(left >= l) {
									t += X.SA(p+o,l);
									o += l; left -= l;
								} else done=true;
								break;
							case 1:
								l = X.U8(p+o); o++;
								dw = X.SA(p+o,4); o += 4;
								if(left >= l*4 && o < pklen) {
									left -= l*4;
									while(l--) t += dw;
								} else done = true;
								break;
							case 2:
								dw = X.SA(p+o,4); o += 4;
								if(left >= l*4 && o<pklen) {
									unp += dw+dw;
									left -= 8;
								} else done = true;
								break;
							case 3:
								l = X.U8(p+o); o++;
								if(left >= l) left-=l;
								else done = true;
								break;
							case -1: done = true; break;
							default: sVersion += "/malformed"; done = true; break;
							}//switch
						}
					  } else //if no RLE sig:
					  t = X.SC(p+o,hksz,'CP1252'); break;
					case -1: ch = X.I32(p,_BE); break; //NumChannels
					case -2: len = X.I32(p,_BE); if(len > 1024) len = "malformed"; break; //TrackLength
					case -3: case -4: case -5: break;
					case -7: extsmp = true; break; //ExternalSamples
					case 10: case 11: case 12: pro=true; break; //Sample Boost/Detune/Phase
					case -6: tmp = Math.round(1.24*Math.min(X.I32(p,_BE),800)); break; //Tempo
					case -12: hksz = 0; break; //EmptySample
					case -10: case -11: case -13: case -14: case -15: case -17: case -18:
					case -19: case -20: case -21: hksz+=X.I32(p,_BE); break;
					default: _log(Hex(p,8)+": "+hkhd+"/"+hkhx+" ("+Hex(hksz,8)+"): ?!?!?!?!?!");
				}
				p += hksz;
			}
			sOption(t); sOption(ch,"ch:"); sOption(len,"len:"); sOption(tmp,"bpm:"); if(done)sOption(outSz(p),"sz:");
			if(extsmp) sOption("extsmp"); if(pro) sVersion+="/Pro"
			}
	}
	else if(X.c("'Synth'") && !X.c("'esi'",5)) {
		sName = "Synthesis module (.SYN)"; bDetected = 1;
		if(X.c("'Synth'",0x1F0E)) {
			sVersion = "v"+X.SA(0x1F13,3);
			if(X.isVerbose()) {
				sOptionT(X.SA(0x1F32,0x1B));
				sOptionT(X.SA(0x1F4E,0x100))
			}
		} else {
			sVersion = "v"+X.SA(5,3);
			if(X.isVerbose()) {
				sOptionT(X.SA(0x24,0x1B));
				sOptionT(X.SA(0x40,0x100))
			}
		}
	}
	else if(X.c("'SYNTRACKER-SONG:'00")) {
		sName = "SynTracker module (.SYNMOD)"; bDetected = 1;
		if(X.isVerbose()) {
			t1 = X.SC(0x14,0x20,'CP1252'); sOptionT(t1,"title/inst: ");
			t2 = X.SC(0x34,0x20,'CP1252'); sOptionT(t2);
			t3 = X.SC(0x54,0x20,'CP1252'); sOptionT(t3);
		}
	}
	else if(X.c("'T0AST'")) {
		// ref T0AST.src.zip/ SoundEnginePlayer.pas, synth.pas
		// Kudos to BeRo for the tracker sources
		sName = "The 0ok Amazing Synth Tracker module (.T0AST)"; bDetected = 1;
		if(X.c("'0OK'",5)) { nV = 1; sVersion = "v1"; } else
		if(X.c("010001",5)) { nV = 2; sVersion = "v2"; } else
		{ nV = -1; sVersion = "v.unk"; }
		if(X.isVerbose() && nV != -1) {
			if(nV == 1) p = 0x1DA;
			else if(nV == 2) p = 0x34A;
			else break;
			drummode = X.U8(p++);
			chipmode = X.U8(p++);
			chs = X.U8(p++); ch = 0;
			if(chs > 16) { sVersion += "/malformed"; break; }
			for(i=0; i<16; i++)
				if(X.U8(p++)) { ch++; p += 3; }
			ins = 0;
			for(i=0; i<16; i++)
				if(X.U8(p++)) { ins++; p += 25; }
			ord = X.U16(p,_LE); p += 2+ord;
			ptn = 0; notes = 0;
			for(k=0; k<=255; k++)
				if(X.U8(p++)) { //gotta do what you gotta do
					ptn++;
					for(i=0; i<chs; i++) {
						j = 0;
						while(j < 0x40) {
							b = X.U8(p++);
							if(b & 0x80) { //RLE!
								c = b & 0x7F;
								if(c) {
									p++;
									while(c && j < 0x40) { notes++; j++; c--; }
								}
								else j++;
							}
							else { //just skipping the note parameters
								if(b&0x01) p++; if(b&0x02) p++; if(b&0x04) p++;
								if(b&0x08) p++; if(b&0x10) p++; if(b&0x20) p++;
								if(b&0x40) {
									c = X.U8(p++);
									while(c && j < 0x40) { notes++; j++; c--; }
								}
								else { notes++; j++; }
							}
						}
					}
				}
			loop = X.U8(p++);
			t_ = X.U32(p,_LE); p += 4;
			t = X.SC(p,t_,'CP1252'); p += t_;
			a_ = X.U32(p,_LE); p += 4;
			a = X.SC(p,a_,'CP1252'); p += a_;
			c_ = X.U32(p,_LE); p += 4;
			c = X.SC(p,c_,'CP1252'); p += c_
			sOption(t);
			sOption(addEllipsis(a,0x80),"by: ");
			sOption(addEllipsis(c,0x100),'msg:"','"');
			sOption("ch:"+ch+" ins:"+ins+" ord:"+ord+" ptn:"+ptn+" notes:"+notes+" loop:"+loop+" sz:"+outSz(p));
		}
	}
	else if(X.c("'T0ASTINS'")) {
		sName = "The 0ok Amazing Synth Tracker instrument file"; bDetected = 1;
		if(X.isVerbose()) sOption(outSz(8+25),"sz:")
	}
	else if(X.c("'TFMD'")) {
		sName = "TFM Music Maker tune (.TFD)"; bDetected = 1;
		if(X.isVerbose()) {
			p = 4;
			t = X.SA(p,0x200); //I've seen CP1251 and CP936 already, nobody seems to care
			p = X.fSig(p,0x200,"00")+1;
			a = X.SA(p,0x200);
			p = X.fSig(p,0x200,"00")+1;
			c = X.SA(p,0x200);
			sOptionT(t); sOptionT(a,"by: "); sOptionT(c);
		}
	}
	else if(X.c("'TFMfmtV2'")) { // this format is very similar to VGE
		sName = "Shiru's TFM Music Maker module (.TFE)"; bDetected = 1;
		if(X.isVerbose()) {
			p = 0;
			//WELP, time to read the entire file, or at least the fun part
			x = [], tobuf = true, p = 0, b = 0x3F; // '?' by default. If this pops up, the file's garbled
			for(n=0, c = X.U8(p++); n < 0x423DD9 && p < X.Sz(); ) {
				// The logic is:
				// a byte is read and if it's not 0x80, put to buffer (and memorised)
				// if the following byte is 80, it's RLE:
				// 	 - the following byte gives 7 bits of the number of repeats of the memorised byte
				//   - if the length byte's bit 7 is set, this is the last byte, otherwise adds the next higher 7 bits
				//	 - if the length is 0, store 0x80
				//	 - else store the memorised byte repeats-1 times
				if(tobuf && n > 0x312) tobuf = false;
				if(c != 0x80) { b = c; n++; c = X.U8(p++); if(tobuf) x.push(b) }
				else {
					c = X.U8(p++); next = true; reps = shl = 0;
					while (next && shl < 57) { //read the vari-len int
						next = !(c & 0x80); c &= 0x7F; reps |= Util.shlu64(c,shl); shl += 7; c = X.U8(p++)
					}
					if(!reps) { if(tobuf) x.push(0x80); n++ } //special case for the RLE character
					else { reps -= 1; n += reps; shl = 0; if(tobuf) for(j=0; j < reps; j++) x.push(b) }
				}
			} p--;
			spd1 = x[8]; spd2 = x[9]; intlv = x[0xA]; ord = x[0xB]; if(!ord) ord = 256; lp = x[0xC];
			d1 = x[0x0D]+(x[0x0E] << 8); d2 = x[0x0F]+(x[0x10] << 8); saves = x[0x11]+(x[0x12] << 8);
			d1 = '20'+(d1&0x7F).padStart(2,'0')+'-'+(((d1>>7)&0xF)+1).padStart(2,'0')+'-'+(((d1>>11)&0x1F)).padStart(2,'0');
			d2 = '20'+(d2&0x7F).padStart(2,'0')+'-'+(((d2>>7)&0xF)+1).padStart(2,'0')+'-'+(((d2>>11)&0x1F)).padStart(2,'0');
			auth = decEncoding(x.slice(0x13,0x52),CP1251); //I've seen CP1251 and CP936 already, but yeah
			title = decEncoding(x.slice(0x53,0x94),CP1251);
			cmt = decEncoding(x.slice(0x93,0x214),CP1251);
			for(i=ptn=0; i < ord; i++) if(x[i+0x213]+1 > ptn) ptn = x[i+0x213]+1;
			sz = n == 0x423DD9 ? p : -1; if(sz < 0) sVersion = sVersion.appendS('malformed:'+Hex(n),'/');
			sOptionT(title); sOptionT(auth,'by: '); sOptionT(cmt);
			sOption('on: '+d1+(d1 != d2? ' to '+d2: ''));
			sOption('spd:'+spd1+'/'+spd2+' ord:'+(lp?lp+'-':'')+ord+' ptn:'+ptn+" intlv:"+intlv+' saves:'+saves+(X.isDeepScan()? ' sz:'+outSz(sz):''));
		}
	}
	else if(X.c("0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F", 0x14)
	  && X.c("FFFF001000000030000000", 0x181)) {
		sName = "The Musical Enlightenment module (.TME)"; bDetected = 1
	}
	else if(X.Sz() >= 0x3000 && X.c("'TRK01/TV.ES.'")) {
		sName = "RamTracker module (.TRK)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SC(0x0F,0x20,'CP850'));
			sOptionT(X.SC(0x2F,0x20,'CP850'),"by: ");
			p = 0x453; ptn = -1; ord = 0;
			for(;;) { t = X.U8(p++); if(t >= 0xFE || p > X.Sz()) break;
				ord++; if(ptn < t) ptn = t }
			ptn++;
			sOption("ord:"+ord+" ptn:"+ptn)
		}
	}
	else if(X.c("'S'8F'NG.'")) {
		sName = "Unique Development module (UDS.+SMP.)"; bDetected = 1
	}
	else if((X.Sz() > 0x133) && X.c("'MAS_UTrack_V00'") && isWithin(X.U8(0xE),0x31,0x34)
	  && X.Sz() > X.U8(0x2F)*0x20+0x50) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_ult.cpp
		//TODO calcsize
		sName = "UltraTracker module (.ULT)"; bDetected = 1;
		nV = X.U8(0xE)-0x30; sVersion = 'v'+['<1.4','1.4','1.5','1.6'][nV-1]; bad = '';
		msgn = X.U8(0x2F); p = 0x30+msgn*0x20;
		smp = X.U8(p++); smpsz = 0; smps = [];
		for(i=0; i < smp; i++) {
			t = X.SC(p,0x20,'CP437').trim(); if(t != '') smps.push(t);
			ssz = X.U32(p+0x38) - X.U32(p+0x34);
			if(ssz < 0) bad = bad.addIfNone('!badsmpsz');
			else smpsz += ((X.U8(p+0x3D)&4)? 2: 1)*ssz; //16-bit flag
			if(nV >= 4) p += 0x42; else p += 0x40;
		}
		for(i=ord=mp=0; i < 0x100; i++) if((o=X.U8(p++)) < 0xFE) { ord++; if(mp < o) mp = o }
		ch = X.U8(p++)+1; ptn = X.U8(p++)+1; mp++;
		p += ch;
		for(c=notes=0; c < ch; c++) for(t=0; t < ptn && p < X.Sz(); t++) {
			row = 0;
			while(row < 64) {
				rp = 1; b = X.U8(p++); if(b == 0xFC) { rp = X.U8(p++); b = X.U8(p++) }
				note = isWithin(b,1,96); p += 4; if(rp+row > 64) rp = 64-row;
				if(!rp) break; row += rp; if(note) notes += rp;
			}
		}
		if(p > X.Sz()) bad = bad.addIfNone('!short');
		sz = p + smpsz;
		if(bad != '') sVersion = sVersion.appendS('/malformed'+bad,'/');
		if(X.isVerbose()) {
			sOptionT(X.SC(0x0F,0x20,'CP437'));
			specialmsg =
				(msgn==23&&sz==297221&&ch==7&&ord==20&&ptn==29&&smp==17&&notes==1572); //Handleless - :) falling and flying :(.ult
			for(p=0x30,i=0,msg=[]; i < msgn; i++) {
				t = decAnsi(p,0x20,CP437,Chars0to1F); if(!specialmsg) t = t.trim();
				if(t != '') msg.push(t); p += 0x20
			}
			msg = addEllipsis(msg.join(specialmsg?'\n':' '), specialmsg?0x2000:0xC0, specialmsg?0x2000:0xA0);
			sOption(msg, 'msg:'+(specialmsg?'\n':'"'), specialmsg?'\n':'"');
			if(!msg.length) sOption(addEllipsis(smps.join(','),0x80,0x60),'smp/msg:"','"');
			sOption('ch:'+ch+' ord:'+ord+' ptn:'+mp+(ptn!=mp?'/'+ptn:'')
				+' smp:'+smp+' notes:'+notes+' sz:'+outSz(sz))
		}
	}
	else if(/(UN0[4-6].*|APUN\x01[1-6])/.test(X.SA(0,6))) {
		// ref https://github.com/sezero/mikmod/blob/master/libmikmod/loaders/load_uni.c
		sName = "UNIMOD module (.UNI)"; bDetected = 1;
		if(X.SA(3,1) != 'N') {
			v = +X.SA(3,1);
			if(v > 6) v = X.U16(4,_LE);
			sVersion = "v"+v;
		}
		else {
			sVersion = "v.APlayer";
			v = 0x100;
		}
		if(X.isVerbose()) {
			p = 4;
			if(v >= 6) {
				if(v == 6) p++;
				flg = X.U16(p,_LE); p+=2;
				ch = X.U8(p++);
				voc = X.U8(p++);
				pos = X.U16(p,_LE); p+=2;
				ptn = X.U16(p); p+=2;
				trk = X.U16(p); p+=2;
				ins = X.U16(p); p+=2;
				smp = X.U16(p); p+=2;
				reppos = X.U16(p); p+=2;
				spd0 = X.U8(p++);
				tmp0 = X.U8(p++);
				vol0 = X.U8(p++);
				if(v >= 0x106) { bpmlimit = X.U16(p,_LE); p+=2; }
				  else bpmlimit = 32;
			}
			else {
				ch = X.U8(p++);
				pos = X.U16(p,_LE); p+=2;
				if(v == 5) { reppos = X.U16(p,_LE); p+=2; }
				  else reppos = 0;
				ptn = X.U16(p,_LE); p+=2;
				trk = X.U16(p,_LE); p+=2;
				ins = X.U16(p,_LE); p+=2;
				smp = 0;
				spd0 = X.U8(p++);
				tmp0 = X.U8(p++);
				p += 256+32; // positions+panning
				flg = X.U8(p++);
			}
			ts = X.U16(p,_LE);
			sOptionT(X.SA(p+2,ts));
			if(v < 0x102) {
				porig = p+2+ts;
				origs = X.U16(porig,_LE);
				if(origs>0) sOptionT(X.SA(porig+2,origs),"orig: ");
				p = porig+2+origs;
				sOptionT(X.SA(p+2,X.U16(p,_LE)))
			}
			sOption("ch:"+ch+" trk:"+trk+" ord:"+pos+" ptn:"+ptn+" ins:"+ins+" smp:"+smp
			  +" spd0:"+spd0+" tmp0:"+tmp0)
		}
	}
	else if(X.c("'VGEfmtV'") && isWithin(nV=X.U8(7),0x31,0x33)) { // this format is very similar to TFM Music Maker
		sName = "Shiru's VGM Music Maker module (.VGE)"; bDetected = 1;
		sVersion = 'v'+(nV-0x30);
		if(X.isVerbose()) {
			switch(nV) {
			case 0x31: case 0x32:
				spd = X.U8(0x10)+'/'+X.U8(0x11);
				break;
			case 0x33:
				spd = X.U8(0x1A)+'/'+X.U8(0x1B);
			}
			sOption('spd:'+spd)
		}
	}
	else if( X.c("'Vgm '") && X.Sz() >= (eof = X.U32(4)+4)
	  && (nV = X.U32(8)) && nV < 0x300 //give'em time tho
	  && ( !X.U32(0x14) || X.c("'Gd3 '",X.U32(0x14)+0x14) )) {
		//ref https://vgmrips.net/wiki/VGM_Specification
		// & https://vgmrips.net/wiki/GD3_Specification
		bDetected = 1; bad = '';
		nV = 1000*(nV>>12)+100*((nV>>8)&0xF)+10*((nV>>4)&0xF)+(nV&0xF); //BCD be like...
		sName = "Video Game Music chiptune stream (.VGM)";
		eoh = X.U32(0x34)+0x34; if(nV < 150 || eoh == 0x34) eoh = 0x40;
		if(nV >= 150 && eoh < 0x40) bad = '!dataofs';
		sVersion = "v"+(nV/100).toFixed(2);
		if(X.isVerbose()) {
			tags = []; //11 of them
			gd3p = X.U32(0x14,_LE)+0x14;
			if(gd3p > 0x14)
			if(X.SA(gd3p,4) === "Gd3 ") {
				sVersion += "/Gd3 v"+(readBytes(gd3p+4,4).join("")/100);
				taglen = X.U32(gd3p+8,_LE); gd3p += 12;
				if(gd3p+taglen > X.Sz()) bad = bad.addIfNone('!tagsz');
				i = 0;
				while(i < 11 && gd3p <= X.Sz()) {
					tpos = X.fSig(gd3p,TOEOF,"0000");
					if(tpos >= 0) {
						tags[i] = X.SU16(gd3p,taglen);
						gd3p += tags[i].length*2+2;
					}
					else {
						tags[i] = ""; gd3p += 2
					}
					i++;
				}
				if(i < 11) bad = bad.addIfNone('!tagnum'); else tagn = Math.max(0,i-1);
				sOption(slashTag(tags[0],tags[1])); sOption(slashTag(tags[6],tags[7]),"by: ");
				sOption(slashTag(tags[2],tags[3]),"for: "); sOption(slashTag(tags[4],tags[5]),"on: ");
				sOption(tags[8],"date: "); sOption(tags[9],"ripper: "); sOption(tags[10],"notes: ")
			}
			smp = X.U32(0x18); if(!smp) bad = bad.addIfNone('!badlen');
			lp = X.U32(0x1C)+0x1C; lpsmp = X.U32(0x20);
			if(lp >= eof || lpsmp > smp) {lp = 0; bad = bad.addIfNone('!badloop') }
			rate = X.U32(0x24);
			if(nV > 100) if(!rate) rate = 'n/a'; else
				if(rate == 50) rate += 'Hz(PAL)'; else if(rate == 60) rate += 'Hz(NTSC)'; else rate += 'Hz';
			chips = [[0xC,'SN76489',0], [0x10,'YM2413',0], [0x2C,'YM2612',110], [0x30,'YM2151',110],
				[0x38,'SegaPCM',151], [0x40,'RF5C68',151], [0x44,'YM2203',151], [0x48,'YM2608',151],
				[0x4C,'YM2610/B',151], [0x50,'YM3812',151], [0x54,'YM3526',151], [0x58,'Y8950',151],
				[0x5C,'YMF262',151], [0x60,'YM278B',151], [0x64,'YMF271',151], [0x68,'YMZ280B',151],
				[0x6C,'RF5C164',151], [0x70,'PWM',151], [0x74,'AY8910',151], [0x80,'GameBoyDMG',161],
				[0x84,'NES_APU',161], [0x88,'MultiPCM',161], [0x8C,'uPD7759',161], [0x90,'OKIM6258',161],
				[0x98,'OKIM6295',161], [0x9C,'K051649/K052539',161], [0xA0,'K054539',161],
				[0xA4,'HuC6280',161], [0xA8,'C140',161], [0xAC,'K053260',161], [0xB0,'Pokey',161],
				[0xB4,'QSound',161], [0xB8,'SCSP',171], [0xC0,'WonderSwan',171], [0xC4,'VSU',171],
				[0xC8,'SAA_1099',171], [0xCC,'ES5503',171], [0xD0,'ES5505/ES5506',171],
				[0xD8,'X1-010',171], [0xDC,'C352',171], [0xE0,'GA20',171], [0xE4,'Mikey/Atari_Lynx',172]
			]; chipn = 0;
			xhdr = 0; if(nV >= 170) { t = X.U32(0xBC); if(t) xhdr = t+0xBC; if(xhdr) {
				xhdsz = X.U32(xhdr); if(!xhdsz) { xhdr = 0; bad = bad.addIfNone('!badxhdr') } } }
			for(i=0; i < chips.length; i++) {
				if(chips[i][0] >= eoh || (xhdr && chips[i][0] > xhdr)) break; clk = X.U32(chips[i][0]);
				b30 = Util.shru64(clk,30); b31 = b30 >> 1; b30 &= 1; clk &= 0x3FFFFFFF;
				if(nV < chips[i][2] || !clk) continue; chipn++;
//_log('@'+Hex(chips[i][0])+' b30:'+b30+' b31:'+b31+' clk:'+clk);
				chip = chips[i][1]; //tentatively
				switch(chips[i][0]) { //all the devils in the details
				case 0xC: if(b30 && b31) chip = 'T6W28';
					if(nV <= 151 && !(X.U8(0x2B)&4)) chip += "/GGStereo";
					break;
				case 0x10: if(nV <= 101 && clk > 5000000) chip = 'YM2612'; break;
				case 0x2C:
					if(nV >= 151) if(b31) chip = 'YM3438';
					if(nV <= 101) { clk1 = X.U32(0x10)&0x3FFFFFFF; if(clk1 > 5000000) clk = clk1 }
					break;
				case 0x30:
					if(nV >= 151) if(b31) chip = 'YM2164';
					if(nV <= 101) { clk1 = X.U32(0x10)&0x3FFFFFFF; if(clk1 < 5000000) clk = clk1 }
					break;
				case 0x4C: if(b31) chip = 'YM2610B'; else chip = 'YM2610'; break;
				case 0x74:
					switch(X.U8(0x78)) {
					case 0: break;  case 1: chip = 'AY8912'; break;  case 2: chip = 'AY8913'; break;
					case 3: chip = 'AY8930'; break;  case 4: chip = 'AY8914'; break;
					case 0x10: chip = 'YM2149'; break;  case 0x11: chip = 'YM3439'; break;
					case 0x12: chip = 'YMZ284'; break;  case 0x13: chip = 'YMZ294'; break;
					default: chip += "-ish"
					}
					break;
				case 0x84: if(b31) chip += '/FDS'; break;
				case 0x90: if(X.U8(0x94)&8) chip += '12bit'; else chip += '10bit'; break;
				case 0x9C: if(b31) chip = 'K052539'; else chip = 'K051649'; break;
				case 0xA8:
					switch(X.U8(0x96)) {
					case 0: chip = 'C140+NamcoSystem2'; break; case 1: chip = 'C140+NamcoSystem21'; break;
					case 2: chip = '219_ASIC+NamcoNA-1/2'; break; default: chip += "-ish"
					}
					break;
				case 0xCC: chip += ':'+X.U8(0xD4)+'ch'; break;
				case 0xD0: if(b31) chip = 'ES5506'; else chip = 'ES5505'; chip += ':'+X.U8(0xD5)+'ch'; break;
				}
				sVersion = sVersion.appendS(chip/*+'@'+clk*/,'#');
			}
			volmod = -1; if(eoh > 0x7C) {
				volmod = X.U8(0x7C); if(volmod > 0xC0) volmod -= 0x100; if(volmod == -63) volmod = -64;
				volmod = (2^(volmod/0x20))*100; volmod = Math.round(volmod)+'%' }
			if(chipn > 2) bad = bad.addIfNone('!toomanychips');
			if(eof < X.Sz())
				if(X.SA(eof,4) == "Vgm ") sOption("multisong");
				else sOption("+extra data");
			sOption('rate: '+rate+' len: '+secondsToTimeStr(Util.divu64(smp+22000,44100))+(lp?' looped':'')
				+(volmod !== -1 && volmod !== ' 100%'?' vol: '+volmod:'')+(xhdr?' xhdr':'')
				+' sz:'+outSz(eof))
		}
		if(bad != '') sVersion = sVersion.appendS("malformed"+bad,'/')
	}
	else if(X.c("00'3T1'")) {
		sName = "VicTracker module (.VT)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0x19E,0x10));
			sOptionT(X.SA(0x1AE,0x10),"by: ");
			sOptionT(X.SA(0x1BE,0x10),"'");
		}
	}
	else if(X.fSig(X.Sz()-0x20,TOEOF,"'VSS0'00") > -1) {
		//TODO thaaat's not a good way to detect a thing...
		sName = "Voodoo Supreme Synthesizer audio (.VSS)"; bDetected = 1
	}
	else if(X.c("'Vortex Tracker II 1.0'")) {
		sName = "Vortex Tracker II audio (.VT2)"; bDetected = 1;
		sVersion = "v"+X.SA(0x12,3).trim();
		if(X.isVerbose()) {
			sOptionT(X.SC(0x1E,0x20,'CP1251'));
			sOptionT(X.SC(0x42,0x20,'CP1251'),"by: ");
		}
	}
	else if(X.c("6000") && X.c("48E7FFFE610000",0x04)
		&& X.c("4CDF7FFF'Nu'",0x0C)) {
		sName = "Wally Beben's module (.WB)"; bDetected = 1
	}
	else if(X.c("'WSRF'",X.Sz()-0x20) && X.c("EA",X.Sz()-0x10)) {
		//ref http://daifukkat.su/docs/wsman/#wsr
		sName = "WonderSwan R programmatic chiptune (.WSR)"; bDetected = 1;
		p = X.Sz()-0x20;
		sVersion = "v"+X.U8(p+4);
		if(X.isVerbose()) {
			sOption(Hex(X.U8(p+0x18)),"CartID:")
			pub = X.U8(p+0x16);
			switch(pub) {
			case 0: sOption("(invalid publisher)"); break;
			case 1: sOption("Bandai"); break;
			case 2: sOption("Taito"); break;
			case 3: sOption("Tomy"); break;
			case 4: sOption("Koei"); break;
			case 5: sOption("Data East"); break;
			case 6: sOption("Asmik Ace"); break;
			case 7: sOption("Media Entertainment"); break;
			case 8: sOption("Nichibutsu"); break;
			case 0x0A: sOption("Coconuts Japan"); break;
			case 0x0B: sOption("Sammy"); break;
			case 0x0C: sOption("Sunsoft"); break;
			case 0x0D: sOption("Mebius"); break;
			case 0x0E: sOption("Banpresto"); break;
			case 0x10: sOption("Jaleco"); break;
			case 0x11: sOption("Imagineer"); break;
			case 0x12: sOption("Konami"); break;
			case 0x16: sOption("Kobunsha"); break;
			case 0x17: sOption("Bottom Up"); break;
			case 0x18: sOption("Kaga Tech"); break;
			case 0x19: sOption("Sunrise"); break;
			case 0x1A: sOption("Cyber Front"); break;
			case 0x1B: sOption("Mega House"); break;
			case 0x1D: sOption("Interbec"); break;
			case 0x1E: sOption("Nihon Application"); break;
			case 0x1F: sOption("Bandai Visual"); break;
			case 0x20: sOption("Athena"); break;
			case 0x21: sOption("KID"); break;
			case 0x22: sOption("HAL Corporation"); break;
			case 0x23: sOption("Yuki Enterprise"); break;
			case 0x24: sOption("Omega Micott"); break;
			case 0x25: sOption("Layup"); break;
			case 0x26: sOption("Kadokawa Shoten"); break;
			case 0x27: sOption("Shall Luck"); break;
			case 0x28: sOption("Squaresoft"); break;
			case 0x2B: sOption("Tom Create"); break;
			case 0x2D: sOption("Namco"); break;
			case 0x2E: sOption("Movic(?)"); break;
			case 0x2F: sOption("E3 Staff(?)"); break;
			case 0x31: sOption("Vanguard"); break;
			case 0x32: sOption("Megatron"); break;
			case 0x33: sOption("Wiz"); break;
			case 0x34: sOption("Capcom"); break;
			default: sOption("(unknown publisher)")
			}
			sOption(X.U8(p+5),"1sttrk: ");
		}
	}
	else if(X.c("3026b2758e66cf11a6d900aa0062ce6c")) { //TODO organise to a non-module file
		sName = "Windows Media (.WMV/WMA)"; bDetected = 1
	}
	else if(X.c("'XAD!'")) {
		sName = "Exotic AdLib module (.XAD)"; bDetected = 1;
		if(X.isVerbose()) {
			sOptionT(X.SA(0x04,0x24));
			sOptionT(X.SA(0x28,0x24),"by: ");
		}
	}
	else if(X.c("'Extended Module: '") && X.c("1A",0x25)
	  && X.U16(0x48) && X.U16(0x48) <= 256) {
		//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_xm.cpp et al.
		V = X.U16(0x3A); charset = 'CP437';
		sName = "Fast Tracker 2 Extended module (.XM)"; bDetected = 1;
		text = tracker = mVlsw = bad = ""; insns = []; smpns = [];
		if(X.isDeepScan()) {
			var hdrp = 0x3C, hdrsz = X.U32(hdrp),
			  restartp = X.U16(hdrp+6), flags = X.U16(hdrp+0x0E),
			  linearSlides = flags&1, xFilter = flags&0x1000;
			ord = X.U16(hdrp+4); chn = X.U16(hdrp+8);
			ptn = X.U16(hdrp+0x0A); ins = X.U16(hdrp+0x0C);
			tmp0 = X.U16(hdrp+0x10); bpm0 = X.U16(hdrp+0x12);
			var ptnhdp = p = hdrp+hdrsz;
		   if(V >= 0x104) for(i=0; i < ptn; i++) p += X.U32(p)+X.U16(p+7);
			//version detection pt.1/9001
			const verUnknown = 0x00, verOldModPlug = 0x01, verNewModPlug = 0x02,
			verMPfBiDi = 0x04, verOpenMPT = 0x08, verConfirmed = 0x10,
			verFT2Generic = 0x20, verOther = 0x40, verFT2Clone = 0x80,
			verDigiTrakker = 0x100, verUNMO3 = 0x200, verEmptyOrders = 0x400;
			var isMadTracker = isOMPTMade = isOXM = mixlevCompatFT2 = false;
			madewith = verUnknown;
			if(X.c("'FastTracker v2.00   '",0x26) && hdrsz === 276) {
				if(V < 0x104) madewith = verFT2Generic | verConfirmed;
				else if(X.fSig(0x11,0x14,"00") > -1)
					madewith = verFT2Clone | verNewModPlug | verEmptyOrders;
				else
					madewith = verFT2Generic | verNewModPlug;
			}
			else if(X.c("'FastTracker v 2.00  '",0x26)) madewith = verOldModPlug;
			else {
				madewith = verUnknown | verConfirmed;
				tracker = X.SC(0x26,0x14,charset).trim();
				if(X.c("'OpenMPT '",0x26)) madewith = verOpenMPT | verConfirmed | verEmptyOrders;
				else if(X.c("'MilkyTracker '",0x26)) { if(!X.c("'       '",0x32)) mixlevCompatFT2 = true }
				else if(X.c("'Fasttracker II clone'",0x26)) madewith = verFT2Generic | verConfirmed;
				else if(X.c("'MadTracker 2.0'00",0x26)) isMadTracker = true;
				else if(X.c("'*Converted '",0x26)) madewith = verDigiTrakker;
			}
			if(xFilter && madewith == (verFT2Generic | verNewModPlug))
				madewith = verFT2Clone | verNewModPlug | verConfirmed;
			//back on track
			smp = 0; var insp = p, itype = -1;
			var anyADPCM = smpReserved = smpsz = 0, sflags = [];
			if(!ord && !verEmptyOrders) ord = 1; //a fix for dark lighthouse.xm
			for(i=0; i < ins; i++) {
				var ihdsz = X.U32(p); if(!ihdsz) ihdsz = 263;
				inst = X.SC(p+4,0x16,'CP437').trim();
				if(inst.length) insns.push(inst);
				var smpn = X.U16(p+0x1B), smphdsz = X.U32(p+0x1D);
				if(madewith == verOldModPlug) { madewith |= verConfirmed;
					if(ihdsz == 245) { mVlsw = "1.00.00.A5"; tracker = "ModPlug Tracker 1.0 alpha" }
					else if(ihdsz == 263) { mVlsw = "1.00.00.B3"; tracker = "ModPlug Tracker 1.0 beta" }
					else madewith = verUnknown | verConfirmed;
				} else if(!smpn) {
					if(ihdsz == 263 && !smphdsz && (madewith & verNewModPlug)) madewith |= verConfirmed;
					else if(ihdsz != 29 && (madewith & verDigiTrakker)) madewith &= ~verDigiTrakker;
					else if((madewith & (verFT2Clone|verFT2Generic)) && ihdsz != 33) madewith = verUnknown;
				}
				if(itype == -1) itype = X.U8(p+0x1A);
				else if(itype != X.U8(p+0x1A) && (madewith & verFT2Generic)) {
					madewith &= ~verFT2Generic; madewith |= verFT2Clone;
				}
				var midichecks = X.U8(p+0xD0) | X.U8(p+0xD1)
				  | X.U16(p+0xD2) | X.U8(p+0xD6);
				p += ihdsz; smp += smpn;
				if(p > X.Sz()) { bad = bad.addIfNone("!short"); break }
				if(V >= 0x104) sflags = [];
				var ssizes = [], slens = 0;
				if(smpn) {
					if(midichecks) madewith &= ~(verOldModPlug | verNewModPlug);
					for(j=0; j < smpn; j++) {
						slen = slens[j] = X.U32(p);
						var sf = X.U8(p+0xE), sr = X.U8(p+0x11),
						  sname = X.SC(p+0x12,22,'CP437').trim(),
						  isADPCM = sr === 0xAD && (!sf & !(sf&0x30)); smpReserved |= sr;
						  if(sname.length) smpns.push(sname);
						sflags.push([sf, isADPCM]); if(isADPCM) anyADPCM = true;
						slens += isADPCM ? 16+(slen+1 >> 1) : slen;
						p += 40; //p+=smphdsz; //considering there are files with smphdsz = 0...
						if((sf&3) == 3 && (madewith&verNewModPlug))
							madewith |= verMPfBiDi;
					}
					smpsz += slens;
					if(V >= 0x104) { if(X.c("'OggS'",p)) isOXM = true; p += slens }
				}
			}
			if(!smpReserved && (madewith&verNewModPlug) && X.fSig(0x11,0x14,"00") > -1)
				madewith |= verConfirmed;
			if(V < 0x104) {
				for(i=0; i < ptn; i++)
					p += X.U32(p)+X.U16(p+(V == 0x102 ? 6 : 7));
				if(X.c("'OggS'",p)) isOXM = true;
				p += smpsz
			}
			basesz = p;
			if(tracker == "") {
				if((madewith&verDigiTrakker) && !smpReserved && (itype ? itype : -1) == -1)
					tracker = "Digitrakker";
				else if(madewith&verFT2Generic)
					tracker = "FastTracker 2 or compatible";
				else tracker = "Unknown"
			}
			//catch plugins, settings, messages from various XM flavours
			fx = 0; xt = "";
			while(p+6 < X.Sz()) {
				t = X.SA(p,4); it = X.U32(p);
				if(it && X.U16(p+4)
				  && (t === "228\x04" || (it & 0x80808080) || !(it & 0x60606060))) {
					//I haven't seen files with that to see how that goes so we'll just cut here!
					break
				}
				if(t === "text") {
					t = X.U32(p+4); p += 8; xt = xt.append("t");
					text = X.SC(p,t,'CP437').trim(); p += t;
					madewith |= verConfirmed
				} else if(/F[0-9X]\d\d/.test(t)) {
					t = X.U32(p+4); if(p+8+t <= X.Sz()) { p += 8+t; fx++ }
					madewith |= verConfirmed
				} else if(t === "MIDI") {
					t = X.U32(p+4); madewith |= verConfirmed;
					if(p+8+t <= X.Sz()) { p += 8+t; xt = xt.append("m") }
				} else if(t === "CHFX" || t === "CNAM" || t === "PNAM") {
					t = X.U32(p+4); if(p+8+t <= X.Sz()) { p += 8+t; }
					madewith |= verConfirmed
				} else if(t === "XTPM") {
					p += 4; xt = xt.append("x"); madewith |= verConfirmed; isOMPTMade = true;
					t = X.SA(p,4);
					while(p+7 < X.Sz()) {
						if(!X.U8(p)) { p++; break }
						code = X.SA(p,4); icode = X.U32(p);
						if(code === "STPM" || code === "228\x04"
						  || (icode & 0x80808080) || !(icode & 0x60606060))
							break;
						prsz = X.U16(p+4); p += 6;
						for(i=0; i < ins; i++) p += prsz }
				} else if(t === "STPM") {
					p += 4; xt = xt.append("s"); madewith |= verConfirmed;
					while(p+6 < X.Sz()) {
						if(!X.U8(p)) { p++; break }
						if(X.c("'VWSL'",p)) { v = 0;
							function itV(v) { return v.slice(0,1)+'.'+v.slice(1,3)+'.'+v.slice(3,5)+'.'+v.slice(5,7) }
							switch(X.U16(p+4)) {
							case 1: v = X.U8(p+6); break; case 2: v = X.U16(p+6); break;
							case 3: v = X.U24(p+6); break; case 8: v = X.U64(p+6); break;
							default: v = X.U32(p+6) }
							if(v) mVlsw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
						}
						p += 6+X.U16(p+4)
					}
				} else break;
			}
			if(madewith&verConfirmed)
				if(madewith&verMPfBiDi) {
					mVlsw = "1.11"; tracker = "ModPlug Tracker 1.0-11";
				} else if(madewith&verNewModPlug) {
					mVlsw = "1.16"; tracker = "ModPlug Tracker 1.0-16";
				}
			if(X.c("'OpenMPT '",0x26)) {
				mVlsw = X.SA(0x2E,12).trim(); madewith = verOpenMPT|verConfirmed;
			}
			if(isOMPTMade && mVlsw < "1.17") mVlsw = "1.17";
			if(mVlsw >= "1.17") tracker = "OpenMPT v"+mVlsw;
			sz = p;
			charset = (mVlsw != "" || isMadTracker) ? 'CP1252' : 'CP437';
		}
		else tracker = X.SC(0x26,0x14,charset).trim();
		if(X.isVerbose()) {
			sOptionT(X.SC(0x11,0x14,charset));
			sOptionT(tracker,"in:");
			if(X.isDeepScan() && isOXM) sOption("OggMod FastTracker 2 (.OXM)","via:");
			if(text.length) sOption(addEllipsis(text,0x100));
			if(insns.length) sOption(addEllipsis(insns.join(" "),0x100),'ins/msg:"','"');
			if(smpns.length) sOption(addEllipsis(smpns.join(" "),0x100),'smp/msg:"','"')
			if(X.isDeepScan()) {
				var info = "chn:"+chn+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" smp:"+smp;
				if(fx) info += " fx:"+fx; if(xt.length) info += " xt:"+xt;
				if(sz != basesz) info += " base_sz:"+basesz; info += " sz:"+outSz(sz); sOption(info)
			}
		}
		sVersion = "v"+(V>>8)+"."+(V&0xFF)+bad+(anyADPCM?"/ADPCMpacked":"");
	}

	else if(X.c("'FORM' 0000000E 'XDIRINFO'  00000002 .... 'CAT ' ........ 'XMIDFORM' ........ 'XMID'")) {
		bDetected = 1;
		sName = "Extended MIDI chiptune (.XMI)";
		if(X.isVerbose()) {
			if((x=X.U8(0x14)) > 1) sOption(x,'×')
			sOption(outSz(0x1E+X.U32(0x1A,_BE)),'sz:')
		}
	}
	else if(X.c("'ofTAZ!'")) {
		sName = "Extra Simple Music module (.XSM)"; bDetected = 1
	}
	else if(X.c("'YM'3.'!'") || X.c("'YM3b!'")
		  || X.c("'YMT'3.'LeOnArD!'") || X.c("'MIX1LeOnArD!'")) {
		//ref https://github.com/cpcsdk/libstsound/blob/master/Ymload.cpp
		bDetected = 1; bad = ''; frm = smp = voc = loop = ddn = 0;
		sName = "ST-Sound chiptune stream (.YM)";
		sV = X.SA(0,4).replace(/!/g,"");
		switch(sV) {
		case "YM1": sVersion = "YM1"; break;
		case "YM2": sVersion = "MADMAX specific"; break;
		case "YM3": sVersion = "YM-Atari"; break;
		case "YM3b": sVersion = "YM-Atari+loopinfo";
			loop = X.U32(X.Sz()-4,_LE); break;
		case "YM4": sVersion = "YM-Atari extended"; break;
		case "YM5": case "YM6": sVersion = "Generic YM2149 extended"; break;
		case "MIX1": sVersion = "Atari Remix digital"; break;
		case "YMT1": case "YMT2": sVersion = "YM-Tracker"; break;
		}
		if(["YM2","YM3","YM3b"/*,"YM4"*/].indexOf(sV) >= 0) //No YM4 in the wilderness, ignoring
			frm = Util.divu64(X.Sz()-4,14);
		if(["YM5","YM6","YMT1","YMT2","MIX1"].indexOf(sV) >= 0) {
			if(X.SA(4,8)!="LeOnArD!") bad = bad.addIfNone('!badsig');
			if((["YM6!","YMT1","YMT2"].indexOf(sV) >=0)
				&& (X.SA(X.Sz()-4)!="End!")) bad = bad.addIfNone('!badfilesz');
			//if(sV==="YM4") p=0x1C; else
			if(["YM5","YM6"].indexOf(sV) >= 0) {
				ddn = X.U16(0x14,_BE);
				loop = X.U32(0x1C,_BE);
				p = X.U16(0x20,_BE)+0x22;
				for(i=0; i < ddn; i++) {
					ds = X.U16(p,_BE); p += 2+ds; if(p >= X.Sz()) {bad = bad.addIfNone('!tooshort'); break}
				}
			} else
			if(sV === "MIX1") {
				p = 0x18;
				smp = X.U32(0x10,_BE);
				mixblk = X.U32(0x14,_BE);
				for(i=0;i<mixblk;i++) p+=12; //u32 smpst,smplen; u16 repeat,rplfreq skipped
			} else
			if(["YMT1","YMT2"].indexOf(sV) >= 0) {
				ddn = X.U16(0x18,_BE);
				voc = X.U16(0x0D,_BE);
				frm = X.U32(0x10,_BE);
				p = 0x1E;
			}
			//read the three tags from here
			if(p>=X.Sz()) bad = bad.addIfNone('!nodata');
			else {
				t = p; t_ = X.fSig(p,TOEOF,"00")-p;
				if(t_ >= 0) {
					p += t_+1; a = p; a_ = X.fSig(p,TOEOF,"00")-p;
					p += a_+1; c = p; c_ = X.fSig(p,TOEOF,"00")-p;
					p += c_+1;
					if(["YM5","YM6"].indexOf(sV) >= 0)	
						if(!X.c("'End!'",p+((frm = X.U32(0xC,_BE)) << 4))) {
							bad = bad.addIfNone('!badframes'); sOption("frm/frames: "+(frm<<4)+"/"+(X.Sz()-p-4)) }
				}
			}
			if(X.isVerbose()) {
				sOptionT(X.SA(t,t_));
				sOptionT(X.SA(a,a_),"by: ");
				sOptionT(X.SA(c,c_));
			}
		}
		if(X.isVerbose()) {
			if(voc) sOption(voc,"voc:");
			if(smp) sOption(outSz(p+smp),'sz:');
			if(ddn) sOption(ddn, "digidrums:")
			if(frm) sOption('len:'+frm+' sz:'+outSz(p+(frm<<4)+4));
		}
		if(bad != '') sVersion= sVersion.appendS('malformed'+bad,'/')
	}
	else if(X.c("'YMST'")) {
		sName = "MYST ST-YM module (.YMST,.YM)"; bDetected = 1;
		if(X.isVerbose()) {
			for(i=0,p=4; i < 48; i++) { p += 8; if(!X.U16(p-8,_BE)) break }
			t = p; t_ = X.fSig(p,TOEOF,"00")-p;
			if(t_ >= 0) {
				p += t_+1; a = p; a_ = X.fSig(p,TOEOF,"00")-p;
				p += a_+1; c = p; c_ = X.fSig(p,TOEOF,"00")-p;
				if(X.isVerbose()) {
					sOptionT(X.SA(t,t_));
					sOptionT(X.SA(a,a_),"by: ");
					sOptionT(X.SA(c,c_));
				}
			}
		}
	}

//pure signature detection end


// AND NOW,
// THE HEAVIEST STUFF GOES DOWN HERE
// THE SANITY CHECK-STYLE DETECTION
// (* It's still hella fast tho, no worries)

function isProtoTracker() {
	if(!X.c("'SONG'") ) return false;
	//ref https://github.com/kometbomb/prototracker/blob/master/doc/FORMAT.TXT
	//TODO tighten somewhat
	secsz = X.U32(4,_BE);
	v = X.U8(8); trk = X.U8(9); p = 0xA;
	fxc = 0; if(v >= 17) { fxc = X.U8(p); p++ }
	title = X.SA(p,0x100);
	if(!X.c("'SEQU'",p+title.length+1+2)) return false;
	return true
}
if(!bDetected && isProtoTracker()) {
		sName = "ProtoTracker module (.SONG)";  bDetected = 1;
		sVersion = sVersion = "v"+v;
		if(X.isVerbose()) {
			sOption(title);
			sOption("trk:"+trk+" fx:"+fxc)
		}
}


function isAnders0land() {
	//ref https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Anders0land/SRC_Anders0land/Anders 0land_v1.asm
	if(!X.c("'mpl'")) return false;
	p = X.U32(4,_BE); if(p > X.Sz() || (p&1)) return false;
	songp = p+8;
	if(!X.c("'mdt'",p)) return false;
	p += X.I32(p+4,_BE); if(p > X.Sz() || (p&1)) return false;
	if(!X.c("'msm'",p)) return false;
	p += X.I32(p+4,_BE); if(p < X.Sz()) return false;
	sz = p;
	smpp = songp+X.I32(songp-4,_BE);
	smpsz = X.I32(smpp-4,_BE);
	smp = (X.I16(songp+20,_BE) - X.I16(songp+18,_BE)) >> 2;
	songsz = songp-8;
	x = (X.I16(songp+4,_BE) - X.I16(songp+2,_BE)) >> 2;
	p = X.I16(songp,_BE);
	ord = X.I16(songp+6,_BE)-p-X.I32(songp+p+12,_BE);
	return true
}
if(!bDetected && isAnders0land()) {
	sName = "Anders 'Zonix' 0land's Music & Player module (.HOT)"; bDetected = 1;
	sVersion = "v"+X.SA(3,1);
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("ord:"+ord+" smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz));
		sOption(outSz(sz),"sz:")
	}
}


function isD00nohdr() {
	// ref https://github.com/adplug/adplug/blob/master/src/d00.h & .cpp
	if(X.Sz() < 40 || X.Sz() >= 0xFFFF) return false;
	if(X.c("0001.... 000D00") && X.c("FFFF",0xB)) nV = 0;
	else if(X.c("01..01.. 000F00") && X.c("FFFF",0xD)) nV = 1; else return false;
	seqp = X.U16(3,_LE); if(seqp < 13 || seqp > X.Sz()) return false; // ptr to sequence pointer table
	x = 1;
	seqtest = X.fSig(seqp+18+9,TOEOF,"0000000000 FFFF")-seqp; // reserved bytes are 0; endmark follows 
	while (seqtest > 0x20) { x++; seqtest -= 0x20; if(!X.c("0000000000",seqp+seqtest)) return false }
	//there may be several sequence tables but they end the same
	trkp = t = X.U16(5,_LE); if(!X.c("FFFF",trkp-2) || trkp < 13) return false; // ptr to track pointer table
	insp = X.U16(7,_LE); if(!X.c("FFFF",insp-2) || insp < 13) return false; // to instrument pointer table
	sz = X.U16(9,_LE); if(!X.c("FFFF",sz-2) || sz < 13) return false; // to message
	if(nV == 1) { sfxp = X.U16(11,_LE); if(!X.c("FFFF",sfxp+8)) return false; } // to sfx
	if(Math.abs(trkp-seqp) < 20 || Math.abs(trkp-insp) < 20 || Math.abs(seqp-insp) < 20) return false;
	oldp = trkp; p = X.U16(trkp,_LE);
	ip = 0, once = 0; // count pointers out of order, heuristic
	while(t < trkp+18 && trkp < X.Sz()) { // check 9 pointers from TPoin for validity
		if(t === trkp) p = oldp = t; // for the first pointer
		if(p) oldp = p;
		p = X.U16(t,_LE); if(!p || p < trkp || p > X.Sz()) return false;
		if(!X.c("FFFF",p-2)) { once++; if(once > 1) return false }
		if(p < oldp) { ip++; if(ip > 2) return false } // some D00s do have pointers out of order
		q = p; while(!X.c("FFFF",q) && q < X.Sz()) q += 2; //check the table data for having a FFFF somewhere near
		if(!X.c("FFFF",q)) { _log("boh. t="+Hex(t)+" p="+Hex(p)+" q="+Hex(q)); return false; }
		t += 2
	}
	msg = ""; t = X.fSig(sz,0x200,"FFFF"); if(t > 0) { // the message exists and ends in FFFF
		msg = X.SC(sz,t-sz,'CP437').trim(); sz = t+2
	} else { // the message may exist but it may also be garbage, checking...
		msg = readBytes(sz,Math.min(sz+0x100,X.Sz())-sz);
		c = charStat(msg,1); if(c.indexOf("allxsc") >= 0) {
			msg = decEncoding(msg,CP437); sz += msg.length; msg = msg.trim()
		} else msg = ''
	}
	return true;
}
if(!bDetected && X.isDeepScan() && isD00nohdr()) {
	sName = "Edlib Tracker module (.D00)?"; sVersion = "old v"+X.U8(0); bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		sOptionT(addEllipsis(msg,0x100,0x80),'msg:"','"');
		sOption("sz:"+outSz(sz))
	}
}


function isProtrackerMOD() {
	//from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
	if(X.Sz() < 0x43C) return false;
	var b = readBytes(0x438,4), s = X.SA(0x438,4), hdr = 0x43C/*1084*/, plist = 0x3B8/*952*/, msmp = 0x1F;
	var isStarTrekker = isGenericMCh = isMdKd = maybeWOW = isHMNT = isInconexia = isNoiseTracker
	  = setMODVBlankTiming = hasLongSmp = hasEmptySmpwVol = hasRepLen0 = false;
	var ibtrsh = 40; //typical threshold for invalid bytes (in samples); _FRAGILE is 1

	//test signatures
	if(/(M\.K\.|M!K!|PATT|NSMS|LARD)/.test(s)) {
		chn = 4; tracker = "generic ProTracker-compatible";
		if(s === "M.K.") { isMdKd = true; maybeWOW = true } }
	else if(/(M&K!|FEST|N\.T\.)/.test(s)) {
		chn = 4; if(s === "N.T.") tracker = "NoiseTracker";
		else { tracker = "His Master's NoiseTracker"; isHMNT = true } isNoiseTracker = true }
	else if(/O[KC]TA/.test(s)) { chn = 8; tracker = "Oktalyzer" }
	else if(/CD[68]1/.test(s)) { chn = b[2]-0x30; tracker = "Oktalyser (Atari)" }
	else if(b === [0x4D,0,0,0] || b === [0x38,0,0,0]) {
		if(b[0] === 0x38) chn = 8; else chn = 4; ibtrsh = 1; // fragile, needs more detects!
		tracker = "Inconexia demo"; isInconexia = true }
	else if(/FA0[4-8]/.test(s)) { chn = b[3]-0x30; tracker = "Digital Tracker"; hdr = 0x440 }
	else if(/(FLT|EXO)[48]/.test(s)) { chn = b[3]-0x30; isStarTrekker = setMODVBlankTiming = true;
		tracker = "Startrekker" }
	else if(/[1-9]CHN/.test(s)) { chn = b[0]-0x30; isGenericMCh = true; tracker = "generic MOD-compatible" }
	else if(/[1-9][0-9]C[HN]/.test(s)) {
		chn = b[0]*10+b[1]-0x210; isGenericMCh = true; tracker = "generic MOD-compatible" }
	else if(/TDZ[1-9]/.test(s)) { chn = b[3]-0x30; tracker = "TakeTracker" }
	//else if(/\.M\.K/.test(s)) { chn = 4; swapBytes = true } just one hacked module, TODO after fast patches are in
	else if(/WARD/.test(s)) { chn = 8; isGenericMCh = true; tracker = "generic MOD-compatible" }
	else return false;
	if(!chn) return false;
	isFLT8 = isStarTrekker && chn == 8;
	restartpos = X.U8(plist-1);
	//test samples
	p = 0x14; wowsmpsz = 0; ib = 0;
	for(; p < 0x3B6; p += 0x1E) {
		var ssz = X.U16(p+0x16,_BE)*2; //length
		wowsmpsz += ssz;
		if(!isHMNT && !hasLongSmp && ssz >= 0x20000) hasLongSmp = true;
		var sft = X.U8(p+0x18);
		if(sft) maybeWOW = false; if(sft > 15) ib++; //finetune <= 15
		var svol = X.U8(p+0x19); if(svol > 0x40) ib++; //volume <= 64
		if(ssz && svol != 0x40) maybeWOW = false;
		if(!hasEmptySmpwVol && !ssz && svol == 64) hasEmptySmpwVol = true;
		if(X.U16(p+0x1A,_BE)*2 > ssz) ib++; //loopstart. looplen gets weird, it's ok
		if(!hasRepLen0 && ssz && !X.U16(p+0x1C,_BE)) hasRepLen0 = true; //looplen
		if(ib > ibtrsh) return false;
	}
	var smpsz31 = wowsmpsz;
	if(restartpos) maybeWOW = false; if(!maybeWOW) wowsmpsz = 0;

	//test patterns
	function cntBadPtnData(ofs) {
		mask = 0xE0; var c = 0;
		for(i = 0; i < chn*64; i++)
			if(X.U8(ofs+i) & mask) c++;
		return c
	}
	p = plist+0x80;
	//get real ptn:
	ord = X.U8(plist-2); if(!ord) return false;
	ol = readBytes(plist,0x80); //orderlist
	if(ord > 128) ord = 128;
	else if(!ord) { ord = 128; while(ord > 1 && !ol[ord-1]) ord-- }
	ptn = iptn = optn = 0; i = plist;
	for(i=0; i < 0x80; i++) {
		if(isFLT8) ol[i] /= 2; var pt = ol[i];
		if(pt < 0x80 && pt >= ptn) { ptn = pt+1; if(ord > i) optn = ptn }
		if(ptn >= iptn) iptn = ptn+1
	}
	//weirdness checks
	if(wowsmpsz && (wowsmpsz+hdr+ptn*8*256 == (X.Sz() & ~1))) {
		if(cntBadPtnData(hdr+ptn*4*256) < 16) chn = 8; //⚠no good for ripping -- best to err toward no WOW
	} else
	if(ptn != optn && cntBadPtnData(hdr+optn*chn*256) < 64) ptn = optn;
	if(iptn > ptn && hdr+ssz+iptn*chn*256 == X.Sz()) ptn = iptn;

	if(maybeWOW && chn === 8) { tracker = "Mod's Grave"; isGenericMCh = true }
	if(restartpos >= ord || (restartpos == 0x78 && chn === 4)) restartpos = 0; //because that's actually bpm

	var onlyAmigaNotes = true, fix7BitPanning = leftPan = extPan = maxPan = 0;
	if(!isNoiseTracker) {
		isNoiseTracker = isMdKd && !hasEmptySmpwVol && !hasLongSmp;
		p = hdr;
		for(pt = 0; pt < ptn; pt++) {
			var ptnbrk = 0;
			for(; p < pt*chn*256; p += 4) {
				var d = readBytes(p,4);
				var np = ((d[0]&0xF)<<8) | d[1]; // note period
				if(np && np != 0xFFF) {
					if(onlyAmigaNotes && (np < 113 || np > 856)) onlyAmigaNotes = isNoiseTracker = false;
				}
				var cmd = d[2]&0xF, prm = d[3] /*, dsmp = (d[2]>>4) | (d[0]&0x10)*/;
				if((cmd > 6 && cmd < 0xA) || (cmd == 0xE && prm > 1)
					|| (cmd == 0xF && prm > 0x1F) || (cmd == 0xD && ++ptnbrk > 1))
					isNoiseTracker = false;
				if(cmd == 8) {
					if(prm > maxPan) maxPan = prm;
					if(prm < 0x80) leftPan = true;
					else if(prm > 0x8F && prm != 0xA4) extPan = true;
				}
			}
		}
		fix7BitPanning = leftPan && !extPan && maxPan >= 0x30;
	}
	if(onlyAmigaNotes && !hasRepLen0 && ['M.K.','M!K!','PATT'].indexOf(s) >= 0) {}
	else if(!onlyAmigaNotes && restartpos == 0x7F && isMdKd && restartpos+2 >= ord)
		tracker = 'Scream Tracker';
	//modsize
	sz = hdr+ptn*chn*256;
	if(sz > X.Sz()) return false;
	p = 0x14; smp = 0; songsz = sz; ib = 0; smps = [];
	var anyADPCM = 0;
	for(; p < 0x3B6; p += 0x1E) {
		if(!isHMNT) { //His Master's Noise have data in sample titles
			t = decAnsi(p,22,CPAmiga).trim(); if(t != "") smps.push(t)
		}
		var ssz = X.U16(p+0x16,_BE)*2;
		if(ssz) smp++;
		//if(isMdKd && onlyAmigaNotes && !hasEmptySmpwVol) ssz = Math.max(ssz,X.U16(p+0x1C,_BE)*2);
		if(X.c("'ADPCM'",sz)) { anyADPCM++; ssz = (ssz+1>>1)+5+16; }
		sz += ssz;
	}
	if(isMdKd && X.c("00115533 2211 040101",sz)) { tracker = 'Tetramed'; sz += 9 } //040101 may have been version numberl
	if(anyADPCM && !isInconexia) tracker += ' (ADPCM packed: '+anyADPCM+')';
	return true
}
if(!bDetected && isProtrackerMOD()) {
	sName = "Amiga Freelancers' Protracker module (.MOD)"; bDetected = 1;
	sVersion = X.SA(0x438,4);
	if(X.isVerbose()) {
		sOptionT(decAnsi(0,20,CPAmiga));
		sOption(tracker,"in: ");
		sOptionT(addEllipsis(smps.join(" "),200),'smp/msg:\"','"');
		sOption("chn:"+chn+" ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
	}
}


function isKRIS() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/ChipTracker/ChipTracker_v3.asm
	// & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
	if(X.Sz() < 0x7C0+0x100 || !X.c("'KRIS'",0x3B8)) return false;
	ord = X.U8(0x3BC); if(ord > 0x80) return false;
	loop = X.U8(0x3BD); if(loop > 0x7F) return false;
	const ibtrsh = 40; //typical threshold for invalid bytes (in samples); _FRAGILE is 1
	p = 0x16; var totalsmpsz = ib = synwf = 0; smps = [];
	for(var i=0; i < 31; p += 0x1E, ++i)
		if(!X.U8(p)) {
			var maxwf = Math.max(X.U8(p+1),X.U8(p+5),X.U8(p+10),X.U8(p+19));
			if(maxwf+1 > synwf) synwf = maxwf+1;
		} else {
			t = X.SC(p,20,"IBM850").trim(); if(t != "" && t != "\x01") smps.push(t);
			var ssz = X.U16(p+0x16,_BE)*2; //length
			totalsmpsz += ssz;
			var sft = X.U8(p+0x18);
			if(sft & 0xF0) ib++; //finetune <= 15
			var svol = X.U8(p+0x19); if(svol > 0x40) ib++; //volume <= 64
			if(X.U16(p+0x1A,_BE)*2 > ssz) ib++;
			if(ib > ibtrsh) return false;
		}
	ptn = 0; p = 0x3BE;
	ord = X.U8(0x3BC);
	for(i=0; i < (ord << 2); i++) {
		t = X.U8(p); p += 2;
		if(ptn < t) ptn = t
	}
	ptn++;
	sz = 0x7C0+(synwf<<6)+(ptn<<8)+totalsmpsz;
	return true;
}
if(!bDetected && isKRIS()) {
	sName = "Krister Wombell's ChipTracker module (.KRIS,.MOD)"; bDetected = 1;
	if(X.isVerbose()) {
		sOption(X.SA(0,16));
		sOptionT(addEllipsis(smps.join(" "),200),'smp/msg:\"','"');
		sOption("ord:"+ord+" ptn:"+ptn+" sz:"+outSz(sz));
	}
}


function isMTNICE() {
	//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_ice.cpp
	if(X.c("'MTN'000000",0x5B8)) tracker = 'MnemoTroN ST';
	else if(X.c("'IT10'00",0x5B8)) tracker = 'Ice Tracker 1.0~1';
	else return false;
	smp = smpsz = 0; smps = [];
	for(p=20; p < 20+30*31; p+=30) {
		t = decAnsi(p,22,CPAmiga).trim(); if(t != '') smps.push(t);
		ssz = X.U16(p+22,_BE); if(ssz) smp++; smpsz += ssz
	}
	ord = X.U8(p++); ptn = X.U8(p++); if(ord > 128) return false;
	for(i=0; i < 128*4; i++) if(X.U8(p++) > ptn) return false;
	sz = 0x5BC+ptn*64*4+smpsz;
	return true
}
if(!bDetected && isMTNICE()) {
	sName = "SoundTracker module (.ST26,.ICE)"; sVersion = "v2.6"; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(decAnsi(0,0x14,CPAmiga));
		sOptionT(addEllipsis(smps.join(' '),0x80),'smps/msg:"','"')
		sOption('in: '+tracker);
		sOption('ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+sz)
	}
}


function isMoonBlaster() {
	//ref https://www.msx.org/wiki/Moonblaster_file_format
	//TODO check for the sanity of more values passed to MSX-AUDIO, like instr/vol
	ord = X.U8(0); ofs = unpsz = 0;
	if(ord > 220 && ord < 255) return false; //not sure if the manual is right about 220 being the maximum
	if(ord == 255) { ofs = 1; ord = 200 } //a subformat. Always 200 positions, 01-filled; all offsets are +1
	else ord++;
	for(i=0; i < 0x20; i++) if(X.U8(0xA3+ofs+i) > 0x16) { /*_log('MBM: instr/vol error');*/ return false }
	tmp0 = X.U8(ofs+0xCD); if(!tmp0) { /*_log('MBM: tempo = 0 error');*/ return false }
	p = ofs+0x178+ord; ptn = 0; for(i=ofs+0x178; i < p; i++) { t = X.U8(i); if(t > ptn) ptn = t }
	// Start pattern number is 1, no need to ptn++. Funnily, the manual says patterns start from 0
	ptnp = p+ofs; ptn1 = oldp = X.U16(ptnp);
	if(ptn1 > X.Sz()) { /*_log('MBM: ptn1 @'+Hex(ptn1)+">"+Hex(X.Sz()));*/ return false }
	for(i=0; p < X.Sz() && i < ptn; i++) {
		p = X.U16(ptnp+i*2);
		if(!i) unpsz = p; // patterns should start from the current unpsz
		if(p < ptnp+i*2 || p > X.Sz()) {
			/*_log('MBM: ptn ptr['+Hex(i)+'] error @'+Hex(ptnp+i*2)+': '+Hex(p));*/ return false }
		if(i && p-oldp > 0x100) { /*_log('MBM: ptn too large @'+Hex(ptnp+i*2));*/ return false }
		oldp = p;
	}
	if(!ofs) { p = ptnp+i*2;
		if((unpsz != p || p > X.Sz())) { /*_log('MBM: pattern offset error: p='+Hex(p));*/ return false }
	} else p = ptn1
	//decruncher: going over all the patterns at once
	while(p < X.Sz()) { c = X.U8(p++);
		if(!c) break; else if(c <= 0xF2) unpsz++; else /*decrunching 0's*/ unpsz += c-0xF2;
	}
	if(p > 0x3DC0) return false;
	sz = p;
	return true
}
if(!bDetected && isMoonBlaster()) {
	//MUS is "FST-song", MBM is song, MBK is drumkit, MBV is voice-e, MBB is voice-b, MBS is samples
	sName = 'MSX MoonBlaster for MoonSound module (.MBM)'; bDetected = 1;
	if(ofs) sVersion = '200-pos.ver.';
	drumkit = X.SA(ofs+0x140,8).trim();
	if(drumkit != 'NONE') sVersion = sVersion.appendS('+'+drumkit+'.MBK','/');
	if(X.isVerbose()) {
		sOption(X.SA(ofs+0xCF,40));
		sOption('tempo0:'+tmp0+' ord:'+ord+' ptn:'+ptn+' unpsz:'+unpsz+' sz:'+outSz(sz))
	}
}

function isOctaMed() {
	//from http://fileformats.archiveteam.org/wiki/OctaMED_module_(MED)
	// & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/ModuleConverters/ModuleConverter/Formats/Med4Format.cs
	// & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/Med/MedWorker.cs
	if(!/MED[\x02-\x04]/.test(X.SA(0,4))) return false;
	nV = X.U8(3); switch(nV) {
		//TODO find the non-\x04 files to test on
		case 2: sVersion = "v1.12"; break;
		case 3: sVersion = "v2.00"; break;
		case 4: sVersion = "v2.10+"; break;
	}
	x = 1; p = sz = ptn = ord = trk = midi = syhy = smp = realsmp = 0; smps = []; cs = bad = "";
	if(nV < 3) {
		//TODO
	} else {
		//going through the sample info
		var smpbmp0 = X.U8(4); p = 5;
		while(smpbmp0) {
			if(smpbmp0&1) { var smpbmp = X.U8(p++);
				while(smpbmp) { smp += smpbmp&1; smpbmp >>= 1 } }
			smpbmp0 >>= 1;
		}
		for(i=0; i < smp; i++) {
			fl = X.U8(p++); smpnl = X.U8(p++);
			smps.push(X.SA(p,smpnl)); p += smpnl;
			if(!(fl&1)) p+=2; if(!(fl&2)) p+=2; if(!(fl&4)) p++; if(!(fl&8)) p++;
			if(!(fl&0x30)) svol = X.U8(p++); if(svol > 0x40) return false;
			if(!(fl&0x40)) p++;
		}
		for(i = 62; i >= 0; i--) if(smps[i] && smps[i].length) {realsmp = i+1; break }
		ptn = X.U16(p,_BE); ord = X.U16(p+2,_BE); if(!ord || ord > 0x100) return false;
		p += 4; for(i=0; i < ord; i++) if(X.U8(p++) > ptn) return false;
		extsmp = !(X.U8(p+3)&8);
		tmp0 = X.U16(p,_BE)+"//"+X.U16(p+4,_BE); p += 26;
		for(i=0; i < 16; i++) if(X.U8(p++) > 0x40) return false;
		mvol = X.U8(p++); if(mvol > 0x40) return false;
		if(p > X.Sz()) return false;
		function SkipMidi() { var f = X.U32(p,_BE); p += 4;
			for (var i=0; i < 32; i++) { if(f < 0) f = -f; if((f & 0x80000000)) {midi++; p++ } f <<= 1 } }
		if(nV === 3) { SkipMidi(); SkipMidi() }
		for(i=0; i < ptn; i++) {
			var hdsz = X.U8(p++);
			var ptntrk = X.U8(p); if(trk < ptntrk) trk = ptntrk;
			var rows = X.U8(p+1), hksz = X.U16(p+2,_BE);
	//_log("ptn["+i+"] @"+Hex(p)+": ("+Hex(hdsz)+") -> "+Hex(p+hdsz+hksz)+"], rows:"+rows+" trk:"+ptntrk);
			p += hdsz+hksz;
		}
		if(!extsmp) {
			var br = new BitReader(p,_BE), smp2process = 0; br.read(1);
			for(i=0; i < realsmp; i++) smp2process += br.read(1); delete br;
	//_log("found "+smp2process+" samples to process");
			p += 8; for(i=0; i < smp2process; i++) {
				hksz = X.U32(p,_BE); hktp = X.U16(p+4,_BE); p += 6;
	//_log("smp["+i+"] ("+Hex(p)+" -> "+Hex(p+hksz)+")")
				if(hktp == 0xFFFF || hktp == 0xFFFE) syhy++; p += hksz;
			}
		}
		if(X.c("'MEDV'",p))
			while(p+8 <= X.Sz() && /[A-Z]{4}/.test(X.SA(p,4))) {
				hkhd = X.SA(p,4); hksz = X.U32(p+4,_BE); p += 8;
				switch(hkhd) {
				case "MEDV": sVersion = "v"+X.U8(p+2)+'.'+X.U8(p+3).padStart(2,'0'); break;
				case "ANNO": cs = X.SC(p,hksz,'CP1252'); break;
				case "HLDC": break;
				default: _log("Unknown MED header: "+hkhd) //shouldn't exist but you know how they get
				}
				p += hksz
			}
	}
	sz = p;
	if(sz > X.Sz()) bad = "!short";
	return true
}
if(!bDetected && isOctaMed()) {
	sName = "OctaMED module (.MED)"; bDetected = 1;
	if(bad != "") sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		sOptionT(cs);
		while(smps[smps.length] == '') delete(smps[smps.length]);
		if(smps.length) sOption("["+smps.join(",")+"]","smps:");
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+(realsmp != smp ? "("+realsmp+")" : "")
		  +(extsmp?"(ext.)":"")+(syhy?" synth+hybrid:"+syhy:"")+(midi?" midi:"+midi:"")+" trk:"+trk+" tmp0:"+tmp0
		  +" mvol:"+mvol+" sz:"+outSz(sz))
	}
}


function isOctaMedMMD() {
	//from https://web.archive.org/web/20220109073109/http://www.textfiles.com/programming/FORMATS/med-form.txt
	if(!X.c("'MMD'")) return false;
	nV = X.U8(3)-48; if(nV < 0 || nV > 3) return false;
	switch(nV) {
		case 0: sname = "MED module (.MED,.MMD0)"; sversion = "v2.1 Med MoDule 0"; break;
		case 1: sname = "OctaMED Professional module (.MED,.MMD1)"; sversion = "v.3.00-4.x"; break;
		case 2: sname = "OctaMED Professional module (.MED,.MMD2)"; sversion = "v5.x"; break;
		case 3: sname = "OctaMED Professional module (.MED,.MMD3)"; sversion = "v6.x?"; break;
	}
	ptnsp = X.U32(0x10,_BE); if(ptnsp < 0x34) return false;
	smpsp = X.U32(0x18,_BE); if(smpsp && smpsp < 0x34) return false;
	expp = X.U32(0x20,_BE); if(expp > X.Sz()) return false;
	sec = p = 0; sngp = X.U32(p+8,_BE);
	if(sngp < 0x34 || sngp > 0xFFFFFEFF-63*8) return false;
	if(X.Sz() < Math.max(sngp+63*8+0x100, ptnsp, smpsp?smpsp:0x34, expp+0x34)) return false;
	songname = anno = iinfo = ""; ch = 4; xsngs = X.U8(0x33); x = expp?xsngs+1:1;
	if(expp) {
		psongname = X.U32(expp+0x2C,_BE);
		if(psongname) {
			songnamelen = X.U32(expp+0x30,_BE);
			if(songnamelen)
				songname = X.SC(psongname,songnamelen,'CP1252');
		}
		pannotxt = X.U32(expp+0x0C,_BE);
		if(pannotxt > 0) {
			annolen = X.U32(expp+0x10,_BE);
			anno = X.SC(pannotxt,annolen,'CP1252');
		}
		pMMDInstrInfo = X.U32(expp+0x14,_BE);
		if(pMMDInstrInfo > 0)
			iinfo = X.SC(expp,40,'CP1252');
	} //if expp
	ptn = 0; ord = []; var ptntp = X.U32(0x10,_BE);
	for(i=0; i < x; i++) {
		p = sngp+63*8;
		ptn1 = X.U16(p,_BE); if(ptn1 > 0x7FFF) ch = 4; ptn += ptn1;
		for(j = 0; j < ptn1; j++) {
			pj = j*4+ptntp; if(pj > X.Sz()) continue;
			pj = X.U32(pj,_BE); if(pj > X.Sz()) continue;
			pj = nV < 1 ? X.U8(pj+4) : X.U16(pj+4,_BE); if(pj > ch) ch = pj
		}
		if(nV < 2) { ord[0] = X.U16(p+2,_BE); if(ord[0] > 256) return false }
		else {
			sec = X.U16(p+2,_BE); trk = X.U16(p+0x10,_BE);
			if(!trk || trk > 0x40) return false;
			sectp = X.U32(p+8,_BE); if(sectp+sec*2 > X.Sz()) continue;
			playseqtp = X.U32(p+4,_BE); nplayseq = X.U16(p+0x12,_BE);
			secs = []; for(j = 0; j < sec; j++) secs.push(X.U16(sectp+j*2,_BE));
			for(j = 0; j < secs.length; j++) if(j <= nplayseq)
				ord.push(X.U16(X.U32(playseqtp,_BE)+0x28,_BE));
		}
		expp = X.U32(sngp+0x20,_BE);
		if(expp && (X.U32(expp,_BE) < sngp || expp > X.Sz())) { x = i+1; break }
		sngp = X.U32(expp,_BE);
	}
	smp = X.U8(p+0x11B); if(smp > 63) return false;
	return true
}
if(!bDetected && isOctaMedMMD()) {
	sName = sname; sVersion = sversion; bDetected = 1;
	if(X.isVerbose()) {
		if(songname != "<unnamed>" && songname != "<ohne Namen>")
			sOption(songname);
		sOptionT(anno);
		sOptionT(iinfo,"ins0:");
		if(x > 1) sOption(x,"×");
		sOption((X.isDeepScan()?"ch:"+ch+" ":"")+"ord:"+ord.join("+")+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(X.U32(4,_BE)))
	}
}


function isAHX() {
	//ref http://lclevy.free.fr/exotica/ahx/ahxformat.txt
	// & https://github.com/pete-gordon/hivelytracker/blob/master/Replayer_Windows/hvl_replay.c
	if(X.Sz() < 30) return false;
	if(X.c("'THX'0.")) fmt = "ahx";
	else if(X.c("'HVL'0.")) fmt = "hvl";
	else return false;
	bad = 0;
	var b6 = X.U8(6);
	trk0saved = b6 >> 7;
	switch((b6 >> 4) & 7) {
	case 0: spd = "50Hz"; break;
	case 1: spd = "100Hz"; break;
	case 2: spd = "150Hz"; break;
	case 3: spd = "200Hz"; break;
	default: bad++;
	}
	ord = X.U16(6,_BE) & 0xFFF;
	if(ord > 999) bad++; if(!ord || ord > 1024) bad++;
	if(fmt === "ahx") {
		lp = X.U16(8,_BE); if(lp >= ord) bad++;
	}
	else chn = (X.U8(8) >> 2)+4;
	if(bad > 1) return false;
	trl = X.U8(10); if(!trl || trl > 64) return false;
	trk = X.U8(11);
	ins = X.U8(12); if(ins > 63) return false;
	sub = X.U8(13);
	if(fmt === "ahx") {
		sz = 14+sub*2+ord*8+trk*trl*3; if(!trk0saved) sz += trl*3
		for(i=0; i < ins; i++) {
			var spls = X.U8(sz+21);
			sz += 22+spls*4;
		}
	}
	else {
		sz = 16+sub*2+ord*chn*2;
		if(trk0saved) i = 1; else i = 0;
		for(; i <= trk; i++)
			for(j=0; j < trl; j++) {
				if(X.U8(sz) == 0x3F) { sz++; continue }
				sz += 5;
			}
		for(i=0; i < ins; i++) {
			var spls = X.U8(sz+21);
			sz += 22+spls*5;
		}
	}
	title = "";
	for(i=0;i <= ins;i++) { var r = sz;
		while(X.U8(sz) && sz<X.Sz()) sz++;
		if(!i) title = X.SA(r,sz-r); sz++
	}
	if(sz > X.Sz()) bad++;
	return true
}
if(!bDetected && isAHX()) {
	if(fmt === "ahx") {
		sName = "Abyss' Highest eXperience module (.AHX)";
		if(!X.U8(3)) sVersion = "v1.00~1.27"; else sVersion = "v2.0+";
	}
	else
		sName = "Hively Tracker module (.HVL)";
	bDetected = 1;
	if(bad) sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		if(title != "") sOption(title); if(sub) sOption(sub,"×");
		n=0; p = 14+sub*2+ord*8; c = trk*trl; if(!trk0saved) c += trl; hp = false;
		for(i=0;i < c;i++) {
			note = X.U8(p+i*3)>>2; if(note) n++;
			if(note > 60) if(fmt == "ahx") hp = true;
		}
		if(hp) sVersion += "/hi-pitch!"
		if(fmt === "ahx")
			sOption("spd:"+spd+" ord:"+ord+" lp:"+lp+" trk:"+trk+" ins:"+ins+" notes:"+n+" sz:"+outSz(sz))
		else
			sOption("ch:"+chn+" spd:"+spd+" ord:"+ord+" trk:"+trk+" ins:"+ins+" notes:"+n+" sz:"+outSz(sz))
	}
}


function isSTM() {
	//ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_stm.cpp
	t = readBytes(0x14,8); for(i = 0; i < 8; i++) { if(t[i] < 0x20 || t[i] >= 0x7F) return false }
	function _isstm() {
		if([0x1A,2].indexOf(X.U8(0x1C)) < 0) return false;
		if([1,2].indexOf(X.U8(0x1D)) < 0) return false;
		if(!X.c("02",0x1E)) return false;
		if([0,10,20,21].indexOf(X.U8(0x1F)) < 0) return false;
		if(X.U8(0x21) > 0x40) return false;
		gvol = X.U8(0x22);
		if(gvol > 0x40 && gvol != 0x58) return false;
		nVm = X.U8(0x1F); ptn = X.U8(0x21);
		if(nVm) maxord = 128; else maxord = 64;
		minsz = 0x30+31*0x20+maxord+ptn*256; if(X.Sz() < minsz) return false;
		smp = 0; ord = -1; tracker = "Scream Tracker 2 module"; ext = "STM"; return true
	}
	function _isstx() {
		if(!X.c("'SCRM'",0x3C)) return false;
		ptnsz = X.U16(0x1C);
		if((ptnsz < 64 && ptnsz != 0x1A) || ptnsz > 0x840) return false;
		t = X.U8(0x2A); if(t > 0x40 && t != 0x58) return false;
		ptn = X.U16(0x30); if(ptn > 64) return false;
		smp = X.U16(0x32); if(smp > 96) return false;
		ord = X.U16(0x34); if(ord > 0x81 && ord != 0x101) return false;
		if(X.U16(0x1E) || X.U32(0x26) || X.U32(0x2C) != 1) return false;
		ptntp = X.U16(0x20)<<4; smptp = X.U16(0x22)<<4;
		chtp = X.U16(0x24)<<4;
		minsz = 0x40 + Math.max(ptntp+ptn*2, smptp+smp*2, chtp+32+ord*5);
		if(X.Sz() < minsz) return false;
		p = chtp+32; for(i=0; i < ord; i++) {
			t = X.U8(p+i*5); if(t > 63 && t != 99 && t != 255) return false;
		}
		tracker = "Scream Tracker Music Interface Kit module"; ext = "STX"; return true;
	}
	isstm = _isstm(); if(!isstm) { isstx = _isstx(); if(!isstx) return false }
	sz = sz1 = minsz; smp0 = ""; max = 0;

	if(isstm) { //STM things
		for(i = 0; i < 31; i++) {
			zero = X.U8(0x30+i*0x20+12);
			if(zero && zero != 46) return false;
			t = X.SC(0x30+i*0x20,12,'CP437').trim();
			if(i < 3 && X.isVerbose()) if(t.length) smp0 = smp0.append(t);
			var sofs = X.U16(0x30+i*0x20+0x0E,_LE) << 4;
			var ssz = X.U16(0x30+i*0x20+0x10,_LE);
			if(ssz && sofs > 0x30 && sofs < X.Sz()) {
				if(ssz) sz1 = sofs+ssz;
				//Actually it seems that if ssz is 1 it still must be 0, but then it'll just ignore
				//  the potentially interesting byte, so let's rip it too, just in case.
				if(sz1 > sz) sz = sz1;
				//We won't count it towards the smp counter though, that one's for the meaningful stuff.
				if(ssz > 1) smp++
			}
		}
		for(i=0; i < maxord; i++) {
			t = X.U8(0x410+i);
			if(t === 99 || t === 255) t = 255;
			else if(t > 63) return false;
			else ord++;
		} ord++;
	}
	else if(isstx) { //STX things
		p = smptp; for(i = 0; i < smp; i++) {
			si = X.U16(p+i*2) << 4;
			t = X.SC(si+0x30,28,'CP437').trim();
			if(i < 3 && X.isVerbose()) if(t.length) smp0 = smp0.append(t);
			if(!si) continue;
			if(X.U8(si) === 1) {
				sofs = X.U16(si+14)<<4;
				if(sofs > max) {
					ssz = X.U32(si+16); if(X.U8(si+31)&4) ssz *= 2; //16bit
					//if(ssz%16) ssz += 16-ssz%16; //the padding. Although STM2STX.EXE v1.0 doesn't pad
					max = sofs; if(sz < max+ssz) sz = max+ssz
				}
			}
		}
		fmt = 1;
		if(ptn && ptnsz != 0x1A) {
			p = X.U16(ptntp) << 4;
			if(p > X.Sz())
				if(X.isHeuristicScan()) {
					bad = bad.addIfNone("!badptnp"); return true
				}
				else return false;
			if(X.U16(p) === ptnsz) fmt = 0;
		}
		// I'll skip parsing the patterns, too much trouble when the samples always come later in the file
	}
	else return false; return true
}
if(!bDetected && isSTM()) {
	sName = tracker+" (."+ext+")"; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SC(0,0x14,'CP437'));
		if(smp0.length) sOption(smp0,"by/smp: ");
		if(ext === "STM") sVersion = "v"+X.U8(0x1E)+"."+nVm;
		else sVersion = "v1."+fmt;
		tmp0 = X.U8(0x20); gvol = X.U8(0x22);
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" gvol:"+gvol+" sz:"+outSz(sz))
	}
}


function isIT() {
	//from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_it.cpp et al.
	// Now, this detection is TOO MUCH
	// I'm still going to do as much as I can, but people should have a choice to live without most of it :D
	// Which is to say, do NOT use deepscan unless you need calcsize
	var t = 0;
	if(X.c("'IMPM'")) type = "it";
	else if(X.c("'tpm.'")) type = "mpt";
	else return false;
	ord = X.U16(0x20,_LE);
	ins = X.U16(0x22,_LE); if(ins > 0x63) t++;
	smp = X.U16(0x24,_LE); if(smp > 255) t++;
	ptn = X.U16(0x26,_LE); if(!ptn && !X.isHeuristicScan()) t++;
	if(X.Sz() < 0xC0+4*(ins+smp+ptn)) return false; //itfileheader size
	cwtv /*make*/ = X.U16(0x28,_LE); cmwt /*compat*/ = X.U16(0x2A,_LE);
	flags = X.U16(0x2C,_LE); special = X.U16(0x2E,_LE);
	gvol = X.U8(0x30); if(gvol > 0x80) t++;
	mvol = X.U8(0x31); if(mvol > 0x80) t++;
	spd0 = X.U8(0x32); if(!spd0) t++;
	tmp0 = X.U8(0x33); if(tmp0 < 31) t++;
	sep = X.U8(0x34); if(sep > 0x80) t++;
	if(t > 1) return false; bad = ""; if(t) bad = bad.addIfNone("!badinitinfo");
	msglen = X.U16(0x36,_LE); msgofs = X.U32(0x38,_LE);
	pwd = X.U8(0x35);
	nreserved = X.U32(0x3C,_LE); sreserved = X.SA(0x3C,4);
	tracker = auth = ""; mVlsw = mVcw = sV ="?"; ch = 1; cord = 0; sz = -1;
	var mptp = lastsmpofs = 0; type = "it"; chnm = []; insnlst = []; smpnlst = [];
	if(X.isDeepScan()) {
		//The DeepScan part of the crazily complex Impulse Tracker/MPTM detection
		//Would be much easier if we didn't have to calculate the size! But we do, so there we go.
		const ITSampleSz = 0x50, ITHistorySz = 8, verLoadLimit = 0x1000;
		const sEmbedMsg = 1, sEditHistory = 2, sEmbedPtnHL = 4, sEmbedMIDIC = 8;
		const fEmbedMIDIC = 0x80, fEFR = 0x1000;
		var interpretModPlugMade = false; var FFchnpan = X.fSig(0x40,0x40,"FF") >= 0;
		var mptV = 0;
		function itV(v) { return v.slice(0,1)+'.'+v.slice(1,3)+'.'+v.slice(3,5)+'.'+v.slice(5,7) }

		//TODO this↓ part is not ripper-friendly but thankfully also likely unnecessary:
		if(X.c("'tpm.'")) { type = "mpt";
			p = X.Sz()-4; mptp = X.U32(p,_LE);
			if(!X.c("'228'04'mptm'",mptp)) mptp = 0;
		} else {
			if(0x888 < cwtv <= 0xFFF) {
				p = X.Sz()-4; mptp = X.U32(p,_LE);
				if(0x100 <= mptp < X.Sz()-4)
					if(X.c("'228'04'mptm'",mptp)) {
						type = "mpt"; if(cwtv >= verLoadLimit) {
							sV = "future"; charset = "UTF8"; extsmp = 0;  return true }
					} else mptp = 0;
				//if the above fails, there's a near-zero chance the ords will be misread and everything goes oof
				//that's expected behaviour, as the v1.17.02.46 format was experimental :)
			}
			//detecting made-with, part 1 of 3496987
			if(type === "it") {
				if((cwtv & 0xF000) === 0x5000) {
					mptV = (cwtv&0xFFF) << 16;
					if(X.c("'OMPT'",0x3C)) interpretModPlugMade = true;
					else if(mptV >= 0x01290000) mptV |= nreserved & 0xFFFF;
					mVlsw = itV(mptV.toString(16).toUpperCase().padStart(7,'0'));
				} else if(cwtv === 0x888 || cmwt === 0x888) {
					interpretModPlugMade = true; mVlsw = "1.17.00.00";
				} else if(cwtv === 0x214 && cmwt === 0x202 && !nreserved) {
					mVlsw = "1.09.00.00"; tracker = "ModPlug Tracker b3.2 - 1.09";
					interpretModPlugMade = true
				} else if(cwtv === 0x300 && cmwt === 0x300 && !nreserved
							&& ord === 256 && sep === 128 && !pwd) {
					mVlsw = "1.17.02.20"; interpretModPlugMade = true
				}
			}
		}
		var b32ord = -1, lastord = 0;
		// 32bit orders: a deprecated format. OpenMPT 1.17.02.46 - 1.17.02.48.
		if(type === "mpt" && 0x88A < cwtv && cwtv <= 0x88D) {
			p = 0xC0; if(X.U16(p,_LE)) return false; p += 2;
			b32ord = X.U32(p,_LE); p += 4;
			if(b32ord > 256 || X.Sz() < p+b32ord*4) return false
		} else p = 0xC0;
		if(b32ord >= 0) ord = b32ord;
		for(i=0; i < ord; i++)
			if(b32ord < 0) { o = X.U8(p++);
				if(o === 0xFF) lastord = 0xFFFF; else if(o === 0xFE) lastord = 0xFFFE; else cord++ }
			else { o = X.U32(p,_LE); p += 4; lastord = o; if(o < 0xFFFF) cord++ }

//_log("read pointers & find the smallest @"+Hex(p));
		//TODO maybe, at some point in the future, audodetect b32ord
		//by making sure these pointers actually lead to intended things, and trying the b32 way if not
		minp = 0xFFFFFFFF;
		var insp = []; for(i=0; i < ins; i++) { t = X.U32(p,_LE); p += 4;
			if(t <= p || t > X.Sz()) return false;
			q = readBytes(t+0x20,0x1A,true); q = decEncoding(q,CP437,false).trim(); if(q != "") insnlst.push(q);
			if(sz < t) sz = t;  if(t && t < minp) minp = t; insp.push(t) }
		var smpp = []; for(i=0; i < smp; i++) { t = X.U32(p,_LE); p += 4;
			if(t > X.Sz()) bad = bad.addIfNone("!short");
			q = readBytes(t+0x14,0x1A,true); q = decEncoding(q,CP437,false).trim(); if(q != "") smpnlst.push(q);
			if(sz < t) sz = t; if(t && t < minp) minp = t; smpp.push(t) }
		var ptnp = []; for(i=0; i < ptn; i++) { t = X.U32(p,_LE); p += 4;
			if((t > 0x1E && t <= p) || t > X.Sz()) return false;
			if(sz < t) sz = t; if(t && t < minp) minp = t; ptnp.push(t) }
		if(special & sEmbedMsg) { minp = Math.min(minp,msgofs);
			if(sz < msgofs+msglen) sz = msgofs+msglen }
		var possiblyUNMO3 = cmwt === 0x214 && (!cwtv || cwtv === 0x0214)
		  && !X.U16(0x1E) && !pwd && !nreserved && !(flags & 0xC0);
		if(possiblyUNMO3 && !ins && smp && p+4*smpp.length+2 <= minp) {
			var oldUNMO3 = true;
			for(i=0; i < smp; i++)
				if(X.U32(p)) { oldUNMO3 = false; p -= i*4; break } else p += 4;
			if(oldUNMO3) tracker = "UNMO3 <= 2.4";
		}
		if(possiblyUNMO3 && !cwtv) tracker = "UNMO3 v0/1";
		if(special & sEditHistory) {
			//is edit history stored?
			var nflt = X.U16(p);
			if(X.Sz() > p+nflt*8 && p+nflt*8 <= minp) {
				p += 2+nflt*8;
				if(possiblyUNMO3 && !nflt)
					if(special & sEmbedPtnHL) tracker = "UNMO3 <= 2.4.0.1";
					else tracker = "UNMO3"
			}
		} else if(possiblyUNMO3 && special <= 1 && !X.U16(p)) {
			tracker = "UNMO3 <= 2.4"; p += 2
		}
		var hasMIDIC = (flags & fEmbedMIDIC) || (special & sEmbedMIDIC);
		if(hasMIDIC) p += 0x1320; if(p > X.Sz()) return false;
//_log("chunks expected @"+Hex(p));
		var isBeroTracker = hasPluginChunks = hasMPTM = false;
		while(p+9 < X.Sz()) {
			hkhd = X.SA(p,4); hksz = X.U32(p+4,_LE);
//_log(hkhd+":"+Hex(p));
			if(hkhd === "MODU") isBeroTracker = true;
			else if(hkhd === "CNAM") { ch = hksz/20; u = p+8; hasMPTM = true;
				for(i = 0; i < ch; i++) {
					t = X.SA(u,20).trim(); u += 20;
					if(t != "") chnm.push(t);
				}
			}
			else if(hkhd === "PNAM") hasMPTM = true;
			else if(hkhd === "CHFX" || /F[X0-9]\d\d/.test(hkhd)) hasPluginChunks = true;
			if(["IMPI","IMPS","XTPM","STPM"].indexOf(hkhd) >= 0
			  || p+8+hksz > X.Sz() || p >= msgofs)
				break;
			//PNAM may have fun info but that's for when someone feels less tedium otherwise
			else p += 8+hksz
		}
		if(cwtv === 0x217 && cmwt === 0x200 && !nreserved && !isBeroTracker) {
			if(hasMPTM || (ord > 0 && lastord == 0xFFFF) || FFchnpan) {
				mVlsw = "1.16.00.00"; tracker = "ModPlug Tracker 1.09-16";
			} else {
				mVlsw = "1.17.00.00"; tracker = "OpenMPT 1.17 (compat.export)";
			}
			interpretModPlugMade = true
		}

		var lastsmpofs = (smp > 0 ? smpp[smp-1]+ITSampleSz : p); var lastsmpco = false;

// Deriving the size of just the last sample (it might already be tasking indeed)
		const/*for IT sample flags*/ s16b=2,sSt=4,sCo=8;
		const/*for cvt flags*/ cSS=1,cBE=2,cD=4,cTo16=8,cOPL=0x40,cExt=0x80,cPCM=0xFF;
		 //Possible codec values:
		 //"sPCM" "uPCM" "dPCM" "fPCM" "IT214" "IT215" "AMS" "DMF" "MDL" "8d16"
		 //"ADPCM" "MT2" "fPCM15" "fPCM23" "fPCMn" "sPCMn" "uLaw" "aLaw"
		extsmp = []; var possibleXMconv = false;
		Ss = []; //for gathering all info
		maxsmpofs = -1;
		for(i=0; i < smp; i++) {
			if(!smpp[i]) continue;
			if(smpp[i] > X.Sz()) { bad = bad.addIfNone("!short"); continue }
			if(!X.c("'IMPS'",smpp[i])) bad = bad.addIfNone("!badsmp");
//_log(X.SA(smpp[i]+4,13)+": "+decAnsi(smpp[i]+0x14,0x1A,CP437,false).trim()+" @"+Hex(smpp[i]))
			var sf = X.U8(smpp[i]+0x12); var cvt = X.U8(smpp[i]+0x2E);
			var slen = X.U32(smpp[i]+0x30); var smpofs = X.U32(smpp[i]+0x48);
			S = { "ofs":0, "slen":0,
				"bits":(sf & s16b)? 16 : 8, "chn":1,// 1 channel if mono, 2 if joint or split stereo
				"en":"LE","codec":"sPCM","bps":0,"length":0 //length is calculated from length in samples
			};
			p = smpofs; S["ofs"] = smpofs; S["slen"] = slen;
			if(cvt === cOPL) p += 12;
			else if(cvt === cExt) {
				t = readVarUInt(p); extsmp.push(X.SA(p+1,t[1]));
				p += t[0]+t[1]; }
			else if(X.c("'fLaC'",smpofs)) { S["codec"] = "FLAC" }
			else if(X.c("'OggS'",smpofs)) { S["codec"] = "Ogg" }
			else { //GetSampleFormat
				S["codec"] = (cvt & cSS) ? "sPCM" : "uPCM";
				if((sf & sSt) && cwtv >= 0x214) S["chn"] = 2;
				if(sf & sCo)
					S["codec"] = (cvt & cD) ? "IT215" : "IT214";
				else {
					if(!(sf & s16b) && cvt == cPCM) S["codec"] = "ADPCM";
					else {
						if(cvt & cBE) S["en"] = "BE";
						if(cvt & cD) S["codec"] = "dPCM";
						if(cvt & cTo16) S["codec"] = "8d16"
					}
				}
				switch(S["codec"]) {
				case "sPCM": case "uPCM": case "dPCM": case "fPCM": case "MT2": case "fPCM15":
				case "fPCM23": case "fPCMn": case "sPCMn": S["bps"] = S["bits"]; break;
				case "8d16": S["bps"] = 16; break;
				case "ADPCM": S["bps"] = 4; break; //encoded header size for this one is 16, 0 otherwise
				case "uLaw": case "aLaw": S["bps"] = 8; break;
				//case "IT214": case "IT215": case "AMS": case "DMF": case "MDL": S["bps"] = 0; //and
				default: S["bps"] = 0 //variable length-encoded
				}
				if(!S["bps"]) lastsmpco = true; //...and we'll decompress it further down
				else {
					S["length"] = slen*S["chn"]*(S["bps"]>>3);
					p += S["length"];
				}
				Ss[i] = S;
			} //GetSampleFormat
			if(lastsmpofs < p) lastsmpofs = p; if(sz < lastsmpofs) sz = lastsmpofs;
			if(S["codec"] === "uPCM" && slen) possibleXMconv = true;
//_log(X.SA(smpp[i]+0x14,0x1A)+" @"+Hex(smpofs)+": sf:"+Hex(sf)+" ["+S["codec"]+"] slen:"+slen+" ch:"+S["chn"]+" bps:"+S["bps"]+" bits:"+S["bits"]+", ends @"+Hex(smpofs+S["length"]));
		}
// Load_it.cpp:845
		if(possibleXMconv && cwtv === 0x204 && cmwt === 0x200 && !special && !nreserved
		  && (flags & (~8)) === 0x15 && gvol === 128 && mvol === 48
		  && sep === 128 && !pwd && !msglen) {
			for(i=0; i < 64; i++) if([0x20,0x40].indexOf(X.U8(0x40+i)) < 0) {
				possibleXMconv = false; break } //testing pans
			if(possibleXMconv) for(i=0; i < 64; i++) if(X.U8(0x80+i) != 0x40) {
				possibleXMconv = false; break } //testing vols
			if(possibleXMconv) for(i=20; i < 26; i++) if(X.U8(4+i)) {
				possibleXMconv = false; break } //testing songname
			if(possibleXMconv) tracker = "XM Conversion"
		}
// Load_it.cpp:885 :: Scan all of the patterns to see how many channels we need
		for(i=0; i < ptn; i++) { t = ptnp[i];
			if(!t || t > X.Sz()) continue;
			var len = X.U16(t,_LE), rows = X.U16(t+2,_LE); t += 4;
			if(!rows || rows > 0x400 || t+4 > X.Sz()) continue; p = t+4;
			var chnMask = [], row = 0;
			while(row < rows && p < X.Sz()) {
				var y = X.U8(p++); if(!y) { row++; continue }
				var c = y&0x7F; if(c) c--;
				if(y&0x80) chnMask[c] = X.U8(p++);
				if(chnMask[c] & 0x0F) if(ch <= c && c < 0x7F) ch = c+1;
				if(chnMask[c] & 1) p++;  if(chnMask[c] & 2) p++;
				if(chnMask[c] & 4) p++;  if(chnMask[c] & 8) p += 2;
			}
			if(lastsmpofs < p) lastsmpofs = p; if(sz < lastsmpofs) sz = lastsmpofs
		}
		if(!ch) ch = 1;
//find the expected length of the final compressed sample
		if(lastsmpofs) { p = lastsmpofs;
			if(lastsmpco) {
				S = Ss[Ss.length-1];
				//smp length calc
				if(S["bps"])
					if(S["bps"]%8) S["length"] = (S["codec"] == "ADPCM"?16:0)
							+Math.ceil((slen+1)/2*S["ch"]);
					else S["length"] = (S["codec"] == "ADPCM"?16:0)
							+S["slen"]*S["bps"]/8*S["chn"];
				else { //let the crazy shit begin.
					// ref https://github.com/nicolasgramlich/AndEngineMODPlayerExtension/blob/master/jni/loaders/itsex.c
					// but mainly OpenMPT's SampleIO::ReadSample :142
					if(S["bps"] == 4 && S["chn"] == 1 && S["en"] == "LE" && S["codec"] == "ADPCM") {
						S["length"] = 16+(S["slen"]+1)/2; p += S["length"]
					} else if(S["slen"] > 1 && (S["codec"] === "IT214" || S["codec"] === "IT215")) { //:165
						// Let's try a quick-n-dirty first.
						var u = p, ulen = 0, quickndirty = 0;
						while(u < X.Sz() && ulen < S["slen"] /*packers have their limits*/) {
							if(X.c("'XTPM'",u) || X.c("'STPM'",u) || X.c("'228'04'mptm'",u))
								{ quickndirty = 1; p = u; break }
							ucsz = X.U16(u,_LE); ulen += ucsz; u += 2+ucsz;
						}
						if(quickndirty) { p = u } else {
						//orrrr if no MPTX block is present, we gotta do it the hard way I guess :<
						//ITDecompression(file,sample,getencoding=it215)
						var bit_n = bit_buf = cend = readdef = 0;
						function readBits(b) {
							while(bit_n < b) { bit_buf |= Util.shlu64(X.U8(p++),bit_n); bit_n += 8 }
							var v = bit_buf & (Util.shlu64(1,b) - 1); bit_buf = Util.shru64(bit_buf,b); bit_n -= b;
							//if(p <= X.Sz()) return v; else return 0 }
							return v }

						if(S["bits"] == 16) { const fetchA=4, lowerB=-8, upperB=7, defW=17 }
						else { const fetchA=3, lowerB=-4, upperB=3, defW=9 }
						for(c = 0; c < S["chn"]; c++) { //ITCompression.cpp:322
							var rs = S["slen"], v = csz = clen = topbit = 0;
							cend = p;
							while(rs && p < X.Sz()) { //:325
								csz = X.U16(p,_LE); p += 2; cend = p+csz;
								if(!csz) { readdef = 10; _log("@"+Hex(p)+" malformed sample?"); continue }
								if(cend > X.Sz()) { c = 0xE0F; bad = bad.addIfNone("!short"); break }
								clen = Math.min(rs, 0x8000>>(S["bits"]>>4));
								var w = defW; bit_buf = bit_n = 0;
								while(clen && p < X.Sz()) { //:357
									if(w > defW) {
_log("! @"+Hex(p)+" w="+Hex(w)+" > "+defW+" rs:"+rs+" clen:"+Hex(clen)+" cend:"+Hex(cend));
										bad = bad.addIfNone("!badITsmp"); rs = 0; break }
									v = readBits(w); topbit = 1<<(w-1);
									if(w < 7) {
										if(v == topbit) {
											v = readBits(fetchA)+1; if(v >= w) v++; w = v; continue
										}
									} else if(w < defW) {
										if(v >= topbit+lowerB && v <= topbit+upperB) {
											v -= topbit-1+lowerB; if(v >= w) v++; w = v;
											continue
										}
									} else if(v & topbit) { w = (v&0xFF)+1; continue }
									clen--; rs--
								}
							}
							if(p > X.Sz()) bad = bad.addIfNone("!short");
						} } //if quickndirty else
					} //if IT215||IT214
					else if(S["codec"] === "FLAC") {
						//TODO, or maybe not
					} else if(S["codec"] === "Ogg") {
						//TODO, or maybe not
					} else if(S["codec"] === "AMS" && S["chn"] == 1) { //SampleIO:170
						//TODO test it at least
						sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
						var srcsz = X.U32(p+4,_LE), pc = X.U8(p+8);
						S["length"] = srcsz+9
					} else if(S["codec"] === "8d16" && S["chn"] == 1 && S["bps"] == 16) {
						sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
						//TODO CopyMonoSample<SC::DecodeInt16Delta8>(sample, source, fsize)
					} else if(S["codec"] === "MDL" && S["chn"] == 1 && S["bps"] <= 16) {
						var srcsz = X.U32(p); S["length"] = 4+srcsz
					} else if(S["codec"] === "DMF" && S["chn"] == 1 && S["bps"] <= 16) {
						sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
						//TODO DMFUnpack, or not to do
					} else if((S["codec"] === "uLaw" || S["codec"] === "aLaw") /*TODO && mono or stereoInterleaved */ && S["bps"] == 16) {
						S["length"] = S["slen"]*S["chn"]
					}
				} //actual length teller
				if(sz < p) sz = p
			}
		}
		var bLoadXIProps = false;
		if(X.c("'XTPM'",p)) { // 'MPTX'
			p += 4;
			while(p+6 < X.Sz()) {
				t = X.SA(p,4); it = X.U32(p);
				if(t === "STPM" || t === "228\x04" || (it&0x80808080) || !(it&0x60606060)) break;
				var prsz = X.U16(p+4,_LE); p += 6;
				for(i=0; i < ins; i++) p += prsz
			}
			bLoadXIProps = true;
			if(sz < p) sz = p;
		}
// Load_it.cpp:978
		interpretModPlugMade |= bLoadXIProps;
// Load_it.cpp:985
		if(X.c("'STPM'",p)) {
			p += 4;
			while(p+7 < X.Sz()) {
				t = X.SA(p,4); it = X.U32(p);
				prsz = X.U16(p+4,_LE);
				if(t === "228\x04" || t == "228\x02") { mptp = p; break; } //TODO check for 228\x02TC!
				if((it&0x80808080) || !(it&0x60606060) || p+6+prsz > X.Sz()) break;
				p += 6;
				switch(t) {
				case "...C": var chn = 0;
					switch(prsz) {
					case 2: chn = X.U16(p,_LE); break; case 3: chn = X.U24(p,_LE); break;
					case 4: chn = X.U32(p,_LE); break; default: chn = X.U8(p) }
					if(chn > ch) ch = chn; break;
				case ".VWC": var v = 0;
					switch(prsz) {
					case 1: v = X.U8(p); break; case 2: v = X.U16(p,_LE); break;
					case 3: v = X.U24(p,_LE); break; case 8: v = X.U64(p,_LE); break;
					default: v = X.U32(p,_LE) }
					if(v) mVcw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
				case "VWSL": var v = 0;
					switch(prsz) {
					case 1: v = X.U8(p); break; case 2: v = X.U16(p,_LE); break;
					case 3: v = X.U24(p,_LE); break; case 8: v = X.U64(p,_LE); break;
					default: v = X.U32(p,_LE) }
					if(v) mVlsw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
				case "AUTH":
					auth = X.SC(p,prsz,"UTF8"); break;
				}
				p += prsz;
			}
			if(sz < p) sz = p;
		}

		tunings = 0;
		if(X.c("'HSCT'",p)) { //the first-ever way of storing the tunings in an mptm
//tuningCollection.cpp
			var skip = false; var v = X.I32(p+4,_LE); p += 8;
			if(v == 1) { //can't find any files to test  this
				sOption("Please send this file to the detection author!!! This is ultra-rare, how did you make it?!");
				p += X.U32(p); p += 4+Math.min(256,l)
			} else if( v == 2 ) {
//_log("tunings text: "+X.UCSD(p));
				p += 1+X.U8(p);
			} else { skip = true; bad = bad.addIfNone("!badtuningver") }
			if(!skip) {
				tunings = X.U32(p+2); p += 6;
				t = X.fSig(p,0x1000,"'FSCT'"); if(t >= p) p = t+4;
			}
			if(sz < p) sz = p;
		}
		else if(X.c("'228'04'mptm'",p)) { //TODO there can be old-ass tunings instead here
			if(!mptp) mptp = p;
// Load_it.cpp:1299
			function readaintle(bits) { //ReadAdaptiveInt32LE et al.
				var b = 0, add = 0, r = 0, oldp = p;
				if(p+1 < X.Sz()) {  b = X.U8(p++);
					if(bits == 16) add = b & 1;  else if(bits == 32) add = b & 3;
					else if(bits == 64) add = (1<<(b&3))-1;  else return 0;
					if(bits == 16) r = b >> 1;  else r = b >> 2;
					for(var i=0; i < add; i++) {  b = 0;
						if(p+1 < X.Sz()) { b = X.U8(p++); r |= b << ((i+1)*8-2);
						} else return 0 }
				} else return 0;
				return r
			}
// serialization_utils.cpp:399
			function mptBeginRead() {
				if(!X.c("'228'",p)) return false; posstart = p; p += 3;
				var l = X.U8(p++); h = X.SA(p,l); p += l;
				flagbyte = 0; hd = X.U8(p++);
				idbytes = ((hd & 3) === 3) ? 4 : (hd & 3); twochar = hd&(1<<6);
				hsz = readaintle(32);
				if(hsz > 1) { t = X.U8(p++); if(!t) flagbyte = X.U8(p); p += hsz-1 }
				if(hd & (1<<4)) t = readaintle(64); var srlV = itV(t.toString(16).toUpperCase().padStart(7,'0'));
				if(mVlsw === "?") mVlsw = srlV+"[mptinfo]";
				if(hd & (1<<5)) p += X.U8(p++); //:477
				if(flagbyte & 1) { t = X.U8(p++);
					if(t&1) idbytes = 65535; else idbytes = t >> 1 }
				fes = 0; if(flagbyte & (1<<1)) fes = readaintle(32); //:496
				hasstartpos=hd&(1<<2); hassz=hd&(1<<3); hasid=idbytes; hasdesc=hd&(1<<7);
				hasmap = hasid || hasstartpos || hassz || hasdesc;
				if(flagbyte&(1<<2)) { t = readaintle(16); //:512
//_log("  @"+Hex(p)+" desc["+t+"]:"+(twochar ? X.SC(p,t,"UTF8") : X.SA(p,t)));
				   p += t*(twochar ? 2 : 1) }
				if(flagbyte&(1<<3)) p += 5; //:517
				entries = readaintle(64); //if(entries > 16000) break;
				if(hasmap) t = readaintle(64); if(p > X.Sz()) return false; //:533
				//endOfHdrData = rposEndOfHdrData = p-posstart;
				rposMapBegin = hasmap ? t : p-posstart;
				return true
			}

			if(mptBeginRead()) {
// serialization_utils.cpp:558
//I've left the pieces of map code intact in case there's fun in finding out what MPT-only tweaks there are
				if(hasmap || fes) {
					p = posstart+rposMapBegin; if(p > X.Sz()) break;
					//var idArray = [], mapdata = [];
					for(i=0; i < entries; i++) {
						var idsz = idbytes;
						if(idsz == 65535) idsz = readaintle(16);
						// md = {"idlen":idsz, "idpos":oldEnd, "id:":"", "rposst":0, "rposend":0, "sz":-1 }
						// var oldEnd = mapdata.length; if(idsz) md["id"] = X.SA(p,idsz);
						p += idsz;
						if(hasstartpos) /*md["rposst"] =*/ readaintle(64);
						if(!fes) { if(hassz) /*md["sz"] =*/ readaintle(64) } //else md["sz"] = fes
						// if(md["sz"] >= 0 && !hasstartpos)
						//     md["rposst"] = !i ? 0 : mapdata[i-1]["rposst"] + mapdata[i-1]["sz"];
						if(hasdesc) { t = readaintle(16); p += t*(twochar ? 2 : 1) }
						//mapdata.push(md)
					}
				} //posmapend = p
			}
			if(sz < p+4) sz = p+4; if(X.U32(p,_LE) != mptp) bad = bad.addIfNone("!badmptptr");
		} //if 228.mptm

// Load_it.cpp:1158
		if(mVlsw === "?" && cwtv === 0x888) mVlsw = "1.17.00.00";
		if(mVlsw != "?" && tracker === "") {
			tracker = "OpenMPT";
			//not sure how many people care about these but...
			isCompatX = (cwtv&0xF000) === 0x5000;
			if(mVlsw === "1.17.00.00" && sreserved != "OMPT") isCompatX = false;
			if(isCompatX) tracker += " (compat. export)";
			else if((mVlsw > "1.17.02.54" && mVlsw < "1.18.02.00" && mVlsw != "1.18.00.00")
			  || (mVlsw > "1.18.02.00" && mVlsw.slice(mVlsw.length-2,mVlsw.length) != "00"))
				tracker += " (test build)"
		}
		else {
			var STdv = 734016 + (cwtv === 0x1FFF ? nreserved : cwtv-0x1050);
			switch(cwtv >> 12) {
			case 0:
				if(isBeroTracker) tracker = "BeRoTracker";
				else if(cwtv === 0x214 && cmwt === 0x200 && flags === 9 && !special
				  && !X.U16(0x3E) && !ins && ptn+1 === ord && gvol === 128 && mvol === 100
				  && spd === 1 && sep === 128 && !pwd && !msglen && !msgofs && !nreserved)
					tracker = "OpenSPC conversion";
				else if(cwtv === 0x214 && cmwt === 0x200 && !X.U16(0x3E) && !nreserved) {
					mVlsw = "1.00.00.A5"; tracker = "ModPlug Tracker 1.00a5"; interpretModPlugMade = true
				} else if(cwtv === 0x214 && cmwt === 0x214 && sreserved === "CHBI")
					tracker = "ChibiTracker";
				else if(cwtv === 0x214 && cmwt === 0x214 && special <= 1 && !pwd && !nreserved
				  && (flags&0x10A6) === 4 && smp > 1 && X.c("'XXXXXXXX.YYY'",smpp[1]+4))
					tracker = "CheeseTracker";
				else if(!cwtv && tracker === "") tracker = "Unknown";
				else if(cmwt < 0x300 && tracker === "") {
					if(cmwt > 0x214) tracker = "Impulse Tracker 2.15";
					else if(cwtv > 0x214) tracker = "Impulse Tracker 2.14p"+(cwtv-0x214)
					else tracker = "Impulse Tracker "+((cwtv&0xF00)>>8)+"."+(cwtv&0xFF).toString(16);
					//a good place to find the total edit time if you care
				}
				break;
			case 1:
				// function SchismEpoch(y,m,d) { var mm = (m+9)%12; var yy = y-mm/10;
				//     return yy*365 + yy/4 - yy/100 + yy/400 + (mm*306+5)/10 + d-1 }
				var mk = cwtv & 0xFFF;
				if(mk <= 0x50) tracker = "Schism Tracker 0."+mk.toString(16);
				else {
					var dt = 734016 + (mk < 0xFFF ? mk-0x050 : nreserved);
					var y = Util.div64(dt*10000+14780, 3652425);
					var ddd = dt - (365*y + Util.div64(y,4) - Util.div64(y,100) + Util.div64(y,400));
					if(ddd < 0) { y--; ddd = dt - (365*y + Util.div64(y,4) - Util.div64(y,100) + Util.div64(y,400)) }
					var mi = Util.div64(100*ddd+52, 3060);
					tracker = "Schism Tracker "+(y+Util.div64(mi+2,12)).padStart(4,'0')+"-"
						+((mi+2)%12+1).padStart(2,'0')
						+"-"+(ddd - Util.div64(mi*306+5,10) + 1).padStart(2,'0')
				} break;
			case 4: tracker = "pyIT "+((cwtv&0xF00)>>8)+"."+((cwtv&0xFF).toString(16)); break;
			case 6: tracker = "BeRoTracker"; break;
			case 7:
				if(cwtv === 0x7FFF && cmwt === 0x215) tracker = "munch.py";
				else tracker = "ITMCK "+((cwtv>>8)&0xF)+"."+((cwtv>>4)&0xF)+"."+(cwtv&0xF);
				break;
			case 0xD:
				if(cwtv == 0xDAEB) tracker = "spc2it";
				else if(cwtv == 0xD1CE) tracker = "itwriter";
				else tracker = "unknown";
			}
		}
	} //if isDeepScan
	if(mVlsw != "?") { charset = 'CP1252'; if(mVcw == "?") sV = mVlsw; else if(mVcw != mVlsw) sV = "cw:"+mVcw+"/lsw:"+mVlsw; else sV = mVcw }
	else { charset = 'CP437'; if(mVcw != "?") sV = mVcw }
	var badc = 0; for(i=0;i<bad.length;i++) if(bad[i] == "!") badc++;
	if(badc < 3) return true
}
if(!bDetected && isIT()) {
	if(type === "it") sName = "Impulse Tracker module (.IT)";
	else /*if(type === "mpt")*/ sName = "OpenMPT module (.MPTM)";
	//else if(type === "alike") sName = "Impulse Tracker-alike ["+X.SA(0,4)+"] module (.IT?)";
	bDetected = 1;
	if(sV != "?") sVersion = "v."+sV; if(bad != "") sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		sOptionT(decAnsi(4,25,charset));
		if(X.isDeepScan()) {
			sOption(tracker,"in: ");
			sOptionT(auth,"by: ")
			if(extsmp.length)
				sOption("ext.smp: \""+addEllipsis(extsmp.join(", "),0x40,0x30)+"\"");
			if(chnm.length)
				sOption("ch.names: \""+addEllipsis(chnm.join(", "),0x40,0x30)+"\"")
		}
		if(special & 1)
			sOptionT(addEllipsis(X.SC(msgofs,Math.min(msglen,0x100),'CP437'),0x100),"msg: ");
		if(insnlst.length)
			sOption("inss: \""+addEllipsis(insnlst.join(" "),0x200,0x30)+"\"");
		if(smpnlst.length)
			sOption("smps: \""+addEllipsis(smpnlst.join(" "),0x200,0x30)+"\"");
		if(X.isDeepScan())
			sOption("ch:"+ch+" ord:"+ord+"("+cord+") ptn:"+ptn+" ins:"+ins+" smp:"+smp+" spd0:"+spd0+" tmp0:"+tmp0+(tunings?"tunings:"+tunings:"")+" sz:"+outSz(sz))
		else
			sOption("ch:"+ch+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" smp:"+smp+" spd0:"+spd0+" tmp0:"+tmp0)
	}
}


function isJ2B() {
	if(X.c("'RIFF'........'AM  '")) align = 1;
	else if(X.c("'RIFF'........'AMFF'")) align = 0;
	else return false; if(!X.c("C5",0x58)) return false;
		sz = X.U32(4,_LE)+8; p = 0x0C; ok = ord = ptn = smp = ins = 0; mptn = -1;
		insts = []; t = "";
		while(p < sz && p < X.Sz()) {
			hkhd = decAnsi(p,4,'CP437',false); hksz = X.U32(p+4,_LE); p += 8;
			switch(hkhd) {
			case "INIT": case "MAIN": ok=1; break;
			case "ORDR": ord = hksz; loop = X.U8(p);
				for(i=p+1; i<p+hksz; i++) if(mptn < X.U8(i)) mptn = X.U8(i); mptn++; break;
			case "PATT": ptn++; break;
			case "RIFF": smp++; break;
			case "INST": ins++;
				t = readBytes(p+2,25,true); t = decEncoding(t,'CP437').trim();
				if(t != "") insts.push(t.trim());
				insn = X.U8(p+1,_LE); subsmp = X.U16(p+30,_LE);
				q = p+0xE1;
				while(q < p+hksz) {
					hhd = decAnsi(q,4,'CP437'); hsz = X.U32(q+4); q += 8;
					if(hhd == "SAMP") smp++;
					t_ = decAnsi(q,0x100,'CP437');
					q += hsz;
if(q < p+hksz && ["SAMP","INST"].indexOf(decAnsi(q,4,'CP437')) < 0) _log(" | ^WTF @"+Hex(q-hsz)+"->"+Hex(q))
				}
				break
			}
			p += hksz+align*(hksz&1)
		}
	if(p > sz || !ok || !ord || !ptn || (!smp && !ins)) return false;
	return true
}
if(!bDetected && isJ2B()) {
	sName = "Jazz Jackrabbit 2/Galaxy Sound System module (.J2B)"; sVersion = "v."+(align?"__":"FF"); bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SA(0x14,64));
		sOptionT(addEllipsis(insts.join('\n'),0x100,0xA0),'msg:"','"');
		sOption("ord:"+ord+" loop:"+loop+" ptn:"+mptn+(mptn!=ptn ? "/"+ptn : "")
			+(ins?" ins:"+ins:"")+(smp?" smp:"+smp:"")+" sz:"+outSz(sz))
	}
}


function isASC1() { //basically ASC0 + loop pos
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundASC1
	if(X.Sz() < 9) return false;
	ptntp = X.U16(2,_LE); ord = X.U8(8); //patterns table ptr & num of positions
	if(ptntp-ord != 9 && ptntp-ord != 72) return false;
	smptp = X.U16(4,_LE); if(smptp > X.Sz()) return false; //samples table ptr
	orntp = X.U16(6,_LE); if(orntp > X.Sz()) return false; //ornaments table ptr
	if(X.U16(smptp,_LE) != 0x40 || X.U16(orntp,_LE) != 0x40) return false;
	ptn = 0; for(i=0; i < ord; i++) { p = X.U8(9+i); if(ptn < p) ptn = p }
	ptn++; if(X.U16(ptntp,_LE) != ptn*6) return false;
	j = X.U16(orntp+0x40-2,_LE)+orntp;
	while((j < X.Sz()) && (j < 65535) && !(X.U8(j)&0x40))  j += 2;
	bad = "";
	if(j>65534 || j>=X.Sz())
	  if(X.isHeuristicScan()) bad = "/malformed";
	  else return false;
	sz = j+2; delay = X.U8(0); loop = X.U8(1);
	return true
}
if(!bDetected && X.isDeepScan() && isASC1()) {
	bDetected = 1; sVersion = "v1.x-2.x"+bad;
	sName = "ASC Sound Master module (.ASC)";
	if(X.isVerbose()) {
		pt = X.fSig(8,0x80,"'ASM COMPILATION OF '")+0x13;
		if(pt > 8) {
			pa = X.fSig(pt+0x13,0x20,"' BY '");
			t = X.SA(pt,pa-pt); a = X.SA(pa+4,0x12);
			sOptionT(t); sOptionT(a,"by: ")
		}
		sOption("ord:"+ord+" ptn:"+ptn+" delay:"+delay+" loop:"+loop+" sz:"+outSz(sz))
	}
}


function isASC0() {
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundASC0
	if(X.Sz() < 8) return false;
	ptntp = X.U16(1,_LE); ord = X.U8(7); //patterns table ptr & num of positions
	if(ptntp-ord != 8 && ptntp-ord != 71) return false;
	smptp = X.U16(3,_LE); if(smptp > X.Sz()) return false; //samples table ptr
	orntp = X.U16(5,_LE); if(orntp > X.Sz()) return false; //ornaments table ptr
	if(X.U16(smptp,_LE) != 0x40 || X.U16(orntp,_LE) != 0x40) return false;
	ptn = 0; for(i=0; i < ord; i++) { p = X.U8(8+i); if(ptn < p) ptn = p; }
	ptn++; if(X.U16(ptntp,_LE) != ptn*6) return false;
	j = X.U16(orntp+0x40-2,_LE)+orntp;
	while((j < X.Sz()) && (j < 65535) && !(X.U8(j)&0x40))  j += 2;
	bad = "";
	if(j > 65534 || j >= X.Sz())
		if(X.isHeuristicScan()) bad = "/malformed";
		else return false;
	sz = j+2; delay = X.U8(0);
	return true
}
if(!bDetected && X.isDeepScan() && isASC0()) {
	bDetected = 1; sVersion = "v0.x"+bad;
	sName = "ASC Sound Master module (.AS0)";
	if(X.isVerbose()) {
		pt = X.fSig(7,0x80,"'ASM COMPILATION OF '")+0x13;
		if(pt > 7) {
			pa = X.fSig(pt+0x13,0x20,"' BY '");
			t = X.SA(pt,pa-pt); a = X.SA(pa+4,0x12);
			sOptionT(t); sOptionT(a,"by: ")
		}
		sOption("ord:"+ord+" ptn:"+ptn+" delay:"+delay+" sz:"+outSz(sz))
	}
}


function isGlobalTracker() {
//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundGTR
	if(X.Sz() < 296) return false;
	adr = X.U16(5); p = 0x27; mp = 0;
	for(i=0,old=X.U16(p)-adr,p+=2; i < 14; i++,old=t) { t = X.U16(p)-adr; //sample ptrs check
		if(t - old < 6 || (t-old-2)%4 || t > X.Sz()) return false; p += 2 }
	for(i=0,old=X.U16(p)-adr,p+=2; i < 15; i++,old=t) { t = X.U16(p)-adr; //ornament ptrs check
		if(t-old < 3 || t > X.Sz() || t-old != 2+X.U8(old+1) /* orn. loop end */) return false; p += 2 }
	sz = t+2+X.U8(t+1); //it's always the ornaments that come last, so we'll take the last one
	for(i=1,old=X.U16(p)-adr,p+=2; i < 32*3; i++,old=t) { t = X.U16(p)-adr; //pattern ptrs check
		if(t - old < 3 || t > X.Sz()) return false; p += 2 }
	return true
}
if(!bDetected && isGlobalTracker()) {
	sName = "Global Tracker module (.GTR)"; bDetected = 1;
	sVersion = 'v'+(X.U8(4)>>4)+'.'+(X.U8(4)&0xF);
	if(X.isVerbose()) {
		dly = X.U8(0); ptn = X.U8(0x125); lp = X.U8(0x126);
		sOptionT(X.SA(7,0x20));
		sOption(' ptn:'+ptn+' delay:'+dly+' sz:'+outSz(sz));
	}
}


function isBPSoundMon() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/BP-Soundmonitor.c
	// & src/BS02-92.S for BPSM version
	if(X.Sz() < 0x200) return false;
	old = false; bad = "";
	if(!/V\.\d/.test(X.SA(0x1A,3)))
		if(X.c("'BPSM'",0x1A)) old = true; else return false;
	ord = X.U16(0x1E,_BE); if(525+ord*16 > X.Sz()) return false;
	allsmpsz = smp = 0;
	for(i=0; i < 15; i++) {
		if(X.U8(0x20+i*0x20) == 0xFF) continue;
		sn = readBytes(0x20+i*0x20,0x18); if(charStat(sn,CS_ALL).indexOf('xsc') < 0) return false;
		slen = X.U16(0x20+0x18+i*0x20,_BE)<<1; lp = X.U16(0x3A+i*0x20,_BE);
		lpl = X.U16(0x3A+i*0x20,_BE); svol = X.U16(0x3E+i*0x20,_BE);
		if(lpl > slen || lp-1 > slen || lpl-1 > slen) return false;
		if(svol > 0x60) return false; else if(svol > 0x40) bad = bad.addIfNone("!badvol")
		allsmpsz += slen; if(slen) smp++
	}
	sz = 0x200+allsmpsz+ord*0x10;
	if(!old) sz += X.U8(0x1D)*0x40; //what IS this value?
	ptn = 0;
	for(i=0; i < ord*16; i+=4) { p = X.U16(0x200+i,_BE); if(p>ptn) ptn = p }
	sz += ptn*0x30;
	return true
}
if(!bDetected && X.isDeepScan() && isBPSoundMon()) {
	bDetected = 1;
	sName = "Brian Postma's SoundMon module (.BP)";
	if(old) sVersion = "old"; else sVersion = "v"+X.SA(0x1C,1);
	if(X.Sz() < sz) bad = bad.addIfNone("!short");
	if(bad != "") sVersion = sVersion.appendS("malformed"+bad,'/');
	if(X.isVerbose()) {
		sOptionT(X.SA(0,0x1A));
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
	}
}


function isFuchs() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FuchsTracker.c
	if(X.Sz() < 0xC0 || !X.c("'SONG'",0xC0) || !X.c("'INST'",0xC8+X.U32(0xC4,_BE))) return false;
	allsmpsz = X.U32(0x0A,_BE);
	if(allsmpsz <= 2 || allsmpsz >= 65535*16) return false;
	smpdescs = 0; smp = 0;
	for(i=0; i < 16; i++) {
		ssz = X.U16(0x0E+i*2,_BE); if(ssz) smp++;
		lpst = X.U16(0x4E+i*2,_BE); if(ssz < lpst) return false;
		if(X.U8(0x2E+i*2) > 0x40) return false; //vol
		smpdescs += ssz
	}
	if(smpdescs <= 2 || smpdescs > allsmpsz) return false;
	ord = X.U8(0x6F); if(!ord || ord > 0x28) return false;
	ptn = 0; //last pattern
	for(i=0; i < 0x28; i++) {
		pt = X.U8(0x71+i*2); if(pt > 40) return false; if(pt > ptn) ptn = pt }
	ptn++; k = ptn*1024; if(k+204 > X.Sz()) return false;
	sz = smpdescs+k+204;
	return true
}
if(!bDetected && isFuchs()) {
	sName = "FuchsTracker module (.FUCHS)";
	bDetected = 1;
	if(X.isVerbose()) sOption('ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
}


function isGMC() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/GMC.c
	// & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_gmc.cpp
	if(X.Sz() < 444 || !X.c("000000",0xF0)) return false;
	//smp descriptions
	allsmpsz = 0; j = 0; smps = [];
	for(k=0; k < 15; k++) {
		o = X.U32(16*k,_BE); if(o > 0x7FFFFF || (o&1)) return false; //ofs
		l = X.U16(4+16*k,_BE)<<1; //length
		if(l > 0xFFFF) return false;
		if(X.U8(6+16*k,_BE)) return false; //zero byte
		if(X.U8(7+16*k) > 0x40) return false; //vol
		a = X.U32(8+16*k,_BE); if(a > 0x7FFFFF || (a&1)) return false; //address
		n = X.U16(0xC+16*k,_BE); //looplen
		if(n > 2 && n > l) return false;
		if(X.U16(0xE+16*k,_BE)&1) return false; //datastart
		allsmpsz += l;
		if(l) smps.push([o,l]);
	}
	if(allsmpsz <= 4) return false;
	smps.sort(function(a,b){return a[0]-b[0]});
	for(k=0; k < smps.length-1; k++)  if(smps[k][0]+smps[k][1] > smps[k+1][0]) return false;
	ord = X.U8(0xF3); //ptn table size
	if(ord > 0x64 || !ord) return false;
	ptn = 0; ords = [];
	for (i=0; i < 100; i++) { //ptn order table
		p = X.U16(0xF4+i*2,_BE);
		if (p & 0x3FF) return false;
		if(i < ord) { p >>= 10; if(p > ptn && p != 63) ptn = p }
	} ptn++;
	if(ptn > 64) return false;
	m = notes = 0; p = 0x1BC; nps = [];
	for (i=0; i < ptn; i++) { //test pattern data
		badnotes = badled = 0;
		for (j = 0; j < 256; j++) {
			d = readBytes(p,4);
			np = (d[0]<<8)+d[1];
			if(np && np != 0xFFFE)
				if(np < 113 || np > 856) badnotes++; else { if(nps.indexOf(np) < 0) nps.push(np); notes++ }
			d[2] &= 0xF;
			if(d[2] == 3 && d[3] > 0x40) badnotes++; //vol effect
			if(d[2] == 4 && d[3] > 0x63) return false;
			if(d[2] == 5 && d[3] > ord+1) return false;
			p += 4; if(p > 0xFFFC || p > X.Sz()) return false
		}
	if(badnotes > 16) { _log("GMCFault: over 16 bad notes"); return false }
	}
	if(!notes && !X.isHeuristicScan()) return false;
	nps.sort(function(a,b){return a-b});
//_log(nps.length+" different nps used: "+nps)
	sz = allsmpsz + ptn*1024 + 0x1BC;
	return true
}
if(!bDetected && X.isDeepScan() && isGMC()) {
	bDetected = 1;
	sName = "Game Music Creator module (.GMC)";
	if(X.isVerbose()) {
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smps.length+" notes:"+notes+"("+nps.length+" diff.) sz:"+outSz(sz)); if(X.Sz() < sz) sVersion = "malformed!short"
	}
}


function isProTracker1() {
	//ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker1.cpp
	// & https://bulba.untergrund.net/Ay_Emul29.src.7z/Formats.pas -> FoundPT1
	if(X.Sz() < 0x66) return false;
	tempo = X.U8(0); if((tempo < 2) || (tempo > 15)) return false; //delay
	ptntp = X.U16(0x43,_LE); if(ptntp >= X.Sz()) return false; //patterns table pointer
	numofpos = X.U8(1);// if(len == 0) return false; //numofpositions, can be corrupted?
	loop = X.U8(2);
	j = 0; j1 = 65535;
	for(i=0; i < 0x10; i++) { //samples pointers
		sp = X.U16(3+i*2,_LE); if(sp > X.Sz()) return false;
		if(j < sp) j=sp;
		op = X.U16(0x23+i*2,_LE); if(op > X.Sz()) return false;
		if(op != 0 && j1 > op) j1 = op
	}
	if((j1 < 0x67) || (j < 0x67) || (j > 65534) || (j > X.Sz())
	  || (j + X.U8(j) * 3 + 2 != j1)) return false;
	j = 0;
	for(i=0; i < 0x10; i++) { //ornaments pointers
		op = X.U16(0x23+i*2,_LE); if(op > X.Sz()) return false;
		if(j < op) j = op
	}
	if(j<0x67) return false;
	len = j+0x40; if(len>65536) return false; if(len>X.Sz()+1) return false;
	j = 0x63;
	while(j <= ptntp && X.U8(j) < 0xFF) j++;
	if(j+1 != ptntp) return false;
	ord = j-0x63; if(loop > ord) return false;
	return true
}
if(!bDetected && X.isDeepScan() && isProTracker1()) {
	sName = "ProTracker module (.PT1)"; sVersion = "v1.x"; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SA(0x45,0x1E)); //musicname
		sOption("tempo:"+tempo+" ord:"+ord+" len:"+len)
	}
}


function isProTracker2() {
	//ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker2.cpp
	// & https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundPT2
	if(X.Sz() < 132) return false; //the smallest actual mod in all collections is 521 long
	tmp = X.U8(0); if(tmp<2 || tmp>15) return false;
	ord = X.U8(1); if(!ord) return false;
	loop = X.U8(2); if(loop > ord) return false;
	smp0 = X.U16(3,_LE); orn0 = X.U16(0x43,_LE);
	if(orn0-smp0>X.Sz()+2 || orn0<smp0) return false;
	ptnp = X.U16(0x63,_LE); if(ptnp<0x65 || ptnp>X.Sz()) return false;
	for(i=0; i < 0x30; i++) { //sample pointers and ornament pointers
		p = X.U16(3+i*2,_LE);
		if(!p && (/*p < 0x65 ||*/ p > X.Sz()-2)) return false; // Ashnar/HBKSA2.pt2 .___.
	}
	if(X.U8(0x64) > 1) return false;
	for(ptn=0; ptn <= 0xFF; ptn++) {
		if(0x83+ptn > X.Sz()-2) return false;
		posn = X.U8(0x83+ptn);
		if(posn == 0xFF) break;
	}
	if(!ptn || ptn>0xFF) return false; //how can it be >0xFF...
	return (ord == ptn)
}
if(!bDetected && X.isDeepScan() && isProTracker2()) {
	sName = "ProTracker module (.PT2)"; sVersion = "v2.x"; bDetected = 1;
	if(X.isVerbose()) {
		if(smp0>0x82 && orn0>0x82 && ptn>0x82)
			sOptionT(X.SA(0x65,0x1E));
		sOption("tmp:"+tmp+" ord:"+ord+" ptn:"+ptn)
	}
}


function isProTracker3() { //PT3's detection, ALSO sanity checks because SOME people -_-
	//ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker3_compiled.cpp
	ftn = X.U8(0x63); //0=Pro Tracker, 1=Sound Tracker, 2=ASM or PSC, 3=RealSound.
	tmp = X.U8(0x64); if((tmp<3)||(tmp>63)) return false;
	len = X.U8(0x65); if(len>0x55) return false;
	loop = X.U8(0x66); if(loop>63) return false;
	pptn = X.U16(0x67,_LE); if(pptn>X.Sz()-2) return false;
	for(i=0;i<0x30;i++) if(X.U16(0x69+i*2,_LE)>X.Sz()-2) return false;
	for(ord=0;ord<=0xFF;ord++) {
		if(0xC9+ord>X.Sz()-2) return false;
		posn=X.U8(0xC9+ord);
		if(posn==0xFF) break;
	}
	if((ord==0) || (ord>0xFF)) return false;
	return (ord==len)
}
if(!bDetected && X.isDeepScan() && isProTracker3()) {
	sName = "ProTracker module (.PT3)"; sVersion = "v3."+X.SA(0x0D,1);
	bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SA(0x1E,0x20));
		sOptionT(X.SA(0x42,0x20),"by: ")
		sOption("tmp:"+tmp+" ord:"+len)
	}
	switch(ftn) {
		case 0: sOption("Pro Tracker tuning"); break;
		case 1: sOption("Sound Tracker tuning"); break;
		case 2: sOption("ASM/PSC tuning"); break;
		case 3: sOption("RealSound tuning"); break;
		default: sOption("Unknown tuning")
	}
}


function isSoundTrackerProUnp() {
	//ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/soundtracker.cpp
	// & https://bulba.untergrund.net/STPDocs.7z -> Xplay79f.txt
	if(X.Sz()<3009+576) //header + patterns
		return false;
	const stpsmpnum=15; p=0;
	for(smp=0;smp<15;smp++) {
		for(i=0;i<32;i++) { j = X.U8(p); if(j>0x0F) return false; p++ } //levels
		for(i=0;i<32;i++) { j = X.U8(p); if(j&0x20) return false; p++ } //noises
		for(i=0;i<32;i++) { p++; j = X.U8(p); if(j>0x1F) return false; p++ } //additions
		for(i=0;i<2;i++) { j = X.U8(p); if(j>0x1F) return false; p++ } //loops
	}
	for(i=0;i<256;i++) { j = X.U8(p); if((j<1) || (j>0x20)) return false; p+=2 } //positions
	// at this point, p should be 15*0x82
	if(X.U8(p)&0x80) return false;
	p++;  p+=32*17; //ornaments
	dly=X.U8(p); if((dly<1) || (dly>0x0F)) return false; p++;
	pts=X.U8(p); if((pts<1) || (pts>0x40)) return false;
	return true;
}
if(!bDetected && X.isDeepScan() && isSoundTrackerProUnp()) {
	sName = "Sound Tracker Pro module (.STF)"; sVersion  = "uncompiled/unpacked"; bDetected = 1;
}


function isSoundTrackerPro() {
	// taken from https://bulba.untergrund.net/STPDocs.7z -> stp.txt
	tmp = X.U8(0); if((tmp == 0) || (tmp > 50)) return false;
	tabl1 = X.U16(1,_LE); if(X.Sz()-2 < tabl1) return false;
	tabl2 = X.U16(3,_LE); if(X.Sz()-2 < tabl2) return false; //pattern descriptors
	tabl3 = X.U16(5,_LE); if(X.Sz()-2 < tabl3) return false; //ornament offsets
	tabl4 = X.U16(7,_LE); if(X.Sz()-2 < tabl4) return false; //sample offsets
	len = X.U8(tabl1); if(tabl2 != tabl1+2+len*2) return false;
	for(i=0;i<len;i++)  if(X.Sz() < X.U16(tabl2+i*2,_LE)) return false;
	if(X.Sz() < tabl4+30) return false;
	for(i=0;i<15;i++)  if(X.Sz() < X.U16(tabl4+i*2,_LE)) return false;

	return true;
}
if(!bDetected && X.isDeepScan() && isSoundTrackerPro()) {
	sName = "Sound Tracker Pro module (.STP)"; sVersion  = "uncompiled"; bDetected = 1;
	sOption("tmp:"+tmp+" ord:"+len)
}


function isSQTracker() {
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundSQT
	if(X.Sz() < 17) return false;
	sz = X.U16(0); if(X.Sz() < sz) return false;
	smptp = X.U16(2); if(smptp < 10) return false; //samples table ptr
	orntp = X.U16(4); ptntp = X.U16(6); //ornaments & patterns table ptrs
	postp = X.U16(8); lpp = X.U16(0xA); //positions table ptr & loop ptr
	if(orntp <= smptp+1) return false; if(ptntp < orntp) return false;
	if(postp <= ptntp) return false; if(lpp < postp) return false;
	membase = smptp-10; //to eventually realign to file, not memory
	if(lpp-membase >= sz) return false;
	i = postp-membase; b = X.U8(i); if(!b) return false; //can't have 0 patterns
	ord = 0; ptn = 0;
	while(b) { //find the highest pattern #
		if(i+7 >= sz) return false;
		ord++;
		if(ptn < (t = b&0x7F)) ptn = t; i += 2; b = X.U8(i);
		if(ptn < (t = b&0x7F)) ptn = t; i += 2; b = X.U8(i);
		if(ptn < (t = b&0x7F)) ptn = t; i += 3; b = X.U8(i)
	} ptn++;
	p = X.U16(smptp-membase+2);
	if(p-ptntp != ptn*2) return false;
	p = 12; len = i+7;
	if(len != sz) return false;
	t = X.U16(12);
	for(i = 1; i <= (orntp-smptp)>>1; i++) {
		p += 2; j3 = X.U16(p);
		if(j3-t != 0x62 && (sz != 1337 || ord != 10 || ptn != 11)) return false;
		t = j3;
	}
	for(i = 1; i <= (ptntp-orntp)>>1; i++) {
		p += 2; j3 = X.U16(p);
		if(j3-t != 0x22) return false;
		t = j3;
	}

	return true;
}
if(!bDetected && isSQTracker()) {
	sName = "Scalex Qjeta Tracker module (.SQT)"; sVersion  = "compiled"; bDetected = 1;
	sOption('ord:'+ord+' ptn:'+ptn+' sz:'+outSz(sz))
}


function isST() {
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundST
	if(X.Sz() < 6) return false;
	tempo = X.U8(0); if(tempo == 0 || tempo > 0x20) return false; //delay
	postp = X.U16(1,_LE); if(postp<0x7E || postp>X.Sz()) return false; //positions ptr
	orntp = X.U16(3,_LE); if(orntp<0x7E || orntp>X.Sz()) return false; //ornaments ptr
	ptntp = X.U16(5,_LE); if(ptntp<0x7E || ptntp>X.Sz()) return false; //patterns ptr
	ord = X.U8(postp);
	j2 = ptntp - orntp; if(j2 == 0) return false; //j2<0: ST, >0: S_SONIC
	fID = false;
	if(j2 > 0) { if((j2%0x21) > 0) return false; }
	else if((j2%0x21) > 0) {
		if((j1<55) || (((j1-55)%0x21) > 0)) return false;
		fID = true; //we'll check for a preset ID at the end
	}
	j = X.U8(postp)*2+3;
	if(j2 < 0) { if(j+j2 != 0) return false; }
	else if(j+postp-orntp != 0) {
		if((ptntp < 82) || (j+postp-ptntp+55 != 0)) return false;
		fID = true;
	}
	j = orntp+0x21;
	if(j > 65535 || j > X.Sz()) return false;
	while(j) { j--; if(X.U8(j) > 0) return false; if(j == orntp) break }
	j = ptntp; j1 = 0; j2 = 0; ptn = 0;
	while((j+6 <= X.Sz()) && (j+6 < 65536) && (X.U8(j) < 0xFF)) {
		//find the highest channel data pointer
		j++; //ptn #
		j2 = X.U16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //channel A data ofs
		j2 = X.U16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //ditto B
		j2 = X.U16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //ditto C
		ptn++
	}
	if((X.U8(j) < 0xFF) ) return false;
	if(j1 > X.Sz()) return false;
	if(X.U8(j1-1) < 0xFF) return false;
	bad = 0; //some modules are a bit more broken! Like "- unknown/ay5.stc" which lost some of its final bytes
	while(1) { //scan through the channel data at the end of file
		if(0x83 <= X.U8(j1) <= 0x8E) j1++; //I don't get what this does, no docs either
		j1++;
		if(j1 > 65535) return false;
		if(j1 > X.Sz())
			if(X.isHeuristicScan()) { bad = 1; break }
			else return false;
		if((X.U8(j1) == 0xFF) || (j1 == X.Sz())) break
	}
	if(X.U8(j1) == 0xFF) len = j1+1;
	// len = X.U16(0x19,_LE); // because ~somebody~ used it for names
	if(fID)
	  if( (X.SA(ptntp-55,29) != 'SOUND TRACKER COMPILATION OF ') &&
		(X.SA(ptntp-55,28) != 'KSA SOFTWARE COMPILATION OF ') ) return false;

	return true;
}
if(!bDetected && X.isDeepScan() && isST()) {
	sName = "Sound Tracker module (.STC,.ZXS)"; sVersion  = "v1.x"; bDetected = 1;
	if(bad) sVersion += "/malformed"+bad;
	sz = X.U16(0x19,_LE); i_d = X.SA(7,18);
	if(['SONG BY ST COMPILE','SONG BY MB COMPILE','SONG BY ST-COMPILE',
	  'SOUND TRACKER v1.1','S.T.FULL EDITION  ','SOUND TRACKER v1.3'].indexOf(i_d) >= 0)
		msg = '';
	else
		msg = i_d;
	if(sz != X.Sz()) { //the message must've hit the size field, HAPPENS
		if(X.isVerbose()) sOption("ord:"+ord+" ptn:"+ptn+" sz:"+outSz(len));
		if(32 <= (sz&0xFF) <= 0x7F) {
			msg += String.fromCharCode(sz&0xFF);
			if(32 <= (sz>>8) <= 0x7F) msg += String.fromCharCode(sz>>8)
		}
	} else
		//if(sz == len)
			if(X.isVerbose()) sOption("ord:"+ord+" ptn:"+ptn+" sz:"+sz); /*else;
		else if(X.isVerbose())
		  sOption("ord:"+ord+" ptn:"+ptn+" sz:"+sz+" calc.sz:"+len); //keeping len in: debug
		  */
	if(X.isVerbose()) sOptionT(msg,"msg: ");
}


function isST3() {
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundST3
	if(X.Sz() < 8) return false;
	orntp = X.U16(5,_LE); if(orntp > X.Sz()-6) return false; //ST3 ornaments/STC patterns table ptr
	ptntp = X.U16(7,_LE); //ST3 patterns table ptr
	j1 = ptntp-orntp; if(j1 <= 0) return false;
	smptp = X.U16(3,_LE); //samples table ptr
	j2 = orntp-smptp; if(j2 <= 0) return false;
	postp = X.U16(1,_LE); //positions table ptr
	j3 = smptp-postp; if(j3 > X.Sz()) return false;
	j4 = postp-9; if(j4<=0) return false;
	fID = false;
	if(j4%130) {
		if(j4 < 55 || ((j4-55)%130)) return false;
		fID = true
	}
	smp = X.U8(smptp); if(!smp || smp>16) return false; // smp # , j5
	j = smp*130+9; if(fID) j += 55;
	if(posptr != j) return false;
	ord = X.U8(j); if(!ord) return false; //pos # , j6
	j += ord*2+1;
	if(smptp != j && smptp != j+2) return false;
	orn = X.U8(orntp); if(!orn || orn>16) return false; // orn # , j7
	j = smptp+smp*2+1+orn*32; if(orntp != j) return false;
	j += orn*2+1; if(X.U16(ptntp,_LE) != j) return false;
	base = X.U16(smptp+1,_LE)-9;
	if(fID) base -= 55; if(base < 0) return false;
	j = X.U16(X.U8(orntp+1),_LE)-base;
	if(j != smptp+j5*2+1) return false;
	for(i=j; i < j+32; i++) if(X.U8(i)) return false;
	j8 = -1; j = postp+2;
	for(i=0; i<ord; i++) {
		b = X.U8(j); if(b%6) return false;
		if(j8 < b) j8 = b; j += 2
	}
	len = ptntp+j8+6;
	if(len-1 < X.Sz()) return false;
	if(base+j > 65536) return false;
	if(fID && !X.c("''KSA SOFTWARE COMPILATION OF ''",9)) return false;
	delay = X.U8(0) //delay
	return true;
}
if(!bDetected && X.isDeepScan() && isST()) {
	sName = "Sound Tracker module (.ST3)"; sVersion  = "v3.0"; bDetected = 1;
	if(bad) sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		sOptionT(X.SA(9,55));
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" delay:"+delay+" @"+Hex(base)+" sz:"+outSz(len));
	}
}


function isSidMon1() {
	//ref http://asle.free.fr/prowiz/prowiz.src.zip / r/sidmon1.c
	// & https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/sidmon1/SIDMon1.0.s
	//I'm just used to calling the base ofs "a0" at this point
	a0 = X.fSig(0,0xFFFF,"41FA.... ....FFD4 43FA.... 228841FA ....D1E8 FFD843FA");
	if(a0 < 0 || (a0&1) || a0 > X.Sz()) return false;
	bad = "";
	p = X.U16(a0+2,_BE); msgp = a0+p+2; if(msgp < a0+16 || msgp > X.Sz()) return false;
//if(debug&&a0)_log("init @"+Hex(a0)+", p="+Hex(p));
	playp = X.fSig(a0+0x10,a0+msgp-6,"4E7548E7FFFE"); if(playp < 0 || (playp&1)) return false;
	smpd = X.U16(a0+2+p-2,_BE); ordp = X.U16(a0+2+p-6,_BE);
	ptnp = X.U16(a0+2+p-10,_BE); if(!smpd || !ordp || !ptnp) return false;
//_log("smpd:"+Hex(a0+2+p+smpd)+" ordp:"+Hex(a0+2+p+ordp)+" ptnp:"+Hex(a0+2+p+ptnp));
	if(a0+2+p+smpd > X.Sz() || a0+2+p+ordp > X.Sz()) return false;
	if(smpd <= ordp && smpd != 1) return false;
	ptn = (ordp-ptnp) >> 6; ord = 0; smps = [];
	if(smpd == 1) { //no samples, traversing orderlist
		ord++; pt = a0+2+p+ordp;
		if(pt+4 > X.Sz()) { sz = pt; return true; } // 1 position
		pt += 4; t = 1; sz = ordp-ptnp;
		while(t < sz) { t = X.U32(sz,_BE); if(sz+4 > X.Sz() || !t) return true; sz += 4 }
		sz -= 4; ord--
	}
	else { //samples present
		ord = (smpd-ordp) >> 2;
		smpd += a0+2+p; //now an absolute offset
		sdsz = X.U16(smpd+2,_BE); smp = sdsz >> 5;
		if(smpd+sdsz > X.Sz() || sdsz%32) return false;
//_log("smpinfo: ["+Hex(sdsz)+"/"+Hex(smp)+"]")
		if(!sdsz) for(;; sdsz += 0x20) { //guess manually
			if(sdsz+0x24 > X.Sz()) return false;
			stp = X.U32(smpd+sdsz+4,_BE); //smp start ofs
			k = X.U32(smpd+sdsz+8,_BE); //loop start ofs?
			endp = X.U32(smpd+sdsz+0xC,_BE); //loop end ofs or smp end ofs
			if(stp > k || k >= endp) break; smp++;
//_log("  smp found @"+Hex(sdsz))
			t = decAnsi(smpd+sdsz+0x10,0x10,CPAmiga).trim(); if(t != "") smps.push(t)
		}
		mendp = 0;
		for(i=0; i < smp; i++) {
			endp = X.U32(smpd+i*0x20+0xC,_BE); if(endp > mendp) mendp = endp;
			t = decAnsi(smpd+i*0x20+0x10,0x10,CPAmiga); if(t != "") smps.push();
		}
//_log("mendp:"+Hex(mendp)+" @"+Hex(smpd+sdsz+4+mendp));
		sz = smpd+sdsz+4+mendp;
		if(sz > X.Sz()) bad = bad.addIfNone("!short");
	}
	return true;
}
if(!bDetected && X.isDeepScan() && isSidMon1()) {
	sName = "SIDMon module (.SID1,.SMN,.SID)"; sVersion  = "v1"; bDetected = 1;
	if(bad != '') sVersion = sVersion.appendS("malformed"+bad,'/');
	if(X.isVerbose()) {
		t = decAnsi(msgp,0x100,CPAmiga).trim();
		if(t != "SID-MON BY R.v.VLIET  (c) 1988") sOptionT(t);
		sOptionT(addEllipsis(smps.join(','),200),'smps:');
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
	}
}


function isSidMon2() {
//from https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/SidMon20/SidMon20Worker.cs
	if(X.Sz() < 0x5A) return false;
	if(!X.c("'SIDMON II - THE MIDI VERSION'", 0x3A)) return false;
	ord = X.U8(2)+1; spd0 = X.U8(3); smp = X.U16(4,_BE);
	ofs = 0x3A+X.U32(6,_BE); songlenlen = X.U32(0xA,_BE); ofs += songlenlen; //yes
	if(ofs != 0x5A) return false;    
	p = 0x0E;
	for(i = 0; i < 11; i++) {
		t = X.U32(p,_BE); if(t > 0x0FFFFF || !t) { _log("fault @"+Hex(p)); return false }
		switch(i) {
		case 0: var ordt = [ofs,t]; break;
		case 1: if(t != ordt[1]) return false; var notexpost = [ofs,t]; break;
		case 2: if(t != notexpost[1]) return false; break;
		case 3: if(t%32) return false; break;
		case 7: if(t != smp) return false; var smpt = [ofs,t]; break;
		case 8: trk = t >> 1; break;
		case 9: var trkt = [ofs,t]; break;
		}
		p += 4; ofs += t; if(ofs > X.Sz()) return false
	}
	ofs = trkt[0]+trkt[1]; if(ofs&1) ofs++;
	smpsz = 0; smps = [];
	for(p = smpt[0]; p < smpt[0]+smpt[1]; p += 0x40) {
		smpsz += X.U16(p+4,_BE) << 1;
		t = decAnsi(p+0x20,0x20,CPAmiga).trim(); if(t != "") smps.push(t)
	}
	sz = ofs+smpsz;  smp >>= 6;
	return true
}
if(!bDetected && isSidMon2()) {
	sName = "SIDMon II module (.SID2)"; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SA(7,0x20));
		sOptionT(X.SA(0x27,0x20),"in: ");
		sOptionT(addEllipsis(smps.join(','),0x100),'smp/msg:"','"');
		sOption("trk:"+trk+" ord:"+ord+" smp:"+smp+" sz:"+outSz(sz))
	}
}


function isADXAHX() { //non-module! sanity check a bit because 8000 is too broad
	if(!X.c("8000")) return false;
	fmt = X.U8(4);
	if([2,3,4,0x10,0x11].indexOf(fmt)<0) return false;
	if(X.U8(6)!=4) return false; //4 bit per sample only
	if([1,2].indexOf(X.U8(7))<0) return false; //1/2 ch only
	nV = X.U8(0x12);
	if([3,4,5].indexOf(nV)<0) return false;
	return true;
}
if(!bDetected && X.isHeuristicScan() && isADXAHX()) {
	//from https://wiki.multimedia.cx/index.php/CRI_ADX_file
	bDetected = 1;
	sVersion = "";
	if([2,3,4].indexOf(fmt) >= 0)
		sName = "CRI ADX 4-bit ADPCM audio (.ADX)";
	else if([0x10,0x11].indexOf(fmt) >= 0)
		sName = "CRI AHX 4-bit ADPCM audio (.AHX)";
	switch(nV) {
		case 3: sVersion = "v3"; break;
		case 4: sVersion = "v4"; break;
		case 5: sVersion = "v4NL"
	}
	switch(fmt) {
		case 2: sVersion+=" PPC"; break;
		case 3: sVersion+=" std"; break;
		case 4: sVersion+=" exSc"; break;
		case 0x11: sVersion+=" AHX" }
	if(X.U8(0x13) > 0) sVersion += " encrypted";
	if(X.isVerbose()) {
		sOption(X.SA(pcprt,6));
		pcprt = X.U16(2,_BE)-2;
		sOption("blk: "+X.U8(5));
		sOption("ch: "+X.U8(7));
		sOption("sr: "+X.U32(8,_BE)+"Hz");
		sOption("smp: "+X.U32(0x0C,_BE));
	}
}


function isDigiIllu() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/DigitalIllusion.c
	if(X.Sz()<17) return false;
	smp = X.U16(0,_BE); if(smp > 31) return false; //num of samples
	ptntp = X.U32(2,_BE); if(ptntp < smp*8+2) return false; //pattern table ptr
	ptndp = X.U32(6,_BE);  if(ptndp-ptntp > 0x80) return false; //pattern data ptr
	smpdp = X.U32(10,_BE); if(smpdp > 0xFFFF) return false; //sample data ptr
	if(ptndp <= ptntp || smpdp <= ptntp || smpdp <= ptndp) return false;
	if(ptntp > X.Sz() || ptndp > X.Sz() || smpdp > X.Sz()) return false;
	allsmpsz = 0; l = 0;
	for(j=0; j < smp; j++) {
		smpsz = X.U16(j*8+14,_BE)<<1; if(smpsz > 0xFFFF) return false;
		lstart = X.U16(j*8+18,_BE)<<1; if(lstart > 0xFFFF || lstart > smpsz) return false;
		lsz = X.U16(j*8+20,_BE)<<1;
		if(lsz > 0xFFFF || lsz > smpsz+2 || lstart+lsz > smpsz+2 || (lstart && lsz <= 2)) return false;
		if((lstart || lsz > 2) && !smpsz) return false;
		if(X.U8(16+j*8) > 0xF || X.U8(17+j*8) > 0x40) return false; //fine & vol checks
		allsmpsz += smpsz
	}
	if(allsmpsz <= 2) return false;
	for(i=ptntp; i < ptndp-1; i++)
		if(X.U8(i) > 0x80) return false;
	if(X.U8(ptndp-1) < 0xFF) return false;
	sz = allsmpsz + X.U32(10,_BE);
	return true
}
if(!bDetected && X.isDeepScan() && isDigiIllu()) {
	sName = "Digital Illusions packed module (.DI)"; bDetected = 1;
	if(X.isVerbose()) {
		sOption(outSz(sz),"sz:"); if(sz > X.Sz()) sVersion = "malformed!short"
	}
}


function isPMD() {
	// ref https://raw.githubusercontent.com/ValleyBell/MidiConverters/master/pmd_SeqFormat.txt
	// & https://web.archive.org/web/20220928062748/https://lithcore.cn/2318/
	if(X.Sz() < 37) return false;
	nV = X.U8(0); if(nV < 0xFF) if(nV > 0x0F) return false;
	for(i=0; i < 13; i++) {
		p = X.U16(1+i*2,_LE);
		if((p && p < 0x1A)  || p+1 > X.Sz()) return false
	}
	extrap = X.U16(0x19,_LE)-3; if(X.U8(extrap+3) != 0xFE) return false;
	extra_type = X.U8(extrap+2); if(extra_type < 0x40 || extra_type > 0x4F) return false;
	if(extra_type < 0x42) ttype = "PCP/P86";
	else if(extra_type<0x48) ttype = "PPS";
	else if(nV==0xFF) ttype = "PPZ"; else ttype = "FM Towns";
	extra = X.U16(extrap,_LE)+1; if((extra > 0 && extra < extrap) || extra > X.Sz()) return false;
	for(i=0; i < 4; i++) {
		p = X.U16(extra+i*2,_LE);
		if(p < 27 || p+1 > X.Sz()) return false
	}
	return true
}
if(/*!bDetected && */X.isDeepScan() && isPMD() ) {
	bDetected = 1;
	sName = "Professional Music Driver module (.M,.M2)";
	sVersion = "v"+nV+" t"+Hex(extra_type).substr(0,2)+":"+ttype/*+" tagptrs:"+Hex(extra)*/;
	if(X.isVerbose()) {
		fnames = [];
		if(extra_type >= 0x48) {
			n = X.SC(X.U16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
			if(n != "") fnames.push("PPZ:"+n)
		}
		if(extra_type >= 0x42) {
			n = X.SC(X.U16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
			if(n != "") fnames.push("PPS:"+n)
		}
		n = X.SC(X.U16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
		if(n != "") fnames.push("PPC/P86:"+n);
		title = X.SC(X.U16(extra,_LE)+1,0x100,"Shift_JIS");
		if(title == "\x1A") title = "";
		artist = X.SC(X.U16(extra+2,_LE)+1,0x100,"Shift_JIS");
		if(artist == "\x1A") artist = "";
		arenji = X.SC(X.U16(extra+4,_LE)+1,0x100,"Shift_JIS");
		if(arenji == "\x1A") arenji = "";
		rem = X.SC(X.U16(extra+6,_LE)+1,0x100,"Shift_JIS");
		if(rem == "\x1A") rem = "";
		sOption(title);
		sOption(artist,"by: ");
		sOption(arenji,"mixed by: ");
		sOption(rem);
		sOption(fnames.join(", "),"fn: ")
	}
}


function isRobHubbardRH() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/RobHubbard/src/RobHubbard_v7.asm
	//the FX files not included -- TODO
	if(!X.c("6000.... 6000.... 6000.... 6000.... 6000....  41FA.... ........ 4E7541FA"))
		return false; //Check2
	a2 = 0x40; d4 = 8;
	do { //loop2
		if(X.U16(a2,_BE) == 0x2418) break;
		a2 += 2; d4--
	} while(d4);
	if(!d4) return false; //found2
	smp = d3 = X.U8(a2-1)+1;
	a2 = 0x36; d4 = 5;
	do { //loop3
		if(X.U16(a2,_BE) == 0x41FA) break;
		a2 += 2; d4--
	} while(d4);
	if(!d4) return false; //found3
	a2 += 2;
	a4 = a2;
	d4 = a2+X.U16(a2,_BE)+2; //minus a3 = moduleptr, 0 in our case
	if(X.U16(a4+2,_BE) == 0xD1FC) d4 += 0x40;
	a3 = d4-2; //again +moduleptr; then moduleptr[a6] = a3 (puts in sampleptr); a6 += 4;
	d5 = 0; a2 = a3;
	do { //loop4
		d1 = X.U32(a3,_BE);
		if(d1 > 0x10000) return false;
		d1 += 6;
		d5 += d1;
		a3 += d1;
		d3--
	} while(d3);
	if(X.U16(a3,_BE) != 0x4E71) return false; //at the EOF now
	a3 = 0; a0 = 130+a3;
	d0 = 10;
	do { //loop
		if(X.U16(a0,_BE) == 0x41EB) { a0 += 2; break }
		a0 += 2; d0--;
	} while(d0);
	if(!d0) return false; //else found
	d1 = 0;
	d2 = X.U16(a0,_BE);
	a3 += d2;
	do { //hop
		a3 += 18; d1++;
	} while(X.U16(a3,_BE));
	d2 = a2-a3;
	do { //petla
		b = X.U8(a3,_BE);
		if(b != 0x84)
			if(b != 0x85)
				{ a3++; d2--; if(d2 < 0) break; else continue }
		d0++; a3++; d2--
	} while(d2 >= 0);
	songsz = d4;
	sz = d4+d5;
	steps = d0;
	x = d1;
	return true;
}
if(!bDetected && X.isDeepScan() && isRobHubbardRH()) {
	sName = "Rob Hubbard's module (.RH)"; sVersion = "v1.4"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("smp:"+smp+" steps:"+steps+" songsz:"+songsz+" sz:"+outSz(sz))
	}
}


function isPuma() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/PumaTracker/src/PumaTracker_v2.asm
	//ref http://lclevy.free.fr/amiga/exotic/puma_str.txt
	// && https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_puma.cpp
	ord = X.U16(12,_BE)+1; if(ord > 256) return false;
	ptn = X.U16(0xE,_BE); if(!ptn || ptn > 128) return false;
	synsmp = X.U16(0x10,_BE); if(!isWithin(synsmp,1,32)) return false; //"number of sound data"
	loop = X.U16(0x12,_BE); 	bad = ''; if(loop >= ord) bad = bad.addIfNone('!badloop');
	if(charStat(readBytes(0,12),1).indexOf('allxsc') < 0) return false;
	for(p=0x50,i=mp=0; i < ord && p < X.Sz(); p+=14,i++) //verify orderlist integrity
		for(c=0; c < 4; c++) {
			if((pt=X.U8(p+c*3)) >= 0x80 || !isWithin(X.I8(p+c*3+2),-0x30,0x30)) return false;
			if(pt > mp) mp = pt
		}
	if(i < ord) return false; mp++;
	minsz = p+ptn*8+4+synsmp*16+4; if(minsz >= X.Sz()) return false;
	for(i=0; i < ptn; i++) { //test all patterns
		if(!X.c("'patt'",p)) return false; p += 4; row = 0;
		for(;row < 32; p += 4) {
			var d = readBytes(p,4); if(d[0]%2 || !isWithin(d[3],1,32-row)) return false; row += d[3]
		} }
	if(!X.c("'patt'",p)) return false; p += 4;
	smp = msmpp = mssz = evc = 0; plim = Math.min(X.Sz(),0x100000);
	for(i = 0; i < 10; i++) { //test waveform tables
		smpp = X.U32(0x14+i*4,_BE); if(!smpp) continue;
		ssz = X.U16(0x3C+i*2,_BE); if(ssz&1) ssz--; ssz <<= 1;
		if(smpp < minsz || smpp > 0x100000) return false;
		if(smpp > msmpp) { msmpp = smpp; mssz = ssz}
		smp++;
	}
	function checkScript(isVol) {
		var isFirst = true;
		while(p+4 < X.Sz()) {
			var d = readBytes(p,4); p += 4;
			if(isFirst && isVol && d[0] != 0xC0) return false;
			switch(d[0]) {
			case 0xA0: evc++; break;
			case 0xB0: evc++; return !(d[1] & 3);
			case 0xC0: if(!isVol) return false; evc++; break;
			case 0xD0:
				if(isVol) return false; evc++;
				if(d[1]&1)/*freezes playback*/ bad = bad.addIfNone('!oddpitch'); break;
			case 0xE0: evc++; return true;
			default:
				if(!isVol && decEncoding(d,CPAmiga) === 'inst') { p -= 4; return evc > 0 }
				return false;
			}
			isFirst = false;
		}
		return evc > 0
	}
	for(i=0; i < synsmp && p < X.Sz(); i++) {
		if(!X.c("'inst'",p)) return false; p += 4;
		if(!checkScript(true)) return false;
		if(!X.c("'insf'",p)) return false; p += 4;
		if(!checkScript(false)) return false;
	}
	if(!X.c("'inst'",p)) return false; p += 4
	if(p > X.Sz()) { sz = -1; bad = bad.addIfNone('!short'); return true }
	sz = Math.max(msmpp+mssz, p);
	return true;
}
if(!bDetected && isPuma()) {
	sName = "PumaTracker module (.PUMA)"; sVersion = "v1.1"; bDetected = 1;
	if(bad != '') sVersion = sVersion.appendS('/malformed'+bad,'/');
	if(X.isVerbose()) {
		sOption(decAnsi(0,0x0C,CPAmiga).trim());
		sOption('ord:'+ord+' ptn:'+mp+(mp!=ptn?'/'+ptn:'')+(smp?' wf.smp:'+smp:'')
			+' syn.smp:'+synsmp+(loop?' lp:'+loop:'')+' events:'+evc+' sz:'+outSz(sz))
	}
}


function isSeanConran() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SeanConran/src/Sean Conran_v3.asm
	//Check2
	if(X.c("0FFF0FE2") || X.c("10000FE2")) {
		if(!X.c("0FC40FA7 0F8B0F6E",4)) return false; //test1
		p = 0x124; //start searching for initp from here
		a0 = 8;
	}
	else
		if(X.c("0F1C0F0E 0F000EF2 0EE40ED6")) {
			p = 0x1C4;
			a0 = 0xA8; //skip to LastCheck
		}
		else return false;
	do { //InitPlayer.FindInit
		t = X.U32(p,_BE); if(!t) return false;
		p += 2; if(p > X.Sz()) return false
	} while([0x45FA,0x43F9,0x41F9].indexOf(t>>16) < 0);
	initp = p-2;
	//LastCheck
	a0 += 0x11C;
	for(d1 = 0; d1 < 0x80; d1++) { //CheckSFX
		if(X.c("7F7F7F7F",a0) || X.c("FFFF",a0)) return false;
		a0 += 2
	}
	// a0 = GetListData(0);
	x = IntAddress = 0; Twin = false;
	do { //Next
		t = X.U16(p,_BE);
		p += 2; if(p > X.Sz()) return false
	} while([0x7000,0x7200].indexOf(t) < 0);
	if(t === 0x7000) { //OneSub
		x = 1;
		// do { //FindSongs
		//     t = X.U16(p,_BE); p += 2; if(p > X.Sz()) return false
		// } while(t != 0x41FA);
		// FirstPos = p+X.I16(p,_BE);
	}
	else { //t=0x7200
		while(!x && p < X.Sz()) {
			if(X.c("00000000",p)) return false;
			if(X.c("21C80070",p)) { //OK4
				// t = p-4; IntAddress = t+X.I16(t,_BE);
				p += 2;
				if(X.c("00003B76",0x24C)) Twin = true;
			} else {
				if(X.c("43FA",p)) {
					t = p+2+X.I16(p+2,_BE); //OK0
					// songsp = t;
					x = (X.I16(t,_BE)-t) >> 3;
				}
				else if(X.c("43EA",p)) {
					t = X.I16(p+2,_BE); //OK2
					// songsp = t;
					x = (X.I16(t,_BE)-t) >> 3;
				}
				else p += 2
			}
		}
		if(!x || p >= X.Sz()) return false
	}
	// there are more checks... although they aren't really necessary here
	return true
}
if(!bDetected && X.isDeepScan() && isSeanConran()) {
	sName = "Sean Conran module (.SCR)"; sVersion = "v1.2"; bDetected = 1;
	if(Twin) sVersion += "/Megatwins"
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
	}
}


function isBenDaglish() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/BennDaglish/Benn Daglishv3.asm
	if(!X.c("6000....6000.... ....6000")) return false; //Check2
	d1 = X.I16(0x02,_BE); if(d1 <= 0 || (d1&1) || d1 > X.Sz()) return false;
	a0 = a1 = a3 = d1+2;
	d1 = X.I16(0x06,_BE); if(d1 <= 0 || (d1&1) || d1 > X.Sz()) return false;
	d1 = X.I16(0x0C,_BE); if(d1 <= 0 || (d1&1) || d1 > X.Sz()) return false;
	if(!X.c("3F006100",a1)  || !X.c("3D7C",a1+6)
	  || !X.c("41FA",a1+12)) return false;

	d0 = 0x7F;
	while(d0) //.l6
		if(X.c("D040D040 D04041FA",a0)) {
			a0 += 8;
			a1 = a0+X.I16(a0,_BE); //note the value's signed
			break;
		}
		else {
			a0 += 2; d0--; if(!d0 || a0 > X.Sz()) return false
		}
	x = 0;
	BD_L7: while(1) {
		d2 = 4;
		while(d2) {
			d0 = X.U16(a1,_BE) & 0xFC00;
			a1 += 2; if(a1 > X.Sz()) return false;
			if(d0) { // .not_subsong
				x--; //last subsong
				if(x < 0) x == 0;
				break BD_L7
			}
			else d2--
		}
		x++
	}
	x++;

	//size calc
	// incorrect on some files like 3d galax.bd or super cars.bd
	// but that's what EaglePlayer has, UADE debugger-tested
	d5 = d6 = 0;
	d0 = 0x80;
	while(d0) { //.l9
		t = X.U16(a0,_BE);
		a0 += 2; if(a0 > X.Sz()) return false;
		if(t === 0x41FA) break; else d0--
	} if(!d0) return false;
	smpi1 = a0+X.U16(a0,_BE); //.ok6 .. SampleInfo1
	a0 = 12+X.I16(12,_BE);
	d0 = 0x80; //.l10
	while(d0) { d0--;
		if(X.c("D040D040 41FA",a0)) break;
		else a0 += 2;
		if(a0 > X.Sz()) return false
	}
	if(d0) { //.ok7
		a0 += 6;
		d0 = X.I16(a0,_BE);
		if(d0&1) a0 = 0;
		else {
			a0 += d0;
			if(X.I16(a0,_BE)) a0 = 0
		}
	} else a0 = 0;
	smpi2 = a0; //.ok8

	a0 = smpi1;
	while(1) { //.com1
		d0 = X.U32(a0,_BE);
		a0 += 4; if(a0 > X.Sz()) return false;
		// Interestingly, the UAE emu sets flags on "move"!
		// So ↓ is a d0 check, would've probably been a0 on a real machine
		if(!d0) break;
		if(d0 >> 16) { a0 -= 4; break }
	}
	a0 -= 8; //.ok9
	smp1 = (a0 - smpi1) >> 2;
	d0 = smpi2;
	if(d0) {
		a0 = d0;
		while(1) { //.com2
			d0 = X.U32(a0,_BE);
			a0 += 4; if(a0 > X.Sz()) return false;
			if(!d0) break;
			if(d0 >> 16){ a0 += 4; break }
		}
		a0 -= 8; //.ok10
		smp2 = (a0 - smpi2) >> 2;
	}
	else smp2 = 0;
	//.one_smp_info

	// calc size #1
	d3 = smp1;
	a2 = smpi1+X.I32(smpi1,_BE); //rel.ptr to smpinfo1 table #0?
	d1 = X.I32(a2,_BE);
	d2 = X.U16(a2+8,_BE);
	i = d4 = 0;
	do { //.l11
		i++; if(i === smp1) break;
		a2 = smpi1+X.I32(smpi1+(i<<2),_BE);
		  if(a2 < 20 || a2 > X.Sz()) return false;
		d4 = X.I32(a2,_BE);
		if(d1 > d4) continue;
		if(d1 != d4) {
			if(d4 != X.I32(a2+4,_BE))
				d6 = X.U16(a2+10,_BE);
			else d6 = 0;
			d5 = X.U16(a2+8,_BE);
		} else {
			d5 = X.U16(a2+8,_BE);
			if(d2 > d5) continue;
		}
		d1 = d4; d2 = d5
	} while(i < smp1);

	//.ok11
	d2 += d6;
	sz = smpi1+d1+(d2<<1);

	// calc size #2
	if(smpi2) {
		d3 = smp2 << 2;
		a2 = smpi2+X.I32(smpi2,_BE);
		d1 = X.I32(a2,_BE);
		d2 = X.U16(a2+8,_BE);
		i = d6 = 0;
		do {
			i += 4; if(i === d3) break;
			a2 = smpi2+X.I32(smpi2+i,_BE);
			if(a2 < 20 || a2 > X.Sz()) return false;
			d4 = X.I32(a2,_BE);
			if(d1 > d4) continue;
			if(d1 != d4) {
				if(d4 != X.I32(a2+4,_BE))
					d6 = X.U16(a2+10,_BE);
				else d6 = 0;
				d5 = X.U16(a2+8,_BE);
			} else {
				d5 = X.U16(a2+8,_BE);
				if(d2 > d5) continue;
			}
			d1 = d4; d2 = d5
		} while(i < d3);

		d2 += d6;
		a1 = smpi1 + d1 + (d2<<1);
		if(sz < a1) sz = a1;
	}
	return true
}
if(!bDetected && X.isDeepScan() && isBenDaglish()) {
	sName = "Ben Daglish's module (.BD)"; sVersion = "v1.2"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
			sOption("smp:"+smp1+"+"+smp2+" sz:"+outSz(sz));
	}
}


function isAndrewParton() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Andrew Parton/SRC_AndrewParton/Andrew Parton_v2.asm
	if(!X.c("'BANK'")) return false;
	for(i=0; i < 20; i++)
		if(X.U32(4+(i<<2),_BE) >= 0x200000) return false;
	for(i=0; i < 40; i++)
		if(X.U32(0x54+(i<<2),_BE) >= 0x10000) return false;
	p = 0x54; q = 4; smp = 0;
	smpt = [];
	s = 0x1E4;
	for(i=0; i < 20; i++) {
		t = X.U32(q,_BE); q += 4;
		if(t) {
			smpt.push(X.SA(s,16).trim());
			smp++; s += 16 + X.U32(p,_BE);
		}
		p += 4
	}
	smpsz = s;
	bad = false;
	while(s < X.Sz()) {
		t = X.U8(s); s += 1;
		if(t === 0xFF) break;
	}
	if(s > X.Sz())
		if(X.isHeuristicScan()) bad = true;
		else return false;
	if(X.U8(s) === 0x2F) { sz = s+1; songsz = sz-smpsz }
	else {sz = s; bad = true }
	return true
}
if(!bDetected && X.isDeepScan() && isAndrewParton()) {
	sName = "Andrew Parton's module (.BYE)"; sVersion = "v1.2"; bDetected = 1;
	if(bad) sVersion += "/malformed";
	if(X.isVerbose())
		sOption("smp:"+smp+" songsz:"+songsz+" smpsz:"+smpsz+" sz:"+outSz(sz))
}


function isAshleyHogg() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Ashley Hogg/SRC_AshleyHogg/Ashley Hogg_v1.asm
	p = 0;
	for(i=0; i < 4; i++) {
		if(!X.c("6000",p)) return false; p += 2;
		d2 = X.I16(p,_BE); if(d2 <= 0 || (d2&1)) return false; p += 2
	}
	if(X.c("6000",p)) { //new check
		p += 2;
		d2 = X.I16(p,_BE); if(d2 <= 0 || (d2&1)) return false; p += 2;
		if(!X.c("6000",p)) return false; p += 2;
		d2 = X.I16(p,_BE); if(d2 <= 0 || (d2&1)) return false;
		p += d2;
		if(!X.c("48E7FFFE 6100",p)) return false; p += 6;
		p += X.I16(p,_BE);
		if(!X.c("4DF9 00DFF000",p)) return false;
		sV = "new";
	} else //old check
		if(X.c("303C0000 662233C0",p))
			sV = "old";
		else return false;

	if(sV === "new") { //new format
		a2 = special = 0x1C;
		a0 = X.I16(2,_BE);
		title = X.SA(special,X.fStr(special,a0-a2,"  ")-a2).trim();
		//skipped a cycle that replaces twin spaces with enters in the info
		do {
			t = X.U16(a0,_BE); a0 += 2;
		} while(t != 0x45FA || a0 > X.Sz());
		a2 = a0; a0 += X.I16(a0,_BE);
		if(a0 > X.Sz()-2)
			if(X.isHeuristicScan()) bad = true; else return false;
		d0 = X.I16(a0,_BE);
		x = d0 >> 2;
		a0 += d0 + X.I16(a0-2,_BE);
		do {
			if(a0 > X.Sz()-2)
				if(X.isHeuristicScan()) bad = true; else return false;
			t = X.I16(a0,_BE); a0 += 2;
		} while(t != 0x03F2);
		sz = a0;
		info = 'title: "'+title+'" sz:'+outSz(sz)
	}
	else { //old format
		special = 0;
		a2 = 16;
		a0 = 2; a0 += X.U16(a0,_BE);
		do { t = X.I16(a0,_BE); a0 += 2; } while(t != 0x1970 || a0 > X.Sz());
		a1 = a0-4; a1 += X.I16(a1,_BE);
		do { t = X.I16(a0,_BE); a0 += 2; } while(t != 0x41FA || a0 > X.Sz());
		a0 += X.I16(a0,_BE);
		x = (a1-a0) >> 4;
		do { t = X.U16(a2,_BE); a2 += 2; } while(t != 0xC2FC || a0 > X.Sz());
		a2 += 4; smpip = a2+X.U16(a2,_BE);
		do { t = X.U16(a2,_BE); a2 += 2; } while(t != 0x47FA || a0 > X.Sz());
		a0 = a2; a2 += X.U16(a2,_BE);
		songsz = a2;
		do { t = X.U16(a0,_BE); a0 += 2; } while(t != 0x49FA || a0 > X.Sz());
		do { t = X.U16(a0,_BE); a0 += 2; } while(t != 0x49FA || a0 > X.Sz());
		a0 += X.U16(a0,_BE);
		smp = (a0-smpip)/0x2C;
		d1 = 0; a1 = smpip;
		a3 = 0x54F3; //value copied from a debugging session ¯\(。⊿°)/¯
		do { //NextInfo
			d2 = X.I32(a1+0x20,_BE);
			if(d2 >= 0 && d1 <= d2) { d1 = d2; a3 = a1 }
			a1 += 0x2C
		} while(a0 > a1);
		d0 = X.U16(a3+0x28,_BE);
		d1 += d0+d0;
		smpsz = d1;
		sz = songsz+smpsz;
		info = "smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz)
	}
	return true
}
if(!bDetected && X.isDeepScan() && isAshleyHogg()) {
	sName = "Ashley Hogg's module (.ASH)"; sVersion = sV; bDetected = 1;
	if(bad) sVersion += "/malformed";
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption(info)
	 }
}


function isCinemaware() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Cinemaware/Cinemaware.asm
	if(!X.c("'IBLK'")) return false;
	d2 = X.U8(4);
	if(!d2 || d2 > 0x80) return false;
	a1 = 0x16+0x8A*d2;
	aseq = X.fSig(a1,0x104,"'ASEQ'");
	if(aseq < 0 || (aseq&1)) return false;
	p = aseq+4; sz = 0; bad = false;
	do {
		sz += 5;
		if(sz+p > X.Sz())
			if(X.isHeuristicScan()) { bad = true; break }
			else return false;
	} while(!X.c("102F00",sz+p-3));
	sz += p; ord = Math.floor((sz-p)/100);
	return true
}
if(!bDetected && X.isDeepScan() && isCinemaware()) {
	sName = "Cinemaware module (.CIN)"; bDetected = 1;
	if(bad) sVersion = "malformed";
	if(X.isVerbose()) {
		sOption("ord:"+ord+" sz:"+outSz(sz)+ " (sans ext.samples)")
	}
}


function isCustomMade() {
	//ref https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/CustomMade/CustomMade_v1.asm
	if(X.Sz() < 3000) return false;
	if(X.c("6000.... 6000") || X.c("4EF9.... ....4EF9")
	  || X.c("4EB9.... ....4EF9")) {
		a1 = 8; var found = false;
		do {
			if(X.c("42280030 42280031 42280032",a1)) found = true;
			a1 += 2
		} while(!found && a1 < 0x198);
	}
	if(!found) return false;
	else if(!X.isVerbose()) return true;
	//otherwise let's dig in for smp, synthsmp, and subsongs!
	a0 = d5 = 0; d7 = 0x800; org = 0; smpi = 0; songst = 0;
	bad = false;
	L = "lp"; //it's even more mind-boggling if rewritten in if..else
	i_lp: while(d7) {
		switch(L) {
		case "lp": // @d366
			if(!X.c("D04149FA",a0)) { L = "2"; break }
			d0 = X.I16(a0+4,_BE); a1 = a0+d0+4;
			Table = a1; L = "lp_c"; break;
		case "2": // play / ptntab  @d37e
			if(X.c("48E7F8FC",a0)) { L = "2.0"; break }
			if(!X.c("48E7FFFE",a0)) { L = "3"; break }
		case "2.0": // @d38e
			d6 = a3;
			if(!d6 || org) d6 = 20;
			else {
				a1 = a0-a3; /*play-oldplay*/ d5 -= a1; /*nowadr-dx*/ org = d5
			}
			while(d6 >= 0) { // 2.lp
				t = X.U16(a0,_BE); a0 += 2;
				if(t === 0x41FA) {
					d0 = X.I16(a0,_BE); // 2.2: @d3ae
					songst = a1 = a0+d0; break // from 2.lp
				}
				d6--
			}
			L = "lp_c"; break;
		case "3": // @d3bc
			if(!X.c("E94847F0",a0)) { L = "4"; break } //ptoff
			d1 = X.I16(a0+4,_BE); L = "lp_c"; break;
		case "4": // @ d3cc
			if(!X.c("00BFD500",a0)) { L = "6"; break} //oldplay
			tmrval = (X.U8(a0-1) << 8) + X.U8(a0+7);
//_log("tmrval:"+Hex(tmrval));
			//tmrval = d0;
			if(X.c("4E71",a0+20)) { L = "lp_c"; break }
			if(X.c("21FC",a0+28)) a3 = X.U32(a0+30,_BE);
			else if(X.c("C000",a0+32)) a3 = X.U32(a0+32,_BE);
			else a3 = X.U32(a0+22,_BE);
			L = "lp_c"; break;
		case "6": // @ d410
			if(!X.c("42A8001C",a0)) { L = "7"; break } //smpinfo
			d0 = X.I16(a0+6,_BE); smpi = a1 = a0+d0+6;
			L = "lp_c"; break;
		case "7":
			if(X.c("E44843FA",a0)) { L = "7.1"; break } //oldvoc1
			if(!X.c("E448207B",a0)) { L = "8"; break } //oldvoc1
		case "7.1": // @d438
			d6 = a4; if(!d6) { L = "lp_c"; break }
			d0 = X.I16(a0+4,_BE); // @d43c
			a4 -= X.I32(a0+d0+4,_BE); //voc0-oldvoc0
			if(!org) { d5 -= a4; org = d5 }
			L = "lp_c"; break;
		case "8": // @d44e
			if(X.c("48E700F0",a0)) { //voc0
				d0 = X.I16(a0+6,_BE); // @d456
				a4 = a0+d0+6;
			}
		case "lp_c": // @d45e
			a0 += 2; if(X.c("1AC01940",a0)) break; // -> ex
			d7--; L = "lp"
		}
	}
	songst += d1; // ex @d46c
	a1 = songst;
	x = 0;
	while(1) { //Find
		if(X.c("DFF0A0",a1+1)) break;
		if(a1 > X.Sz()) { bad = true; return true }
		x++; a1 += 16
	}

	a1 = smpi; smp = synsmp = 0;
	while(!X.U32(a1+28,_BE)) {
		a2 = X.U32(a1,_BE)-org;
		a24 = X.U16(a2+4,_BE);
		if(a24 === 2 || a24 === 16) smp++; else synsmp++;
		a1 += 32
	}
	return true
}

if(!bDetected && X.isDeepScan() && isCustomMade()) {
	sName = "Ivo Zoer & Ron Klaren's CustomMade module (.CM)"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("smp+syn:"+smp+"+"+synsmp+" timer:"+tmrval)
		//" Origin:"+Hex(org)+" SongsTab:"+Hex(songst)+" SamplesInfo:"+Hex(smpi)+" Table:"+Hex(Table))
	}
}


function isDaveLoweNew() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/DaveLoweNew/src/Dave Lowe New.s
	// ref Dave Lowe New_v2.asm
	a1 = 0; bad = ""; d1 = X.I16(0,_BE);
	if(d1 === 4) { if(!X.U32(0x18,_BE))  a1 += 4 }
	else if(d1 === 8)  a1 += 4;
	else return false;
	a1 += 4; a2 = a1;
	for(d2=0; d2 < 4; d2++) { //FirstCheck
		if(X.I16(a1,_BE)) return false; a1 += 2;
		d1 = X.I16(a1,_BE); a1 += 2; if(d1 <= 0 || (d1&1)) return false;
	}
	for(d0=0; d0 < 4; d0++) { //SecondCheck
		a1 = d1 = X.I32(a2,_BE); a2 += 4;
		d2 = X.I32(a1,_BE);
		if(X.I16(a1,_BE)) return false; a1 += 2;
		d1 = X.I16(a1,_BE); a1 += 2;
		if(d1 <= 0 || (d1&1)) return false;
		a1 = d2;
		t = X.I16(a1,_BE);
		if(t === 0x30) { a1 += 2; t = X.I16(a1,_BE); }
		if(t === 12) {
			a1 += 6;
			t = X.I16(a1,_BE); a1 += 2;
			if(t === 4) break;
		}
	}
	d2 = X.I32(a1,_BE); if(X.I16(d2,_BE) != 1) return false;
	a1 = a2 = d2 >> 16; if(a1) return false;
	fmt = 1;
	if(!X.U32(0x18,_BE)) {
		fmt = 0;
		x = (X.I16(2,_BE)-8) >> 5
	}
	else {
		x = 0; p = X.U16(0,_BE);
		while(p < X.Sz()) {
			t = X.I16(p,_BE); p += 2; if(t) break;
			t = X.I16(p,_BE); p += 2; if(!t || (t&1)) break;
			x++
		}
		x >>= 2
	}
	bad = 0; sz = 0; a1 = d2;
	while(a1 < X.Sz()) {
		a2 = a1; a1 -= 14;
		if(X.I16(a1,_BE) != 1) break
	}
	//smpi = a2;
	t = X.I16(a2-2,_BE);
	if(t != 8 && t != 0) a2 += 14;
	sz = 0; smp = 0;
	while(a2 < X.Sz()) { //CheckInfo
		t = X.I16(a2,_BE); if(t != 1) break; else smp++;
		a2 += 2;
		d2 = X.I32(a2,_BE); a2 += 6; t = X.I32(a2,_BE);
		if(t > d2) d2 = t;
		if(d2 > sz) sz = d2;
		a2 += 6
	}
	if(sz > X.Sz()) bad = bad.addIfNone("!short");
	if(a2 >= X.Sz()) { bad = bad.addIfNone("!badsmpinfo"); return true }
	sz += 0xFE;
	return true
}
if(!bDetected && X.isDeepScan() && isDaveLoweNew()) {
	sName = 'Dave "Uncle Art" Lowe New module (.DLN)'; bDetected = 1;
	sVersion = "f."+fmt; if(bad != "") sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("smp:"+smp+" sz:"+outSz(sz));
	}
}


function isDesire() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Desire/src/Desire_v1.asm
	if(X.Sz() <= 2500) return false;
	a2 = 0; a0 = 8;
	for(d1=0; d1 < 4; d1++) { if(!X.c("00010101",a0)) return false; a0 += 16 }
	a1 = 400+a0;
	do {
		if(a0 === a1) return false;
		t = X.I16(a0,_BE); a0 += 2;
	} while(t != 0x49FA);
	a0 += 2;
	if(!X.c("45F900DF F000357C 00FF009E 41FA",a0)) return false; a0 += 14;
	a0 += X.I16(a0,_BE);
	if(a0 != a2) return false;
	a1 = 0xF0;
	do { t = X.U16(a1,_BE); a1 += 2
	} while(t != 0x49FA && a1 < X.Sz()); a1 -=2; //FindPlay
	do { t = X.U16(a1,_BE); a1 += 2 //FindSongs
	} while(t != 0x47FA && a1 < X.Sz());
	a2 = a1+20; a1 += X.I16(a1,_BE); x = 0; a1++;
	do { //CheckSongs
		d1 = X.U8(a1); a1++; if(d1) x++; else break;
	} while(d1 != X.U8(a1) && a1 < X.Sz());
	do { t = X.U16(a2,_BE); a2 += 2 //Find1
	} while(t != 0xE341 && a2 < X.Sz());
	do { t = X.U16(a2,_BE); a2 += 2 //Find2
	} while(t != 0x47FA && a2 < X.Sz());
	smptsz = a1 = a2+X.I16(a2,_BE);
	do { t = X.U16(a2,_BE); a2 += 2 //Find3
	} while(t != 0x47FA && a2 < X.Sz());
	a3 = a2; a2 += X.I16(a2,_BE); smpp = a2;
	a0 = a2; d4 = a1-a2; a2 = a1+d4; smp = 0;
	do { t = X.I16(a1,_BE); a1 += 2; if(t) smp++; } while(a1 < a2); //NextS,NoSamp
	do { t = X.U16(a3,_BE); a3 += 2 //Find4
	} while(t != 0x47FA && a3 < X.Sz());
	a3 += 2;
	d6 = X.U16(a3,_BE) & 0x0E00;
	if(d6) d6 >>= 9; else d6 = 8;
	ruch = d6; //Skip8
	d1 = X.I16(a0,_BE); songsz = d2 = d1 << d6;
	t= d1;d1=d2;d2 =t; d5 = 0;
	do { //NextOff
		d0 = X.I16(a0,_BE); a0 += 2;
		if(d0 > d2) { d2 = d0; d5 = X.U16(a0+d4-2,_BE) }
	} while(a0 < smptsz);
	d2 <<= d6; d5 <<= 1; d2 += d5;
	sz = d2; smpsz = sz-songsz
	return true
}
if(!bDetected && X.isDeepScan() && isDesire()) {
	sName = "Desire player module (.DSR)"; bDetected = 1;
	sVersion = "v1.0";
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz))
		if(sz < X.Sz()) sVersion += "/malformed!short"
	}
}


function isDavidWhittaker() {
	// from https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/DavidWhittaker/DavidWhittakerWorker.cs
	// <space>sfx.dw not detected, -sfx.dw may be detected sometimes
	if(X.Sz() < 2048) return false;
	const sl = Math.min(X.Sz(), 0x4000); //search buffer length
	//and now for the NP routine ExtractInfoFromInitFunction
	sz = x = ch = smp = sqwfsmp = 0;
	for(i = 0; i < sl; i += 2) if(X.c("47FA",i) && (X.U8(i+2)&0xF0) == 0xF0) break;
	if(i >= sl-6) return false;
	sofs = i+2+X.I16(i+2,_BE);
	for(; i < sl; i += 2) if(X.c("6100",i)) break; if(i >= sl-6) return false;
	sinit = i; ssmpsinit = i;
//_log("ssmpsinit:"+Hex(ssmpsinit))
	if(X.c("6100",i+4)) ssmpsinit = i+6+X.I16(i+6,_BE);
	for(i = ssmpsinit; i < sl; i += 2) if(X.c("4A2B",i)) break;
	if(i >= sl-36) return false;
	if(!X.c("66",i+4)) {
		for(i = sinit; i < sl; i += 2) if(X.c("41EB",i)) break;
		if(i >= sl-36) return false;
		smpdp = sofs+X.I16(i+2,_BE);
		i += 4; if(X.U8(i+4) != 0x72) return false;
		smp = X.U8(i+5)+1;
		for(; i < sl-4; i += 2) if(X.c("41EB....E34F",i)) break;
		if(i >= sl-4) return false;
		chvolp = sofs+X.I16(i+2,_BE);
//_log("@"+Hex(i)+" smpdatap:"+Hex(smpdp))
		//extract subsong info:
		for(i = sinit; i < sl; i += 2) if(X.c("41EB....17",i)) break;
		if(i >= sl-4) return false;
		sstp = sofs+X.I16(i+2,_BE);
		is32bp = true; old = true;
	}
	else {
		old = false;
		if(!X.U8(i+5)) i += 2;
		if(!X.c("41FA",i+6)) return false;
		smpdp = X.I16(i+8,_BE)+i+8;
		i += 10; if(X.c("2748....D0FC",i)) {
			smpdp += X.U16(i+6,_BE);
			i += 12; if(!X.c("D0FC",i)) return false;
			smpdp += X.U16(i+2,_BE); i += 4
		}
//_log("@"+Hex(i)+" smpdatap:"+Hex(smpdp))
		if(!X.c("4BFA....72",i)) return false;
		smpip = X.I16(i+2,_BE)+i+2;
		smp = X.U8(i+5)+1;
		for(i = sinit; i < sl-4; i += 2) if(X.c("41FA",i) && !X.c("4B",i+4)) break;
		if(i >= sl-4) return false;
		if(!X.c("1230",i+4) && !X.c("3770",i+4)) return false;
		sstp = i+2+X.I16(i+2,_BE);
		i += 4; for(; i < sl-8; i += 2) if(X.c("41FA",i) && !X.c("23",i+4)) break;
		if(i >= sl-8) return false;
		if(X.c("2070",i+4)) is32bp = true;
		else if(X.c("3070",i+4)) is32bp = false;
		else return false;
	}
//_log("sstp:"+Hex(sstp))
	//load sample info, get size:
	p = smpdp;
	for(i = 0; i < smp; i++) {
		ssz = X.U32(p,_BE); p += 6;
//_log("smpd["+i+"] @"+Hex(p-6)+" sz:"+outSz(ssz));
		if(p+ssz > X.Sz()) { bad = bad.addIfNone("!short"); return true }
		p += ssz; sz = p;
	}
	for(i = 0; i < sl; i += 2) if(X.c("47FA",i)) {
		if(i >= sl-10) return false;
		if (X.c("4A2B....67",i+4)) {
			if(X.c("33FC",i+10) || X.c("177C",i+10) || X.c("08B9",i+10))
				continue;
			break
		}
	}
	splay = i;
//_log("splay:"+Hex(i))
	ch = 0;
	for(i = splay; i < splay + 200; i += 2) if(X.U8(i) == 0x7E) {
		ch = X.U8(i+1);
		if(!ch) {
			for(; i < splay + 500; i += 2)
				if(X.c("BE7C",i) || X.c("BE3C",i)) { ch = X.U8(i+3); break }
		} else ch++;
		break
	}
	if(!ch) return false;
//_log("ch:"+ch)
	//square waveforms?
	for(i = splay; i < splay+100; i += 2)
		if(X.c("207A.... 303A",i)) {
			sqwfsmp = (X.I16(i+2,_BE)+i+2 - smpip)/12;
			if(!X.c("31BC",i+14) && !X.c("11BC",i+14)) return false;
			if((X.U8(i+20) & 0xF0) != 0x50 || X.U8(i+21) != 0x6B) return false
			if(!X.c("0C6B",i+24)) return false;
			if(!X.c("31BC",i+38) && !X.c("11BC",i+38)) return false;
			if(!X.c("0C6B",i+48)) return false;
		}
	//find diff.player parts, just the sanity checks
	if(old) {
		for(i = splay; i < sl; i += 2) if(X.c("7000",i)) break;
		if(X.U8(i+2) != 0x10) return false;
		var rdtrkcmdp = i
	} else {
		for(i = splay; i < sl; i += 2) if(X.c("5368",i)) break;
		if(i >= sl-16) return false; if(X.U8(i+4) != 0x67) return false;
		var rdtrkcmdp = X.U8(i+5)+i+6;
		if(X.U8(i+12) != 0x66) return false;
		for(i = rdtrkcmdp; i < sl; i += 2) if(X.c("45FA.... 322D",i)) break;
		if( i >= sl-6) return false;
		i = X.U16(i+2,_BE)+i+2;
		if(i >= sl-72*2) return false;
		if(!X.c("1000",i) && !X.c("2000",i)) return false
	}
	//checks for transposes:
	for(i = rdtrkcmdp; i < sl; i += 2) if(X.c("6B00",i)) break; if(i >= sl-6) return false;
	var endlyc = endlym = false; //:667
	for(i = splay; i < splay + 100; i += 2) if(X.c("103A",i)) {
		endlyc = true; if(X.c("C0FC",i+6)) endlym = true; break }
//_log("@"+Hex(i)+" dlyc?"+endlyc+" dlym?"+endlym)
	x = 0; p = sstp; minpp = 0xFFFFFFFF; //:966
	sngspd = dlyspd = 0; bad = "";
	while(p+8 < minpp) {
		if(endlyc) { sngspd = X.U8(p++); dlyspd = X.U8(p++) }
		else { sngspd = X.U16(p,_BE); p += 2; dlyspd = 0 }
		if(sngspd > 0xFF) break;
		for(i = 0; i < ch; i++) {
			if(is32bp) { t = X.U32(p,_BE); p += 4 }
			else { t = X.U16(p,_BE); p += 2 }
			if(minpp > sofs+t) minpp = sofs+t;
//_log(x+" ordp:"+Hex(t))
		}
		if(p > X.Sz()) { bad = bad.addIfNone("!short"); break }
		x++
	}
	return true
}
if(!bDetected && X.isDeepScan() && isDavidWhittaker()) {
	sName = "David Whittaker's module (.DW)";
	if(old) sVersion = "old"; else sVersion = "new"; bDetected = 1;
	if(bad != "") sVersion += "/malformed"+bad;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("ch:"+ch+" smp:"+smp+(sqwfsmp ? " sqwf.smp:"+sqwfsmp : "")
			+" spd:"+sngspd+" sz:"+outSz(sz))
	}
}


function isFashionTracker() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/FashionTracker-v1.0/FashionTracker.asm
	if(!X.c("13FC0040 ........ 4E710439 0001")
	  || !X.c("66F44E75 48E7FFFE",18)) return false;
	a2 = 0; bad = "";
	while(!X.c("2379",a2) && a2 <= 1000) a2 += 2; //FindOri
	if(a2 > 1000) //corrupt module
		if(X.isHeuristicScan()) bad = "!badorigin"; else return false;
	org = X.U32(a2-4,_BE);
	a2 = 0; d1 = 1;
	do { //GetValues:
		// a3 = smplen    a3+4 = smpp      a3+8 = smplp
		// a3+12 = smpn+vol      a3+16 = ptn     a3+20 = songpositions
		if(X.c("23D1",a2)) { // L???
			smplen = X.I32(a2+8,_BE)-org;
			smpp = X.I32(a2-6,_BE)-org;
			smplp = X.I32(a2+24,_BE)-org;
			smpnvol = X.I32(a2-30,_BE)-org;
			d1 -= 6;
		}
		else if(X.c("C0FC0400",a2)) { //NoL
			ptn = X.I32(a2+6,_BE)-org;
			d1 += 3;
		}
		else if(X.c("0C790400",a2)) { //NoPa
			songpos = X.I32(a2+12,_BE)-org;
			len = X.I32(a2+34,_BE);
			d1 += 2;
		}
		a2 += 2 //NextW
	} while(a2 < 1000);
	if(d1)
		if(X.isHeuristicScan()) bad = bad.addIfNone("!badval"); else return false;
	a1 = smpp; songsz = X.I32(smpp,_BE)-org;
	d0 = X.I32(a1-4,_BE) << 1;
	a2 = smplen; d1 = a1-a2; a1 += d1; d1 >>= 2; smp = d1;
	d1 = X.I32(a1-4,_BE)-org;
	sz = d0+d1; smpsz = sz-songsz;
	a1 = songpos; a2 = a1+len; ptn = 0;
	do { t = X.U8(a1); if(ptn < t) ptn = t; a1++ } while(a1 < a2); //GetPat
	ptn++;
	d1 = (d0-1)*6; d0 = d1; d1 *= 0x376B;
	dur = Math.floor(d1*64/(709379-3));
	return true
}
if(!bDetected && X.isDeepScan() && isFashionTracker()) {
	sName = "Fashion Tracker module (.EX)"; bDetected = 1;
	sVersion = "v1.0"; if(bad) sVersion += "/malformed."+bad;
	if(X.isVerbose()) {
		sOption("ord:"+len+" ptn:"+ptn+" smp:"+smp+" songsz:"+songsz+" sz:"+outSz(sz))
	}
}

function isSMUS() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SonixMusicDriver/Sonix Music Driver_v1.asm
	a0 = a1 = 0; title = "";
	if(X.c("'FORM'")) {
		if(!X.c("'SMUSSHDR'",8) || !X.U8(0x17) || !X.c("'NAME'",0x18))
			return false;
		d1 = X.U32(0x1C,_BE); if(d1 >> 31) return false; t_ = d1;
		d1 = (d1+1) & 0xFFFFFFFE; a1 = 0x20+d1;
		if(!X.c("'SNX1'",a1)) return false; a1 += 4;
		d1 = X.U32(a1,_BE); if(d1 >> 31) return false; a1 += 4;
		d1 = (d1+1) & 0xFFFFFFFE; a1 += d1;
		insinfp = a1;
		realsmp = []; ins = 0;
		do { //MoreIns
			if(!X.c("'INS1'",a1)) return false; a1 += 4;
			d1 = X.U32(a1,_BE); if(d1 >> 31) return false; a1 += 4;
			d1 = (d1+1) & 0xFFFFFFFE; if((d1 >> 24) > 0x3F) return false;
			if(X.U8(a1+1)) return false;
			realsmp.push(X.U8(a1));
			a1 += d1; ins++
		} while(!X.c("'TRAK'",a1));
		sz = a1; trk = 0; while(sz < X.Sz()) {
			hkhd = X.SA(sz,4); hksz = X.U32(sz+4,_BE);
			if(hkhd != "TRAK") break; trk++; sz += 8+hksz }
_log("trk:"+trk+" sz:"+outSz(sz))
		title = X.SA(0x20,t_);
		fmt = 2; ext = "smus"
	}
	else {
		if(!(X.U16(0,_BE) & 0xF0)) {
			d3 = 20; d1 = 4;
			while(d1) {
				d2 = X.I32(a0,_BE); if(d2 <= 0 || (d2&1)) return false;
				a0 += 4; d3 += d2; d1--
			}
			if(d3 >= X.Sz()) return false;
			a0 += 4; d1 = 4;
			while(d1) {
				t = X.U16(a0,_BE); if(!(t & 8000)) return false;
				if(t != 0xFFFF) if((t >>16) > 0x84) return false;
				a0 += X.I32(a1,_BE); a1 += 4; d1--
			}
			if(!X.U8(a0)) return false;
			fmt = 0; ext = "snx"
		}
		else { //TinyCheck
			if(X.Sz() < 333) return false;
			a1 = 0x30; if(X.I32(a1,_BE) != 0x140) return false;
			a1 += 4; d1 = 3;
			while(d1) { //NextPos2
				d2 = X.I32(a1,_BE); a1 += 4;
				if(d2 <= 0 || (d2&1) || d2 > X.Sz()) return false;
				if(X.I16(d2,_BE) != -1) {
					if(X.I32(d2,_BE) || X.I16(d2+4,_BE)) return false;
					t = X.U8(d2+6,_BE); if(!(t&0x80) || t > 0x82) return false
				}
				d1--
			}
			fmt = 1; ext = "tiny"
		}
	}
	//TODO CalcSize
	return true
}
if(!bDetected && X.isDeepScan() && isSMUS()) {
	sName = " Sonix Music Driver module (."+ext+")"; sVersion = "f."+fmt; bDetected = 1;
	if(X.isVerbose()) {
		if(title != "") sOption(title)
	}
}

function isAMBK() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/ambk/AMOS.s
	if(X.c("'AmBk'")) a0 = p = 4; else if(X.c("C0")) a0 = p = 0; else return false;
	//if(!X.c("80",p+4)) return false;
_log(Hex(X.U32(p+4,_BE)&((1<<28)-1)));
	if(!X.c("'Music   '",p+8)) return false;
	p += 0x14; p += X.I32(p,_BE);
	if(X.I16(p,_BE) != 6) return false; p += 2; r = p;
	p += X.I16(p+2,_BE)-2;
	p = X.I16(p,_BE); if(p != -2) if(p) return false;

	title = X.SA(r+0x0C,0x20);
	p = r+0x1E+X.I16(r+0x1E,_BE); ord = -1;
	do { ord++; d1 = X.I16(p,_BE); p += 2; } while(p < X.Sz() && d1 >= 0);
	if(a0) d3 = 4+(X.I32(a0+4,_BE) & 0xFFFFFF);
	else d3 = X.I32(a0,_BE) & 0xFFFFFF; sz = d3+8;
	bad = ""; if(sz > X.Sz()) bad = bad.addIfNone("!short");
	p = r+X.I16(r+6,_BE);
	ptn = 0;
	while(p < X.Sz()) { //.such
		d1 = X.I16(p,_BE); p += 2; if(d1 < 0) break;
		if(d1 > ptn) ptn = d1
	}
	ptn++;
	if(!X.I16(p,_BE)) p += 2;
	if(X.I16(p,_BE) != ptn) bad = bad.addIfNone("!badptn");
	q = a0+0x20; smp = d1 = X.I16(q,_BE); q += 0x10;
	d7 = 0;
	while(d1 && q < X.Sz()) {
		d2 = X.I16(q,_BE);
		if(!d2) d2 = X.I16(q-6,_BE);
		d7 += d2 << 1; q += 0x20; d1--
	}
	smpsz = d7;
	songsz = sz-d7;
	return true
}
if(!bDetected && X.isDeepScan() && isAMBK()) {
	sName = " AMOS Music Bank module (.ABK)"; bDetected = 1;
	if(bad != "") sVersion = "malformed"+bad;
	if(X.isVerbose()) {
		sOption(title);
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+/*" smpsz:"+Hex(smpsz)+" songsz:"+Hex(songsz)+*/" sz:"+outSz(sz))
	}
}

function isTuneFish4() {
	// taken from tunefish src\tunefish4player\tf4player.cpp
	ins = X.I16(0,_LE); if(ins > 32) return false;
	if(!X.c("'INST'",4+ins*2)) return false; t = 8+ins*114;
	if(!X.c("'SONG'",t)) return false;
	t += 4; songsz = 0; for(i=0; i<ins; i++) songsz += X.U16(4+i*2,_LE);
	if(!X.c("'ENDS'",t+songsz*4)) return false;
	sz = t+songsz*4+4;
	return true;
}
if(!bDetected && X.isDeepScan() && isTuneFish4()) {
	sName = "TuneFish module (.TF4)"; sVersion  = "v4"; bDetected = 1;
	if(X.isVerbose())
		sOption("ins:"+ins+" tempo:"+X.U16(2,_LE)+" songsz:"+Hex(songsz)+" sz:"+outSz(sz))
}

function isJH() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Jochen_Hippel_ST/Jochen Hippel_v1.asm
	a0 = 0; tfmx = 0;
	if(X.c("'MCMD'")) { fmt = -1; tfmx = a0 }
	else if(X.c("48E7FFFE")) {
		fmt = -1; a0 += 4; d1 = 0;
		if(!X.c("61",a0)) return false;
		d1 = X.U8(a0+1); if(!d1 || (d1&1)) return false; a0 += d1+2;
		if(!X.c("2F006100",a0)) return false; a0 += 4+X.I16(a0+4,_BE);
		if(!X.c("41FA",a0)) return false;
		if(!X.c("41FA",a0+0x12)) return false;
		a0 += 0x14; a0 += X.I16(a0,_BE);
		if(!X.c("'MCMD'",a0)) return false;
		tfmx = a0
	} else { // NoMCMD
		fmt = 0;
		if(!X.c("60",a0++)) return false;
		d1 = X.U8(a0++);
		if(!d1) {
			fmt = 1; d1 = X.I16(a0,_BE); if(d1 < 0 || (d1&1)) return false;
			if(!X.c("6000",a0+2)) return false;
			a0 += d1;
			if(!X.c("48E7FFFE",a0)) return false; a0 += 4;
		} else { // No3
			if(d1&1) return false; a0 += d1;
			if(!X.c("48E7FFFE",a0)) return false; a0 += 4;
			if(!X.c("6100",a0)) return false; a0 += 2; //astaroth.hip, prince.hip fail this one and have more jumps, iono
			a0 += X.I16(a0,_BE);
			if(!X.c("2F006100",a0)) return false; a0 += 4;
			a0 += X.I16(a0,_BE);
			if(!X.c("41FA",a0)) return false; a0 += 0x14;
		} // Later:
		t = X.c("41FA",a0); a0 += 2;
		if(!t) { t = X.c("41FA",a0); a0 += 2; if(!t) return false }
		a0 += X.I16(a0,_BE);
		tfmx = a0;
		if(!X.c("'TFMX'00",a0)) return false; a0 += 4;
		if(!X.I16(a0+0x0C,_BE)) { fmt = "sfx"; sz = 0; x = 1; return true }
		d1 = (2+X.I16(a0,_BE)+X.I16(a0+2,_BE)) << 6;
		d2 = (1+X.I16(a0+4,_BE))*X.I16(a0+8,_BE);
		d3 = (1+X.I16(a0+6,_BE))*12;
		d1 += d2+d3+(1+X.I16(a0+0x0C,_BE))*6+0x20;
		a0 += d1+0x0E;
		if(X.I32(a0,_BE)) return false; a0 += 4;
		d2 = X.I16(a0,_BE); if(!d2) return false;
		d2 *= 2; if(d2 != X.I32(a0+0x1A,_BE)) return false;
	}
	sz = a0 = 0; x = 1; a1 = a0+tfmx;
	if(fmt < 0) { d0 = 0; d1 = 0x12 } else { d0 = 2; d1 = 0x20 }
	d0 += X.I16(a1+4,_BE)+X.I16(a1+6,_BE); d0 <<= 6;
	d0 += d1; if(fmt < 0) d1 = 0; else d1 = 1;  d1 += X.I16(a1+8,_BE); ptn = d1;
	d1 *= X.I16(a1+0x0C,_BE); d0 += d1;  if(fmt < 0) d1 = 0; else d1 = 1;
	d1 += X.I16(a1+0x0A,_BE); d1 *= 12; d0 += d1;  a2 = subp = a1+d0; d1 = 1;
	if(fmt < 0) { d2 = X.I16(a1+0x0E,_BE); d1 = d2<<3 }
	else { d2 = X.I16(a1+0x10,_BE); d1 = (d1+d2)*6 }
	x = d2; d0 += d1; a2 = smpinfop = a1+d0;
	if(fmt < 0) { d1 = X.I16(a1+0x10,_BE); d2 = 0x1C }
	else { d1 = X.I16(a1+0x12,_BE); d2 = 0x1E }
	smp = d1; d1 *= d2; d0 += d1;
	smpp = a2 = a1+d0; a1 = smpp; if(fmt < 0) a1 += 2;
	smpsz = X.I16(a1-8,_BE)*2+X.I32(a1-0x0C,_BE);
	songsz = a2-a0; sz = songsz+smpsz;
	return true
}
if(!bDetected && X.isDeepScan() && isJH()) {
	if(fmt < 0) ext = ".MCMD"; else ext = ".SOG";
	sName = "Jochen 'Mad Max' Hippel's module ("+ext+",.HIP)"; sVersion = "f."+fmt; bDetected = 1;
	if(X.Sz() < sz) sVersion += "!short";
	if(X.isVerbose()) {
		if(x>1) sOption(x,"×");
		sOption("ptn:"+ptn+" smp:"+smp+/*" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+*/" sz:"+outSz(sz))
	}
}

function isJH7V() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Jochen_Hippel_7V/Jochen Hippel 7V_v2.asm
	p = 0;
	if(X.c("6000")) { p = 2;
		d1 = X.I16(p,_BE); if(d1 <= 0 || (d1&1)) return false; p = d1+2;
		p = 4+X.fSig(p,20,"308141FA"); if(p < 4) return false;
		d1 = X.I16(p,_BE); if(d1 <= 0 || (d1&1)) return false;
		p += d1;
	}
	if(!X.c("'TFMX'00",p)) return false;
	d0 = d1 = ( 2+X.I16(p+4,_BE) + X.I16(p+6,_BE) ) << 6;
	d0 += 0x20;
	ptn = d2 = X.I16(p+8,_BE)+1;
	d3 = (X.I16(p+0x0A,_BE)+1)*0x1C;
	d2 *= X.I16(p+0x0C,_BE);
	d0 += d2;
	d1 += d2 + d3;
	if(X.U8(p+4+d0)) txt = X.SA(p+4+d0,24);
	else txt = "";
	x = X.I16(p+0x10,_BE);
	d2 = (x+1) << 3;
	ins = X.I16(p+0x12,_BE);
	insip = p+d1+d2+0x20;
	if(X.I32(insip+0x12,_BE)) return false; //1st smp ofs
	d2 = 2*X.I16(insip+0x16,_BE);
	if(!d2) return false;
	if(d2 != X.I32(insip+0x16+0x1E-4,_BE)) return false;
	var mofs = 0; var msz = 0; //find max ofs instead of one just from last smp, just in case
	for(i=0;i < ins;i++) {
		var sofs = X.I32(insip+i*0x1E+0x12,_BE);
		var ssz = X.U16(insip+i*0x1E+0x16,_BE);
		if(sofs >= mofs) { mofs = sofs; msz = ssz } }
	smpp = insip+ins*0x1E;
	sz = smpp+mofs+msz*2;
//_log("insip:"+Hex(insip)+" smpp:"+Hex(smpp)+" sz:"+outSz(sz));
	return true;
}
if(!bDetected && X.isDeepScan() && isJH7V()) {
	sName = "Jochen 'Mad Max' Hippel's module (.HIP7,.S7G)"; sVersion = "7V"; bDetected = 1;
	if(!X.c("'TFMX'")) sVersion += "+replayer";
	if(X.Sz() < sz) sVersion += "!short";
	if(X.isVerbose()) {
		if(txt != "") sOptionT(txt);
		if(x>1) sOption(x,"×");
		sOption("ptn:"+ptn+" ins:"+ins+" sz:"+outSz(sz))
	}
}

function isJHCoSo() {
	//from uade Jochen_Hippel_COSO player, check routine @ e476t
	//initplayer disassembly: d e5c0 500
	if(!X.c("'COSO'")) return false;
	if(!X.c("'TFMX'",0x20)) return false;
	if(!X.I16(0x30,_BE) || !X.I16(0x40,_BE)) return false;
	var songsz = X.I32(0x1C,_BE); if(songsz > X.Sz()) return false;
	//@e4ac:
	a2 = X.I32(4,_BE); d6 = d7 = 0;
	d0 = X.I16(0x24,_BE); //ord?
	do { if(a1 > X.Sz()) return false;
		a1 = X.I16(a2,_BE); a2 += 2;
		if(X.U8(a1) === 0xE2)
			if(X.U8(a1+1)&0x80) d7++; else d6++;
	d0--} while(d0 >= 0);
	if(d7 >= d6) return false;
	ptn = X.I16(0x28,_BE)+1; smp = X.I16(0x32,_BE);
	a0 = X.I32(0x18,_BE)+smp*10; //_log("a0:"+Hex(a0));
	smpsz = X.I32(a0-10,_BE) + X.I16(a0-6,_BE)*2;
	sz = songsz+smpsz;
	x = X.I16(0x30,_BE);
	return true
}
if(!bDetected && X.isDeepScan() && isJHCoSo()) {
	sName = "Jochen 'Mad Max' Hippel's module (.HIPC)"; sVersion = "packed"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,"×");
		sOption("ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
	}
}

function isJHST() {
	//from http://wt.exotica.org.uk/files/sources/SRC_JochenHippelST.lzx
	//Test routine @e3fc, init @e57e
	a0 = a1 = a4 = d4 = 0; d1 = 0x80; d0 = X.Sz(); bad = msg = "";
	//func @e84e, returns d0 = 2 if this:
	while(d1) {
		if(a0 > X.Sz() || a1 > X.Sz()) return false;
		if(d1 != 0x80) { //detect the files that start with MMME,TFMX,COSO
			t = X.I16(a1,_BE); a1 += 2;
			if(t === 0x41FA) { d2 = X.I16(a1,_BE); if(d2 >= 0 && !(d2&1)) a0 = a1+d2 }
		}
		if(X.c("'MMME'",a0)) { d0 = 2; break; }
		else if(X.c("'TFMX'",a0)) {
			if(X.I16(a0+4,_BE) >= 0x200) d0 = 0;
			else if(!X.I16(a0+0x10,_BE)) d0 = 0;
			else { // function @e8b0
				//func.@e8b0~e8c8
				d0 = X.I16(a0+4,_BE); a1 = a0+0x20; d6 = d7 = 0;
				do { if(X.U8(a1) === 0xE2) // in func.e928
						if(X.U8(a1+1)&0x80) d6++; else d7++;
					a1 += 0x40;
				d0--} while(d0 >= 0);
				if(d6 > d7) d0 = 2; else d0 = 5; //@e8a8
			}
			break;
		}
		else if(X.c("'COSO'",a0)) {
			if(!X.I16(a0+0x30)) return false; //FX check
			if(!X.I32(a0+0x18)) return false;
			if(X.c("'TFMX'",a0+0x20)) { //@e984
				a2 = a0+X.I32(a0+4,_BE); d6 = d7 = 0;
				d0 = X.I16(a0+0x24,_BE);
				do {
					if(X.I16(a0+0x40,_BE)) { a1 = a0+X.I16(a2,_BE); a2 += 2 }
					else { a1 = a0+X.I32(a2,_BE); a2 += 4 }
					if(a1 > X.Sz()) return false;
					if(X.U8(a1) === 0xE2) {
						if(X.U8(a1+1)&0x80) d7++; else d6++;
					}
				d0--} while(d0 >= 0);
				if(d7 < d6) d0 = 4; else d0 = 2
			}
			else if(X.c("'MMME'",a0+0x20)) d0 = 2; else d0 = 0;
			break;
		}
		d0 -= 2;
		d1--
	}
	if(d0 != 2) return false;
	if(X.c("'LSMP'",a0+0x1C)) lsmp = 1; else lsmp = 0;
	//a0 -= a4; if(a0 && debug)_log("pc+@e170 (mod ofs?): "+a0);

	//size calc
	d0 = X.Sz()-a0; d4 = a0; songsz = smpsz = 0; sz = sz1 = -1;
	if(!X.c("'COSO'",a0) && !X.c("'MMME'",a0+0x20)) { sV = "unpacked";
		d6 = a0; d1 = 2; a2 = a0;
		d1 = (2+X.I16(a0+4,_BE)+X.I16(a0+6,_BE)) << 6;
		d2 = (1+X.I16(a0+8,_BE)) * X.I16(a0+0xC,_BE);
		d3 = (1+X.I16(a0+0xA,_BE))*12; //e5f8
		d1 += d2+d3; d2 = 2+X.I16(a0+0x10,_BE);
		d1 += (d2+X.I16(a0+0x12,_BE))*6+0x20;
		d2 = 0x20; d3 = d7 = 0; //e612
		d0 -= d1;
		if(d0 < 0) /*@e814*/ { d0 = 0x1C; bad = bad.addIfNone("!short") }
		else if(d0 > 0) { //e618
			a1 = d1+a2;
			t = X.I16(a1,_BE);
			if(t === 0x80 || t === 0x100) {
				d7 = a1;
				do { d3 = X.I16(a1,_BE); a1 += 8;
				} while(X.I16(a1,_BE) && a1 < X.Sz()); //e63e
				d0 -= d3; if(d0 < 0)
					d0 = 0x1C;
			}
			else { t >>= 8; p = a1+1; msg = [];
				while(t && p < X.Sz()) { msg.push(t); t = X.U8(p++); }
				msg = decEncoding(msg,CPAmiga,1,Chars0to1FLF)
			}
		}
		d0 = d1; smpsz = d3;
		songsz = d1; sz = a0+songsz+smpsz; sz1 = (msg != "" ? sz+msg.length+1 : sz);
		x = X.U16(a0+0x10,_BE); if(x > 100) return false
	} else { sV = "packed"; a1 = a0; d3 = 0;
		d3 = (1+X.I16(a0+0x32,_BE))*6; d2 = d3+X.I32(a0+0x18,_BE);
		d3 = 0; d0 -= d2;
		if(d0 < 0) /*@e814*/ d0 = 0x1C;
		if(d0 > 0) {
			a1 += d2; t = X.I16(a1,_BE);
			if(t == 0x80 && t == 0x100) {
				do { d3 = X.I16(a1,_BE); a1 += 8;
				} while(X.I16(a1,_BE) && a1 < X.Sz());
			d0 -= d2;
			 if(d0 < 0)
				d0 = 0x1C
		} else { t >>= 8; p = a1+1; msg = [];
				while(t && p < X.Sz()) { msg.push(t); t = X.U8(p++); }
				msg = decEncoding(msg,CPAmiga,1,Chars0to1FLF)
			}
		}
		/*d2 += d4;*/ songsz = d2; smpsz = d3; sz = a0+d3+d2;
		if(msg.length < 3) msg = "";
		sz1 = (msg.length ? sz+msg.length+1 : sz);
		x = X.U16(a0+0x30,_BE); if(x > 100) return false;
	}
	return true;
}
if(!bDetected && isJHST()) {
	sName = "Jochen 'Mad Max' Hippel's Atari ST module (.HST,.SOC,.SOG)"; bDetected = 1
	sVersion = sV; if(bad != "") sVersion = sVersion.appendS("malformed"+bad,"/");
	if(X.isVerbose()) {
		sOptionT(msg,'msg:"','"');
		info = "";
		if(lsmp) info = info.appendS("ext.samples"," ")
		if(sz > 0) info = info.appendS("sz:"+outSz(sz, sz1)," ");
		if(smpsz) info = (/*"songsz:"+Hex(songsz)+" "+*/"smpsz:"+Hex(smpsz)).appendS(info," ");
		if(x > 1) info = ("×"+x).appendS(info," ");
		sOption(info)
	}
}

function isEMS() {
	//ref UADE's EMS player disassembly: run with -d then "f d3c2"
	if(X.Sz() <= 0x53A) return false; // fault is @d432
	if(X.U8(0) != 1) return false;
	if(X.U8(1) > 0x25) return false;
	t = X.I16(2,_BE); if(!t || t > 0x80) return false;
	d1 = X.U16(4,_BE); if(d1 > 0x3F) return false; d = d1 & 0xFF;
	p = 6;
	while(p < 0x86) { t = X.U8(p); p++; if(d < t) return false; }
	c = 0;
	for(p = 0x13A; p < 0x53A; p += 4) { //@d40c
		t = X.I16(p,_BE); if(t < 0) return false;
		if(t)
			if([
			  0x6B0, 0x650, 0x5F4, 0x5A0, 0x54C, 0x500, 0x4B8, 0x474,
			  0x434, 0x3F8, 0x3C0, 0x38A, 0x358, 0x328, 0x2FA, 0x2D0,
			  0x2A6, 0x280, 0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,
			  0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140, 0x12E, 0x11D,
			  0x10D, 0xFE, 0xF0, 0xE2, 0xD6, 0xCA, 0xBE, 0xB4,
			  0xAA, 0xA0, 0x97, 0x8F, 0x97, 0x7F, 0x78, 0x71
			  ].indexOf(t) < 0)
				return false;
			else c++;
	}
	if(!c) return false;
	return true
}
if(!bDetected && X.isDeepScan() && isEMS()) {
	sName = "Editeur Musical Sequentiel module (.EMS)"; bDetected = 1;
}

function isDZ() {
	//from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/playes/dz/DariusZendeh_mod.s
	if(X.Sz() < 0x500 || !X.c("48E7..F0 41FA.... 4CD80600")) return false;
	if(X.c("78",2) && X.c("0C0000FF",0xC)) sversion = "strange";
	else
		if(!X.c("00",2) || !X.fSig(0xC,0x240,"700033FC 000F00DF F09641FA"))
			return false;
	soptions = "";
	if(X.c("4A44",0xC)) {
		sversion = "type 1";
		if(X.isVerbose()) {
			p = X.fSig(0,0x80,"0C04.... 66..41FA");
			if(p >= 0) soption = "×"+(X.U16(p+2,_BE)+3)
		}
	}
	else if(X.c("4A00",0xC)) sversion = "type 2";
	else if(X.c("0C00",0xC)) sversion = "type 3";
	return true
}
if(!bDetected && isDZ()) {
	sName = 'Darius "Mark II" Zendeh module (.DZ)'; sVersion = sversion; sOptions = soptions; bDetected = 1
}

function isMkII() {
	//ref reversing of the UADE player
	// & http://old.exotica.org.uk/tunes/formats/mk2/mk2_v1.zip
	if(!X.c("41FA",4) || X.Sz() < 0x500) return false;
	//if(X.fSig(0x200,0x200,"'.ZADS89.'")>=0) ← Some zero-fill it--not used by the replayer
	p = 8; i = 0x120;
	do { t = X.c("E742",p); p += 2; if(t) break; i-- } while(i); if(!i) return false;
	do { t = X.c("41FA",p); p += 2; if(t) break; i-- } while(i); if(!i) return false;
//_log("mkII 41FA search: i="+i+" @"+p)
	a3 = p; p += X.I16(p,_BE); //p goes to sampleinfo
	d5 = X.I16(a3+2,_BE); //d5 is maybe (or not) "move.w (a0+d2+?), (a3+?)"
	i = 0x18; do { t = X.c("D1FA",a3); a3 += 2; if(t) break; i-- } while(i);
//_log("mkII D1FA search: i="+i+" @"+a3)
	if(!i) {
		if(d5 == 0x3770) return false; d5 = 0; a5 = a3-0x1C; i = 0x10;
	} else { a5 = a3; a3 += X.I16(a3,_BE) }
//_log("mkII: 3770 and a5 + a3skip")
	do { t = X.c("41FA",a5); a5 -= 2; if(t) break; i-- } while(i);
//_log("mkII: 5, i="+i+" @"+Hex(a5))
	return i
}
if(isMkII()) {
	//DZ and MkII may detect some modules together, meaning a player of either format will play them
	_setResult("audio","Mark II Sound System module (.MK2)","","","")
	//sName = "Mark II Sound System module (.MK2)"; sVersion = ""; bDetected = 1;
}


// BELOW THIS POINT ARE THE DETECTIONS THAT ARE IFFIER THAN THE REST

// like too loose and hits other known formats, or like potentially taking up unknown cycle#

// They all are in dire need of amending, and they're set to go after the other detections,
// so you won't miss the good matches because of these


function isHSC() {
	//ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/hsc_load.c
	if(X.Sz() < 1587+1152 || X.Sz() > 59187+1) return false;
	p = 128*12; ptn = 0; ord = -1;
	for(i=0; i < 0x33; i++) {
		bt = X.U8(p+i);
		if(bt == 0xFF) break;
		if(ptn < bt) ptn = bt; ord++ }
	if(!i || !ptn || i > 0x32 || ptn > 0x30) return false; //test number of patterns
//_log("1: i="+i+" ptn="+ptn);
	p += 0x33;
	for(i=0; i < ptn; i++) {
		for(j=0; j < 0x40; j++) {
			for(k=0; k < 9; k++) {
				n = X.U8(p+j*18+k*2);
				m = X.U8(p+j*18+k*2+1);
				if(m > 6 && m < 0x10 && n != 0x80) return false; //test effects 07..0F
				msn = (m&0xF0)>>4;
				if(6 < msn && msn < 10) return false; //test effects 7x..9x
//_log("  3: i="+i+" j="+j+" k="+k+" n["+Hex(p+j*9+k*2)+"]="+Hex(n)+" m="+Hex(m)+" msn="+msn);
			}
		}
		p += 64*9*2
	}
	return true
}
if(!bDetected && X.isHeuristicScan() && isHSC()) {
	sName = "HSC Adlib Composer/HSC-Tracker module (.HSC)"; bDetected = 1;
	if(X.isVerbose()) sOption = "ord:"+ord+" ptn:"+ptn;
}

function isJamCracker() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/JamCracker.c
	if(!X.isHeuristicScan() && !X.c("'BeEp'")) return false;
	smp = X.U8(5);
	if(!smp || (smp > 0x1F)) return false;
	if(X.U8(6+smp*40) > 0) return false;
	allsmpsz = 0;
	for(k=0; k < smp; k++) {
		l = X.U32(38+k*40,_BE);
		if(!l || (l > X.Sz())) return false;
		allsmpsz += l;
	}
	ord = X.U16(6+smp*40,_BE);
	if(ord > 0xFF) return false;
	ptns = 6+smp*40+2;
	ptn = X.U16(ptns+ord*6,_BE);
	if(ptn > 0xFF) return false;

	ptn0 = X.U32(ptns+2,_BE);
	ptns += ord*6;
	trkdtsz = X.U32(ptns-4,_BE)-ptn0;
	addlns = X.U8(ptns-5)*4*8;
	sz = ptns + 2 + ptn*2 + allsmpsz + trkdtsz + addlns; //doesn't include the Xag tags
	return true
}
if(!bDetected && isJamCracker()) {
	sName = "JamCracker/Pro module (.JAM,.JC)"; bDetected = 1; bad = "";
	if(X.isHeuristicScan()) {
		if(sz+19 == X.Sz()) { //obviously not for rippers
			sVersion = "v1.0a (Xag)";
			if(X.isVerbose()) sOptionT(X.SA(sz,19)); //this version adds these
			sz += 19;
		}
		if(!X.c("'BeEp'")) bad = bad.addIfNone("!badsig");
	}
	if(sz > X.Sz()) bad = bad.addIfNone("!short");
	if(bad != "") sVersion = appendS("malformed"+bad,"/");
	if(X.isVerbose())
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
}

function isJBM() {
	//ref https://github.com/adplug/adplug/blob/master/src/jbm.cpp
	//TODO for ex. demo3.jbm has a pointer to outside of the file,
	//  and it seems to work in a player but these are sanity checks ffs... Dunno this format
	if(X.U16(0,_LE) != 2) return false;
	if(!X.c("00FF01FF02FF03FF04FF05FF06FF07FF08FF09FF0AFFFD00", 0x36
)) return false; //just from the files modland has, 'cause the detection's' wonky
	seqt = X.U16(4,_LE); if(!seqt || seqt < 30 || seqt >= X.Sz()) return false;
	inst = X.U16(6,_LE); if(!inst || inst <= seqt || inst >= X.Sz()) return false;
	ins = (X.Sz()-inst) >> 4;
//_log("fs="+Hex(X.Sz())+", inst="+Hex(inst)+", ins="+ins);
	mptn = 0xFFFF; ch = 0; trk=[];
	for(i=0; i < 11; i++) {
		p = X.U16(10+i*2,_LE); trk[i] = p;
		if(p) { ch++;
			if(p <= seqt || p >= inst) return false;
			if(p < mptn) mptn = p
		}
	}
//_log("trk:"+trk);
	ptn = (mptn-seqt) >> 1;
//_log("("+mptn+"-"+seqt+")/2="+ptn);
	for(i=0; i < 5/*ptn*/; i++) { //TODO make a full check when the format is more clear!
		if(trk[i]) {
			p = X.U16(seqt+i*2,_LE);
//_log(seqt+"["+i+"*2]="+Hex(p));
			if(X.U16(p-1,_LE) != 0xFDFF) return false;
			if(p <= seqt || p >= inst) return false;
		}
	}
	i = X.U16(2,_LE); tmr = (1193810.0 / (i ? i : 0xFFFF)).toFixed(2);
	return true
}
if(!bDetected && X.isHeuristicScan() && isJBM()) {
	sName = "Johannes Bjerregaard Adlib module (.JBM)"; bDetected = 1;
	if(X.isVerbose())
		sOption("tmr:"+tmr+" ch:"+ch+" ptn:"+ptn+" ins:"+ins)
}


function isSoundFX() {
	//from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_sfx.cpp
	// & https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SoundFX/src/SoundFX_V2.asm
	if(X.c("'SONG'",0x3C) && X.Sz() > 0x294) msmp = 15;
	else if(X.c("'SO31'",0x7C) && X.Sz() > 0x4B4) msmp = 31;
	else return false;
	hdrp = 4*msmp; smptp = hdrp+0x14; ordp = smptp+msmp*30;
	ord = X.U8(ordp,_BE); if(ord > 128) return false;
	t = X.U16(hdrp+4,_BE); if(t < 178 /*== 0*/) return false;
	tmp0 = (14565*122/t).toFixed(0); spd0 = 6;
	smp = smpsz = ic = 0; smps = [];
	for(i=0; i < msmp; i++) {
		ssz = X.U32(i*4,_BE); if(ssz > 0x20000) return false; smpsz += ssz; if(ssz) smp++;
		t = readBytes(smptp+i*30,20);
		for(c=0; c < 20; c++) if(t[c] && t[c] < 0x20) ic++; if(ic >= 128) return false;
		t = decEncoding(t,CPAmiga);
		if(t.trim() != "") smps.push(t.trim())
		smth = X.U16(smptp+i*30+2,_BE);
	}
	return true
}
if(!bDetected && isSoundFX()) {
	sName = msmp == 15 ? "SoundFX module (.SFX)" : "SoundFX 2 / MultiMedia Sound module (.MMS)";
	if(msmp == 15) sVersion = 'v1.0-8'; bDetected = 1;
	if(X.isVerbose()) {
		p += 2; ptn = 0;
		for(i=ordp+2; i < ordp+2+ord; i++) { t = X.U8(i); if(t > ptn) ptn = t } ptn++;
		sz = ordp + 0x82 + ptn*0x400 + smpsz; if(msmp == 31) sz += 6;
		lp = X.U8(ordp+1); if(lp > ord) sVersion = sVersion.appendS('malformed!badloop','/');
		//unk = readBytes(hdrp+6,14); unksum = 0; for(i=0;i<unk.length;i++) unksum += unk[i];
		//if(unksum > 0) sOption('unk.bytes:'+unk);
		sOption('tmp0:'+tmp0+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+(lp?' loop:'+lp:'')+' sz:'+outSz(sz))
	}
}


function isJesperOlsen() {
	//ref https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Jesper_Olsen/Jesper Olsen_v1.asm
	fmt = ''; smp = smpp = 0; sz = -1; x = 1;
	if(X.c("6000")) {//old
		p = 0; d1 = 3;
		do {
			if(!X.c("6000",p)) return false;
			if((t = X.I16(p+2,_BE)) <= 0 || t % 2) return false; p += 4
		} while(d1--);
		p = p0 = 6+X.I16(6,_BE);
		if(!X.c("4A406B00",p)) { //even older
			p -= 4;
			if(X.c("C0FC",p)) p += 2;
			else
				for(i=0; i < 16 && p < X.Sz(); i++,p+=2)
					if(X.c("02800000",p)) { if(!X.c("00FFC0FC",p+4)) return false; p += 8; break }
			if(p > X.Sz()) return false;
			mulu = X.I16(p,_BE); p += 800; q = p+900;
			while(p < X.Sz()) {
				if(X.c("6AE064E0",p)) break;
				p += 2; if(p === q) return false
			}
			if(p > X.Sz()) return false;
			fmt = 'old'; p = p0;
			while(p < X.Sz()) { t = X.I16(p,_BE); p += 2;
				if(t >= 0 && (t & 0x40FA) == 0x40FA) { t = X.I16(p,_BE); p += 2; if(t >= 0) break } }
			p += t-2; var songp_in_explayp = p;
			x = X.I16(p,_BE)-p;
			if(x < mulu) x = 1; else x = Util.divu64(x,mulu);
			//p is now in a2 and a1 is the filesize
			while(p < X.Sz()) {
				if(X.c("'FORM' 0000???? '8SVXVHDR'",p)) { if(!smpp) smpp = p; smp++; p += 4+X.I32(p+4,_BE)+2 }
				p += 2; if(p >= X.Sz()) break
			}
			if(p > X.Sz()) return false; sz = p;
			p = 0xA+X.I16(0xA,_BE);
			while(p < X.Sz()) { t = X.c("43FA",p); p += 2; if(t) break }
			var Lea = p, VoiceTmp = p+X.I16(p,_BE); p += 2; //skipping the part about a flag
			d1 = X.c("6000",12) ? "7FFE" : "7FFF";
			while(p < X.Sz()) { t = X.c(d1,p); p += 2; if(t) break }
			while(p < X.Sz()) { t = X.c("336C",p); p += 2; if(t) break }
			var Value = X.I16(p+2,_BE)
			//now, I'm not very sure if we _need_ Lea, VoiceTmp, Value for our purposes, but we have'em!
		} else {
			if(!X.c("000641FA",p+4)) return false; p += 8;
			p += X.I16(p,_BE)+4; if(!X.c("00017FFF",p)) return false;
			fmt = 'second';
			p = p0+8+X.I16(p0+8,_BE); var SongPtr = p;
			p += 8; x = 0; d2 = X.I32(p+0xA,_BE);
			while(p < X.Sz()) { if(d2 != X.I32(p+0xA,_BE)) break; x++; p += 0x1A }
			p = smpp = SongPtr+X.I32(SongPtr,_BE); //smpinfo
			while(p < X.Sz()) {
				if(X.c("'FORM' 00?????? '8SVXVHDR'",p)) { smp++; p += 4+X.I32(p+4,_BE)+2 }
				p += 2; if(p >= X.Sz()) break
			}
			if(p > X.Sz()) return false; sz = p;
			p = SongPtr; d1 = X.U32(p,_BE); d2 = X.U32(p+0x12,_BE);
			if(d2 <= d1) p += d1; //that'll be our song size
			else {
				d3 = d2-X.I32(p+0x16,_BE);
				sz = d2+d3+p; if(sz == 35944) x -= 1; //I don't have this file to test
				if(sz > X.Sz()) return false;
				//d3 <<= 2; p += X.I32(p,_BE); d3 += p; songsz = d3; smpsz = sz-songsz
			}
		}
	} else if(X.c("0000 ???? 00017FFF")) { //the Harald Hårdtand game is once again different
		var z = 0; for(p = 8; p < 0x10; p += 2) {
			t = X.I16(p,_BE); if(!t) { z++; continue }
			if(t < 0 || t%2 || !(X.c("7FFF",t-2) || X.c("7FFE",t-4))) return false;
		} if(z > 1) return false;
		p = 2+X.I16(2,_BE);
		if(p > X.Sz() || !X.c("'FORM' 00?????? '8SVXVHDR'",p)) return false;
		while(p < X.Sz()) {
			if(X.c("'FORM' 00?????? '8SVXVHDR'",p)) { smp++; p += 4+X.I32(p+4,_BE)+2 }
			p += 2; if(p >= X.Sz()) break
		}
		if(p > X.Sz()) return false;
		sz = p; fmt = 'harald'
	} else { //this detection is a tad broken in the eagleplayer
		if((p = X.U16(0,_BE)) > 0x200 || p < 4 || p % 2) return false; p += 2;
		for(i=2; i < p; i += 2) if((t = X.I16(i,_BE)) <= 0 || t % 2 || !X.c("7FFF",t-2)) return false;
		fmt = 'latest';
		x = X.U16(0,_BE); p = smptp = X.U16(x+2,_BE); d3 = X.U16(x,_BE); x >>= 1; x--;
		if(x != 1) if(X.c("0000",d3-6) || !X.c("7F00",d3-4)) x--;
		smp0p = X.U16(p,_BE); var lo = hi = X.U24(smp0p+0xF,_BE);
		smp = ssz = 0;
		while(p < smp0p && smp < 128) { if(p > X.Sz()) return false;
			smp++; pp = X.U16(p,_BE);
			sofs = X.U24(pp+0xF,_BE);
			if(lo > sofs) lo = sofs;
			if(hi <= sofs) {
				hi = sofs; ssz = X.I16(pp+0x12,_BE);
				if(!X.c("'BODY'",sofs-8)) { //a raw sample
					//the original test was if(!X.U8(pp+0xE)) - this fails with, say, georgglaxo ingame.jo
					sofs = X.U32(pp+0x14,_BE);
					if(sofs > hi) { hi = sofs; ssz = 2*X.U16(pp+0x18,_BE) }
				}
			}
			if(hi > X.Sz()) return false;
			if(pp < smp0p) smp0p = pp;
//_log('smp '+smp+'. @'+Hex(pp)+' maxofs:'+Hex(hi)+' ssz0E:'+Hex(X.U24(pp+0xF,_BE))+' ssz18:'+Hex(X.U16(pp+0x18,_BE))
//+(X.c("'BODY'",sofs-8)?' IFF ['+Hex(X.U32(sofs-4,_BE))+'-'+Hex(sofs+X.U32(sofs-4,_BE))+']':' raw'))
			p += 2;
		}
		if(X.c("'BODY'00'",lo-8)) lo = X.fSig("'FORM'00??????",lo-150,lo-20); songsz = lo;
		if(X.c("'BODY'00'",hi-8)) hi += X.U32(hi-4,_BE); //BODY tag skip
		else hi += ssz<<1;
		sz = hi;
	}

	return true;
}
if(!bDetected && isJesperOlsen()) {
	sName = "Jesper Olsen's module (.JO)"; sVersion = fmt; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x'); sOption('smp:'+smp+' sz:'+outSz(sz))
	}
}


function isJasonBrooke() {
	//from reversing the eagleplayer
	// SubSongs at +04; SongSize at +14; Samples at +1C; CalcSize at +24;
	// SamplesSize at +2C; ord at +34; SynthSamples at +3C
	// LoadSize at +0C; SynthSamples at +3C; Steps at +44; AuthorName at +4C => F2;
	// Prefix at +54 => 139
	fmt = ''; p = 0;
	function findPlus(s) { while(p < X.Sz()) { t = X.c(s,p); p += 2; if(t) break } }
	function findOrPlus(s) { while(p < X.Sz()) { if(X.c(s,p)) break; p += 2 } }
	function minusFind(s) { while(p > 0) { p -= 2; if(X.c(s,p)) break } }
	if(X.c("48E7F0F0")) { if(!X.c("424047FA FFF84A2B",4)) return false; fmt = 'old'; // -1
		if(!X.isVerbose()) return true;
		a0 = p = 12; findPlus("43FA"); d7 = p+X.U16(p,_BE)+1;
		findPlus("228A"); p += 2; d2 = p-8+X.U16(p-8,_BE); //the first synsmp ptr?
		findPlus("137B"); d6 = a4 = p+X.U16(p,_BE)-1; x = 1;
		while(d6 < X.Sz()) { d6 += 10; if(!X.U16(d6,_BE)) break; if(X.U8(d6)) break; x++ }
		minusFind("48E7"); var hi, lo = X.U16(a4+2,_BE); //p & a4 -> stack
		for(d0 = x; d0--;) { //d852~62
			a4 += 2; for(d3=4; d3--;) { t = X.U16(a4,_BE); a4 += 2; if(lo > t) lo = t }
		}
		p = lo; lo = hi = X.U16(p,_BE); p += 2;
		do { //d870~84
			t = X.U16(p,_BE); p += 2; if(t) { if(t < lo) lo = t; if(t > hi) hi = t; }
		} while(p < d7);
		a3 = lo; synsmp = (lo-d2)>>7;
		p = hi;
		while(p < X.Sz()) if([0x85,0x87].indexOf(X.U8(p)) >= 0) break; else p++; //d89a~ac
		d0 = 0; do { //d8ae~c6
			while(a3 < X.Sz()) if([0x85,0x87].indexOf(X.U8(a3)) >= 0) break; else a3++;
			a3++; d0++
		} while(a3 < p);
		p++; a3 = d6; if(a3) { //d8c8~f6
			for(d1=0x80,found=false; d1-- && !found;) { t = X.c("41FA",a3); a3 += 2; if(t) found = true }
			if(found) { //d8de
				a3 += X.U16(a3,_BE);
				while(a3 < X.Sz()) { if(X.c("FF",a3+4) || X.c("FF",a3+6)) a3 += 0x12; else break }
				p = a3;
			}
			sz = p; steps = d0 //d8fa
		}
	}
	else {
		p = 0xAC; while(p < 0xE0) { t = X.c("48E7",p); p += 2; if(t) break } if(p > 0xE0) return false; 
		if(!X.c("F8FC",p) && !X.c("F8F8",p)) return false;
		if(!X.c("08F90001 00BFE001 33FC0780 00DFF09A 47FA",p+2)) return false;
		p += 0x14; if(0x10000-p-X.U16(p,_BE)) return false;
		fmt = 'new'; if(!X.isVerbose()) return true; p = 0;
		findPlus("48E7"); findPlus("49F9"); p += 6; findPlus("7600"); d0 = X.U16(p-10,_BE);
		findOrPlus("10320000"); a4 = p-2+X.U16(p-2,_BE);
		findPlus("48E7"); p = a3 = p-6; findPlus("43FA"); p += X.U16(p,_BE)-1;
		a4 -= p; d3 = Util.divu64(a4,0x12); x = 1; d2 = 0;
		for(; p < X.Sz(); x++) { //d74e~5c
			p += 0x12; d2 = X.U16(p,_BE); if(X.U8(p,_BE)) break; d2--; if(d2 <= 0) break; }
		if(d3 < x) x = d3; //d75e~64
		p = a3; minusFind("45FA"); d0 = p = p-2+X.U16(p-2,_BE);
		p += 4; synsmp = smp = d5 = 0; smpp = p;
		while(p < X.Sz()) {
			if((t = X.U32(p,_BE)) > d0) { //d784~a8
				if(!smp) d5 = t; if(t <= d5) d5 = a4 = t; smp++; p += 10; if(p > a4) break;
			} else { synsmp++; p += 10 }
		}
		i = smp+synsmp; sz = 0; p = smpp;
		while(i-- && p < X.Sz()) { t = X.U32(p,_BE)+X.U16(p+4,_BE); if(sz < t) sz = t; p += 10 } //~d7cc
	}
	return true
}
if(!bDetected && isJasonBrooke()) {
	sName = "Jason C. Brooke's module (.JB,.JCB)"; sVersion = fmt; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		sOption((fmt=='new'?'wf.smp:'+smp:'steps:'+steps)+' syn.smp:'+synsmp+' sz:'+outSz(sz))
	}
}


function isKrisHatlelid() {
	// SubSongs at +04; SamplesSize at +0C; Length at +14; Samples at +1C
	if(!X.c("000003F3 00000000 00000003 00000000 00000002 ???????? 40?????? 00000001 000003E9"))
		return false;
	twofiles = false; p = 0x30;
	d1 = X.U32(0x14,_BE) & ~(1<<30); if(X.U32(0x24,_BE) != d1) return false;
	if(X.c("60000016",0x28)) { if(!X.c("0000ABCD",0x2C)) return false } else p = 0x18;
	if((p == 0x30 && !X.c("B07C0000",0x40)) || p == 0x18) {
		if(!X.c("41F90000 00004E75",p+0x10)) return false; twofiles = true
	}
	p = 0x40; smpp = 0;
	while(!smpp && p < X.Sz()) { t = X.fSig(p,TOEOF,"'FORM'"); if(!t%2) smpp = t; else p = Math.max(t+1,p+1) }
	if(smpp < 0) return false;
	smp = 0;
	while(p < X.Sz()) { if(!X.c("'FORM' ???????? '8SVXVHDR'",p)) break; smp++; p += 4+X.U32(p+4,_BE) }
// var psz = X.fSig(0x24,0x40,"000003EA")+4; if(psz < 4) return false; sz = psz+4+(X.U32(psz,_BE)<<2);
// if((t = (X.U32(sz,_BE)>>3)<<3) != 0x3EC) _log(" the chunk at sz is not 3EC but "+Hex(t));
// else t = X.U32(sz+4,_BE);
// if(p != sz) _log("sz differs! "+Hex(sz)+" vs "+Hex(p))
// TODO parse hunks↑ tooб AND detect/debug SubSongs!
	sz = p;
	return true;
}
if(!bDetected && isKrisHatlelid()) {
	sName = "Kris Hatlelid's module (.KH"+(twofiles?'+SONGPLAY':'')+")"; bDetected = 1;
	if(X.isVerbose()) {
		sOption('sz:'+outSz(sz))
	}
}


function isKefrensSoundMachine() {
	//ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/KefrensSoundMachine.c
	if(X.Sz()<1537) return false;
	if(X.U8(15) != 0x61) return false;
	for(k=0; k<15; k++)
		if(X.U8(54+k*32) > 0x40) return false;
	trks = 0;
	for(k=0; k<1024; k++) {
		k_ = X.U8(k+512);
		if(k_ == 0xFF) break;
		if(k_ > trks) trks = k_;
	}
	if(k == 1024) return false;
	if(trks == 0) return false;
	if(1536 + trks*192 + 64*3 > X.Sz()) return false;
	for(k=0; k<=trks; k++)
		for(l=0; l<64; l++)
			if(X.U8(1536 + k*192 + l*3) > 0x24) return false;

	allsmpsz = 0;
	for(k=0; k<15; k++)
		allsmpsz += X.U16(52+k*32,_BE);
	sz = (trks+1)*192 + allsmpsz + 1536;
	return true
}
if(!bDetected && X.isHeuristicScan() && isKefrensSoundMachine()) {
	sName = "Kefrens Sound Machine module (.KSM)"; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(X.SA(2,13));
		sOption("trk:"+trks+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
		if(sz > X.Sz()) sVersion = "malformed!short"
	}
}


function isImagesMusicSystem() { //goes before Soundtracker .STK for speed
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/ImagesMusicSystem/src/Images Music System_v3.asm
	//is this a hack of Soundtracker? Vice versa?
	if(X.Sz() < 0x73C) return false;
	smpp = X.U32(0x438,_BE); if(smpp <= 0x43C) return false;
	d1h = (smpp-0x43C) % 0x300; if(d1h) return false;
	ptn = d2 = Util.div64(smpp-0x43C,0x300); ord = X.U8(0x3B6); if(ord > 127) return false;
	mptn = 0;
	for(i = 0; i <= ord; i++) { t = X.U8(0x3B8+i); if(t > 127) return false; if (mptn < t) mptn = t }
	if(ptn != mptn+1) return false;
	smp = smpsz = 0; smps = []; bad = ""; var smpns = [];
	for(i = 0; i < 31; i++) {
		if(X.isVerbose()) {
			q = X.SC(0x14+i*30,0x16,'CP437').trim();
			if(q.length && !(/ST-[0-9A-F]+:/i.test(q))) snames.push(q) }
		t = X.U16(0x14+i*30+0x16,_BE);
		if(t) { smp++; smpsz += t<<1; smpns.push(i) }
	}
	msmp = smpns[smpns.length-1];
	songsz = X.U32(0x438,_BE); sz = songsz+smpsz;
	if(sz < X.Sz()) bad = bad.addIfNone("!short");

	a1 = 0x3B8; d1 = ord; x = a0 = notes = 0;
	if(X.isVerbose()) {
	  do { //NextPos
		a0 = 0x43C+X.U8(a1)*0x300; a3 = a0+0x300;
		a1++;
		do { //NextPatPos
			var z = readBytes(a0,2), nsmp = ((z[0] & 0xC0) >> 2) | (z[1] >> 4);
			if(z[0] & 0x3F) { notes++; if(debug && nsmp > msmp) bad = bad.addIfNone("!missingsmp#"+nsmp) }
			if(z[1] & 0x0F === 0x0B) { x++; break }
			else a0 += 3
		} while(a0 < a3 && a0 < X.Sz()); d1--
	  } while(d1);
	  if(!x) x = 1;
	} else x = 1;
	if(songsz === 28732 && smpsz === 12898 && X.c("'beast-busters1.st'") )
		x = 11;
	return true
}
if(!bDetected && X.isDeepScan() && isImagesMusicSystem()) {
	sName = "Images Music System module (.IMS)"; bDetected = 1;
	sVersion = "v1.0"; if(bad != "") sVersion = sVersion.appendS("malformed"+bad,"/");
	if(X.isVerbose()) {
		sOption(X.SC(0,20,'CP437')); if(x > 1) sOption(x,"×");
		sOption(addEllipsis(snames.join(","),0x100,0xA0),'smp/msg:"','"');
		sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
	}
}


function isSoundtrackerMOD() { //goes after IMS for speed
	//ref http://asle.free.fr/prowiz/prowiz.src.zip / r/SoundTracker.c
	// & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
	if(X.Sz() < 555) return false;
	function invalidChars(buf) {
		var c = 0; for(var i = 0; i < buf.length; i++) {
			if(buf[i] && buf[i] < 0x20 && [-1,0xA,0xD,0xE].indexOf(buf[i]) < 0) c++;
		} return c
	}
	function npTest(n) { const valid = [0, 856,808,762,720,678,640,604,570,538,508,480,453,
		  428,404,381,360,339,320,302,285,269,254,240,226,
		  214,202,190,180,170,160,151,143,135,127,120,113];
		return valid.indexOf(n) >= 0 || valid.indexOf(n-1) >= 0 || valid.indexOf(n+1) >= 0
	}
	const UST1_00=0,UST1_80=1,ST2_00_Exterminator=2,ST_III=3,ST_IX=4,MST1_00=5,ST2_00=6;
	var ic = 0, hasDiskNames = true, minV = UST1_00;
	ic = invalidChars(readBytes(0,20));
	bad = ""; allsmpsz = allvols = smp = minV = 0; snames = [];
	for(k=0; k < 15; k++) {
		var sn = readBytes(20+k*30,22,true), sname = decEncoding(sn,CPAmiga).trim();
		if(/ST-[0-9A-F]+:/i.test(sname)) ic -= 4; else snames.push(sname);
		ic += invalidChars(sn);
		if(debug>1)_log("STKsmp["+k+'] "'+sname.trim()+'" invalid chars:'+ic+" ("+charStat(sn,1)+")")
		if(ic > 10) { if(debug>1)_log("STKFault: title or sample names too broken"); return false; }
		if(X.U8(20+k*30) && !/^st-[0-9a-f]\d:/i.test(sname))
			hasDiskNames = false;
		slen = X.U16(42+k*30,_BE)*2;
		if(slen > 0x12000) { if(debug>1)_log("STKFault: sample #"+k+" too large:"+Hex(slen)); return false; }
		if(X.U8(44+k*30) > 15) { bad = bad.addIfNone("!finetune"); ic+=16 } //finetune
		svol = X.U8(45+k*30); if(svol > 0x40) return false;
		sls = X.U16(46+k*30,_BE), sll = X.U16(48+k*30,_BE);
		if(slen > 9998 || sls > 4999) minV = Math.max(minV,MST1_00);
		if(slen) smp++;
		allsmpsz += slen; allvols += svol;
//if(debug>1)_log("smp["+k+"] <"+X.SA(20+k*30,22)+"> ["+slen+"] @"+Hex(20+k*30)+" -> vol:"+svol+" loopstart:"+sls+" looplen:"+sll);
		if(sll > slen+2) {
			if(debug>1)_log("STK!smp "+k+" LpLen "+sll+" > sSz "+slen); bad = bad.addIfNone("!sLpLen>sSz") }
		if(slen && sls >= slen) {
			if(debug>1)_log("STK!smp "+k+" LpStart "+sls+" >= sSz "+slen); bad = bad.addIfNone("!sLpStart>=sSz") }
		if(sls && !sll) {
			if(debug>1)_log("STK!smp "+k+" LpStart "+sls+" LpSz 0"); bad = bad.addIfNone("!sLpStart0LpSz") }
		if(sls && !slen) {
			if(debug>1)_log("STK!smp "+k+" LpStart "+sls+" sSz 0"); bad = bad.addIfNone("!sLpStart0SSz") }
	}
//_log("5:allsmpsz="+allsmpsz+" allvols="+allvols);
	if(allsmpsz < 8 || !allvols) { if(debug>1)_log("Fault: smps too small or no smpvols"); return false; }
	ord = X.U8(0x1D6);
	if(ord > 128) return false;
	restartp = X.U8(0x1D7);
	if(restartp > 220) return false;
	if(!restartp || /jjk55/.test(X.SA(0,6))) restartp = 0x78;
	bpm0 = 125;
	if(restartp != 0x78) {
		bpm0 = ((709379*125/50)/((240-restartp)*122)).toFixed(2); //between ≈60 and ≈976
		if(minV > UST1_80) minV = Math.max(minV, hasDiskNames?ST_IX:MST1_00);
		else minV = Math.max(minV, hasDiskNames?UST1_80:ST2_00_Exterminator);
	}
	ptn = offptn = badptn = ord_ = 0; usedptns = []; usedsmps = [];
	for(o=0; o < 128; o++) {
		x = X.U8(0x1D8+o); if(x > 63) { if(debug>1)_log("STKFault: impossible ptn #"+x); return false; }
		if(ptn <= x) {
			ptn = x+1;
			if(o < ord) { offptn = ptn; if(usedptns.indexOf(x) < 0) usedptns.push(x) }
		}
		if(x >= badptn) badptn = ptn+1;
		if(x) ord_++; //a heuristic to count out suspiciously many zeroes
	} ord_++;
//_log("ptn:"+ptn+" off.ptn:"+offptn+" badptn:"+badptn+" used ptns:"+usedptns)
	songszoffptn = 0x258+0x400*offptn;
	if(songszoffptn > X.Sz()) { if(debug>1)_log("STKFault: patterns cut below "+songszoffptn); return false; }
	ic = tnDxx = notes = 0;
	for(i=0; i < ptn; i++) {
		emptycmd = nDxx = badnote = ptnic = ptnotes = 0;
		for(row=0; row < 64; row++) for (chn=0; chn < 4; chn++) {
			var ofs = 0x258+(i<<10)+(row<<4)+(chn<<2);
			var x = readBytes(ofs, 4);
			if(X.isDeepScan()) {
				if(!emptycmd && !x[0] && !x[1] && !x[2] && !x[3]) {
					emptycmd++; if(emptycmd > 32) minV = ST2_00
				} else emptycmd = 0 }
			var smpn = (x[0]&0xF0) | ((x[2]&0xF0) >> 4), np = ((x[0]&0xF) << 8) + x[1], fx = x[2]&0xF;
//if(debug>2)_log("@"+Hex(ofs)+" ptn "+i+": "+Hex(x[0])+" "+Hex(x[1])+" "+Hex(x[2])+" "+Hex(x[3])+": smp#"+smpn+" np:"+Hex(np)+" fx:"+Hex(fx));
			if(usedptns.indexOf(i) >= 0)
				if(smpn > 15) { ptnic++;
//if(debug>2)_log("   bad smp "+np+" @"+Hex(ofs)+" ptn"+i+" row"+row+" chn"+chn)
				}
				else if(usedsmps.indexOf(smpn) < 0) usedsmps.push(smpn);
			if((X.isDeepScan() || notes < 100) && !npTest(np)) { ptnic += 2; badnote = true;
//if(debug>2)_log("   bad note "+np+" @"+Hex(ofs)+" ptn"+i+" row"+row+" chn"+chn)
			} else if(np) ptnotes++;
			if(X.isDeepScan()) switch(fx) {
			case 1: case 2:
				if(x[3] > 0x1F && minV == UST1_80) minV = hasDiskNames ? UST1_80 : UST1_00;
				else if(fx == 1 && x[3] > 0 && x[3] < 3) minV = Math.max(minV,ST2_00_Exterminator);
				else if(fx == 1 && (x[3] == 0x37 || x[3] == 0x47) && minV <= ST2_00_Exterminator)
					minV = hasDiskNames ? UST1_80 : UST1_00;
				break;
			case 0xB: minV = ST2_00; break;
			case 0xC: case 0xD: case 0xE: minV = Math.max(minV,ST2_00_Exterminator);
				if(fx == 0xD) { emptycmd = 1; if(!x[3] && !row) break; nDxx++ } break;
			case 0xF: if(minV < ST_III) minV = ST_III; break;
			}
		}
		if(ptn >= offptn && ptnic > 64) ptn = offptn;
		else { ic += ptnic; notes += ptnotes; if(badnote) bad = bad.addIfNone("!badnote") }
		if(ic > Math.max(512,ptn*128)) { if(debug>1)_log("STKFault: "+ic+" invalid characters"); return false }
		if(nDxx && nDxx < 3) minV = ST2_00; tnDxx += nDxx;
	}
	if(tnDxx > ptn+32 && minV == ST2_00) minV = MST1_00;
	if(X.isDeepScan()) switch(minV) {
	case UST1_00: tracker = "Ultimate ST 1.0~21"; break;
	case UST1_80: tracker = "Ultimate ST 1.8~2.0"; break;
	case ST2_00_Exterminator: tracker = "ST 2.0 / D.O.C. ST II"; break;
	case ST_III: tracker = "Defjam ST III / Alpha Flight ST IV / D.O.C. ST IV / VI"; break;
	case ST_IX: tracker = "D.O.C. ST IX"; break;
	case MST1_00: tracker = "Master ST 1.0"; break;
	case ST2_00: tracker = "ST 2.0~2"; break;
	default: tracker = "???"
	}
	sz = 0x258+0x400*ptn+allsmpsz;
	var sizediff = Math.abs(sz-X.Sz()), orddiff = Math.abs(ord-ord_);
	var nzord = ord?ord:0.0001, badscore = 0;
	if(ic > 20) badscore++; if(smp < 3 || smp == 16) badscore++;
	if(sizediff > 2048) badscore++; if(sz > X.Sz()) badscore++;
	if(!restartp) badscore++; if(!ord || !ord_ || badptn > ptn+2) badscore++; if(orddiff/nzord > 0.8) badscore++;
	if(!ptn) badscore++; if(notes < 2) badscore++; if(allvols < smp) badscore++; if(bpm0 > 300) badscore++;
//_log("ic="+ic+" smp="+smp+" sizediff="+sizediff+" ord||ptn="+(ord||ord_||ptn)+" maxptn="+(badptn>ptn+2)+" od%="+(orddiff/nzord).toFixed(1)+" notes:"+notes+" bpm0:"+bpm0+" badscore:"+badscore)
	if(badscore >= 4) { //three free strikes
		// If there are enough bad notes, the samples are kinda few or kinda many,
		//the size is more than two patterns' lengths different (especially smaller),
		//either no length or no "official" patterns are there, and the orderlist is mostly zeroes,
		//and it's just a couple notes, and the avg volume's too low, and the start speed is weird...
		if(debug>1)_log("STKFault: too suspicious"); return false }
	if(ic) bad = bad.addIfNone("!baddata="+ic);
	if(sz > X.Sz()) bad = bad.addIfNone("!short");
	return true;
}
if(!bDetected && isSoundtrackerMOD()) {
	sName = "SoundTracker module (.STK,.MOD)"; bDetected = 1;
	if(bad != "") sVersion = "malformed"+bad;
	if(X.isVerbose()) {
		sOptionT(X.SA(0,0x14));
		if(X.isDeepScan()) sOption(tracker,"in:");
		sOption(snames.join(" "),"smps/msg:");
		sOption(" bpm0:"+bpm0+" ord:"+ord+(ord_!=ord?"/"+ord_:"")+" ptn:"+ptn
			+(offptn != ptn ? "/"+offptn : "")+" smp:"+smp+(X.isDeepScan() ? " notes:"+notes : "")
			+" sz:"+outSz(sz))
	}
}


function isPaulRobotham() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_teamPaulRobotham/src/Paul%20Robotham_v1.asm
	trk = X.U16(0,_BE); d2 = X.U16(2,_BE); d3 = X.U16(4,_BE); smp = X.U16(6,_BE); p = 8;
	if(!trk || trk > 4 || !d2 || !d3 || !smp) return false;
	mp = 0; trks = [];
	for(d1=trk;d1;d1--) { t = X.U32(p,_BE); p += 4; if((t>>16) || !t) return false; trks.push(t); if(mp < t) mp = t } //stpos
_log('StartPos -> '+Hex(p));
	for(;d2;d2--) { t = X.I32(p,_BE); p += 4; if(t <= 0 || t%2) return false; if(mp < t) mp = t } //Next1
	d2 = X.I32(p,_BE);
_log('Next1 -> '+Hex(p))
	for(;d3;d3--) { t = X.I32(p,_BE); p += 4; if(t <= 0 || t%2) return false; if(mp < t) mp = t } //Next2
_log('Next2 -> '+Hex(p))
	p += smp*12; if(p != d2) return false;
	for(d2=15;d2;d2--,p+=16) if(!X.c("3F3F3F3F 3F3F3F3F 3F3F3F3F 3F3F3F3F",p)) return false;
	return true
}
if(!bDetected && isPaulRobotham()) {
	sName = "Paul Robotham's module (.DAT+.SSD)"; bDetected = true;
	if(X.isVerbose()) sOption('trk:'+trk+' smp:'+smp)
}


function isDMM() {
	// ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/digitalmusicmaker.cpp
	for(i=0; i < 6; i++) if(!(0xC0 <= X.U8(i*2+1) <= 0xFF)) return false;
	ptnsz = X.U8(0x0C); if(ptnsz & 0x87) return false;
	ptn = -1; for(i=0; i < 0x32; i++) {
		j = X.U8(0x0E+i); if(j > 0x17) return false;
		if(j > ptn) ptn = j;
	} ptn++;
	tempo = X.U8(0x40); if(tempo < 3 || tempo > 30) return false;
	loop = X.U8(0x41); if(loop > 0x32) return false;
	ord = X.U8(0x43); if(!ord || ord > 0x32) return false;
	hss = X.U8(0x44); if(hss < 2 || hss > 0x38) return false;
	bad = false; smp = 0;
	for(i=0; i < 16; i++) {
		smpst = X.U16(0x5A+i*16+9,_LE);
		smplm = X.U16(0x5A+i*16+12,_LE);
		smplp = X.U16(0x5A+i*16+14,_LE);
		if(smpst > smplm || smpst < 49152 || smplm < 49152) return false;
		if(smplp < 49152 || smplp > smplm || smpst > smplp
		  || (smplm > 49152 && smplm-smplp < 6)) {
			bad = true;
			if(!X.isHeuristicScan()) return false
		}
		if(smpst < smplm) smp++;
	}
	return true
}
if(!bDetected && X.isDeepScan() && isDMM()) {
	bDetected = 1; sVersion = "v1.x"; if(bad) sVersion += "/malformed";
	sName = "Digital Music Maker module (.DMM)";
	if(X.isVerbose())
		sOption("tempo:"+tempo+" ord:"+ord+" ptn:"+ptn+" smp:"+smp+" loop:"+loop)
}


function isComposerC67() {
	// from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_c67.cpp
	if(X.Sz() < 0xBA2) return false; //7A2+400
	spd = X.U8(0); if(!isWithin(spd,1,15)) return false; ord = 0; ptn = [];
	for (i=0; i < 256; i++) if((o=X.U8(0x6A2+i)) != 0xFF) { // check orders
		if(o >= 128) return false; if(i > ord) ord = i; if(ptn.indexOf(o) < 0) ptn.push(o) }
	ord++; ptn = ptn.length; nonz = false; lp = X.U8(1); if(lp > ord) return false; smp = opl = smpsz = 0;
	for (i=0; i < 32; i++) { // checking sample records
		if(X.U8(2+i*13+12) || X.U32(0x1A2+i*16) || X.U8(0x3A2+i*13+12)) return false;
		var _l = X.U32(0x1A2+i*16+4), _ls = X.U32(0x1A2+i*16+8), _le = X.U32(0x1A2+i*16+12);
		if(_l > 0xFFFFF) return false; if(_l && _le < 0xFFFFF && (_le > _l || _ls > _le)) return false;
		_opl = !X.c("00000000 00000000 000000",0x542+i*11);
		if(X.U8(0x542+i*11) & 0xF0) return false;
		if(X.U8(0x542+i*11+5) & 0xFC) return false;
		if(X.U8(0x542+i*11+10) & 0xFC) return false;
		if(_l) { smp++; smpsz += _l }  if(_opl) opl++;
	}
	bad = false; ptnend = notes = 0;
	for (i=0; i < 128; i++) { // checking patterns
		p = 0xBA2+X.U32(0x7A2+i*4,_LE); ptnlen = X.U32(0x9A2+i*4,_LE);
		if(!bad) bad = (ptnlen < 3 || ptnlen > 0x1000);
		if(p > 0xFFFFFF || (X.Sz() < p+ptnlen)) return false;
		if(p+ptnlen > ptnend) ptnend = p+ptnlen;
		for(r = 0; p < ptnend;) {
			var cmd = X.U8(p++);
			if(cmd <= 0xC) { p += 2; notes++ }
			else if(cmd >= 0x20 && cmd <= 0x2C) p++;
			else if(cmd == 0x40) r += X.U8(p++);
			else if(cmd == 0x60) break;
			else return false
		}
		if(r > 64) return false;
	}
	sz = ptnend+smpsz;
	return smp+opl > 0
}
if(!bDetected && isComposerC67()) {
	sName = "CDFM/Composer 670 module (.C67)"; bDetected = 1;
	if(bad) sVersion = "malformed!badptn";
	if(X.isVerbose()) sOption('spd:'+spd+' ord:'+ord+(lp?' lp:'+lp:'')+' ptn:'+ptn+' smp:'+smp+' fm:'+opl+' notes:'+notes+' sz:'+outSz(sz))
}


function isComposer670() {
	spd = X.U8(0); if(!isWithin(spd,1,15)) return false;
	ord = X.U8(1); if(!ord || ord >= 128) return false;
	ptn = X.U8(2); if(!ptn) return false; //while 0 is possible, there's no way we'll reliably detect the file then
	smp = X.U8(3); if(smp > 0x20) return false;
	opl = X.U8(4); lp = X.U8(5); if(lp > ord) return false;
	smpp = X.U32(6); if(smpp > X.Sz()) return false;
	p = 0xA; for(i=0; i < ord; i++) if(X.U8(p++) >= ptn) return false;
	ptnpt = p; p += ptn*4;
	for(i=smpsz=0; i < smp; i++,p+=16) {
		if(X.U32(p)) return false;
		var _l = X.U32(p+4), _ls = X.U32(p+8), _le = X.U32(p+12);
		if(!_l || _l > 0xFFFFF) return false; smpsz += _l;
		if(_le < 0xFFFFF && (_le > _l || _ls > _le)) return false
	}
	for(i=0; i < opl; i++,p+=11) {
		if(X.U8(p) & 0xF0) return false;
		if(X.U8(p+5) & 0xFC) return false;
		if(X.U8(p+10) & 0xFC) return false;
	}
	for(i=notes=0,ptnp=p,bad=false; i < ptn; i++) {
		p = ptnp+X.U32(ptnpt+i*4); if(p > smpp-3) return false;
		if(i >= ptn-1) ptnend = smpp; else ptnend = ptnp+X.U32(ptnpt+i*4+4);
		if(!isWithin(ptnend - p, 3,0x1000)) return false;
		for(r = 0; p < ptnend;) {
			var cmd = X.U8(p++);
			if(cmd <= 0xC) { p += 2; notes++ }
			else if(cmd >= 0x20 && cmd <= 0x2C) p++;
			else if(cmd == 0x40) r += X.U8(p++);
			else if(cmd == 0x60) break;
			else return false
		}
		if(r > 64) return false
	}
	sz = smpsz+smpp;
	return opl+smp > 0
}
if(!bDetected && isComposer670()) {
	sName = "CDFM/Composer 670 module (.670)"; sVersion = 'compact'; bDetected = 1;
	if(bad) sVersion = "malformed!badptn";
	if(X.isVerbose()) sOption('spd:'+spd+' ord:'+ord+(lp?' lp:'+lp:'')+' ptn:'+ptn+' smp:'+smp+' fm:'+opl+' notes:'+notes+' sz:'+outSz(sz))
}


function isSeanConnolly() {
	//from UADE player check RE
	if(!X.c("6000.... 6000.... 6000")) return false;
	a0 = 2+X.I16(2,_BE);
	if(X.c("48E77FFE E98841FA",a0)) v = 2;
	else if(X.c("48E740F0 4A006B0A",a0)) v = 3;
	else return false;
	return true
}
if(!bDetected && isSeanConnolly()) {
	sName = 'Sean "Odie" Connolly\'s module (.SCN)'; sVersion = "v"+v; bDetected = 1
}


function isAVP() {
	//from reversing the eagleplayer
	d1 = "48E7FCFE"; a0 = 0;
	if(X.c(d1)) { // @d440 -> d47c
		if(X.c("45FA",0xDC)) return false;
		if(!X.c("E9417000 41FA",4)) return false;
		if(X.c(d1,0x94) || X.c(d1,0xA4) || X.c(d1,0xA8)) {
			fmt = 0; base = 8; return true }
		else return false
	} else if(X.c("2F0841FA")) { //@d444 -> d4ac
		a0 += 4; a0 += X.I16(a0,_BE) + 0x1C;
		if(!X.c("45FA",a0+0xDC)) return false;
		a0 += 4; if(!X.c("E9417000 41FA",a0)) return false; a0 += 4;
		if(X.c(d1,a0+0x8C) || X.c(d1,a0+0x9C) || X.c(d1,a0+0xA0)) {
			fmt = 0; base = a0; return true }
	} else if(X.c(d1,0x1C)) { //@d44c -> d4b0
		a0 += 0x1C; if(!X.c("45FA",a0+0xDC)) return false;
		a0 += 4; if(!X.c("E9417000 41FA",a0)) return false; a0 += 4;
		if(X.c(d1,a0+0x8C) || X.c(d1,a0+0x9C) || X.c(d1,a0+0xA0)) {
			fmt = 0; base = a0; return true }
	} else if(X.c("6000")) { //@d452 -> d4d0
		if(!X.c("6000.... 6000.... 6000.... 6000.... 6000.... 6000.... 6000",4)) return false;
		if(X.c("6000",0x20) && !X.c("6000",0x24)) return false;
		a0 = 0xE; a0 += X.I16(a0,_BE);
		if(X.c(d1,a0)) { //@d50e -> d4be
			if(!X.c("45FA",a0+0x10C) && !X.c("E942",a0+0x112)) return false;
			a0 += 4; if(!X.c("E9417000 41FA",a0)) return false; a0 += 4;
			if(X.c(d1,a0+0x8C) || X.c(d1,a0+0x9C) || X.c(d1,a0+0xA0)) {
				fmt = 0; base = a0; return true }
		}
		a0 = 0x1A+X.I16(0x1A,_BE); if(!X.c(d1+"43FA",a0)) return false;
		fmt = 1; base = a0; return true
	} else { //@d45a
		do { if(X.c("2F0841FA",a0+0x1C)) break; a0 += 2 } while(a0 < 0xA);
		if(a0 == 0xA) return false; //else -> d46e
		i = 0x4C; do { if(X.c(d1,a0)) break; a0 += 2; i-- } while(i);
		if(!i) return false; //else -> d4be
		if(!X.c("45FA",a0+0x10C) && !X.c("E942",a0+0x112)) return false;
		a0 += 4; if(!X.c("E9417000 41FA",a0)) return false; a0 += 4;
		if(X.c(d1,a0+0x8C) || X.c(d1,a0+0x9C) || X.c(d1,a0+0xA0)) {
			fmt = 0; base = a0; return true }
	}
	return false
}
if(!bDetected && isAVP()) {
	sName = "Activision Pro module (.AVP)"; sVersion = "f."+fmt; bDetected = 1;
	if(X.isVerbose()) {
		sOption(Hex(base),"base:");
		//TODO: derive some info
	}
}


function isJP() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/JasonPage/src/Jason%20Page_v5.s
	fmt = 0;
	if(X.c("0002") && !(X.U8(3)&1)) for(;;) {
		t = X.I16(4,_BE); if(t&1) break; if(X.I16(t)&1) break;
		d0 = X.I16(0x30,_BE); if(d0 > X.Sz()) break;
		for(a1 = 2; a1 <= 0x2E; a1 += 2) {
			t = X.U16(a1,_BE); if(!t || (t&1) || t > X.Sz() || d0 <= t) break;
		}
		if(a1 < 0x30) break;
		if((X.U16(t,_BE)&0xF00) == 0xF00) fmt = 2; else fmt = 1; break
	}
	if(!fmt)
		if(X.I16(0,_BE) || X.I16(0x80,_BE) || !X.c("00000CBE",0x84)
		  || !X.c("000308BE",0xCB6) || !X.c("000309BE",0xCBA)) return false;
		else fmt = 3;
	smpsz = smp = 0; x = 1;
	if(fmt == 3) {
		sz = X.U32(0x8BA,_BE)+2; if(sz < 0x8BC || sz > X.Sz()) return false;
		t0 = X.U32(0,_BE);
		for(i = 4; i < 0x80; i += 4) {
			t1 = X.U32(i,_BE); t = t1-t0; if(t < 0) return false;
			smpsz += t; if(t) smp++; t0 = t1 }
		smpsz += 0x80;
		if(sz == 7382 && smpsz == 19290) x = 16 /*realms ingame*/;
		else { x = 0; a1 = 0x6BE; do {
				d0 = X.U32(a1,_BE); a1 += 4; if(X.U16(d0,_BE) == 0xFF00) break; x++
			} while(a1 < sz) }
	} else {
		sz = X.U16(0x30,_BE); a0 = X.U16(0xC,_BE)+2; a1 = X.U16(0x1C,_BE); x = (a1-a0)>>1;
		for(i = X.U16(2,_BE); i < sz-4; i += 4) { slen = X.U32(i,_BE); smpsz += slen; if(slen) smp++ }
	}
	return true;
}
if(!bDetected && isJP()) {
	switch(fmt) {
	case 1: sName = "Jason Page's module (.JP)"; break; case 2: sName = "Jason Page's module (.JPN)"; break;
	case 3: sName = "Jason Page's old module (.JP)"; } sVersion = "f."+fmt; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		sOption("smp:"+smp+" smp.sz:"+smpsz+" song sz:"+outSz(sz))
	}
}


function isMikeDavies() {
	//ref reversing the eagleplayer
	a2 = 0xF2;
	if(!X.c("0C40",a2)) { a2 -= 6; if(!X.c("33C0",a2)) return false }
	if(!X.c("48E7FFFE 61064CDF 7FFF4E75",a2-12)) return false;
	a2 = X.fSig(a2,0xFFFF,"6000")+2; if(a2 < 2) return false;
	t = X.I16(a2,_BE); if(!t || t < 0 || (t&1)) return false;
	a2 += t; if(!X.c("2D58",a2)) return false;
	a2 = 0;
	function findOrPlus(s) { while(a2 < X.Sz()) { if(X.c(s,a2)) break; a2 += 2 } } // couple helper functions
	function plusAndFind(s) { while(a2 < X.Sz()) { a2 += 2; if(X.c(s,a2-2)) break } }
	function errorP(s) { _log('MD: '+s); patchClear(); return false }
	var a4 = []; // the "wach get out!!!" memory part. Local vars
	//we skip a4.push(0) *2 unlike the original code, so analogously, a4[] points @d138 instead of d134
	findOrPlus("48E7FFFE"); a4.push(a2>>16); a4.push(a2&0xFFFF); //d3a2~d3ae
	findOrPlus("33FC0001"); //d3b0~d3ba
	a2 += 4; rel = X.U32(a2,_BE);
	plusAndFind("41F9"); a4.push(a2>>16); a4.push(a2&0xFFFF);
	d1 = X.I32(a2,_BE); //d3c0~d3c8
	plusAndFind("B1FC"); d0 = X.U32(a2,_BE); //d3ca-d3d0
	x = d0 = (d0-d1) >> 4;

	plusAndFind("41F9"); a4.push(a2>>16); a4.push(a2&0xFFFF); //d3da~d3e0
	plusAndFind("B1FC"); a4.push(a2>>16); a4.push(a2&0xFFFF); //d3e2~d3e8
	plusAndFind("303C"); //d3ea~d3ee
	findOrPlus("48E7FFFE ........ 6100"); a4.push(a2>>16); a4.push(a2&0xFFFF); //d3f6~d40a
	plusAndFind("41F9"); a4.push(a2>>16); a4.push(a2&0xFFFF);
	d2 = X.U32(a2,_BE); //d40c~d414
	plusAndFind("B1FC"); d3 = X.U32(a2,_BE); //d416~d41c
	d3 = d4 = d3-d2; var mo = d3%0x18; d3 = Util.divu64(d3,0x18); //d41e~d42e
	if(mo) {
		d4 += 4; mo = d4%0x18; d4 = Util.divu64(d4,0x18);
		if(mo) return errorP('@d436 error 5'); //originally, error code 5
		d3 = d4;
		if(X.c("4654443C",0x7D8)) { //d44c~44; patch #1 adjusting 3 positions
			a3 = 0x770;
			for(d4=2; d4 >= 0; d4--) {
				a3 -= 4; wpU32be(a3+4,rpU32be(a3));
			}
		}
	}
	a4.push(d3>>16); a4.push(d3&0xFFFF); //d45a
	plusAndFind("4BF9"); d5 = rpU32be(a2)-rel; //d45c~64
	if(a2 > X.Sz()) return errorP('@d464 a2 too high');
	if(!X.isDeepScan()) { patchClear(); return true }

	a4.push(d5>>16); a4.push(d5&0xFFFF); //d466
	a4.push(0); //d468
	a5 = 0x4ED2; a4.push(a5>>16); a4.push(a5&0xFFFF); //d46a; delitrackerglobals

	//d472~d4fa: patch #2 (changes certain offsets in the player code to relative)
	a3 = (a4[0]<<16)+a4[1];
	p2cycle: while(a3 < X.Sz()) { //from d472...
		switch(rpU16be(a3)) {
		case 0x33EE: case 0x33FC: a3 += 2; //d4ce & ↓
		case 0x3039: case 0x33C0: case 0x33C1: case 0x3C39: case 0x41F9:
		case 0x4279: case 0x4A79: case 0x4BF9: case 0x4DF9: case 0x5379: case 0xB1FC: //d4d0
			a3 += 2; a3_ = rpU32be(a3); if((a3_>>16) == 0x00DF) break;
			wpU32be(a3,a3_-rel); a3 += 4; break;
		case 0x33F9: //d4de
			a3 += 2; a3_ = rpU32be(a3);
			if((a3_>>16) == 0x00DF) a3 += 4; else { wpU32be(a3,a3_-rel); a3 += 4 }
			a3_ = rpU32be(a3); if((a3_>>16) != 0x00DF) { wpU32be(a3,a3_-rel); a3 += 4 } break;
		default: a3 += 2; if(a3 >= d5) break p2cycle;
		}
	}
	d4 = d3; //d4fe
	a2 = (a4[10]<<16)+a4[11]; //d500
	a2 = rpU32be(a2); //d504
	a2_ = rpU32be(a2)-rel; wpU32be(a2,a2_); //d506~8
	d2 = a2_; //d50a~c
	d5 = a2_+2*rpU32be(a2+4); a2 += 0x18; a2_ = rpU32be(a2); d3 -= 2; //patch1; d50e~1a
	if(d3 > 0x80) return errorP('@d51a badsmp:'+Hex(d3));
	while(d3 >= 0) { //d51c & d53a
		a2_ -= rel; wpU32be(a2,a2_); //d51e~d520
		if(d5 <= a2_) d5 = a2_+2*rpU32be(a2+4);
		if(d2 > a2_) d2 = a2_; //d530~d534
		a2 += 0x18; a2_ = rpU32be(a2); //patch1
	d3--}
	a4.push(a2>>16); a4.push(a2&0xFFFF); //d53e. a2+4 = start of samples
	a2 = rpU32be((a4[4]<<16)+a4[5]); //d540~4
	a3 = (a4[6]<<16)+a4[7]; a3_ = rpU32be(a3); //d546
	d3 = Util.divu64(a3_-a2, 0x18); //d54c~50
	a4.push(d3); //d554
	smp = d3+d4; //d556~8
	d4 = d3; a2_ = rpU32be(a2); //d55c
	if(d4 > 0x80) return errorP('@d55e d4='+Hex(d4)+' > 80h');
	while(d4 > 0) { //d55e~60 & d57e
		a2_ -= rel; wpU32be(a2,a2_); //d562~4
		if(d5 <= a2_) d5 = a2_+2*rpU32be(a2+4);
		if(d2 > a2_) d2 = a2_; //d574~8
		a2 += 0x18; a2_ = rpU32be(a2);
	d4--}
	if(a2 > (a4[19]<<16)+a4[20]) { a4[19] = a2>>16; a4[20] = a2&0xFFFF } //d582~8
	d0 <<= 2; //d58e
 	a2 = rpU32be((a4[2]<<16)+a4[3]); a2_ = rpU32be(a2); //d592~6
	d3 = 0;
	do {
		if(a2_ != 0xFFFFFFFF && d3 < a2_-rel) d3 = a2_-rel; //d59c~a8
		a2 += 4; a2_ = rpU32be(a2); //d5aa
	d0--} while(a2 < X.Sz() && d0 > 0);
	a3 = (a4[8]<<16)+a4[9]; //d5b0
	while(a3 < X.Sz()) { a3 += 2; if(X.c("0C40",a3-2)) break } //d5b4~8
	d0 = rpU16be(a3+0x16); a4.push(d0); //d5ba~e
	if(d5 > d3) { //d5c8~a
		if(d5 > X.Sz()) return errorP('@d5ce error 1C'); //d5cc~e; return errorcode 1C
		a2 = d2; a1 = (a4[19]<<16)+a4[20];
		do { //from d5d8...
			if(rpU16be(a1) == d0) { a1 += 6; wpU32be(a1,rpU32be(a1)-rel) } //d5d8~e0; patching #3?...
			a1 += 2; // d5e2
		} while(a1 != a2 && a1 < X.Sz())
		a2 = d5;
	} else { //from d5ee
		if(d3 > X.Sz()) return errorP('@d5ee error 1C'); //d5ee~f0; return errorcode 1C
		a2 = d3; //d5f4; d4 and a3 = filesize
		while(a2 < X.Sz()) { //d5fc -> sub_d65c
			a2 += 2; if([0x94,0xFE,0xFF,0x1FE,0x1FF].indexOf(rpU16be(a2-2)) >= 0) break }
		a1 = d5; a1_ = rpU32be(a1); //d5f8
		do { // from d5fa...
			if(d0 == (a1_>>16)) {
				a1 += 6; a1_ = rpU32be(a1)-rel; wpU32be(a1,a1_);
				if(d3 < a1_) d3 = a1_;
			}
			a1 += 2; a1_ = rpU32be(a1); //d60a
		} while(a2 != a1); //d60c~e to d5fa
		if(a2 <= d3) { //d610~2
			a2 = d3;
			while(a2 < X.Sz()) {
				a2 += 2; if([0x94,0xFE,0xFF,0x1FE,0x1FF].indexOf(rpU16be(a2-2)) >= 0) break; }
		}
	}
	patchClear();
	sz = a2; //d618~a
	smpsz = d5-d2;
	songsz = a2-smpsz;
	return true
}
if(!bDetected && isMikeDavies()) {
	sName = "Mike Davies module (.MD)"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		if(X.isDeepScan()) sOption('smp:'+smp+' songsz:'+Hex(songsz)+' smpsz:'+Hex(smpsz)+' sz:'+outSz(sz))
	}
}

function isMXDRV() {
	// fmt https://www.vector.co.jp/download/file/dos/art/fh003454.html / DOC/MDXFORM.DOC
	// ref ditto / SRC/MDX2MUS.ASM
	// useful links: https://gorry.haun.org/mx/index_e.html
	if(X.Sz() < 4+2+9*2 || X.Sz() > 0xFFFF) return false; //minimum header
	da1 = X.fSig(0,Math.min(0x400,X.Sz()),"0D0A1A"); //the longest name encountered on modland was 99 bytes long
	if(da1 < 0) return false;
	for(i = 0; i < da1; i++) { c = X.U8(i); if(c < 0x20 && c != 9 && c != 0x1B) return false }
	t = X.SC(0,da1,"SJIS");
	p = X.fSig(da1+3,Math.min(15,X.Sz()-da1),"00"); if(p < 0) return false;
	if(da1+3 != p)
		pdxfn = X.SA(da1+3,p);
	else pdxfn = "";
	p++; ofs = p;
	comp = false;
	if(X.SA(ofs+4,4) == "LZX ") {
		lzxsz = X.U32(ofs+0x12,_BE);
//_log("ofs="+ofs+", lzxsz = "+lzxsz+" += "+(ofs+lzxsz));
		if(lzxsz) comp = true;
		return true; // stop right there because omg no
	}

	vd = X.U16(ofs,_BE)+ofs;
	if(vd > X.Sz()) return false;
	p += 2;
	var chn = X.U16(p,_BE)+ofs;
	if(chn < p+2 || chn > X.Sz()-2) return false;
	ch = (chn-p)>>1;
	if(ch != 16 && ch != 9) return false;
	oldchn = chn;
	for(k=1; k < ch; k++) { //check the rest of channels
		p += 2; if(p > X.Sz()-2) return false;
		chn = X.U16(p,_BE)+ofs;
		if(chn < oldchn || chn > X.Sz()-2) return false;
		oldchn = chn
	}
	if(vd < chn) return false;
	return true;
}
if(!bDetected && X.isDeepScan() && isMXDRV()) {
	sName = "X68k MXDRV module (.MDX)"; bDetected = 1;
	if(pdxfn != "") sVersion = "+ "+pdxfn;
	if(ch==16) sVersion += "#EX-PCM";
	if(comp) sVersion += "(compressed)";
	if(X.isVerbose()) {
		sOption(t);
	}
}


function isSilmarils() {
	//from UADE player RE
	a0 = 0; if(!X.c("0016",a0+4)) { a0 += 6; if(!X.c("0016",a0+4)) return false }
	if(!X.c("0000 00000000 00000000 ....0020 ....0020",a0+6)) return false;
	d1 = X.U32(a0+0xE,_BE); if(d1&1) return false;
	if(!X.c("00000058",a0+d1)) return false;
	a2 = a0+0x58; a3 = 0; if(X.c("0016",0xA)) a3 += 6;
	a3 += X.U32(a3+0xE,_BE); a3 += X.U32(a3,_BE);
	a2 = a3; x = smp = sz = d0 = d3 = 0;
	while(a3 < X.Sz()) {
		a3 = a2; a2 += 4;
		if(!X.U32(a3,_BE) || X.U8(a3,_BE)) { //→e684
			a3 = sz; sz += X.U32(a3+2,_BE); break;
		} else {
			a3 += X.I32(a3,_BE); if(a3 > sz) sz = a3; if(a3 > X.Sz()) return false;
			if(X.c("FF02",a3+6)) x++;
			else {
				t = X.U32(a3+2,_BE); if(t > X.Sz()) return false;
				smp++; d3 += t; if(d0 < t) d0 = t
			}
		}
	}
	if(a3 > X.Sz() || sz > X.Sz()) return false;
	smpsz = d3;
	return true
}
if(!bDetected && isSilmarils()) {
	sName = "Silmarils module (.MOK)"; bDetected = 1;
	bad = "";
	if(x > 3) bad = bad.addIfNone("!subsongs>3");
	if(smp > 0x20) bad = bad.addIfNone("!smp>32");
	if(bad != "") sVersion = sVersion.appendS("malformed"+bad,'/');
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		sOption("smp:"+smp+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz))
	}
}


function isDUX() {
	//from the UADE player's RE
	sz = X.U32(0,_BE); if(sz > X.Sz() || (sz&1)) return false;
	if(!X.c("FFFF FFFFFFFF",sz-6)) return false;
	for(smp=0,a0=4,d1=8; d1; a0+=12,d1--) { //samples
		t = X.U32(a0,_BE); if((t&1) || t > sz) return false; if(t) smp++
	}
	smpsz = X.U32(a0,_BE); a1 = a0+smpsz+0x10;
	d0 = 0; while(a1 < X.Sz()) { t = X.I16(a1,_BE); if(t < 0) break; d0 += t; a1 += 4 }
	d0 >>= 4; unpsz = Util.div64(d0*14187, 44336);
	for(d1=3; d1; a0 += 4, d1--) { t = X.U32(a0,_BE); if(!t || (t&1) || t > sz) return false } //voices?
	if(!X.c("FFFF FFFFFFFF",a0+t-6)) return false;
	return true
}
if(!bDetected && isDUX()) {
	sName = "GT Game Systems module (.DUX)"; bDetected = 1;
	if(X.isVerbose()) {
		ord = 1+Util.div64(X.U32(0x68,_BE)-smpsz, 0x3C);
		sOption("ord:"+ord+" smp:"+smp+" songsz:"+Hex(unpsz)+" smpsz:"+Hex(smpsz-0x70)+" sz:"+outSz(sz))
	}
}


function isBuzzic10() { // doesn't detect empty modules with no sound, as the format's already too sparse
	if(X.Sz() < 0xAEA8) return false;
	susv = 0; // suspicious values like zero volume
	ins = X.U32(0x4A00); if(!ins || ins > 128) return false;
	trk = X.U32(0x8E8C); if(!trk || trk > 64) return false;
	//not that many tracks even fit on screen tho', and there's no horizontal scrolling, so it'd be much fewer
	//however, technically, it does have the format space even to fit 128 tracks. But only 64 names for them.
	ptn = X.U32(0x8E84); if(!ptn || ptn > 128) return false;
	gvol = X.F32(0x8E94); if(gvol < 0 || gvol > 2) return false; gvol = Math.round(gvol*100)+'%';
	bpm = X.U8(0x8E98); if(bpm < 10) susv++;
	start = X.U32(0x8E9C); ord = X.U32(0x8EA0);
	if(!ord) susv++; if(ord > 127 || start > ord) return false; ord++;
	floop = X.U32(0x8EA4); if(floop > 1) return false;
	for(i = 0; i < ins; i++) { //test instruments
		p = i*0x94; if(p+0x93 > Math.min(X.Sz(), 0x4A00)) return false;
		var mute = X.U8(p+0x80); if(mute > 1) return false;
		var note = X.I8(p+0x81); if(!note) susv++;
		if(note < 0 && -note > trk) return false; //track link has to call a track
		var nend = X.I8(p+0x82); if(nend < 0 && -nend > trk) return false;
		var len = X.U8(p+0x83); if(len > 127) return false;
		var vol = X.I8(p+0x84); if(vol < 0 && -vol > trk) return false;
		if(!len) susv++; if(!vol) susv++;
		var osc = X.U8(p+0x85); if(osc > 3) return false; if(osc < 3 && !note) susv++; //only noise doesn't need the note
		var dtn = X.I8(p+0x86); if(note > 0 && note+dtn <= 0) return false; if(dtn < -32 || dtn > 64) susv++;
		var atk = X.U8(p+0x87), dcy = X.U8(p+0x88), sus = X.U8(p+0x89);
		if(dcy < atk || sus < dcy || sus > 127 || atk > 127 || sus > 127) return false;
		if(!atk && !dcy && !sus) susv++;
		var cut = X.I8(p+0x8A); if(cut < 0 && -cut > trk) return false; //cutoff mod @ +0x8B
		var res = X.I8(p+0x8C); if(res < 0 && -res > trk) return false;
		var dly = X.U8(p+0x8D), rep = X.U8(p+0x8E), damp = X.U8(p+0x8F);
		if(dly > 127 || rep > 127 || damp > 127) return false;
		var lvwrap = X.I8(p+0x90);
		if(lvwrap < 0 && lvwrap > trk) return false;
		var lvhard = X.I8(p+0x91);
		if(lvhard < 0 && lvhard > trk) return false;
		var comprlo = X.U8(p+0x92), comprhi = X.U8(p+0x93);
		if((comprlo > 127 || comprhi > 127) || (!comprhi && comprlo)
			|| (comprhi && comprhi <= comprlo)) return false // they CAN both be 0...
	}
	if(susv > ins*3) { _log("BUZFault: data too suspicious"); return false }
	for(i = 0x4A04; i < 0x4E84; i++) if(X.U8(i) > 127) return false; // testing patterns
	for(i = 0x4E84; i < 0x4E84+64*128; i++) //testing tracks... all 64 of'em
		if(X.U8(i) > ptn) { _log("BUZFault @"+Hex(i)+": bad ptn"); return false }
	if(!isAllZeroes(0x4E84+64*128,64*128))
		return false; // the uneditable tracks would stay as 0, fair enough
	// there are absolutely no checks made on the text values so there's no meaning in checking those
	return true
}
if(!bDetected && X.isDeepScan() && isBuzzic10()) {
	sName = "Buzzic module (.BUZ)"; sVersion = 'v1.0'; bDetected = 1;
	sOption('trk:'+trk+' ord:'+ord+(start?' (from '+start+')':'')+' ptn:'+ptn+' ins:'+ins
		+' bpm:'+bpm+' gvol:'+gvol+' sz:44712')
}


function isBuzzic11() { //detection only slightly different from the above
	if(X.Sz() < 0xB0A8) return false;
	susv = 0;
	ins = X.U32(0x4C00); if(!ins || ins > 128) return false;
	trk = X.U32(0x908C); if(!trk || trk > 64) return false; // this editor has better limits on max tracks
	ptn = X.U32(0x9084); if(!ptn || ptn > 128) return false;
	gvol = X.F32(0x9094); if(gvol < 0 | gvol > 1.91) return false; gvol = Math.round(gvol*100)+'%';
	bpm = X.U8(0x9098); if(bpm < 10) susv++;
	start = X.U32(0x909C); ord = X.U32(0x90A0);
	if(!ord) susv++; if(ord > 127 || start > ord) return false; ord++;
	floop = X.U32(0x90A4); if(floop > 1) return false;
	for(i = 0; i < ins; i++) {
		p = i*0x98; if(p+0x97 > Math.min(X.Sz(), 0x4A00)) return false;
		var mute = X.U8(p+0x80); if(mute > 1) return false;
		var note = X.I8(p+0x81); if(!note) susv++;
		if(note < 0 && -note > trk) return false;
		var nend = X.I8(p+0x82); if(nend < 0 && -nend > trk) return false;
		var len = X.U8(p+0x83); if(len > 127) return false;
		var vol = X.I8(p+0x84); if(vol < 0 && -vol > trk) return false;
		if(!len) susv++; if(!vol) susv++;
		var osc = X.U8(p+0x85); if(osc > 3) return false; if(osc < 3 && !note) susv++;
		var dtn = X.I8(p+0x86); if(note > 0 && note+dtn <= 0) return false; if(dtn < -32 || dtn > 64) susv++;
		var atk = X.U8(p+0x87), dcy = X.U8(p+0x88), sus = X.U8(p+0x89);
		if(dcy < atk || sus < dcy || sus > 127 || atk > 127 || sus > 127) return false;
		if(!atk && !dcy && !sus) susv++;
		var cut = X.I8(p+0x8A); if(cut < 0 && -cut > trk) return false;
		var res = X.I8(p+0x8C); if(res < 0 && -res > trk) return false;
		var dly = X.U8(p+0x8D), rep = X.U8(p+0x8E), damp = X.U8(p+0x8F);
		if(dly > 127 || rep > 127 || damp > 127) return false;
		var lvwrap = X.I8(p+0x90);
		if(lvwrap < 0 && lvwrap > trk) return false;
		var lvhard = X.I8(p+0x91);
		if(lvhard < 0 && lvhard > trk) return false;
		var comprlo = X.U8(p+0x92), comprhi = X.U8(p+0x93);
		if((comprlo > 127 || comprhi > 127) || (!comprhi && comprlo)
			|| (comprhi && comprhi <= comprlo)) return false
		var pan = X.I8(p+0x94); if(pan > 118) return false; // funny—looks like an input filter typo, ah well
		var fltfreq = X.I8(p+0x95), fltres = X.I8(p+0x97); //frqmod @ +0x96
		if((fltfreq < 0 && -fltfreq > trk) || (fltres < 0 && -fltres > trk)) return false
	}
	if(susv > ins*3) { _log("BUZ1Fault: too suspicious"); return false }
	for(i = 0x4C04; i < 0x5084; i++) if(X.U8(i) > 127) return false; // testing patterns
	for(i = 0x5084; i < 0x7084; i++) //testing tracks... orderlists? all 64 of'em
		if(X.U8(i) > ptn) { _log("BUZ1Fault @"+Hex(i)); return false }
	if(!isAllZeroes(0x7084,0x2000)) return false;
	return true
}
if(!bDetected && X.isDeepScan() && isBuzzic11()) {
	sName = "Buzzic module (.BUZ)"; sVersion = 'v1.1'; bDetected = 1;
	sOption('trk:'+trk+' ord:'+ord+(start?' (from '+start+')':'')+' ptn:'+ptn+' ins:'+ins
		+' bpm:'+bpm+' gvol:'+gvol+' sz:45224')
}


function isBuzzic2() {
	//from the app sources
	if(!X.c("'buz2'0200")) return false;
	gvol = X.F32(8); if(!isWithin(gvol,0,2)) return false;
	bpm = X.U32(0xC); if(!isWithin(bpm,10,200)) return false;
	startpos = X.U32(0x10); ord = X.U32(0x14)+1; if(startpos >= ord) return false;
	looping = X.U8(0x18); if(looping > 1) return false;
	nV = X.U16(6); p = 0x9C; sz = -1; bad = "";
	maxptn = maxtrk = 64; maxtml /*max template melody length*/ = 128;
	ptn = X.U32(p); trk = X.U32(p+4); p += 8;
	if(nV >= 1) { maxptn = X.I32(p); maxtrk = X.I32(p+4); maxtml = X.I32(p+8); p += 12 }
	p += maxptn*16+maxtrk*maxtml;
	trks = [];
	for(i=0; i < 64; i++) { t = X.SA(p,0x40).trim(); if(t != "") trks.push(t); p += 0x40 }
	ptns = [];
	for(i=0; i < 64; i++) { t = X.SA(p,0x40).trim(); if(t != "") ptns.push(t); p += 0x40 }
	ins = X.U32(p); p += 4;
	if(p > X.Sz()) return false;
	inss = []; totalops = 0;
	for(i=0; i < ins; i++) {
		t = X.SA(p,0x80).trim(); if(t != "") inss.push(t);
		var opcnt = X.U32(p+0x184); totalops += opcnt; p += 0x2410+opcnt*0x228;
	}
	sz = p; if(sz > X.Sz()) bad = bad.addIfNone('!short');
	return true;
}
if(!bDetected && isBuzzic2()) {
	sName = "Buzzic module (.buz2)"; bDetected = 1; sVersion = 'v2.'+nV.padStart(2,'0');
	if(X.isVerbose()) {
		//sOptionT(addEllipsis(trks.join(','),0xA0,0x80),'trks:"','"');
		//sOptionT(addEllipsis(ptns.join(','),0xA0,0x80),'ptns:"','"');
		//sOptionT(addEllipsis(inss.join(','),0xA0,0x80),'insts:"','"');
		sOption('bpm:'+bpm+' trk:'+trk+' ptn:'+ptn+' ins:'+ins+' g.vol:'+Math.round(gvol*100)+'% ops:'+totalops+' sz:'+outSz(sz))
	}
}


function isInfogramesRH2() {
	if(!X.c("000200")) return false; //+04: speed? I saw 1 through 7
	if(!X.c("FF000F",X.U16(4,_BE)+1)) return false;
	sz = X.U16(0x10,_BE)+2;
	for(i=10,oldp = X.U16(8,_BE); i < 0x20; i += 2) { if(i == 0x10) continue;
		p = X.U16(i,_BE); if(p <= oldp || p+1 > X.Sz()) return false; if(!X.c("FF",p+1)) return false;
		oldp = p
	}
	return true;
}
if(!bDetected && isInfogramesRH2()) {
	sName = "Infogrames (RobHubbard2) module (.DUM&.INS)"; bDetected = 1;
	if(X.isVerbose()) sOption(outSz(sz),'sz:')
	//TODO get info
}


function isSpecialFX() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SpecialFX/Special%20FX_v2.asm
	if(!X.c("6000")) return false;
	d2 = X.U16(2,_BE); if(d2 <= 0 || d2%2) return false; a1 = 2; a0 = 4;
	for(i = 0; i < 3; i++) { if(!X.c("6000",a0)) return false; t = X.U16(a0+2,_BE); a0 += 4; if(t <= 0 || t%2) return false }
	a1 += d2;
	if(X.c("6000",a0)) {
		for(i = 0; i < 3; i++) { if(!X.c("6000",a0)) return false; t = X.U16(a0+2,_BE); a0 += 4; if(t <= 0 || t%2) return false }
		if(X.c("6000",a0)) return false;
	} else { //OldFor
		if(!X.c("6100",a1)) return false; a1 += 4
	}
	if(!X.c("41F9",a1)) return false; a1 += 6; if(!X.c("43F9",a1)) return false;
	//TODO subsongs
	return true;
}
if(!bDetected && isSpecialFX()) {
	sName = "M.Cannon & J.Dunn's Special FX module (.JD)"; bDetected = 1
}

function isSpecialFXST() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SpecialFX_ST/src/Special%20FX%20ST_v3.asm
	if(X.Sz() < 2300) return false;
	if(!X.c("00000000") && !X.c("0101") && !X.c("00000101") && !X.c("6000")) return false;
	a1 = 140; a2 = X.Sz()-4; a0 = 0;
	while(a0 < X.Sz()) { if(X.c("00090800",a0)) break; a0 += 2; if(a1 == a0) return false }
	if(!X.c("01120900", a0+74) && !X.c("02240A00", a0+74*2) && !X.c("00090800", a0+74*3)
	  && !X.c("01120900", a0+74*4) && !X.c("02240A00", a0+74*5)) return false;
	a0 = a1+1860;
	while(a0 < X.Sz()) { if(X.c("0EF80E10",a0)) break; a0 += 2; if(a0 >= a2) return false }
	//init
	sz = -1; var d1 = 10, a1 = d7 = p1 = p2 = 0;
	while(a1 < X.Sz()) { d0 -= 2; a1 += 2; if(X.c("E740",a1-2)) break } //Find1
	x = X.U16(a1-12,_BE);
	p1 = a1-4+X.U16(a1-4,_BE);
	while(a1 < X.Sz()) { //Find2
		if(X.c("101A234A",a1)) {
			a1 += d1 }
		else if(X.c("4880D040",a1)) break; //noSpec, break to OK2
		a1 += 2
	}
	p2 = a1-2+X.U16(a1-2,_BE); //useful
	while(a1 < X.Sz()) { if(X.c("08C70007",a1)) break; a1 += 2 } //Find3
	a2 = a1; if(a1 >= X.Sz()) return false;
	while(a2 > 0) { a2 -=2; if(X.c("41FA",a2)) break } //Find4
	while(a1 < X.Sz()) //Find5
		if(X.c("41F9",a1)) { a2 = X.U16(a1+2,_BE); break }
		else { t = X.c("41FA",a1); a1 += 2; if(t) { a2 = a1+X.U16(a1,_BE); break }
	}
	while(a1 < X.Sz()) { if(X.c("08870002",a1)) break; a1 += 2 } //Find6
	a2 = a1+6; //OK6
	if(X.c("41F9",a2-2)) { a2 = X.U16(a2,_BE); d7 = a2 }
	else a2 += X.U16(a2,_BE);
	while(a1 < X.Sz()) { if(X.c("08C70002",a1)) break; a1 += 2 } //Find7
	a1 += d1; a2 = a1;
	if(X.c("41F9",a2-2)) a2 = X.U16(a2,_BE); else a2 += X.U16(a2,_BE);
	while(a1 < X.Sz()) { t = X.c("41FA",a1); a1 += 2; if(t) break } //Find8
	a2 = a1+X.U16(a1,_BE);
	var skip = 0; bad = '';
	while(a1 < X.Sz()) { if(X.c("0EF80E10",a1)) { skip = 1; break } if(X.c("7000101A",a1)) break; a1 += 2 } //Find9
	if(!skip) {
		a2 = a1-2+X.U16(a1-2,_BE);
		while(a1 < X.Sz()) //FindA
			if(X.c("0EF80E10",a1)) { skip = 1; break }
			else { t = X.c("41FA",a1); a1 += 2; if(t) break }
		a2 = a1; a1 += X.U16(a1,_BE);
	}
	if(!skip) if(a1 > X.Sz()) { bad = bad.addIfNone('!short'); skip = 2 }
	if(!skip) { sz = a1; if(!d7) skip = 1 }
	if(!skip) while(a2 < X.Sz()) { //FindEnd
		if(X.c("41FA",a2)) sz = a1 = a2+2+X.U16(a2+2,_BE)+14;
		if(!X.c("110010",a2)) break; else a2 += 2;
	}
	//EndTable skipped -> Skip2
	if(skip != 2) {
		d0 = p2-p1;
		if(d0 > 0) d0 >>= 3;
		if(d0 > 0) x = d0
	}
	return true
}
if(!bDetected && isSpecialFXST()) {
	sName = "Special FX ST module (.DODA)"; bDetected = 1;
	if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		if(sz != -1) sOption(outSz(sz),'sz:')
	}
}


function isTronicDP() {
	a0 = d1 = 0x10+X.U16(2,_BE)+X.U16(6,_BE)+X.U16(10,_BE)+X.U16(14,_BE);
	if(a0 > X.Sz() || a0%2) return false;
	a0 += X.U16(a0,_BE); if(a0%2) return false;
	if(!X.c("005800B0",a0+4)) return false;
	return true
}
if(!bDetected && isTronicDP()) {
	sName = "Tronic Delta Packer module (.DP)"; bDetected = 1; sOption('in:TronicTracker(?)')
}


function isDigitalSonixChrome() {
	//ref UADE player RE
	if(!X.U16(0)) return false; smp = X.U8(2); if(!smp) return false; ord = X.U8(3); if(ord < 2) return false;
	d2 = smpsz = X.U32(4,_BE); if(!d2 || d2%2 || d2 > X.Sz() || d2 > 0x80000) return false;
	d3 = X.U32(8,_BE); if(d3 > 0x20000) return false;
	a0 = 0xC; x = 0;
	for(i=ord-1; i; i--) {
		d4 = X.I32(a0,_BE); if(d4 < 0 || d4 > 0x20000 || d4%2) return false; if(!X.U8(a0+4)) x++; a0 += 6
	}
	if(!X.c("00000000 0000",a0)) return false; a0 += 6; //@d2de~e4
	d3 <<= 2; a0 += d3; //d2e6~8
	a2 = a0+smp*0x12; //d2ea~f4
	s = 0;
	while(a0 < X.Sz() && a0 < a2) {
		d1 = X.I32(a0+2,_BE); if(d1 <= 0 || d1 > d2) return false;
		d0 = X.U32(a0+0xC,_BE); if(d0 > d2) return false;
		if(X.U8(a0+0x10) > 0x40 || X.U8(a0+0x11)) return false; //this check's not in the player
		a0 += 0x12;
	}
	if(a0 > X.Sz() || d2 != d0+X.I32(a0-0x10,_BE)) return false;
	sz = a0+smpsz;
	return true
}
if(!bDetected && isDigitalSonixChrome()) {
	sName = "Digital Sonix & Chrome module (.DSC)"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'×');
		sOption('ord:'+ord+' smp:'+smp+' sz:'+outSz(sz))
	}
}


function isPollyTracker() {
	//A trivial RLE compression with AE as the special command and a few glitches.
	//Underneath that, a trivial format that also messes up the lowcase letters for no reason.
	//Everything's space-padded, even the samples.
	if(!X.c("AE") || X.Sz() < 121) return false;
	p = 1; sz = unpsz = -1; var o = end = lc = 0; var u = Array(0x10000); var last = [-1,-2,-3,-4];
	function rB(q) { //this exists just to check for the unpacked sequences, we actually have better checks
		last[lc++] = X.U8(q); if(lc > 3) lc = 0;
		//PT packs sequences over 3 ch long so any we encounter in the file is cause enough to break off
		if(last[0] === last[1] === last[2] === last[3]) { end = 1; return -1 }
		else return X.U8(q)
	}
	function ret(s) { delete u; if(debug)_log(s); return false }
	while(!end && o < (X.isDeepScan() ? 65536 : 0x100) && p < Math.min(X.isDeepScan() ? 65535: 0x100,X.Sz())) {
		t = rB(p++);
		if(t == 0xAE) {
			c = rB(p++); if(c < 0) return ret("PTFault: bad count");
			if(!c) { end = true; break }
			else if(c === 1) { u[o++] = 0xAE; continue }
			b = rB(p++); if(b < 0) return ret("PTFault: bad byte");
			for(;c; c--) u[o++] = b
		}
		else u[o++] = t
	}
	if((tempo = u[0x1FC1]) < 0x10) return ret("PTFault: tempo < 10h");
	if(u[0x1F80] || u[0x1F90]) return ret('PTFault: nonzero smp#0');
	if(o%0x100) //fixing what seems like the packer's glitch. The lil bud always picks up some extra bytes
		while(o%0x100 && u[o-1] === 0x20) o--;
	if(o%0x100 || (X.isDeepScan() && o < 8192) || o > 65535 || p > 65535)
		return ret('PTFault: bad range! o='+Hex(o)+' p='+Hex(p));
	if((modsz = ((u[0x1FC3]+0x10) << 8)) != o)
		return ret('PTFault: allsmpsz mismatch: '+Hex(modsz)+' != '+Hex(o));
	var msmp = [4,12]; smp = 0;
	for(i=0x1F81; i < 0x1F90; i++) { // find max sample, count samples included
		if(u[i] >= 0x10) smp++; if(u[i] > msmp[0]) { msmp[0] = u[i]; msmp[1] = u[i+0x10] }
	}
	if((lastsmpend = ((msmp[0]+0x10)<<8) + (msmp[1]<<8)) != o)
		return ret('PTFault: last smp end '+Hex(lastsmpend)+' != '+Hex(o));
	notes = ic = 0;
	for(i=0; i < Math.min(0x1F00,o); i++) //test all the patterns, it's still fast
		//Notes aren't allowed to have smp#0 or note#0 but be a non-zero byte.
		//But PT v1.2 is glitchy, and earlier versions clearly allowed smp#0, so it does sometimes happen.
		if(u[i] && u[i] != 0xF0) { notes++; if((u[i]&0xF0) == u[i]) ic++ }
	if(X.isDeepScan()) for(i=0x2000; i < o; i++) //test all the samples
		if(u[i] > 0x3F) return ret('PTFault: bad sample @'+Hex(i));
	unpsz = o; sz = p; ord = ptn = 0;
	for(i = 0x1F00; i < 0x1F80; i++) { if(!u[i]) break; ord++; if((pt = u[i]-0xDF) > ptn) ptn = pt }
	if(ic > ptn<<2) return ret('PTFault: '+ic+' bad notes');
	// Let's adapt the text info too.
	for(i = 0x1FA0; i < 0x1FC0; i++) { if(u[i] && u[i] < 0x1A) u[i] += 0x60 }
	info = decEncoding(u.slice(0x1FA0,0x1FB0),CPAmiga);
	composer = decEncoding(u.slice(0x1FB0,0x1FC0),CPAmiga);
	delete u;

	return true;
}
if(!bDetected && isPollyTracker()) {
	sName = "Polly Tracker module (.MOD)"; bDetected = 1;
	if(X.isVerbose() && X.isDeepScan()) {
		sOptionT(info); sOptionT(composer,'by:');
		sOption('tempo:'+Hex(tempo)+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' notes:'+notes+' unpsz:'+unpsz+' sz:'+outSz(sz))
	}
}


function isPaulShields() {
	//ref https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/PaulShields/src/Paul%20Shields_v3.asm
	if(!X.c("00000000 0000000 0000")) return false;
	if((t = X.U16(0xA4)) === X.U16(0xA8) && t === X.U16(0xAC) && t === X.U16(0xB0)
	  && (t = X.I16(0xA0,_BE)) > 0 && !(t%2) && X.c("00B400B6",t)) { fmt = 1; p = 0xA6 }
	else if((t = X.U16(0x204)) === X.U16(0x208) && t === X.U16(0x20C) && t === X.U16(0x210)
	  && (t = X.I16(0x200,_BE)) > 0 && !(t%2) && X.c("02140216",t)) { fmt = -1; p = 0x206 }
	else if((t = X.U16(0x202)) === X.U16(0x206) && t === X.U16(0x20A) && t === X.U16(0x20E)
	  && (t = X.I16(0x204,_BE)) > 0 && !(t%2) && (X.c("FFEC",t-2) || X.c("FFE8",t-2))) { fmt = 0; p = 0x204 }
	else return false;
	mp = 0; a3 = 0;
	for(i=0; i < 4; i++) { t = X.U16(p,_BE); if(mp < t) mp = t; p += 4 }
	a3 += mp; mp = X.U16(a3-2,_BE); while(a3 < X.Sz()) { t = X.U16(a3,_BE); a3 += 2; if(t == mp) break }
	smpp = a3; if(!isWithin(smpp,0x400,0x4000)) return false;
	if(fmt == 1) a3 = X.I16(0xAA,_BE); else if(fmt == 0) a3 = X.I16(0x204,_BE); else a3 = X.I16(0x20A,_BE);
	ord = 0; while(a3 < X.Sz()) { t = X.U16(a3,_BE); a3 += 2; if(t == mp) break; ord++ }
	if(!isWithin(ord,1,0x80)) return false;
	for(i=p=smp=smpsz=0; i < 0x10; i++) {
		p += 2; if(fmt != 1) p += 20; if(t = X.U16(p,_BE)) { smpsz += t; smp++ } p += 8; if(fmt != 1) p += 2;
	}
	smpsz <<= 1; if(!smp || !isWithin(smpsz,0x2000,0x20000)) return false;
	sz = smpp+smpsz;
	return true;
}
if(!bDetected && isPaulShields()) {
	sName = "Paul Shields' module (.PS)"; sVersion = 'f.'+fmt; bDetected = 1;	
	if(X.isVerbose()) {
		sOption('ord:'+ord+' smp:'+smp+' smpsz:'+Hex(smpsz)+' sz:'+outSz(sz))
	}
}


function isROL() {
	if(!X.c("00000400")) return false;
	if(!X.c("0000000000000000 0000000000000000 00000000 0000'Tempo'00", 0xA0)) return false;
	if([0,0x43,0x5C].indexOf(X.U8(4)) < 0 || [0,0x6F,0x72].indexOf(X.U8(5)) < 0 || [0,0x6F,0x70].indexOf(X.U8(6)) < 0
 		|| [0,0x6C,0x79].indexOf(X.U8(7)) < 0 || [0,0x6C,0x72].indexOf(X.U8(8)) < 0) return false;
	if(!isWithin(X.U16(0x2C),2,0x10) || !isWithin(X.U16(0x2E),2,0x10)) return false;
	if(!X.U8(0x30) || X.U8(0x31) || !X.U8(0x32) || X.U16(0x33) || (mode=X.U8(0x35)) > 1) return false;
	q = true;
	for(i=0x36; i < 0x4C && q; i += 2) if(X.U16(i) > 0x10FF) q = false;
	for(; i < 0x90 && q; i += 2) if(X.U16(i) > 0x1FF) q = false;
	if(!q) return false;
	p = 0xCB+X.U16(0xC9)*6; //skip tempo events
	voices = mode ? 9: 11; mtln = mdur = notev = insev = volev = pitev = 0;
	for(i=0; i < voices; i++) {
		p += 0xF; dur = 0;
		tln = X.U16(p); p += 2;
		if(tln) do { dur += X.U16(p+2); p += 4; notev++ } while (dur < tln || p > X.Sz()); //load note events
		if(tln > mtln) mtln = tln; if(dur > mdur) mdur = dur;
		p += 0xF;
		insev += t=X.U16(p); p += 2+14*t+0xF; //skip ins.events
		volev += t=X.U16(p); p += 2+6*t+0xF; //skip vol.events
		pitev += t=X.U16(p); p += 2+6*t; //skip pitch events
	}
	sz = p;
	return true;
}
if(!bDetected && isROL()) {
	sName = "AdLib Visual Composer module (.ROL)"; bDetected = 1;
	if(X.isVerbose()) {
		c = X.SA(4,40); if(c != '\\roll\\default') sOption(c);
		sOption('ch:'+voices+' rhythm:'+X.U16(0x2C)+'/'+X.U16(0x2E)
			+' bpm0:'+X.F32(0xC5).toFixed(1)+' len:'+mdur+' notes:'+notev+' ins.ev:'+insev
			+' ins.ev:'+insev+' vol.ev:'+volev+' pitchev:'+pitev+' sz:'+outSz(sz))
	}
}


function isGYM() {
	if(X.Sz() < 0x1AC || !X.c("'GYMX'")) return false;
	var t = readBytes(4,0x1A8); for(i=0; i < t.length; i++) if(isWithin(t[i],1,0xA) || isWithin(t[i],0xE,0x1F)) return false;
	return true
}
if(!bDetected && isGYM()) {
	sName = "Sega Genesis/Mega Drive YM2612 chiptune (.GYM)"; bDetected = 1;
	if((unpsz=X.U32(0x1A8)) < 2) sVersion = sVersion.append('unpacked');
	if(X.isVerbose()) {
		sOptionT(X.SC(0x4,0x20,'CP1252'));
		sOptionT(X.SC(0x24,0x20,'CP1252'),'for: ');
		sOptionT(X.SC(0x44,0x20,'CP1252'),'at: ');
		sOptionT(X.SC(0x64,0x20,'CP1252'),'emu: ')
		sOptionT(X.SC(0x84,0x20,'CP1252'),'by: ');
		sOptionT(X.SC(0xA4,0x100,'CP1252'));
		if(unpsz > 2) sOptionT(X.U32(0x1A8),'unp.sz:')
	}
}


function isSoundMaster() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/SoundMaster/Sound%20Master_v1.asm
	if(!X.c("6000.... 6000.... 6000") || (p=base=a6=X.I16(2,_BE)+2) <= 0 || p%2
		|| (playp=X.I16(6,_BE)+6) <= 0 || playp%2) return false;
	for(p1 = p+30; p < p1; p += 2) if(X.c("47FA",p-2)) break; if(p >= p1) return false;
	var lea1 = p; t = Math.min(X.Sz(),0x20000);
	for(; p < t; p += 2) if(X.c("4E75",p-2)) break; if(p >= X.Sz() || p >= t) return false;
	fmt = 'old'; if(X.c("177C0000",p-8)) { fmt = 'new'; p -= 6 }
	if(!X.c("00BFE001",p-6)) return false;

	p = base; x = 1; d0 = 0;
	if(X.c("1740",p+6) || X.c("1740",p+4)) {
		for(; p < X.Sz(); p += 2) if(X.c("47FA",p-2)) break;  p += X.I16(p,_BE); a6 = p;
	} else skipold: do { //a one-time "loop" to have a way to break off
		if(X.c("3C00",p)) {
			p = lea1; a3 = a6 = p+X.I16(p,_BE);
			for(; p < X.Sz(); p += 2) if(X.c("7600",p-2)) break;  a3 += X.I16(p-4,_BE)+3; d0 = 7;
			for(; d0; a3+=3,x++,d0--) {
				if(!X.U16(a3,_BE) && X.U8(a3+2) == 1) break;
			}
			d0 = X.U8(a3-1);
			break skipold;
		} else
			if(X.c("4A00",p+0x28)) { x++; d0 = X.I16(p+0x34,_BE) }
		for(; p < X.Sz(); p += 2) if(X.c("47FA",p-2)) break;  p += X.I16(p,_BE); a6 = p;
	} while(0);
	ord = d0;

	for(p = 0xC; p < X.Sz(); p += 2) if(X.c("1743",p-2)) break; a3 = a6; pos = a6+X.I16(p,_BE);
	for(p = 0xC; p < X.Sz(); p += 2) if(X.c("5203",p-2)) break;
	sz = -1; patchable = X.c("177C", p+10);
	if(patchable) {
		a2 = base; a3 += X.I16(p+2,_BE);
		if(!ord) ord = X.U8(a3);
		if(fmt == 'new') {
			for(p = base+2; p < X.Sz(); p += 2) if(X.c("41EB",p-2)) break;
			a3 = a6; a6 += X.I16(p,_BE); a3 += X.I16(p+4,_BE); a6 += X.I32(a3,_BE); smpip = a6;
			songsz = a6+X.I16(a3-2,_BE);
			smp = smpsz = 0;
			for(;a6 < X.Sz();) {
				d3 = X.I32(a6,_BE); a6 += 4;
				if(d3 > 0 && (ssz = X.U16(a6,_BE)<<1)) {
					smp++; d3 += ssz;
					if(smpsz < d3) smpsz = d3;
				}
				a6 += 6; if(a6 >= songsz) break
			}
		} else { // old format
			for(p = base+2; p < X.Sz(); p += 2) if(X.c("3D70",p-2)) break;
			for(; p < X.Sz(); p += 2) if(X.c("D5F0",p-2)) break;
			a3 = a6+X.I16(p-4,_BE); a6 += X.I32(a3,_BE); t = a6; a6 += X.U16(p-0x12,_BE);
			smpip = a6; smp = smpsz = 0; a3 = a6+0x80;
			for(d1 = 32; d1; d1--) {
				if(X.U16(a3,_BE) > 1) {
					smp++; ssz = X.U16(a3,_BE)<<1; d3 = ssz+X.I32(a6,_BE); if(smpsz < d3) smpsz = d3;
				}
				a6 += 4; a3 += 2
			}
			songsz = t+X.U16(p-8,_BE);
		}
		sz = songsz+smpsz
	}
	return true
}
if(!bDetected && isSoundMaster()) {
	sName = "Sound Master module (.SM,.SMPRO,.SM3)"; sVersion = fmt; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		if(patchable) sOption('ord:'+ord+' sz:'+outSz(sz));
		//else sOption('unpatchable or precooked');
	}
}


function isTomyTracker() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/TomyTracker/src/Tomy%20Tracker_v2.asm
	if(X.Sz() < 0x6C0) return false;
	if((sz=X.U32(0,_BE)) > 0x200000 || sz%2) return false;
	if((d2=X.U32(4,_BE)) > sz || d2%2) return false;
	d2 -= 0x2C0; if(d2 & 0x3FF) return false;
	ptn = d2 >>= 10; d2--; p = 0x1BC; if(X.U8(p)) return false;
	ord = d1 = X.I8(p+1); if(ord < 0) return false; p += 4; d2 <<= 10; d3 = 0;
	for(; d1; p+=2,d1--) { if((t=X.I16(p,_BE)) & 0x3FF) return false; if(d3 < t) d3 = t }
	if(d2 != d3) return false;
	for(smp = 0, p = 14; p < 0x1C0; p += 0xE) if(X.U16(p,_BE)) smp++;
	return true
}
if(!bDetected && isTomyTracker()) {
	sName = "Tom Pakarinen's Tomy Tracker module (.SG)"; bDetected = 1;
	if(X.isVerbose()) {
		sOption('ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
	}
}


function isSoundcontrol() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Soundcontrol/src/Soundcontrol_v3.asm
	if(!X.c("0003",0x20))
		if(!X.c("0002",0x20) || X.U32(0x1C,_BE)) return false;
	if(X.U16(0x10,_BE)) return false;
	if((p = X.I16(0x12,_BE)) < 0 || p % 2) return false;
	if(!X.c("FFFF0000 0400",p+0x3E)) return false;
	title = readBytes(0,0x10); if(charStat(title,1).indexOf('allasc') < 0) return false;
	title = decEncoding(title,"CP437");
	a2 = 0; a3 = sz = 0x40+X.U32(0x10,_BE);
	d2 = X.U32(0x14,_BE); sz += d2; a4 = sz;
	d1 = X.U32(0x18,_BE); sz += d1+X.U32(0x1C,_BE); d2 -= 0x400;
	if (X.Sz() < sz) return false;
	ord = d1 = Util.divu64(d1,12); d1--;
	voc = 0;
	for(d3 = 6; d3; d3--,a4 += 2)  for(p = a4,d4 = ord; d4; d4--,p += 12)  if(X.U16(p,_BE)) { voc++; break }
	a3 += 0x43C; smp = d3 = 0;
	while(d2 > d3) { d4 = X.U32(a3,_BE); a3 += d4; d3 += d4; smp++ }
	x = 1; nv = 0; sv = '';
	if(X.U32(0x1C,_BE)) //crude fixes, go
		switch(sz) {
		case 95960: x = 2; //hndongame2; fallthrough
		case 54544: sv = 'v4.0'; break; //hndtitle
		case 81906: sv = 'v5.0'; break //hndintro
		}
	else
		switch(sz) {
		case 126446: x = 3; sv = 'v3.0'; break; //number9
		case 136612: x = 2; sv = 'v3.0'; break; //domination 1
		case 154704: x = 2; //dynatsong; fallthrough
		case 103808: sv = 'v3.2'; break; //eleven6
		}
	for(p = 0x40, ptn = 0; p < 0x240; p += 2) if(X.U16(p,_BE)) ptn++;
	return true
}
if(!bDetected && isSoundcontrol()) {
	sName = "Holger Gehrmann's Soundcontrol module (.SCT)"; sVersion = sv; bDetected = 1;
	if(X.isVerbose()) {
		sOptionT(title); if(x > 1) sOption(x,'x');
		sOption('ch:'+voc+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
	}
}


function isSoundfactory() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Soundfactory/src/Soundfactory_v2.asm
	if((sz=X.U32(0,_BE)) > 0x200000 || sz > X.Sz()) return false;
	x = 16; lastfound = false;
	for(p=0x13; p >= 4; p--) { if((t=X.U8(p)) > 0xF) return false; if(!lastfound && t) {x = p-4; lastfound = true } }
	m = X.U32(0x14,_BE);
	for(p = 0x18; p < 0x114; p+=4) { if((t=X.U32(p,_BE)) > 0x200000 || t > sz) return false; if(m > t) m = t }
	if(m != 0x114) return false;
	if(X.isDeepScan()) {
		p = 0x114; smp = smpsz = 0;
		for(;p < sz-8;) {
			//seek 84h at an even offset
			do { q = X.fSig(p,sz-8-p,"84"); if(q < 0) { p = sz; continue} else p = q+(q%2) } while(p < sz && !q%2);
//_log("found "+Hex(q))
			if(p < sz && (d1=X.U8(p+1)) <= 0x1F) {
				d1 >>= 2; smpp[d1] = p;
				d4 = X.U16(p+2,_BE)<<1; smpsz += d4-38; smp++; p += d4
			} else p += 2;
		}
		if(!smp || smp > 32) return false;
	}
	return true
}
if(!bDetected && isSoundfactory()) {
	sName = "Soundfactory module (.PSF)"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		sOption((X.isDeepScan()?'smp:'+smp+' smpsz:'+Hex(smpsz)+' ':'')+'sz:'+outSz(sz))
	}
}


function isBeniTracker() {
	//from https://modland.com/pub/software/trackers/DOS/Beni%20Tracker/Beni%20Tracker%20v1.8.rar / BT18.BAS -> SUB loadmod
	//ref BT18.DOC & https://modland.com/pub/software/trackers/DOS/Beni%20Tracker/Beni%20Tracker%20v1.3.rar / BeniTrk.doc

	ord = X.U8(0); ptn = X.U8(1); ins = X.U8(2);
	if(!ptn || ptn > 0xCC || ins > 0x1F) return false;
	var ptnmap = [], insmap = [];
	p = 3; mptn = mins = 0;
	for(i=0; i < ptn; i++) {
		if((t = X.U8(p++)) > 0xCB) return false; if(mptn < t) mptn = t;
		if(ptnmap.indexOf(t) < 0) ptnmap.push(t) }
	if(ins) {
		for(i=0; i < ins; i++) {
			if(!isWithin(t = X.U8(p++),1,0x1F)) return false; if(mins < t) mins = t;
			if(insmap.indexOf(t) < 0) insmap.push(t) }
	}
	rptn = ptnmap.length; rins = insmap.length; mptn++;
	if(rptn != ptn || (ins && rins != ins)) return false;
	for(o = 0; o < ord; o++)
		for(i=0; i < 9; i++)
			if(ptnmap.indexOf(X.U8(p++)) < 0) return false;
	notes = 0;
	for(i=0; i < ptn*0x40; i++,p+=3) {
		var nt = X.U8(p) >> 4, ns = (X.U8(p) & 1) << 4 | X.U8(p+1) >> 4;
		if(nt > 12 || ns && insmap.indexOf(ns) < 0) return false;
		if(nt != 12) notes++;
	}
	p += ins*0xB;
	if(X.c("'B.J.'",p)) { p += 4; sv = 'v1.8' } else sv = 'v1.3';
	if(sv == 'v1.3' && ptn > 100) return false;
	sz = p; return true;
}
if(!bDetected && isBeniTracker()) {
	sName = 'BeniTracker Adlib module (.PIS)'; sVersion = sv; bDetected = 1;
	if(X.isVerbose()) {
		sOption('ord:'+ord+' ptn:'+ptn+(ptn!=mptn?'/'+mptn:'')+' ins:'+ins+(ins!=mins?'/'+mins:'')
			+' notes:'+notes+' sz:'+outSz(sz))
	}
}


function isSteveBarrett() {
	//ref reversing the eagleplayer and the module's player code
//SubSongs at +04; Samples at +1C; CalcSize at +24; Length at +3C;
// LoadSize at +0C; SongSize at +14; SamplesSize at +2C; SpecialInfo at +34; ExtraInfo at +44;
	for(p=0; p < 0x10; p += 4) if(!X.c("6000",p) || (t=X.U16(p+2,_BE)) > X.Sz() || t%2) return false;
	
	t = X.I16(6,_BE)+6; if(!X.c("49FA....1940....4E75 43FA.... 49FA.... 41FA.... 45FA",t)
		|| !X.c("'FORM'66",t+0x1C)) return false;
	t = X.I16(10,_BE)+10;
	if(!X.c("49FA....4CFA00FF",t) || !X.c("103A.... 660C 1940.... 6100... .6000",t+10)
		|| !X.c("2A4C DAD8 22CD 2A4C DAD8 22CD 2A4C DAD8 22CD",t+0x34)) return false;
	t = X.I16(0xE,_BE)+0xE;
	if(!X.c("70002A7C 00DFF0A8 3A803B40",t) || !X.c("1A801B40 00011B40 00021B40",t+0x1A)) return false; 
	initp = X.I16(2,_BE)+2; if(!X.c("43FA....49FA....41FA....45FA",initp) || !X.c("'FORM'66",initp+0x12)) return false;
	a0 = 0;
	for(p = initp; p < initp+0x10; p += 2) //find the sample ptr
		if(X.c("41FA",p-2)) { a0 = p+X.I16(p,_BE); break }
	if((p=6+X.fSig(initp,0x1000,"E7404281")) < 6) return false; t = p+X.I16(p,_BE); //possibly orderlist ptr
	for(; p < X.Sz(); p += 2) if(X.c("41FA",p-2)) break;
	for(; p < X.Sz(); p += 2) if(X.c("D08043FA",p)) break; p += 4; p += X.I16(p,_BE);
	x = (p-t)>>3;
	for(p = a0,smp=0; p < X.Sz(); smp++) {
		if(!X.c("'FORM'........'8SVXVHDR'",p)) { if(!X.U32(p)) p += 4; break }
		p += 8+X.U32(p+4,_BE);
	}
	sz = p; if(!smp) return false;
	return true;
}
if(!bDetected && isSteveBarrett()) {
	sName = "Steve Barrett's module (.SB)"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		sOption('smp:'+smp+' sz:'+outSz(sz))
	}
}


function isQuartetPSG() {
	// ref the eagleplayer RE
	if(X.Sz() < 0x400 || !X.c("49FA",0x10)) return false;
	for(p=0; p < 0x10; p += 4)
		if(!X.c("6000",p) || (t=X.I16(p+2,_BE)) <= 0 || t%2) return false;
	if(!X.c("48E7FFFE 4DFA.... 51EE.... 41FA",p=2+X.I16(2,_BE))) return false; 
	if(!X.c("48E7FFFE 4DFA.... 4A2E.... 6700.... 70033F00 49FA",p=6+X.I16(6,_BE))) return false; 
	if(!X.c("48E7FFFE 4DFA.... 51EE.... 6100",p=10+X.I16(10,_BE))) return false; 

	for(p = 0x10; p < X.Sz(); p += 2) if(X.c("40C2",p)) break;
	for(; p < X.Sz(); p += 2) if(X.c("41FA",p)) break;
	p += 6; sz = p+X.I16(p,_BE); p += 2;
	for(; p < X.Sz(); p += 2) if(X.c("43E9",p-2)) break;
	sz += X.I16(p,_BE) << 1; a3 = sz; if(sz > X.Sz()) return false;
	for(a0 = 0; p < X.Sz(); p += 2) {
		if(X.c("206C0032",p)) { a0 += 8; p += 4 } if(X.c("08380007",p) || X.c("08390007",p)) break
	}
	for(; p < sz; p += 2) if(X.c("40C1",p)) break;
	for(p += 4; p < X.Sz(); p += 2) if(X.c("4CDF",p)) break;
	for(; p < sz; p += 2) if(X.I32(p,_BE) == 0x56) break;
	for(x = 0; p < a3; p += 2) if(X.I16(p,_BE) == 0x46) x++;
	x >>= 2;
	return true
}
if(!bDetected && isQuartetPSG()) {
	sName = "Illusions/Microdeal Quartet module (SQT.)";
	sVersion = "PSG synth"; bDetected = 1;
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		sOption(outSz(sz),'sz:')
	}
}


function isSonicArranger() {
	//from https://gitlab.com/uade-music-player/uade/-/blob/master/amigasrc/players/wanted_team/Sonic_Arranger/Sonic Arranger_v1.asm
	p = modp = smp = synsmp = ord = ptn = 0; x = 1; sv = bad = ''; owner = [];
	if(X.c("'SOARV1.0STBL'")) {
		x = X.I32(0xC,_BE); spd0 = X.U8(0x11); ptnlen0 = X.U8(0x13); pst0 = X.U16(0x14,_BE);
		ped0 = X.U16(0x16,_BE); lp0 = X.U16(0x18,_BE); irqps0 = X.U16(0x1A,_BE);
		if(!isWithin(spd0,2,16) || !isWithin(irqps0,16,120) || pst0 > ped0 || lp0 > ped0) return false;
		p = 0x10+x*12; if(!X.c("'OVTB'",p)) return false; //"OverTable"
		ord = X.I32(p+4,_BE); if(ped0 > ord) return false;
		p += 0x18+(ord << 4); if(!X.c("'NTBL'",p-0x10)) return false;
		ntbl = X.I32(p-0xC,_BE); p += ntbl << 2; if(!X.c("'INST'",p-8)) return false;
		ptn = Util.divu64(ntbl,0x10); ins = X.I32(p-4,_BE); inss = [];
		for(i=0; i < ins; i++,p+=0x98) {
			if(X.U16(p,_BE)) synsmp++; else smp++;
			t = readBytes(p+0x7A,0x1E,true);
			t = decEncoding(t,CPAmiga).trim(); if(t != '') inss.push(t)
		}
		if(!X.c("'SD8B'",p)) return false; smp = sc = X.I32(p+4,_BE); p += 8+smp*0x26;
		pp = p; p += smp << 2; for(; sc-- && p < X.Sz(); pp += 4) p += X.U32(pp,_BE);
		if(!X.c("'SYWT'",p)) return false; synsmp = X.I32(p+4,_BE); p += 8+(synsmp << 7);
		if(!X.c("'SYAR'",p)) return false; t = X.I32(p+4,_BE); p += 8+(t << 7);
		if(!X.c("'SYAF'",p)) return false; t = X.I32(p+4,_BE); p += 8+(t << 7);
		if(!X.c("'EDAT'",p)) return false; sz = p+24; sv = 'song';
		} else {
		if(X.c("4EFA")) { //detect & skip replay
			if((t=X.I16(2,_BE)) <= 0 || t%2) return false;
			if(!X.c("48E7FFFE 41FA",t+2) || !X.c("201045F0 0800228A 43FA",t+0xE)
				|| !X.c("20280004 45F00800 228A 43FA",t+0x1A)) return false;
			if((p=X.I16(t+8,_BE)) <= 0 || p%2) return false;
			p += t+8; modp = p; sv = '+replayer';
		} else sv = 'pure';
		if(p+0x28 > X.Sz() || !X.c("00000028",p)) return false;
		spd0 = X.U16(p+0x28,_BE); irqps0 = X.U16(p+0x32,_BE); lp0 = X.U16(p+0x30,_BE);
		if(!isWithin(spd0,2,16) || !isWithin(irqps0,16,120)) return false;
		x = (X.I32(p+4,_BE)-0x28)/12; if(x == 1 || X.I16(p+0x3C,_BE) < 0) x = 1;
		var ptrs = [0];
		for(i=0; i < 8; i++) {
			t = X.I32(p,_BE); p += 4; if(t <= 0 || t%2 || t < ptrs[ptrs.length-1]) return false; ptrs.push(t)
		}
		var ordp = ptrs[2], ptnp = ptrs[3], insp = ptrs[4], a = ptrs[5], sc = X.I32(modp+t,_BE);
		//ord recs contain u16 references and a couple control bytes ST and NT
		ord = Util.div64(ptnp-ordp,0x10); ptn = Util.div64(insp-ptnp,0x40); ins = Util.div64(a-insp,0x98);
		p = modp+t; if(p > X.Sz()) return false;
		for(i=0; i < ins; i++) if(X.U16(modp+insp+i*0x98,_BE)) synsmp++; else smp++;
		if(smp != sc) bad = bad.addIfNone('!inconsistentsmpcnt'+sc); //be cool to understand how that happens...
		p += 4;
		if(smp) for(pp=p, p+=sc<<2; sc-- && p < X.Sz(); pp += 4) p += X.U32(pp,_BE);
		if(!X.c("'deadbeef'",p)) if(X.isHeuristicScan()) bad = bad.addIfNone('!badeof'); else return false;
		p += 12; sc = p; //skip 'deadbeef    '; sc is now for counting the string length
		// There's no real need to include the "programowner" info into calcsize but let's;
		// otherwise the eagleplayer thinks the file is "too short", although nobody really cares...
		for(lim = Math.min(p+0x400,X.Sz()); p < lim;) {
			c = X.U8(p++)^0xFF; if(c == 0xFF || isWithin(c,0,8) || isWithin(c,11,12) || isWithin(c,14,0x1F)) break;
			if(c == 0xA) { c = 0x20; if(owner[owner.length-1] == 0x20) continue }
			owner.push(c)
		}
		if(owner[owner.length-1] == 0x20) owner.pop();
		sz = p;
		if(c != 0xFF && p-sc != 0x50) bad = bad.addIfNone('!badinfo');
	}
	bpm0 = (15*irqps0/spd0).toFixed(1); owner = decEncoding(owner,CPAmiga).trim();
	return true
}
if(!bDetected && isSonicArranger()) {
	sName = "BrainTrace Design's Sonic Arranger module (.SA"+(sv == 'pure'?',.LION':'')+")"; bDetected = 1;
	sVersion = sv; if(bad != '') sVersion = sVersion.appendS('malformed:'+bad,'/');
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		if(owner != '') sOption(addEllipsis(owner,0xA0,0x80),'info:"','"');
		sOption('bpm0:'+bpm0+(ord?' ord:'+ord:'')+(lp0?' lp0:'+lp0:'')+(ptn?' ptn:'+ptn:'')+(ins?' ins:'+ins:'')
			+(smp?' wf.smp:'+smp:'')+(synsmp?' syn.smp:'+synsmp:'')+' sz:'+outSz(sz))
	}
}
if(!bDetected && X.c("'SASI'00000000000100")) {
	sName = "BrainTrace Design/MEDIA Verlags Sonic Arranger synth instrument"; bDetected = 1
}


function isJT() {
	if(X.Sz() < 1700) return false;
	for(p = 0; p < 40; p += 2) if(X.c("02390001",p)) break; if(p == 40) return false;
	p += 8; if(!X.c("66..4E75",p++)) return false; if((t = X.I8(p++)) <= 0) return false;
	p += t; if(X.c("4A39",p))  for(j=4; j--; p += 0x12) if(!X.c("4A39",p)) return false;
	if(!X.c("78001839",p)) return false;
	for(j = p;;) { p = X.fSig(j,0x40000,"1400E302"); if(p < 0) return false; else if(i % 2) { j = p+1; continue } else break }
	var org = X.U32(p+6,_BE);
	for(;;) { p = X.fSig(j,0x40000,"03580328"); if(p < 0) return false; else if(i % 2) { j = p+1; continue } else break }
	org -= p;
	for(p=0; p < 0x40000; p += 2) if(X.c("B23C00FF",p) || X.c("0C0100FF",p)) break;  if(p >= 0x40000) return false;
	for(; p < 0x40000; p += 2) if(X.c("267C",p-2)) break;  if(p >= 0x40000) return false; p += 4; //?
	for(; p < 0x40000; p += 2) if(X.c("49F9",p-2)) break;  if(p >= 0x40000) return false; smpip = X.U32(p,_BE)-org; p += 4;
	for(; p < 0x40000; p += 2) if(X.c("0026267C",p-4)) break;  if(p >= 0x40000) return false; p += 4; //?
	for(; p < 0x40000; p += 2) if(X.c("23F4",p)) break;  if(p >= 0x40000) return false;
	subsongp = X.U32(p-4,_BE)-org; p += 6; x = 0;
	for(p=subsongp; p < 0x40000; p += 0x12) {
		if(X.U8(p+16) != 12) break; t = X.U32(p,_BE);
		if(X.U32(p+4,_BE) != t || X.U32(p+8,_BE) != t || X.U32(p+12,_BE) != t) x++; else break
	}
	j = X.U32(smpip,_BE)-org; i = smpip+4; info = (j-i) >> 2;
	d0 = X.I32(j+4,_BE); d1 = X.I16(j+2,_BE)*2 + d0;
	for(smp = 1; i < j; i += 4) {
		t = X.U32(i,_BE)-org; d3 = X.U32(t+4,_BE); if(!d3) continue;
		d4 = X.I16(t+2,_BE)*2+d3; if(d3 < d0) d0 = d3; if(d4 > d1) d1 = d4; smp++
	}
	songsz = d0-org; sz = d1-org;
	for(i=0; i < 0x40000; i+=2) if(X.c("4E75",i-2)) break;
	specialmsg = ''; //I've no modules to test this on
	if((d1=X.U8(i-3)) != 2) specialmsg = X.SC(i,0x100,CPAmiga);
	return true;
}
if(!bDetected && isJT()) {
	sName = "Jeroen Tel's module (.JT)"; bDetected = 1;
	if(X.isVerbose()) {
		if(specialmsg != '') sOption(specialmsg); if(x > 1) sOption(x,'x');
		sOption('smp:'+smp+' songsz:'+Hex(songsz)+' sz:'+outSz(sz))
	}
}


function isTCBTracker() {
	//ref ftp://ftp.scene.org/pub/resources/gotpapers/manuals/tcb_tracker_1.0_manual_1990.pdf
	if(X.Sz() <= 0x132 || !X.c("'AN COOL'") || (ptn = X.U32(8,_BE)) > 127 || (tmp0 = X.U8(0xC)) > 15
		|| X.U8(0xD) || (ord = X.I8(0x8E)) <= 0) return false;
	if(X.SA(7,1) == '!') fmt = 0; else if(X.SA(7,1) == '.') fmt = 1; else return false;
	if(!fmt) ptnp = 0x110; else ptnp = 0x132; smpp = ptnp+ptn*0x200; if(smpp+0xD4 > X.Sz()) return false;
	if(!X.c("FFFFFFFF00000000",smpp+0xCC) || X.U32(smpp+0x44,_BE) != 0xD4) return false;
	smp = 0; p = smpp+0x48;
	for(i=0; i < 16 && p < X.Sz(); i++) if(X.U32(p,_BE) != 1) smp++;
	sz = smpp+X.U32(smpp,_BE);
	return true
}
if(!bDetected && isTCBTracker()) {
		sName = "Anders 'AN Cool' Nilsson's TCB Tracker module (.TCB)"; sVersion = 'f.'+fmt; bDetected = 1;
		if(X.isVerbose())
			sOption('tempo:'+tmp0+' ord:'+ord+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz));
	}


function is669() {
	//from https://modland.com/pub/documents/format_documentation/Composer%20669,%20Unis%20669%20(.669).txt
	if(!X.c("'if'") && !X.c("'JN'") || X.Sz() < 0x1F1+0x600) return false; //not easy to make the signature more deficient
	if((smp=X.U8(0x6E)) > 0x40 || (ptn=X.U8(0x6F)) > 0x80 || (lp=X.U8(0x70)) >= 0x80) return false;
	for(ic=0,i=2; i < 0x6D; i++) if(isWithin(X.U8(i),1,31) && ++ic > 40) return false;
	for(i=ord=0; i < 0x80; i++) { //from 71h: order list; from F1h: ptn tempos; from 171h: ptn breaks
		if((o=X.U8(0x71+i)) >= ptn && o < 0xFE) return false; if(o < 0x80 && !(t=X.U8(0xF1+i))) return false;
		if(t > 15 || X.U8(0x171+i) >= 0x40) return false; if(o < 0xFE) ord++
	}
	sz = 0x1F1+smp*0x19+ptn*0x600;
	if(X.Sz() < sz) return false;
	for(i = rsmp = 0; i < smp; i++) { //smp filename may have custom messages (the lunar forest), but too rarely
		ssz = X.U32(0x1FE+i*0x19); slps = X.U32(0x202+i*0x19); slpe = X.U32(0x206+i*0x19);
		if(ssz) rsmp++; else continue;
		if(ssz > 0x4000000) return false; //a sanity check from openmpt
		if([0xFFFFF,0xF0FFFF,0xFFFFFFFF].indexOf(slps) < 0 && slps > ssz) return false;
		if([0xFFFFF,0xF0FFFF,0xFFFFFFFF].indexOf(slpe) < 0 && (slpe > ssz || slpe < slps)) return false;
		sz += ssz;
	}
	return true
}
if(!bDetected && is669()) {
	sName = ( X.c("'if'")? "Composer": "UNIS" )+" 669 module (.669)"; bDetected = 1;
	if(X.isVerbose()) {
		t = X.SC(0x02,36,'CP850').trim(); //the seemingly most-used encoding for'em
		t = t.appendS(X.SC(0x26,36,'CP850').trim(),' '); t = t.appendS(X.SC(0x4A,36,'CP850').trim(),' ');
		sOption(t); sOption('ord:'+ord+(lp?' loop:'+lp:'')+' ptn:'+ptn+' smp:'+smp+' sz:'+outSz(sz))
	}
}


function isLDS() {
	//ref https://github.com/adplug/adplug/blob/master/src/lds.cpp
	if(X.Sz() < 136) return false; if((mode = X.U8(0)) > 2) return false;
	if(X.U8(1) > 0x1F && X.U8(1) != 0xD0) return false; //speed, byte1
	if(X.U8(2) < 0x42 || X.U8(2) > 0x43) return false; //speed, byte2
	if((tempo = X.U8(3)) < 3 || tempo > 0x1F || ((ptnsz = X.U8(4)) & 0x87)) return false;
	for(i=5; i < 14; i++) if(X.U8(i) > 4) return false; if((regbd = X.U8(0xE)) > 2) return false;
	if(!(ins = X.U16(0xF)) || ins > 0x3F) return false;
	for(p = 0x11; p < 0x11+0x2E*ins; p += 0x2E) {
		if(X.U8(p+4) > 3 && X.U8(p+4) != 0xE1 || X.U8(p+9) > 4) return false;
		if(X.U8(p+0xA) > 0x7F || (X.U8(p+0xC) & 0xC0)) return false;
		if(!isWithin(X.I8(p+0x15), -0x30, 0x30) || !isWithin(X.I8(p+0x17), -0x30, 0x30)) return false;
		if(!isWithin(X.I8(p+0x1B), -0x30, 0x30) || !isWithin(X.I8(p+0x20), -0x30, 0x30)) return false;
		if((X.U8(p+0x24) > 0x30) || X.U8(p+0x25) > 1 || !isWithin(X.I8(p+0x27), -4, 0)) return false;
		if(isInside(X.U8(p+0x2B), 0x30, 0xE8) || X.U16(p+0x2C)) return false;
	}
	ord = X.U16(p); if(!ord || ord > 0x60) return false; p += 2; mp = -1; ptnp = p+ord*9*3+2;
	for(i=0; i < ord; i++) for(j=0; j < 9; j++) {
			pt = X.U16(p); if(pt%2 || pt+ptnp > X.Sz() || pt > 0x4000) return false; if(mp < pt) mp = pt; p += 3;
		}
	digisnd = X.U16(p); p += 2; sz = ptnp+digisnd; //digital sounds value seems to point just beyond the patterns!
	if(X.Sz() < sz) return false; ptn = Util.divu64(digisnd,2);
	return true
}
if(!bDetected && isLDS()) {
	sName = 'Loudness Sound System Ad Lib module (.LDS)'; bDetected = true;
	if(X.isVerbose()) sOption('ch:9 mode:'+mode+' spd:'+X.U16(1)+' tempo:'+tempo
		+' ord:'+ord+' ptn:'+ptn+' ins:'+ins+' sz:'+outSz(sz))
}


function isKSM() {
	for(i=0; i < 4; i++) if(!isWithin(X.U8(i),0,0x57)) return false;
	for(; i < 0xB; i++) if(X.U8(i)) return false;  for(; i < 0x10; i++) if(X.U8(i) < 0xFB) return false;
	for(; i < 0x20; i++) if([0xF0,4,6,8,0xC].indexOf(X.U8(i)) < 0) return false;
	for(c=0; i < 0x25; i++) if(X.U8(i) > 5) return false; else if(!X.U8(i)) c++; if(c > 3) return false;
	for(; i < 0x2A; i++) if(X.U8(i)) return false;  for(; i < 0x30; i++) if(X.U8(i) > 1) return false;
	for(i=0x40; i < 0x50; i++) if(!isWithin(X.U8(i),0x20,0x3F)) return false;
	if(!X.c("32323232 323232",0x44)) return false;
	return true
}
if(!bDetected && isKSM()) {
	sName = "Ken Silverman's Adlib module (.KSM)"; bDetected = true;
	if(X.isVerbose()) sOption('notes:'+(notes=X.U16(0x50))+' sz:'+outSz(0x52+notes*4))
}


function isFredF() {
	//ref https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/Fred/FredWorker.cs
	var p = modp = ofsdiff = 0; bad = '';
	function _check0() {
		if(X.Sz() < 0xB0E) return false; if(!X.c("4EFA....4EFA....4EFA....4EFA")) return false;
		p = X.I16(2,_BE)+2; if(p <= 0x10 || p%2) return false;
		// The init routine just has 2 variants it seems, the second I only found in furball*.fred.
		// Maybe out in the wild there are more but:
		if(!X.c("123A.... B0016200 007E47FA 08761680 45FA0873 49FA086E D5C01892 E7887E03 7C0041FA 08D8",p)
		  && !X.c("42380001 123A.... B0016200 007E47FA 08B61680 45FA08B3 49FA08AE D5C01892 E7887E03 7C0041FA 0918",p))
			return false;
		// and this is as-is from NP. Not sure why this specific way of looking for it
		for(i=0; i < 4; i++) if(X.c("123A....B001",p+i*2)) { modp = p+i*2+1+X.I16(p+i*2+2,_BE); break }
		if(i == 4 || modp > X.Sz()) return false;
		for(; i < 60; i++) if(X.c("47FA....D7FA",p+i*2)) { ofsdiff = p+(i+1)*2+X.I16(p+i*2+2,_BE); return true }
		return false
	}
	if(!_check0()) return false; p = modp+1; x = X.U8(p++)+1; if(x > 10) return false; spd0 = X.U8(p++);
	spds = []; for(i=0; i < 10; i++) { t = X.U8(p++); if(spds.indexOf(t) < 0) spds.push(t) } p++;
	insp = ofsdiff+X.U32(p,_BE); trkp = ofsdiff+X.U32(p+4,_BE);
	p += 0x26C+x*8 /*replay data & subsong starts*/; if(p > X.Sz()) return false;
	ord = (Math.min(insp,trkp) - p) >> 1; ptnsz = Math.abs(insp-trkp);
	for(i=ptn=0; i < ptnsz;) {
		while(X.U8(trkp+(i++)) != 0x80) if(i === ptnsz) break;
		if(i == ptnsz && X.U8(trkp+i-1) != 0x80) break; ptn++
	}
	for(syn=smp=0,msmpp=0x7fffffff,Msmpp=smpe=0,p=insp;;) {
		var smpp = X.U32(p,_BE), tsyn = X.I16(p+4,_BE), ssz = X.U16(p+6,_BE) << 1;
		var tins = X.U8(p+0x27); if(p+0x28 >= X.Sz() || p+0x3F >= msmpp) break;
		if(tins != 0xFF)
			if(smpp) {
				smpp += ofsdiff; if(smpp > X.Sz()) break;
				if(smpp < msmpp) msmpp = smpp;
				if(smpp > Msmpp && smpp < X.Sz()) { Msmpp = smpp; smpe = smpp+ssz } smp++
			} else {
				if(tsyn === -779) /* giants-1.fred fix*/ break;  if(tsyn != -1 && tins != 0) return false;  syn++
			}
		p += 0x40;
	}
	if(smp) sz = smpe; else sz = p;
	return true
}
if(!bDetected && isFredF()) {
	sName = "Fred & Julien Clermonte's Fred Editor module (.FRED,.FRD)"; sVersion = 'final'; bDetected = 1;
	if(bad != '') sVersion = sVersion.appendS('malformed'+bad,'/');
	if(X.isVerbose()) {
		if(x > 1) sOption(x,'x');
		sOption('tempo:'+spds.sort().join('-')+' ord:'+ord+' ptn:'+ptn+(smp?' smp:'+smp:'')+(syn?' syn:'+syn:'')+' sz:'+outSz(sz))
	}
}


// new stable detections :go here: ↑


function isUltima6M() {
	//ref https://github.com/adplug/adplug/blob/master/src/u6m.cpp
	if(X.Sz() < 421 || !isWithin(unpsz=X.U16(0),0x30E,0xB21) || !X.c('00000007',2)) return false; //a rough filter
	// a finer filter: actually unpacking LZW and double-checking there
	var C, cW, o = 0, lim = Math.min(X.Sz(),unpsz), br = new BitReader(4), end = false,
		pW = 0, cwsz = 9, nextcw = 0x102, stk = [],
		dictsz = defdictsz = 0x1000, dict = [], contains = 0x102;
	for(i=0; i < dictsz-0x100; i++) dict.push({'rt':0, 'cw':0}) //new dictionary
	var dest = []; for(i=0; i < unpsz; i++) dest.push(0); //unpacked module
	function getstring(codeword) { //puts the string into a stack
		var c = codeword; while(c > 0xFF) { stk.push(dict[c-0x100]['rt']); c = dict[c-0x100]['cw'] } stk.push(c & 0xFF)
	}
	while(!end && br.offset <= lim && o <= unpsz) { //lzw decomp
		if(cwsz < 9 || cwsz > 12) return false; cW = br.read(cwsz);
		switch(cW) {
			case 0x100: cwsz = 9; nextcw = 0x102; dictsz = 0x200; contains = 0x102; //re-init the dictionary
				if(cwsz < 9 || cwsz > 12) return false; cW = br.read(cwsz); if(o >= unpsz) return false;
				dest[o++] = cW & 0xFF; break;
			case 0x101: end = true; break; //end of data
			default:
				getstring(cW < nextcw ? cW : pW); //codeword is already in the dictionary? get cW; else, pW
				C = stk[stk.length-1] & 0xFF;
				if(cW < nextcw)
					while(stk.length) { if(o >= unpsz) return false; dest[o++] = stk.pop() & 0xFF
					}
				else { //codeword not yet defined
					while(stk.length) { if(o >= unpsz) return false; dest[o++] = C; stk.pop() }
					if(cW != nextcw) return false;
				}
				if(contains < dictsz) { dict[contains-0x100] = { 'rt':C, 'cw':pW }; contains++ }
				nextcw++; if(nextcw >= dictsz) if(cwsz < 12) { cwsz++; dictsz <<= 1 }					
		}
		pW = cW
	}
	if(!end) return false;
	sz = br.offset;
	if(dest[0] != 0x83 || dest[1] || (dest[2]&8) || (dest[3]&0x20) || (dest[5]&0x80) || dest[6] > 1) return false;
//_logBase64(toBase64(dest)); //outputs the unpacked module for further saving
	return true
}
if(!bDetected && isUltima6M()) {
	sName = 'Ultima 6 Adlib module (.M)'; sOption('unpsz:'+unpsz+' sz:'+outSz(sz)); bDetected = 1
}


function isQuartetST() {
	if(!isWithin(X.U16(0,_BE),4,0x10)) return false;
	if(!isWithin(X.U16(2,_BE),8,0x20)) return false;
	if(!isWithin(X.U16(4,_BE),4,0x30)) return false;
	if(!isWithin(X.U8(6),1,4) || !X.c("0400000000",7)) return false;
	if(!X.c("'WT'",0xC) && X.U32(0xC,_BE)) return false;
	if(!isWithin(X.U8(0x17),3,0x77)) return false;
	if((t=X.U32(0x18,_BE)) > 0x4C || (t&3)) return false;
	sz = 0x10; var szlim = Math.min(X.Sz(),0xFFFF); x = 0;
	for(p=0x1C; p < Math.min(X.Sz()-1,0x400); p+=12) if(!isWithin(X.U16(p,_BE),0x46,0x6C)) return false;
	while(sz < szlim) {
		if(!X.c("0056",sz)) break; x++;
		for(i=0; i < 4; i++) for(sz += 2; sz < szlim; sz+=2) if(X.c("0046",sz)) break;
		sz += 0xC;
		break //TODO? Fugue.4v or Sweet Dreams.4v have more info after the 4 iterations, but it looks funny there
//_log(Hex(sz))
	}
	spd0 = X.U16(0xE,_BE);
	return !x || sz <= szlim
}
if(!bDetected && isQuartetST()) {
	sName = "Illusions/Microdeal Quartet module (QTS.+SMP., .4V+.SET)";
	sVersion = "samples"; bDetected = 1;
	if(X.isVerbose()) {
		//if(x > 1) sOption(x,'voices?'); //TODO for the same reason
		sOption((spd0?'spd0:'+spd0+' ':'')+'sz:'+outSz(sz))
	}
}


function isADL() {
	//ref https://github.com/adplug/adplug/blob/master/src/adl.cpp -> CadlPlayer::load
	if(X.Sz() < 720 || X.Sz() > 0xFFFF) return false; //v1 min. size
	p = 0; nV = 4; ofs = 500 /*0x1F4*/; //track entries
	for(i=0; i < 250; i++) { //500=1F4h is the trackentries buffer
		w = X.U16(p,_LE); p += 2;
		if(500 <= w && w < 0xFFFF) { //in v4, all entries are within this range
			nV = 3; ofs = 120 /*0x78*/; break
		}
	}
	p = ofs; var z = true;
	if(nV < 4) {
		nprog = 150; //for v1
		for(i=0; i < nprog; i++) {
			w = X.U16(p,_LE); p += 2;
			if(w) { z = false;
				if(w != 0xFFFF && w+ofs > X.Sz()) return false;
				if(w < 600) return false;
				if(w < 1000) { nV = 1; if(X.U16(120,_LE) != 0x258) return false }
			}
			if(nV > 1) {
				if(X.Sz() < 1120) return false; //v2/3 min. size
				nprog = 250 /*0xFA*/;
				for(i=150; i < nprog; i++) { //check the rest of'em
					w = X.U16(p,_LE); p += 2;
					if(w) { z = false;
						if(w != 0xFFFF && (w+ofs > X.Sz() || ofs < nprog)) return false;
						if(w < 1000) return false;
					}}}}
		} else { //v4
		if(X.Sz() < 2500) return false;
		nprog = 500;
		for(i=0; i < nprog; i++) { //check the rest of'em
			w = X.U16(p,_LE); p += 2;
			if(w) { z = false;
				if(w != 0xFFFF && (w+ofs > X.Sz() || ofs < nprog)) return false;
				if(w < 2000) return false;
			}}}
	return !z
	//whilst it'd be nice to have instruments (same len as prog ptr tbl) subsong detection...
	//... it's too much already
	//TODO false positives keep occurring though! Gotta invent and do some more sanity checks
}
if(!bDetected && X.isDeepScan() && isADL()) {
	sName = "Westwood ADL module (.ADL)"; bDetected = 1;
	switch(nV) {
	case 1: sVersion = "v1"; break;
	case 3: sVersion = "v2.x"; break;
	case 4: sVersion = "v3"
	}
}


function isFLS() {
	//this detection is based purely on sanity checks
	//  AND a tiiiiiny ~bruteforce~ of membase, which is why it comes in last
	//ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundFLS
	if(X.Sz() < 6) return false;
	//because it can get SO SLOW and the biggest module known is 6652 bytes long:
	if(X.Sz() > 8192) return false;
	postp = X.U16(0x00); orntp = X.U16(0x02); //positions & ornaments table ptrs
	smptp = X.U16(0x04); ptnp = 6; //samples table ptr & patterns
	j = postp - smptp; if(j < 0 || (j&3)) return false;
	j = orntp-16; if(j < 0) return false;
	while(1) {
		j2 = smptp+2-j;
		if(j2 > X.Sz()+6) return false; // tentatively
		if(8 <= j2 && j2 < X.Sz()) {
			j3 = X.U16(j2);
			j1 = j3-j;
			if(8 <= j1 && j1 < X.Sz()) {
				j3 = X.U16(j2-4);
				j2 = j3 - j;
				if(6 <= j2 && j2 < X.Sz())
					if(j1-j2 == 0x20) {
						j2 = X.U16(ptnp+2)-j;
						if(21 < j2 && j2 < X.Sz()) {
							j1 = X.U16(ptnp)-j;
							if(20 < j1 && j1 < X.Sz())
								if(!X.U8(j1-1))
									while(j1 <= X.Sz() && X.U8(j1) < 0xFF) {
										while(1) {
											bt = X.U8(j1);
											if(bt <= 0x5F || bt == 0x80 || bt == 0x81) { j1++; break }
											else if(0x82 <= bt <= 0x8E) j1++;
											if(j1 > X.Sz()) break;
										}
									}
							if(j1+1 == j2) break;
						}
					}
			}
		}
		j--; if(j < 0) break;
	}
	if(j < 0) return false;
	if((postp-j > X.Sz()) || (postp-j < 16)) return false;
	sz = X.U16(postp-j-2)+0x60-j;
	if(sz <= postp-j) return false;
	if(sz > X.Sz()+1) return false;
	if((smptp-j)&1) return false;
	membase = j;
	return true;
}
if(!bDetected && X.isDeepScan() && isFLS()) {
	sName = "Flash Tracker module (.FLS)"; sVersion = "@"+Hex(j); bDetected = 1;
	if(X.isVerbose()) sOption = "sz:"+outSz(sz)
}



//the followng signature detectors seem shaky to me [Kae] so they're heuristic and in parallel
//_setResult(TYPE, NAME, VERSION, OPTIONS)

if(X.isHeuristicScan()) { //parallel block

	// Let's start with just judging by the extensions:
	if(extIs("imf") || extIs("wlf")) { //a detector is quite impossible to produce...
		if(extIs("imf"))
			freq = "560Hz (or 280Hz if Duke Nukem II)";
		else
			freq = "700Hz";
		_setResult("audio","id/Apogee Music Format chiptune (.IMF)","","freq: "+freq);
	}
	else if(extIs("svar") && X.c("'PK'")) { //TODO detect in-zip
		_setResult("audio","SVArTracker module (.SVAR)","","");
	}

	//these are compares at 0, so they can be non-parallel, to optimise it all
	if(X.c("8400")) {
		if(X.U8(2) == 0xF0) sversion = "adv."; else sversion = "";
		_setResult("audio","Sierra Adlib chiptune (.SCI)",sversion,"")
	}
	else if(X.c("1C52")) {
		_setResult("audio","Sound Interface System module (.LEM)","","")
	}
	else if(X.c("'NED'")) {
		_setResult("audio","Nerd Tracker ][ module (.NED)","","")
	}
	else if(X.c("'MODU'")) {
		if(X.isVerbose())
			_setResult("audio","NovoTrade Packer module (.NTP)","",X.SA(4,16));
		else _setResult("audio","NovoTrade Packer module (.NTP)","","")
	}
	else if((X.c("'Ice!'") || X.c("'ICE!'"))) {
	//TODO find a sanity check to narrow it down, then move it to the other sanities
		dsize = X.U32(8,_BE);
		_setResult("audio","Atari ST module (.SND,.SNDH)","compressed","orig.sz:"+dsize+" sz:"+outSz(X.U32(4,_BE)))
	}

	function isVortex1() {
		//ref https://bulba.untergrund.net/Ay_Emul30alpha11.src.7z / Formats.pas
		// & https://f.rdw.se/AY-3-8910-datasheet.pdf
		hdr = X.SA(0,2);
		if(['AY','YM','ay','ym'].indexOf(hdr) < 0 || X.U8(2) > 6) return false;
		hasyear = ['AY','YM'].indexOf(hdr) < 0; yr = 0;
		if(hasyear) {
			yr = X.U16(0xA,_LE);
			if(yr && (yr < 1980 || yr > 2050)) return false;
		}
		unpsz = X.U32(hasyear?0xC:0xA,_LE);
		if(unpsz < 2 || unpsz > 0xA00000) return false; // maxsize set a bit up from enlight '97 megamix.vtx
		chipfrq = X.U32(5,_LE); if(chipfrq < 800000 || chipfrq > 4000000) return false; //2M would be ok
		intfrq = X.U8(9);
		return true
	}
	if(isVortex1()) {
		if(!X.U8(2)) sversion = "1ch"; else sversion = "2ch";
		switch (hdr) {
			case 'AY': case 'ay': sversion += " AY-3-8910/12"; break;
			case 'YM': case 'ym': sversion += " YM2149"; break;
			default: sversion += " unk.chip" // never reached
		}
		if(X.isVerbose()) {
			p=hasyear?0x10:0x0E; t = X.SC(p,Math.min(256,X.Sz()-p),'CP1251'); sOptionT(t);
			p+=t.length+1; a = X.SC(p,Math.min(256,X.Sz()-p),'CP1251'); sOptionT(a,"by:");
			p+=a.length+1; if(hasyear) {
				if(yr) sOption(yr,"'");
				pr = X.SC(p,Math.min(256,X.Sz()-p),'CP1251'); p+=pr.length+1;
				sOptionT(pr,"for:");
				tn = X.SA(p,Math.min(256,X.Sz()-p)); sOptionT(tn,"in:"); p+=tn.length+1;
				c = X.SA(p,Math.min(256,X.Sz()-p)); sOptionT(c); //p+=c.length+1;
			}
			sOption((X.U8(2)&7).toString(2).padStart(3,'0'),"mode:");
			sOption(chipfrq,"chip freq:","Hz"); sOption(intfrq,"int.freq:","kHz")
			t = Util.div64(unpsz,intfrq*14); sOption("time:"+secondsToTimeStr(t));
			loop = X.U16(3,_LE); if(loop) sOption(loop,"loop:");
		}
		_setResult("audio","Vortex Project chiptune (.VTX)",sversion,sOptions); sOptions = ""
	}


	function isMXDRVPDX() {
		// the format is very very very basic so .ADL overlaps happen
		//and it's not possible to test more
		if(X.Sz() < 0x300 || X.Sz() > 96*8+96*65535) return false;
		sz = 0;
		for(i=0; i<96; i++) {
			p = X.U32(i*8,_BE);
			if(!p) continue;
			if(p < 0x300) return false;
			sz++;
			s = X.U16(i*8+4,_BE);
			if(s > 0xFFFF || p+s > X.Sz()) return false;
		}
		if(!sz) return false;
		return true
	}
	if(isMXDRVPDX())
		_setResult("audio","X68k MXDRV PCM resource (.PDX)","","entries:"+sz)

	function isKDM() {
		// ref http://advsys.net/ken/kdmsongs.zip/KDMSRC.ZIP/kdmwin/KDMENG.C
		if(X.Sz() < 12) return false;
		if(X.U32(0,_LE)) return false; //kdmversionum
		numnotes = X.U32(4,_LE); if(!numnotes || numnotes > 8192) return false;
		numtracks = X.U32(8,_LE); if(!numtracks || numtracks > 256) return false;
		sz = 12+numtracks*4+numnotes*11;
		if((sz != X.Sz()) && (sz != X.Sz()+numnotes)) return false;
		return true
	}
	if(isKDM()) {
		if(sz > X.Sz()) sversion = "no panning effects"; else sversion = "";
		if(X.isVerbose()) soption = "trk:"+numtracks+" notes:"+numnotes; else soption = "";
		_setResult("audio","Ken's Digital Music module (.KDM)",sversion,soption);
	}

	function isSQDigitalTracker() {
		//from https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/sqdigitaltracker.cpp
		for(i=0; i < 8; i++) {
			if(X.U8(0xC0+i*4)) return false;
			q = X.U8(0xC0+i*4+1); if(q < 0x80 || q > 0xC0) return false;
			q = X.U8(0xC0+i*4+2); if(q < 0x58 || q > 0x5F) return false;
			q = X.U8(0xC0+i*4+3); if(!q || q > 0x80) return false;
		}
		for(i=0; i < 0x20; i++) {
			t_ = X.U8(0x100+i);
			if(t_[i] < 0x20 || t_[i] > 0x7F) return false
		}
		t = X.SA(0x100,0x20);
		ptn = -1;
		for(i=0; i < 100; i++) {
			q = X.U8(0x1A0+i); if(q > 0x1F) return false;
			if(ptn < q) ptn = q;
		} ptn++;
		if(X.U8(0x204) != 0xFF) return false;
//_log(1);
		tempo = X.U8(0x210);
//_log("tempo:"+tempo);
		if(tempo < 2 || tempo > 10) return false;
		len = X.U8(0x212);
//_log("len:"+len);
		if(!len || len > 0x64) return false; len++;
		loop = X.U8(0x211);
//_log("loop:"+loop);
		if(loop >= len) return false;
		return true
	}
	if(X.isDeepScan() && isSQDigitalTracker()) {
		soption = "";
		if(X.isVerbose()) {
			soption = soption.append(t.trim());
			soption = soption.append("tempo:"+tempo+" ord:"+len+" ptn:"+ptn+" loop:"+loop);
		}
		_setResult("audio","SQ Digital Tracker module (.SQD,.M)","",soption)
	}

	function isDigitalStudio() {
		//ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/digitalstudio.cpp
		if(X.Sz() < 111104) return false;
		lp = X.U8(0); if(lp > 63) return false;
		ptn = 0;
		for(i = 1; i < 100; i++) { pt = X.U8(i); if(pt > 0x1F) return false; if(pt > ptn) ptn = pt } ptn++;
		tmp = X.U8(0x64); ord = X.U8(0x65); if(!ord || ord > 100) return false; if(lp > ord) return false;
		title = readBytes(0x66,28); for(i=0; i < 28; i++) if(title[i] < 0x20 || title[i] > 0x7F) return false;
		title = decEncoding(title,CPSpeccy);
		compiled = !isAllZeroes(0xC8,0x38); allsmp = smp4bit = 0;
		smps = [];
		for(p=0x100; p < 0x200; p += 0x10) {
			var sst = X.U16(p), stlp = X.U16(p+2), spage = X.U8(p+4), sninbank = X.U8(p+5), ssz = X.U16(p+6);
			for(i=8; i < 16; i++) if(X.I8(p+i) < 0x20) return false;
			t = decAnsi(p+8,8,CPSpeccy,false).trim(); if(t != '') smps.push(t)
		}

		return true;
	}
	if(isDigitalStudio()) {
		sname = "Underground Systems Digital Studio module (.DST)";
		sversion = compiled?'compiled':'';
		if(X.isVerbose()) {
			sOptionT(title); sOptionT(smps.join(' '),'smp/msg:"','"');
			sOption('ord:'+ord+' ptn:'+ptn+(lp?' loop:'+lp:'')+' page0:'+X.U8(0x200)+' sz:'+outSz(compiled?0x1C200:0x1B200))
			soption = sOptions; sOptions = ''
		} else soption = '';
		_setResult('audio',sname,sversion,soption)
	}

	function isExtremeTrackerZX() {
		//from https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/extremetracker1.cpp
		if(X.Sz() < 112640) return false;
		if((lp = X.U8(0)) > 63) return false;
		if((tmp = X.U8(1)) < 3 || tmp > 0xF) return false;
		if((ord = X.U8(2)) > 64) return false;
		title = readBytes(3,30); for(i=0; i < 30; i++) if(title[i] < 0x20 || title[i] > 0x7F) return false;
		title = decEncoding(title,CPSpeccy);
		ptn = 0;
		for(p = 0x22; p < 0x86; p++) { if((pt = X.U8(p)) > 0x1F) return false; if(pt > ptn) ptn = pt } ptn++;
		for(; p < 0xA6; p++) { var ps = X.U8(p); if(ps < 4 || ps > 0x40) return false }
		var qstvw = [0x51,0x53,0x54,0x56,0x57];
		for(i=0; p < 0xBA; i++, p += 4) {
			if(X.U8(p) > 0x7C) return false; if(X.U8(p+1) != qstvw[i]) return false;
			if(X.U8(p+2) && X.U8(p+2) < 0x84) return false; if(X.U8(p+3) > 0x10) return false
		}
		var sus = 0; for(p=0xBC; p < 0xCB; p++) if(X.U8(p)) sus++; if(sus > 3) return false;
		var sig = readBytes(0xCB,0x35,true); if(charStat(sig,true).indexOf('allasc') <= 0) return false;
		if(X.U8(0xFF)) return false;
		smps = [];
		for(p = 0x100; p < 0x200; p+= 0x10) {
			if(qstvw.indexOf(X.U8(p+4)) < 0) return false;
			if(X.U8(p+5) > 0x10) return false; if(X.U8(p+6) > 0x7C) return false;
			t = readBytes(p+8,8); for(i=0; i < 8; i++) if(t[i] < 0x20 || t[i] > 0x7F) return false;
			t = decEncoding(title,CPSpeccy).trim(); if(t != '') smps.push(t)
		}
	}
	if(isExtremeTrackerZX()) {
		sname = "Red Limited's Extreme Tracker module (.ET1)";
		if(X.isVerbose()) {
			sOptionT(title); sOptionT(smps.join(' '),'smp/msg:"','"');
			sOption('tmp0:'+tmp+' ord:'+ord+' ptn:'+ptn+(lp?' loop:'+lp:'')+' sz:'+outSz(112640))
			soption = sOptions; sOptions = ''
		} else soption = '';
		_setResult('audio',sname,sversion,soption)
	}

	function isMON() {
		if(!X.c("4EFA....4EFA....4EFA")) return false;
		var t = X.U16(2,_BE); if (t > X.Sz() - 10) return false;
		return X.c("4BFA.... 08AD 0000",t+2)
	}
	if(isMON())
		_setResult("audio","Maniacs of Noise module (.MON)?","","")

	function isJHSTSMP() {
		if(X.Sz() < 0x80) return false;
		oldp = smp = 0;
		for(i = 0; i < 0x80; i += 8) {
			p = X.U16(i,_BE); if(p > X.Sz()) return false;
			if(p) { if(p < oldp) return false; oldp = p; smp++ }
			if(!X.c("0010 01000000",i+2) && !X.c("0000 00000000",i+2)) return false;
		}
		if(smp < 8) return false; // I only saw files with at least 10
		return true
	}
	if(isJHSTSMP()) _setResult("audio","Jochen 'Mad Max' Hippel's Atari ST sample set (SMP.set)","",
			"smp:"+smp+" sz:"+outSz(oldp))


	function isV2M() {
		//ref https://github.com/farbrausch/fr_public/blob/master/v2/v2mplayer.cpp ::InitBase
		//with kind commentary from Tammo "kb" Hinrichs
		notes = chs = 0;
		if(X.Sz() < 480) return false;
		timediv = X.U32(0,_LE); if(!timediv || [0x60,0x80,480].indexOf(timediv) < 0) return false; //empirical
		maxtime = X.U32(4); if(!maxtime) return false;
		gdnum = X.U32(8); if(!gdnum) return false;
		p = 12+10*gdnum; var pcs = pbs = ccs = 0;
		for(ch=0; ch < 16; ch++) {
			notenum = X.U32(p); p += 4; notes += notenum;
			if(notenum > 1000000) return false; // nobody'll write a million-note synth epic, will they @_@
			if(notenum) { chs++;
				p += 5*notenum; if(p > X.Sz()) return false;
				pcnum = X.U32(p); pcs += pcnum; p += 4+4*pcnum; //pgm change events
				pbnum = X.U32(p); pbs += pbnum; p += 4+5*pbnum; //pitch bends
				for(cn=0; cn < 7; cn++) {
					ccnum = X.U32(p); ccs += ccnum; p += 4+4*ccnum; //control changes
				}
			}
			if(p > X.Sz()) return false;
		}
		if(!notes || ccs > notes*50 || pbs > notes*3 || pcs > notes*3 || maxtime < notes) { //*50 & 3 are arbitrary
//_l2r('V2M',p,'notes:'+notes+' pcs:'+pcs+' pbs:'+pbs+' ccs:'+ccs)
			return false }
		size = X.U32(p); if(size > 0x4000) return false; //globals
		p += 4+size; if(p > X.Sz()) return false;
		size = X.U32(p); if(size > 0x100000) return false; //patchmap
		p += 4+size; if(p > X.Sz()) return false;
//_l2r('V2M',p,'speech -> '+Hex(p+4+X.U32(p)))
		spsize = X.U32(p); p += 4; if(spsize < 0x2000) {
			for(i=p+4; i < p+4+X.U32(p)*4; i+=4) if(!isWithin(X.U32(i), X.U32(p)*4, p-4+spsize)) return false;
			p += spsize //speech size -- except tons of files are shorter than that, so we'll read the pointers!
		}
		sz = p; return true;
	}
	if(!bDetected && X.isDeepScan() && isV2M()) {
		sName = "farbrausch V2 Synthesizer module (.V2M)"; bDetected = 1;
		if(X.isVerbose())
			sOptions = 'ch:'+chs+' notes:'+notes+' timediv:'+timediv+' maxtime:'+maxtime
				+(spsize?' syn.speech':'')+' sz:'+outSz(sz);
	}


	function isTwinTeam() {
		if(!X.c("01000000", 0x06) || X.Sz() < 1500 || X.Sz() > 9000 || X.calculateEntropy(12,1200) < 7.8)
			return false; //a quick check because that's a lot of algo
		//ideally, get the checks from https://github.com/adplug/adplug/blob/master/src/dmo.cpp
		return true
	}
	if(isTwinTeam()) {
		_setResult("audio","TwinTeam's Twin Trackplayer module (.DMO)","",'unp.sz:'+X.U16(0xC))
	}


} //isHeuristicScan parallel block

	return result(bShowType,bShowVersion,bShowOptions);
}
/* beautify ignore:end */