1 /**
  2  * This file is part of the Web Enabled Audio and Sound Enhancement Library (aka the Weasel audio library) Copyright 2011 - 2013 Warren Willmey. It is covered by the GNU General Public License version 3 as published by the Free Software Foundation, you should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
  3  */
  4 
  5 if( undefined == window.weasel ) window.weasel = {};
  6 
  7 // ---------------------------------------------------------------------------
  8 /**
  9  * Sniff out what type of Sound Tracker Module is present, give a reason if possible.
 10  * @constructor
 11  * 
 12  * @author Warren Willmey 2011.
 13  */
 14 weasel.ModuleSniffer = function()
 15 {
 16 	this.sModuleType = 'Unknown';
 17 	this.sReason = 'Data not examined';
 18 	this.oSniffReasons = {};
 19 	this.iUniquePatterns = -1;
 20 	// Has a The Jungle Command Soundtracker 2 exclusive command been found?
 21 	//
 22 	this.bTJCSpecificCommandFound = false;
 23 	// There are valid TJC modules that use D 00 and E 00 and E 01 (which are also valid DOC 9 commands).
 24 	// See if there are any other parameters values used to indicate
 25 	// this is a TJC modules.
 26 	//
 27 	this.bTJCSlideCommandsLookValid = false;
 28 
 29 	// Keep tracker of the Maximum Panning Value used, used for detecting if
 30 	// 7bit panning (Fasttracker 1.0) is being used or 8bits.
 31 	// Work out the Standard Deviation for Panning so that we can better identify
 32 	// went 7bit panning is used (lots of typos in Panning values results in miss identification).
 33 	//
 34 	this.iMaxPanningPosition = 0;
 35 	this.aPanningPositions = [];
 36 	this.fPanningMean = 0.0;
 37 	this.fPanningVariance = 0.0;
 38 	this.fPanningStandardDeviation = 0.0;
 39 	this.iPanningEffectTotal = 0;
 40 
 41 	// Coarse Panning is Extended Command E8x. The origin of this command stems from Pentagons (Zwerg Zwack) ZZPlay routine for the GUS, which got adopted in MTM.
 42 	//
 43 	this.bCoarsePanningUsed = false;
 44 
 45 	this.__reset();
 46 };
 47 
 48 // ---------------------------------------------------------------------------
 49 /** Different types of Soundtracker Modules the sniffer can identify.
 50  * 
 51  * @const
 52  * @enum {string}
 53  */
 54 weasel.ModuleSniffer.prototype.SupportedModules = {
 55 		  UltimateSoundTracker121	: 'Ultimate Soundtracker 1.21'
 56 		, UltimateSoundTracker18	: 'Ultimate Soundtracker 1.8'
 57 		, DOCSoundTracker9			: 'DOC Soundtracker 9'
 58 		, DOCSoundTracker22			: 'DOC Soundtracker 2.2'
 59 		, TJCSoundTracker2			: 'TJC Soundtracker 2'
 60 		, DefJamSoundTracker3		: 'Def Jam Soundtracker 3'
 61 		, SpreadpointSoundTracker23	: 'Spreadpoint Soundtracker 2.3'
 62 		, NoiseTracker11			: 'Noisetracker 1.1'
 63 		, NoiseTracker20			: 'Noisetracker 2.0'
 64 		, ProTrackerMK				: 'Protracker M.K.'
 65 		, SpreadpointSoundTracker25	: 'Spreadpoint Soundtracker 2.5'
 66 		, FSTModule					: 'Taketracker/Fasttracker'
 67 };
 68 
 69 // ---------------------------------------------------------------------------
 70 /**
 71  * Reset module sniffer to initial state.
 72  * 
 73  * @private
 74  */
 75 weasel.ModuleSniffer.prototype.__reset = function( )
 76 {
 77 	this.iUniquePatterns = -1;
 78 	this.sModuleType = 'Unknown';
 79 	this.sReason = 'Data not examined';
 80 	this.oSniffReasons = {};
 81 	this.bTJCSpecificCommandFound = false;
 82 	this.bTJCSlideCommandsLookValid = false;
 83 	this.iMaxPanningPosition = 0;
 84 	this.bCoarsePanningUsed = false;
 85 };
 86 
 87 // ---------------------------------------------------------------------------
 88 /** Scan song length and pattern table for validity.
 89  * 
 90  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
 91  * 
 92  * @return {bool} true if failed, false if passed UltimateSoundTracker scan.
 93  * 
 94  * @private
 95  */
 96 weasel.ModuleSniffer.prototype.__UltimateSoundtrackerLengthOfSong = function( aModuleData )
 97 {
 98 	// Check minimum size of module header.
 99 	//
100 	if( aModuleData.length < weasel.FormatUltimateSoundTracker121.MinimumHeaderSize )
101 	{
102 		this.sReason = 'Data size is too small for Module, need at least ' + weasel.FormatUltimateSoundTracker121.MinimumHeaderSize + ' bytes for a header and at least one song pattern (not including instruments).';
103 		return true;
104 	}
105 
106 	// Check minimum length of song.
107 	//
108 	if( weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongLength ) < 1 )
109 	{
110 		this.sReason = 'Song length too short (minimum length of 1 pattern).';
111 		return true;
112 	}
113 
114 	// Check maximum length of song (only room for 128 patterns in pattern sequence).
115 	//
116 	if( weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongLength ) > weasel.FormatUltimateSoundTracker121.SequenceTableLength )
117 	{
118 		this.sReason = 'Song length too long (maximum 128 patterns).';
119 		return true;
120 	}
121 	
122 	return false;
123 };
124 
125 
126 // ---------------------------------------------------------------------------
127 /** Check song length and pattern table for validity of M.K. (Michael Kleps) 31
128  * instrument Soundtracker.
129  * 
130  * @param {Array|Uint8Array} aModuleData = The suspected M.K. Soundtracker module in array format.
131  * 
132  * @return {bool} true if failed, false if passed M.K. Soundtracker scan.
133  * 
134  * @private
135  */
136 weasel.ModuleSniffer.prototype.__MichaelKlepsSoundtrackerLengthOfSong = function( aModuleData )
137 {
138 	// Check minimum size of module header.
139 	//
140 	if( aModuleData.length < weasel.FormatSpreadpointSoundTracker23.MinimumHeaderSize )
141 	{
142 		this.sReason = 'Data size is too small for Module, need at least ' + weasel.FormatSpreadpointSoundTracker23.MinimumHeaderSize + ' bytes for a header and at least one song pattern (not including instruments).';
143 		return true;
144 	}
145 
146 	return this.__SoundtrackerLengthOfSong( aModuleData );
147 };
148 
149 
150 // ---------------------------------------------------------------------------
151 /** Check song minimum and maximum length of Soundtracker mod.
152  * 
153  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
154  * 
155  * @return {bool} true if failed, false if passed Soundtracker scan.
156  * 
157  * @private
158  */
159 weasel.ModuleSniffer.prototype.__SoundtrackerLengthOfSong = function( aModuleData )
160 {
161 	// Check minimum length of song.
162 	//
163 	if( weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength ) < 1 )
164 	{
165 		this.sReason = 'Song length too short (minimum length of 1 pattern).';
166 		return true;
167 	}
168 
169 	// Check maximum length of song (only room for 128 patterns in pattern sequence).
170 	//
171 	if( weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength ) > weasel.FormatUltimateSoundTracker121.SequenceTableLength )
172 	{
173 		this.sReason = 'Song length too long (maximum 128 patterns).';
174 		return true;
175 	}
176 
177 	return false;
178 };
179 
180 // ---------------------------------------------------------------------------
181 /** Check song length and pattern table for validity of Taketracker/Fasttracker.
182  * 
183  * @param {Array|Uint8Array} aModuleData = The suspected Taketracker/Fasttracker module in array format.
184  * 
185  * @return {bool} true if failed, false if passed Taketracker/Fasttracker scan.
186  * 
187  * @private
188  */
189 weasel.ModuleSniffer.prototype.__FSTLengthOfSong = function( aModuleData )
190 {
191 	// Check minimum size of module header.
192 	//
193 	if( aModuleData.length < weasel.FormatFSTModule.MinimumHeaderSize )
194 	{
195 		this.sReason = 'Data size is too small for Module, need at least ' + weasel.FormatFSTModule.MinimumHeaderSize + ' bytes for a header and at least one song pattern (not including instruments).';
196 		return true;
197 	}
198 
199 	return this.__SoundtrackerLengthOfSong( aModuleData );
200 };
201 
202 // ---------------------------------------------------------------------------
203 /** Scan the whole sequence table (not just the length of the song) 
204  * for the unique number of patterns used (the samples are stored after the patterns).
205  * 
206  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
207  * 
208  * @return {int} The number of unique patterns.
209  * 
210  * @private
211  */
212 weasel.ModuleSniffer.prototype.__uniqueNumberOfPatterns = function( aModuleData )
213 {
214 	return this.__uniquePatterns( aModuleData, weasel.FormatUltimateSoundTracker121.PatternSequenceTable, weasel.FormatUltimateSoundTracker121.SequenceTableLength );
215 };
216 
217 // ---------------------------------------------------------------------------
218 /** For a M.K. Module scan the whole sequence table (not just the length of the song) 
219  * for the unique number of patterns used (the samples are stored after the patterns).
220  * 
221  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
222  * 
223  * @return {int} The number of unique patterns.
224  * 
225  * @private
226  */
227 weasel.ModuleSniffer.prototype.__MKUniqueNumberOfPatterns = function( aModuleData )
228 {
229 	return this.__uniquePatterns( aModuleData, weasel.FormatSpreadpointSoundTracker23.PatternSequenceTable, weasel.FormatUltimateSoundTracker121.SequenceTableLength );
230 };
231 
232 // ---------------------------------------------------------------------------
233 /** Scan the whole sequence table (not just the length of the song) 
234  * for the unique number of patterns used (the samples are stored after the patterns).
235  * 
236  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
237  * @param {int} iSequenceTableOffset = The offset into the module data of the sequence table.
238  * @param {int} iSequenceTableLength = The length of the sequence table.
239  * 
240  * @return {int} The number of unique patterns.
241  * 
242  * @private
243  */
244 weasel.ModuleSniffer.prototype.__uniquePatterns = function( aModuleData, iSequenceTableOffset, iSequenceTableLength )
245 {
246 	if( this.iUniquePatterns > -1 )
247 	{
248 		return this.iUniquePatterns;
249 	}
250 
251 	var iFindMaxPattern = 0;
252 
253 	for( var iSequences = 0; iSequences < iSequenceTableLength; iSequences++ )
254 	{
255 		var iPatternNumber = weasel.Helper.getByte( aModuleData, iSequenceTableOffset + iSequences );
256 		
257 		if( iPatternNumber > iFindMaxPattern )
258 		{
259 			iFindMaxPattern = iPatternNumber;
260 		}
261 	}
262 
263 	this.iUniquePatterns = iFindMaxPattern + 1;
264 
265 	return this.iUniquePatterns;
266 };
267 
268 // ---------------------------------------------------------------------------
269 /** Check number of patterns in a M.K. module.
270  * 
271  * @param {Array|Uint8Array} aModuleData = The suspected M.K. Soundtracker module in array format.
272  * 
273  * @return {bool} true if failed, false if number of patterns is OK.
274  * 
275  * @private
276  */
277 weasel.ModuleSniffer.prototype.__MKSoundtrackerCheckNumberOfPatterns = function( aModuleData )
278 {
279 	// Check that there are no crazy pattern numbers, which might indicate just binary data (not a module).
280 	//
281 	var iUniquePatterns = this.__MKUniqueNumberOfPatterns( aModuleData );
282 
283 	if( iUniquePatterns > weasel.FormatUltimateSoundTracker121.MaxUniquePatterns )
284 	{
285 		this.sReason = 'Pattern sequence number to big (expected 0-63).';
286 		return true;
287 	}
288 
289 	// Check that the module data provided is big enough to contain all patterns (not including samples),
290 	//
291 	var iModuleSize = ( iUniquePatterns * weasel.FormatUltimateSoundTracker121.PatternSize ) + weasel.FormatSpreadpointSoundTracker23.PatternData;
292 	
293 	if( iModuleSize > aModuleData.length )
294 	{
295 		this.sReason = 'Soundtracker Module data too small to contain all patterns in pattern sequence. There are ' + (iUniquePatterns) + ' pattern(s), which would need at least ' + iModuleSize + ' bytes (not including samples).';
296 		return true;
297 	}
298 
299 	return false;
300 };
301 
302 // ---------------------------------------------------------------------------
303 /** Get number of channels in a Taketracker/Fasttracker module.
304  * BE AWARE only use this function AFTER the module has been identified as a FST module (as it does NO ERROR CHECKING!).
305  * 
306  * @param {Array|Uint8Array} aModuleData = The suspected Taketracker/Fasttracker module in array format.
307  * 
308  * @return {int} The number of channels used in the FST module [2-32].
309  * 
310  * @private
311  */
312 weasel.ModuleSniffer.prototype.__FSTGetNumberOfChannels = function( aModuleData )
313 {
314 	var iHighChannelDigit = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint ) - 48;
315 	var iLowChannelDigit = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint +1 ) - 48;
316 
317 	if( iHighChannelDigit < 0 )
318 	{
319 		iHighChannelDigit = 0;
320 	}
321 
322 	if( iLowChannelDigit < 0 )
323 	{
324 		iLowChannelDigit = 0;
325 	}
326 
327 	if( iLowChannelDigit > 9 )
328 	{
329 		// xCHN FST Marker used. So 2-9 channels.
330 		//
331 		return iHighChannelDigit;
332 	}
333 
334 	// xxCH FST Marker user so 10-32 channels.
335 	//
336 	var iChannels = (iHighChannelDigit * 10) + iLowChannelDigit;
337 
338 	return iChannels;
339 };
340 
341 // ---------------------------------------------------------------------------
342 /** Check number of patterns in a Taketracker/Fasttracker module.
343  * 
344  * @param {Array|Uint8Array} aModuleData = The suspected Taketracker/Fasttracker module in array format.
345  * 
346  * @return {bool} true if failed, false if number of patterns is OK.
347  * 
348  * @private
349  */
350 weasel.ModuleSniffer.prototype.__FSTCheckNumberOfPatterns = function( aModuleData )
351 {
352 	// Check that there are no crazy pattern numbers, which might indicate just binary data (not a module).l
353 	//
354 	var iUniquePatterns = this.__MKUniqueNumberOfPatterns( aModuleData );
355 	var iChannels = this.__FSTGetNumberOfChannels( aModuleData );
356 
357 	if( iChannels < 2 || iChannels > 32 )
358 	{
359 		this.sReason = 'Taketracker/Fasttracker Module has out of range number of expected channels [2-32], this module has: ' + iChannels + '.';
360 		return true;
361 	}
362 
363 	// Check that the module data provided is big enough to contain all patterns (not including samples),
364 	// bearing in mind that a FST module can have 2-32 channels (means different pattern sizes). 
365 	//
366 	var iModuleSize = ( iChannels * iUniquePatterns * ( weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern * weasel.FormatUltimateSoundTracker121.BytesPerRowCell ) ) + weasel.FormatSpreadpointSoundTracker23.PatternData;
367 	
368 	if( iModuleSize > aModuleData.length )
369 	{
370 		this.sReason = 'Taketracker/Fasttracker Module data too small to contain all patterns in pattern sequence. There are ' + iChannels + ' channels and ' + (iUniquePatterns) + ' pattern(s), which would need at least ' + iModuleSize + ' bytes (not including samples).';
371 		return true;
372 	}
373 
374 	return false;
375 };
376 
377 
378 // ---------------------------------------------------------------------------
379 /** Check number of patterns in module (this is common between various different
380  * Soundtrackers such as Ultimate Soundtracker, DOC Soundtracker 9, The Masters Soundtracker etc).
381  * 
382  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
383  * 
384  * @return {bool} true if failed, false if number of patterns is OK.
385  * 
386  * @private
387  */
388 weasel.ModuleSniffer.prototype.__SoundtrackerCheckNumberOfPatterns = function( aModuleData )
389 {
390 	// Check that there are no crazy pattern numbers, which might indicate just binary data (not a module).
391 	//
392 	var iUniquePatterns = this.__uniqueNumberOfPatterns( aModuleData );
393 
394 	if( iUniquePatterns > weasel.FormatUltimateSoundTracker121.MaxUniquePatterns )
395 	{
396 		this.sReason = 'Pattern sequence number to big (expected 0-63).';
397 		return true;
398 	}
399 
400 	// Check that the module data provided is big enough to contain all patterns (not including samples),
401 	//
402 	var iModuleSize = ( iUniquePatterns * weasel.FormatUltimateSoundTracker121.PatternSize ) + weasel.FormatUltimateSoundTracker121.PatternData;
403 	
404 	if( iModuleSize > aModuleData.length )
405 	{
406 		this.sReason = 'Soundtracker Module data too small to contain all patterns in pattern sequence. There are ' + (iUniquePatterns) + ' pattern(s), which would need at least ' + iModuleSize + ' bytes (not including samples).';
407 		return true;
408 	}
409 
410 	return false;
411 };
412 
413 // ---------------------------------------------------------------------------
414 /** Scan Instruments details (size, loop lengths, volume etc) to check they are valid.
415  * 
416  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
417  * @param {weasel.ModuleSniffer.SupportedModules} sModuleScanTypeName = The module scan type, which is used when display an error.
418  * @param {int} iMaxSampleSize = The maximum allowed sample size to check against.
419  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
420  * 				(allows playing of some slightly different formats that are not 
421  * 				technically Ultimate Soundtracker 1.21 modules (different effect
422  * 				numbers) or ones that have corrupted data/bad rips).
423  * 
424  * @return {bool} true if failed, false if passed UltimateSoundTracker scan.
425  * 
426  * @private
427  */
428 weasel.ModuleSniffer.prototype.__UltimateSoundtrackerCheckInstruments = function( aModuleData, sModuleScanTypeName, iMaxSampleSize, bNotSoStrict )
429 {
430 	var iUniquePatterns = this.__uniqueNumberOfPatterns( aModuleData );
431 
432 	var iStartAddressOfSample = ((iUniquePatterns * weasel.FormatUltimateSoundTracker121.PatternSize) + weasel.FormatUltimateSoundTracker121.ModuleHeaderSize);
433 
434 	// Check maximum length and max volume of samples.
435 	//
436 	var iInstrumentOffset = weasel.FormatUltimateSoundTracker121.SampleHeaders;
437 	var bAllSamplesZeroVolume = true;
438 	var iSampleLengthsTotal = 0;
439 
440 	for( var iInstrumentNumber = 0; iInstrumentNumber < weasel.FormatUltimateSoundTracker121.NumberOfInstruments; iInstrumentNumber++ )
441 	{
442 		var iSampleLength = weasel.Helper.getWord( aModuleData,  iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.LengthInWords ) * 2;
443 		var iInstrumentVolume = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.Volume );
444 		var iRepeatOffset = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.RepeatOffsetInBytes );
445 		var iRepeatLength = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.RepeatLengthInWords ) * 2;
446 
447 		iInstrumentOffset += weasel.FormatUltimateSoundTracker121.SampleHeader.HeaderSize;
448 		iSampleLengthsTotal += iSampleLength;
449 
450 		// Check sample length is not longer than maximum.
451 		//
452 		if( iSampleLength > iMaxSampleSize )
453 		{
454 			this.sReason = 'Instrument #' + (iInstrumentNumber + 1) + ' sample length too long for ' + sModuleScanTypeName + ', which is limited to ' + iMaxSampleSize + ' bytes in length.';
455 			return true;
456 		}
457 
458 		if( iInstrumentVolume > 0 )
459 			bAllSamplesZeroVolume = false;
460 
461 		// Check volume is not greater than maximum.
462 		//
463 		if( iInstrumentVolume > 64 )
464 		{
465 			this.sReason = 'Instrument #' + (iInstrumentNumber + 1) + ' volume to large, 0-64 range only.';
466 			return true;
467 		}
468 
469 		// Is sample is looped?
470 		//
471 		if( iRepeatLength >= 4 )
472 		{
473 			// Check that sample loop points fit within actual sample data, but only if sample is exists.
474 			//
475 			// It should also be pointed out that the loop length *may* be bigger than the length of the sample..
476 			// This is usually due to user error when they added the sample to their Patch List. The majority of these
477 			// occurrences are usually because the loop start point has been set to 2, the sample would
478 			// be fine IF the loop start point had been set to zero.
479 			// The behaviour of the Sniffer is now to let "overlaying samples" through, as the default Ultimate/Soundtracker
480 			// replay routine just treats ALL the sample's data as one big contiguous block of data not as individual samples.
481 			//
482 			if( (iStartAddressOfSample + iRepeatOffset + iRepeatLength) > aModuleData.length && iSampleLength > 0 )
483 			{
484 				// Sample loop end point is past the end of the sample data, which is the end of the file!
485 				//
486 				this.sReason = 'Instrument Sample #' + (iInstrumentNumber + 1) + ' loop point (File offset byte ' + (iStartAddressOfSample + iRepeatOffset) + ') and loop length (' + iRepeatLength + ' bytes) do not fit within the modules file size: ' + (aModuleData.length) + ' bytes.';
487 				return true;
488 			}
489 		}
490 
491 		iStartAddressOfSample += iSampleLength;
492 	}
493 
494 	// If all samples are of zero length then its not a module.
495 	//
496 	if( 0 == iSampleLengthsTotal )
497 	{
498 		this.sReason = 'All instrument samples have zero length.';
499 		return true;
500 	}
501 
502 	// Ultimate Soundtracker does not give you the ability to adjust volume with the effects column
503 	// so all samples must have a volume or you wont hear anything.
504 	//
505 	if( bAllSamplesZeroVolume &&  sModuleScanTypeName != weasel.ModuleSniffer.prototype.SupportedModules.DOCSoundTracker9 )
506 	{
507 		this.sReason = 'All instrument samples have zero volume.';
508 		return true;
509 	}
510 
511 	// Check that all Samples can fit in the Module data along with the Module header.
512 	// Some modules may need fix due to bad rips at this point.
513 	//
514 	if( weasel.FormatUltimateSoundTracker121.MinimumHeaderSize + iSampleLengthsTotal > aModuleData.length )
515 	{
516 		this.sReason = 'Total Instrument Sample lengths are too big to fit in module (computed length is: ' + (weasel.FormatUltimateSoundTracker121.MinimumHeaderSize + iSampleLengthsTotal) + ' bytes but only have ' + (aModuleData.length) + ' bytes).';
517 		return true;
518 	}
519 
520 
521 
522 	// Check that computed Header + Patterns + Samples all fit within provided data.
523 	//
524 	var iComputedModuleSize = weasel.FormatUltimateSoundTracker121.PatternData + iSampleLengthsTotal + ( iUniquePatterns * weasel.FormatUltimateSoundTracker121.PatternSize);
525 	var iMinimumSize = bNotSoStrict ? aModuleData.length - 16 : aModuleData.length;
526 	if( iComputedModuleSize > aModuleData.length || iComputedModuleSize < iMinimumSize )
527 	{
528 		this.sReason = 'Computed module file size mismatch, computed size (header + patterns + samples) is: ' + iComputedModuleSize + ' bytes, but file is ' + aModuleData.length + ' bytes.';
529 		return true;
530 	}
531 
532 	return false;
533 };
534 
535 // ---------------------------------------------------------------------------
536 /** Scan Instruments details (size, loop lengths, volume etc) to check they are valid
537  * for a M.K. module (more specifically a Spreadpoint Soundtracker 2.3/2.4 as other versions
538  * do not have their loop offset in bytes).
539  * 
540  * @param {Array|Uint8Array} aModuleData = The suspected Spreadpoint Soundtracker 2.3/2.4 module in array format.
541  * @param {weasel.ModuleSniffer.SupportedModules} sModuleScanTypeName = The module scan type, which is used when display an error.
542  * @param {int} iMaxSampleSize = The maximum allowed sample size to check against.
543  * @param {bool}	bNoisetrackerSampleLoops = Noisetracker expects the Sample Repeat Offset to be in words not bytes, this allows
544  * 				having the repeat point > 64k offset (Ultimate Soundtracker to Spreadpoint 2.4 the repeat point is < 64k).
545  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
546  * 				(allows playing of some slightly different formats that are not 
547  * 				technically  Spreadpoint Soundtracker 2.3/2.4 modules (different effect
548  * 				numbers) or ones that have corrupted data/bad rips).
549  * @param {bool}	bProtrackerFineTune = Protracker stores the fine tune value in the upper byte of the Volume, causing volume checks to fail, set to true for Protracker Mode, which ignores the fine tuning.
550  * @param {int}	iPatternSize = The Pattern Size for this module (allows FST modules to use this function).
551  * 
552  * 
553  * @return {bool} true if failed, false if passed Spreadpoint SoundTracker 2.3/2.4 scan.
554  * 
555  * @private
556  */
557 weasel.ModuleSniffer.prototype.__Spreadpoint23SoundtrackerCheckInstruments = function( aModuleData, sModuleScanTypeName, iMaxSampleSize, bNoisetrackerSampleLoops, bNotSoStrict, bProtrackerFineTune, iPatternSize )
558 {
559 	var iUniquePatterns = this.__MKUniqueNumberOfPatterns( aModuleData );
560 
561 	var iStartAddressOfSample = ((iUniquePatterns * iPatternSize) + weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize);
562 
563 	// Check maximum length and max volume of samples.
564 	//
565 	var iInstrumentOffset = weasel.FormatUltimateSoundTracker121.SampleHeaders;
566 	var iSampleLengthsTotal = 0;
567 
568 	for( var iInstrumentNumber = 0; iInstrumentNumber < weasel.FormatSpreadpointSoundTracker23.NumberOfInstruments; iInstrumentNumber++ )
569 	{
570 		var iSampleLength = weasel.Helper.getWord( aModuleData,  iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.LengthInWords ) * 2;
571 		var iInstrumentVolume = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.Volume ) & (bNotSoStrict ? 0xff : 0xffff);
572 		var iRepeatOffset = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.RepeatOffsetInBytes );
573 		var iRepeatLength = weasel.Helper.getWord( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.RepeatLengthInWords ) * 2;
574 		var iFineTune = weasel.Helper.getByte( aModuleData, iInstrumentOffset + weasel.FormatUltimateSoundTracker121.SampleHeader.Volume ) & 0xf;
575 
576 		iInstrumentOffset += weasel.FormatUltimateSoundTracker121.SampleHeader.HeaderSize;
577 		iSampleLengthsTotal += iSampleLength;
578 
579 		if( bNoisetrackerSampleLoops )
580 		{
581 			iRepeatOffset *= 2;
582 		}
583 
584 		// Check sample length is not longer than maximum.
585 		//
586 		if( iSampleLength > iMaxSampleSize )
587 		{
588 			this.sReason = 'Instrument #' + (iInstrumentNumber + 1) + ' sample length too long for ' + sModuleScanTypeName + ', which is limited to ' + iMaxSampleSize + ' bytes in length.';
589 			return true;
590 		}
591 
592 		if( bProtrackerFineTune )
593 		{
594 			// Remove (Protracker) fine tune value stored in Volume field.
595 			//
596 			iInstrumentVolume &= 0xff;
597 		}
598 
599 		// Check volume is not greater than maximum.
600 		//
601 		if( iInstrumentVolume > 64 )
602 		{
603 			this.sReason = 'Instrument #' + (iInstrumentNumber + 1) + ' volume to large, 0-64 range only.';
604 			return true;
605 		}
606 
607 		// Is sample is looped?
608 		//
609 		if( iRepeatLength >= 4 )
610 		{
611 			// Check that sample loop points fit within actual sample data, but only if sample is exists.
612 			//
613 			// It should also be pointed out that the loop length *may* be bigger than the length of the sample..
614 			// This is usually due to user error when they added the sample to their Patch List. The majority of these
615 			// occurrences are usually because the loop start point has been set to 2, the sample would
616 			// be fine IF the loop start point had been set to zero.
617 			// The behaviour of the Sniffer is now to let "overlaying samples" through, as the default Ultimate/Soundtracker
618 			// replay routine just treats ALL the sample's data as one big contiguous block of data not as individual samples.
619 			// There is only one problem with this approach, Protracker's Extended Command "Invert Loop"
620 			// Which modifies the actual sample data - which could now be altering one or more samples.
621 			//
622 			if( (iStartAddressOfSample + iRepeatOffset + iRepeatLength) > aModuleData.length && iSampleLength > 0 )
623 			{
624 				// Sample loop end point is past the end of the sample data, which is the end of the file!
625 				//
626 				this.sReason = 'Instrument Sample #' + (iInstrumentNumber + 1) + ' loop point (File offset byte ' + (iStartAddressOfSample + iRepeatOffset) + ') and loop length (' + iRepeatLength + ' bytes) do not fit within the modules file size: ' + (aModuleData.length) + ' bytes.';
627 				return true;
628 			}
629 		}
630 
631 		iStartAddressOfSample += iSampleLength;
632 	}
633 
634 	// If all samples are of zero length then its not a module.
635 	//
636 	if( 0 == iSampleLengthsTotal )
637 	{
638 		this.sReason = 'All instrument samples have zero length.';
639 		return true;
640 	}
641 
642 	// Check that all Samples can fit in the Module data along with the Module header.
643 	// Some modules may need fix due to bad rips at this point.
644 	//
645 	if( weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize + iPatternSize + iSampleLengthsTotal > aModuleData.length )
646 	{
647 		this.sReason = 'Total Instrument Sample lengths are too big to fit in module (computed length is: ' + (weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize + iPatternSize + iSampleLengthsTotal) + ' bytes but only have ' + (aModuleData.length) + ' bytes).';
648 		return true;
649 	}
650 
651 	// Check that computed Header + Patterns + Samples all fit within provided data.
652 	//
653 	var iComputedModuleSize = weasel.FormatSpreadpointSoundTracker23.PatternData + iSampleLengthsTotal + ( iUniquePatterns * iPatternSize);
654 	var iMinimumSize = bNotSoStrict ? aModuleData.length - 16 : aModuleData.length;
655 	if( iComputedModuleSize > aModuleData.length || iComputedModuleSize < iMinimumSize )
656 	{
657 		this.sReason = 'Computed module file size mismatch, computed size (header + patterns + samples) is: ' + iComputedModuleSize + ' bytes, but file is ' + aModuleData.length + ' bytes.';
658 		return true;
659 	}
660 
661 	return false;
662 };
663 
664 // ---------------------------------------------------------------------------
665 /** Check to see if an Effect Type & Effect Data are compatible with
666  * Ultimate Soundtracker 1.21.
667  * 
668  * @param {int} iEffectType = The Effect Type to test.
669  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
670  * 
671  * @return {bool} True = Effect Type and Data are compatible with Ultimate Soundtracker 1.21, false = Effect Type and Data are not compatible.
672  * 
673  * @private
674  */
675 weasel.ModuleSniffer.prototype.__ultimateSoundtracker121EffectCheck = function( iEffectType, iEffectData )
676 {
677 	return		 iEffectType == weasel.FormatUltimateSoundTracker121.Effects.Arpeggio  && !( iEffectData == 1 || iEffectData == 2 )
678 			||	 iEffectType == weasel.FormatUltimateSoundTracker121.Effects.Pitchbend && !( ((iEffectData >> 4) & 0xf) != 0 && (iEffectData & 0xf) != 0 ) 
679 			||	(iEffectType == weasel.FormatUltimateSoundTracker121.Effects.None && iEffectData == 0 );
680 };
681 
682 // ---------------------------------------------------------------------------
683 /** Check to see if an Effect Type & Effect Data are compatible with
684  * DOC Soundtracker 9.
685  * 
686  * @param {int} iEffectType = The Effect Type to test.
687  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
688  * 
689  * @return {bool} True = Effect Type and Data are compatible with DOC Soundtracker 9, false = Effect Type and Data are not compatible.
690  * 
691  * @private
692  */
693 weasel.ModuleSniffer.prototype.__DOCSoundtracker9EffectCheck = function( iEffectType, iEffectData )
694 {
695 	return		 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Arpeggio
696 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendUp 
697 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendDown
698 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Volume
699 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.Filter && ( iEffectData == 0 || iEffectData == 1 ) )
700 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.TickSpeed && ( iEffectData >= 2 && iEffectData <= 15 ) );
701 };
702 
703 // ---------------------------------------------------------------------------
704 /** Check to see if an Effect Type & Effect Data are compatible with
705  * DOC Soundtracker 2.2.
706  * 
707  * @param {int} iEffectType = The Effect Type to test.
708  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
709  * 
710  * @return {bool} True = Effect Type and Data are compatible with DOC Soundtracker 2.2, false = Effect Type and Data are not compatible.
711  * 
712  * @private
713  */
714 weasel.ModuleSniffer.prototype.__DOCSoundtracker22EffectCheck = function( iEffectType, iEffectData )
715 {
716 	return		 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Arpeggio
717 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendUp 
718 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendDown
719 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump && ( iEffectData >= 0 && iEffectData <= 127 ) )
720 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Volume
721 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.PatternBreak && ( iEffectData == 0 ) )
722 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.Filter && ( iEffectData == 0 || iEffectData == 1 ) )
723 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.TickSpeed && ( iEffectData >= 0 && iEffectData <= 15 ) );
724 };
725 
726 // ---------------------------------------------------------------------------
727 /** Check to see if an Effect Type & Effect Data are compatible with
728  * TJC Soundtracker 2.
729  * 
730  * @param {int} iEffectType = The Effect Type to test.
731  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
732  * 
733  * @return {bool} True = Effect Type and Data are compatible with TJC Soundtracker 2, false = Effect Type and Data are not compatible.
734  * 
735  * @private
736  */
737 weasel.ModuleSniffer.prototype.__TJCSoundtracker2EffectCheck = function( iEffectType, iEffectData )
738 {
739 	if(	(	 iEffectType >= weasel.FormatTJCSoundTracker2.Effects.ModulateVolume
740 		&&	 iEffectType <= weasel.FormatTJCSoundTracker2.Effects.ModulatePeriodPitchbendDown)
741 		&&	 iEffectData != 0 )
742 	{
743 		// Has a TJC exclusive command been found?
744 		//
745 		this.bTJCSpecificCommandFound = true;
746 	}
747 	
748 	if( 	(iEffectType == weasel.FormatTJCSoundTracker2.Effects.SlideVolume && iEffectData > 0)
749 		||	(iEffectType == weasel.FormatTJCSoundTracker2.Effects.AutoSlide && iEffectData > 1)	)
750 	{
751 		// There are valid TJC modules that use D 00 and E 00 and E 01 (which are also valid DOC 9 commands).
752 		// See if there are any other parameters values used to indicate
753 		// this is a TJC modules.
754 		//
755 		this.bTJCSlideCommandsLookValid = true;
756 	}
757 
758 	// Allow a Tick Speed of 6 through, as this is the correct Tick Speed for TJC Soundtracker 2.
759 	//
760 	return		 iEffectType >= weasel.FormatTJCSoundTracker2.Effects.Arpeggio
761 			&&	 iEffectType <= weasel.FormatTJCSoundTracker2.Effects.AutoSlide
762 			||	(iEffectType == weasel.FormatDefJamSoundTracker3.Effects.TickSpeed && iEffectData == 6 );
763 };
764 
765 // ---------------------------------------------------------------------------
766 /** Check to see if an Effect Type & Effect Data are compatible with the M.K.
767  * Spreadpoint Soundtracker 2.3/2.4.
768  * 
769  * @param {int} iEffectType = The Effect Type to test.
770  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
771  * 
772  * @return {bool} True = Effect Type and Data are compatible with Spreadpoint Soundtracker 2.3/2.4, false = Effect Type and Data are not compatible.
773  * 
774  * @private
775  */
776 weasel.ModuleSniffer.prototype.__SpreadpointSoundtracker23EffectCheck = function( iEffectType, iEffectData )
777 {
778 	return		 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Arpeggio
779 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendUp 
780 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendDown
781 			||	 iEffectType == weasel.FormatSpreadpointSoundTracker23.Effects.VolumeSlide
782 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump && ( iEffectData >= 0 && iEffectData <= 127 ) )
783 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Volume
784 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.PatternBreak && ( iEffectData == 0 ) )
785 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.Filter && ( iEffectData == 0 || iEffectData == 1 ) )
786 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.TickSpeed && ( iEffectData >= 0 && iEffectData <= 15 ) );
787 };
788 
789 // ---------------------------------------------------------------------------
790 /** Check to see if an Effect Type & Effect Data are compatible with the
791  * Noisetracker 1.1.
792  * 
793  * @param {int} iEffectType = The Effect Type to test.
794  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
795  * 
796  * @return {bool} True = Effect Type and Data are compatible with Noisetracker 1.1, false = Effect Type and Data are not compatible.
797  * 
798  * @private
799  */
800 weasel.ModuleSniffer.prototype.__Noisetracker11EffectCheck = function( iEffectType, iEffectData )
801 {
802 	return		 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Arpeggio
803 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendUp 
804 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendDown
805 			||	 iEffectType == weasel.FormatNoiseTracker11.Effects.TonePortamento
806 			||	 iEffectType == weasel.FormatNoiseTracker11.Effects.Vibrato
807 			||	 iEffectType == weasel.FormatSpreadpointSoundTracker23.Effects.VolumeSlide
808 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump && ( iEffectData >= 0 && iEffectData <= 127 ) )
809 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Volume
810 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.PatternBreak && ( iEffectData == 0 ) )
811 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.Filter && ( iEffectData == 0 || iEffectData == 1 ) )
812 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.TickSpeed;
813 };
814 
815 // ---------------------------------------------------------------------------
816 /** Check to see if an Effect Type & Effect Data are compatible with the
817  * Noisetracker 2.0.
818  * 
819  * @param {int} iEffectType = The Effect Type to test.
820  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
821  * 
822  * @return {bool} True = Effect Type and Data are compatible with Noisetracker 2.0, false = Effect Type and Data are not compatible.
823  * 
824  * @private
825  */
826 weasel.ModuleSniffer.prototype.__Noisetracker20EffectCheck = function( iEffectType, iEffectData )
827 {
828 	return		 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Arpeggio
829 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendUp 
830 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.PitchbendDown
831 			||	 iEffectType == weasel.FormatNoiseTracker11.Effects.TonePortamento
832 			||	 iEffectType == weasel.FormatNoiseTracker11.Effects.Vibrato
833 			||	 iEffectType == weasel.FormatNoiseTracker20.Effects.TonePortamentoAndVolumeSlide
834 			||	 iEffectType == weasel.FormatNoiseTracker20.Effects.VibratoAndVolumeSlide
835 			||	 iEffectType == weasel.FormatSpreadpointSoundTracker23.Effects.VolumeSlide
836 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump && ( iEffectData >= 0 && iEffectData <= 127 ) )
837 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.Volume
838 			||	(iEffectType == weasel.FormatDOCSoundTracker22.Effects.PatternBreak && ( iEffectData == 0 ) )
839 			||	(iEffectType == weasel.FormatDOCSoundTracker9.Effects.Filter && ( iEffectData == 0 || iEffectData == 1 ) )
840 			||	 iEffectType == weasel.FormatDOCSoundTracker9.Effects.TickSpeed;
841 };
842 
843 // ---------------------------------------------------------------------------
844 /** Check to see if an Effect Type & Effect Data are compatible with
845  * Protracker, accept anything!
846  * (Technically not correct due to Effect 8 & E8x not being used, but later it gets used as panning.)
847  * 
848  * @param {int} iEffectType = The Effect Type to test.
849  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
850  * 
851  * @return {bool} True = Effect Type and Data are compatible with Protracker, false = Effect Type and Data are not compatible.
852  * 
853  * @private
854  */
855 weasel.ModuleSniffer.prototype.__ProtrackerEffectCheck = function( iEffectType, iEffectData )
856 {
857 	return !(8 == iEffectType || ( 0xE == iEffectType && ( 8 == iEffectData >> 4 ) ));
858 };
859 
860 
861 // ---------------------------------------------------------------------------
862 /** Check to see if an Effect Type & Effect Data are compatible with
863  * FastTracker/TakeTracker, which will accept anything!
864  * Scan to see if 7-bit panning is used (which is just the 8xx command, E8x is 4 bit panning position.)
865  * 
866  * @param {int} iEffectType = The Effect Type to test.
867  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
868  * 
869  * @return {bool} True = Effect Type and Data are compatible with FST, false = Effect Type and Data are not compatible.
870  * 
871  * @private
872  */
873 weasel.ModuleSniffer.prototype.__FSTEffectCheck = function( iEffectType, iEffectData )
874 {
875 	if( (8 == iEffectType || ( 0xE == iEffectType && ( 8 == iEffectData >> 4 ) )) )
876 	{
877 		var iPanning = 0;
878 
879 		if( 8 == iEffectType )
880 		{
881 			iPanning = iEffectData;
882 			this.fPanningMean += iPanning;
883 			this.iPanningEffectTotal++;
884 			this.aPanningPositions.push( iPanning );
885 		}
886 		else
887 		{
888 			// Could be a 7 or 8 bit module - so ignore.
889 			//
890 			this.bCoarsePanningUsed = true;
891 		}
892 
893 		if( iPanning > this.iMaxPanningPosition )
894 		{
895 			this.iMaxPanningPosition = iPanning;
896 		}
897 	}
898 
899 	return true;
900 };
901 
902 // ---------------------------------------------------------------------------
903 /** Check to see if an Effect Type & Effect Data are compatible with
904  * Def Jam Soundtracker 3 (same as TJC Soundtracker 2 plus Set Tick Speed Effect to the format).
905  * 
906  * @param {int} iEffectType = The Effect Type to test.
907  * @param {int} iEffectData = The Effect Data associated with the Effect Type.
908  * 
909  * @return {bool} True = Effect Type and Data are compatible with Def Jam Soundtracker 3, false = Effect Type and Data are not compatible.
910  * 
911  * @private
912  */
913 weasel.ModuleSniffer.prototype.__DefJamSoundtracker3EffectCheck = function( iEffectType, iEffectData )
914 {
915 	if(	(	 iEffectType >= weasel.FormatTJCSoundTracker2.Effects.ModulateVolume
916 		&&	 iEffectType <= weasel.FormatTJCSoundTracker2.Effects.ModulatePeriodPitchbendDown)
917 		&&	 iEffectData != 0 )
918 	{
919 		// Has a TJC exclusive command been found?
920 		//
921 		this.bTJCSpecificCommandFound = true;
922 	}
923 	
924 	if( 	(iEffectType == weasel.FormatTJCSoundTracker2.Effects.SlideVolume && iEffectData > 0)
925 		||	(iEffectType == weasel.FormatTJCSoundTracker2.Effects.AutoSlide && iEffectData > 1)	)
926 	{
927 		// There are valid TJC modules that use D 00 and E 00 and E 01 (which are also valid DOC 9 commands).
928 		// See if there are any other parameters values used to indicate
929 		// this is a TJC modules.
930 		//
931 		this.bTJCSlideCommandsLookValid = true;
932 	}
933 
934 	return		 iEffectType >= weasel.FormatTJCSoundTracker2.Effects.Arpeggio
935 			&&	 iEffectType <= weasel.FormatTJCSoundTracker2.Effects.AutoSlide
936 			||	(iEffectType == weasel.FormatDefJamSoundTracker3.Effects.TickSpeed && ( iEffectData >= 0 && iEffectData <= 15 ) );
937 };
938 
939 // ---------------------------------------------------------------------------
940 /** Scan pattern data to see if effects are suitable for the desired version
941  * of Soundtracker (by passing in the appropriate effect checker function for the
942  * soundtracker).
943  * 
944  * @param {Number} a = First number to compare.
945  * @param {Number} b = Second number to compare. 
946  * 
947  * @return {Number}  0 = Numbers the same, < 0 B is greater than A, > 0 A is greater than B.
948  * 
949  * @private
950  */
951 weasel.ModuleSniffer.prototype.__compareNumbers = function( a, b )
952 {
953 	return a - b;
954 };
955 
956 // ---------------------------------------------------------------------------
957 /** Scan pattern data to see if effects are suitable for the desired version
958  * of Soundtracker (by passing in the appropriate effect checker function for the
959  * soundtracker).
960  * 
961  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
962  * @param {function} fEffectChecker = The function to use that checks the 
963  * 				Effect Type and Effect Data for validity.
964  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
965  * 				(allows playing of some slightly different formats that are not 
966  * 				technically the desired Soundtracker modules (different effect
967  * 				numbers) or ones that have corrupted data/bad rips).
968  * 
969  * @return {bool} true if failed, false if passed SoundTracker Effects Scan.
970  * 
971  * @private
972  */
973 weasel.ModuleSniffer.prototype.__soundtrackerScanPatternEffects = function( aModuleData, fEffectChecker, bNotSoStrict )
974 {
975 	if( bNotSoStrict )
976 	{
977 		return false;
978 	}
979 
980 	if( 'function' !== typeof( fEffectChecker ) )
981 	{
982 		this.sReason = 'Internal error, a bad (or missing) effect checker has been passed to the Pattern Effect scanner (part of the Module Sniffer) and is unable to scan the pattern effects.';
983 		return true;
984 	}
985 
986 	var iSongLength = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongLength );
987 	var aUsedPatterns = new Array();
988 
989 	for( var iSequences = 0; iSequences < iSongLength; iSequences++ )
990 	{
991 		var iPatternNumber = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.PatternSequenceTable + iSequences );
992 
993 		aUsedPatterns.push( iPatternNumber );
994 	}
995 
996 	aUsedPatterns = aUsedPatterns.sort( this.__compareNumbers );
997 
998 	var iOldPatternNumber = -1;
999 
1000 	for( var iLength = aUsedPatterns.length, iIndex = 0; iIndex < iLength; iIndex++ )
1001 	{
1002 		var iPatternNumber = aUsedPatterns[ iIndex ];
1003 
1004 		// Skip pattern if we have already checked it.
1005 		//
1006 		if( iOldPatternNumber == iPatternNumber )
1007 		{
1008 			continue;
1009 		}
1010 
1011 		iOldPatternNumber = iPatternNumber;
1012 
1013 		var iPattenOffset = (iPatternNumber * weasel.FormatUltimateSoundTracker121.PatternSize) + weasel.FormatUltimateSoundTracker121.PatternSize + weasel.FormatUltimateSoundTracker121.PatternData;
1014 
1015 		for( var iNumberOfCells = weasel.FormatUltimateSoundTracker121.PatternSize / weasel.FormatUltimateSoundTracker121.BytesPerRowCell; --iNumberOfCells >= 0; )
1016 		{
1017 			iPattenOffset -= weasel.FormatUltimateSoundTracker121.BytesPerRowCell;
1018 
1019 			var iEffectType = weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.Effect ) & weasel.FormatUltimateSoundTracker121.Channel.EffectMask;
1020 			var iEffectData = weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.EffectParameters );
1021 
1022 			if( !bNotSoStrict &&  !fEffectChecker.call( this, iEffectType, iEffectData ) )
1023 			{
1024 				this.sReason = 'Unsupported sound effects used "0x' + (iEffectType >>> 4).toString( 16 )+ (iEffectType & 0xf).toString( 16 ) + '" with effect data "0x' + (iEffectData >>> 4).toString( 16 )+ (iEffectData & 0xf).toString( 16 ) + '" in Pattern: ' + iPatternNumber + ', Row: ' +  ((iNumberOfCells / weasel.FormatUltimateSoundTracker121.NumberOfChannels)|0 ) + ', Channel: ' + ( (iNumberOfCells % weasel.FormatUltimateSoundTracker121.NumberOfChannels ) +1) +'.';
1025 				return true;
1026 			}
1027 
1028 		}
1029 	}
1030 
1031 	return false;
1032 };
1033 
1034 // ---------------------------------------------------------------------------
1035 /** Although ANY (12bit) note period value can be placed in a pattern cell in
1036  * reality it is only the period values for the 3 octave range and zero which are
1037  * present. In fact Protracker needs the standard expected values as they are
1038  * used for finding the fine tune values. Due to corrupt disks and dodgy packers
1039  * its not that uncommon to find non-valid values sooner or later. This function
1040  * checks that the Note Period is valid.
1041  * 
1042  * @param {int} iNotePeriod = The Note Period to check.
1043  * 
1044  * @return {bool} true if failed, false if passed SoundTracker Effects Scan.
1045  * 
1046  * @private
1047  */
1048 weasel.ModuleSniffer.prototype.__checkBadNotePeriod = function( iNotePeriod )
1049 {
1050 	if( iNotePeriod > 856 )
1051 	{
1052 		// Note periods above lowest note C-1 are invalid.
1053 		//
1054 		return true;
1055 	}
1056 
1057 	// Reduce scan search to the beginning of each octave.
1058 	//
1059 	for( var iNoteToCheck = (iNotePeriod >= 453 ? 12 : iNotePeriod >= 226 ? 24 : 37), aValidNotePeriods = weasel.FormatUltimateSoundTracker121.PeriodTable; --iNoteToCheck >= 0; )
1060 	{
1061 		var iValidPeriod = aValidNotePeriods[ iNoteToCheck ];
1062 
1063 		if( iNotePeriod == iValidPeriod )
1064 		{
1065 			// Note Period OK.
1066 			//
1067 			return false;
1068 		}
1069 
1070 		if( iNotePeriod < iValidPeriod )
1071 		{
1072 			// Reduce scan time for bad note periods.
1073 			// TODO Would be better to replace with a binary tree though, or hash table.
1074 			//
1075 			return true;
1076 		}
1077 	}
1078 
1079 	return true;
1080 };
1081 
1082 // ---------------------------------------------------------------------------
1083 /** FST/TakeTracker use an extended version of Soundtracker/Protrackers note 
1084  * period range. The Note Period is supposed to fit within a (12bit) note period value
1085  * but FT2 will allow you to save notes that spill overing the the lowest bit of
1086  * the instrument number (13bit note period). This corrupts the song on playback
1087  * IF the instrument number < 15 (because now it becomes instrument no. + 16).
1088  * Also of interest is that TakeTracker
1089  * uses a few notes of different period values (F#6, G-6, G#6, A-6).
1090  * FT2 has approximately an 8 octave range, from A-0 (4064) to B-7 (28).
1091  * 
1092  * @param {int} iNotePeriod = The Note Period to check.
1093  * 
1094  * @return {bool} true if failed, false if passed FastTracker/TakeTracker Effects Scan.
1095  * 
1096  * @private
1097  */
1098 weasel.ModuleSniffer.prototype.__checkBadNotePeriodFST = function( iNotePeriod )
1099 {
1100 	if( iNotePeriod > 4064 )
1101 	{
1102 		// Note periods above lowest note C-1 are invalid.
1103 		//
1104 		return true;
1105 	}
1106 	
1107 	if( iNotePeriod != 0 && iNotePeriod < 28 )
1108 	{
1109 		// Note periods above B-7 are invalid (excluding 0 of course).
1110 		//
1111 		return true;
1112 	}
1113 
1114 	return false;
1115 };
1116 
1117 // ---------------------------------------------------------------------------
1118 /** Scan pattern data to see if effects are suitable for the desired version
1119  * of M.K. Soundtracker (by passing in the appropriate effect checker function for the
1120  * soundtracker).
1121  * 
1122  * @param {Array|Uint8Array} aModuleData = The suspected M.K. Soundtracker module in array format.
1123  * @param {function} fEffectChecker = The function to use that checks the 
1124  * 				Effect Type and Effect Data for validity.
1125  * @param {function} fNotePeriodChecker = The function to use that checks the 
1126  * 				Note Period for validity, Protracker, FST & TakeTracker all use different note period ranges.
1127  * @param {integer} iPatternSize = The size of a pattern in this module format 
1128  * (needed so that FST can use this function).
1129  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1130  * 				(allows playing of some slightly different formats that are not 
1131  * 				technically the desired Soundtracker modules (different effect
1132  * 				numbers) or ones that have corrupted data/bad rips).
1133  * 
1134  * @return {bool} true if failed, false if passed SoundTracker Effects Scan.
1135  * 
1136  * @private
1137  */
1138 weasel.ModuleSniffer.prototype.__MKSoundtrackerScanPatternEffects = function( aModuleData, fEffectChecker, fNotePeriodChecker, iPatternSize, bNotSoStrict )
1139 {
1140 	if( bNotSoStrict )
1141 	{
1142 		return false;
1143 	}
1144 
1145 	if( 'function' !== typeof( fEffectChecker ) )
1146 	{
1147 		this.sReason = 'Internal error, a bad (or missing) effect checker has been passed to the Pattern Effect scanner (part of the Module Sniffer) and is unable to scan the pattern effects.';
1148 		return true;
1149 	}
1150 
1151 	if( 'function' !== typeof( fNotePeriodChecker ) )
1152 	{
1153 		this.sReason = 'Internal error, a bad (or missing) note period checker has been passed to the Pattern Effect scanner (part of the Module Sniffer) and is unable to scan the pattern note period values.';
1154 		return true;
1155 	}
1156 
1157 	var iNoOfChannels = (iPatternSize / (weasel.FormatUltimateSoundTracker121.BytesPerRowCell * weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern) ) |0;
1158 	var iSongLength = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength );
1159 	var aUsedPatterns = new Array();
1160 
1161 	for( var iSequences = 0; iSequences < iSongLength; iSequences++ )
1162 	{
1163 		var iPatternNumber = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.PatternSequenceTable + iSequences );
1164 
1165 		aUsedPatterns.push( iPatternNumber );
1166 	}
1167 
1168 	aUsedPatterns = aUsedPatterns.sort( this.__compareNumbers );
1169 
1170 	var iOldPatternNumber = -1;
1171 
1172 	for( var iLength = aUsedPatterns.length, iIndex = 0; iIndex < iLength; iIndex++ )
1173 	{
1174 		var iPatternNumber = aUsedPatterns[ iIndex ];
1175 
1176 		// Skip pattern if we have already checked it.
1177 		//
1178 		if( iOldPatternNumber == iPatternNumber )
1179 		{
1180 			continue;
1181 		}
1182 
1183 		iOldPatternNumber = iPatternNumber;
1184 
1185 		var iPattenOffset = (iPatternNumber * iPatternSize) + iPatternSize + weasel.FormatSpreadpointSoundTracker23.PatternData;
1186 
1187 		for( var iNumberOfCells = (iPatternSize / weasel.FormatUltimateSoundTracker121.BytesPerRowCell) |0; --iNumberOfCells >= 0; )
1188 		{
1189 			iPattenOffset -= weasel.FormatUltimateSoundTracker121.BytesPerRowCell;
1190 
1191 			var iInstrumentNumber = ((weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.InstrumentNumber ) >> 4) & 0xf ) | (weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.Note ) & 0xf0 );
1192 			var iNotePeriod = weasel.Helper.getWord( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.Note ) & 0xfff;
1193 
1194 			if( 0 != iNotePeriod && fNotePeriodChecker( iNotePeriod ) )
1195 			{
1196 				this.sReason = 'Nonstandard note period value used, maybe corrupt: "' + ( iNotePeriod ) + '" in Pattern: ' + iPatternNumber + ', Row: ' +  ((iNumberOfCells / iNoOfChannels)|0 ) + ', Channel: ' + ( (iNumberOfCells % iNoOfChannels ) +1) +'.';
1197 				return true;
1198 			}
1199 
1200 			if( iInstrumentNumber > 31 )
1201 			{
1202 				this.sReason = 'Too high Instrument Number found: "' + ( iInstrumentNumber ) + '" (maximum is 31) in Pattern: ' + iPatternNumber + ', Row: ' +  ((iNumberOfCells / iNoOfChannels)|0 ) + ', Channel: ' + ( (iNumberOfCells % iNoOfChannels ) +1) +'.';
1203 				return true;
1204 			}
1205 
1206 			var iEffectType = weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.Effect ) & weasel.FormatUltimateSoundTracker121.Channel.EffectMask;
1207 			var iEffectData = weasel.Helper.getByte( aModuleData, iPattenOffset + weasel.FormatUltimateSoundTracker121.Channel.EffectParameters );
1208 
1209 			if( !bNotSoStrict &&  !fEffectChecker.call( this, iEffectType, iEffectData ) )
1210 			{
1211 				this.sReason = 'Unsupported sound effects used "0x' + (iEffectType >>> 4).toString( 16 )+ (iEffectType & 0xf).toString( 16 ) + '" with effect data "0x' + (iEffectData >>> 4).toString( 16 )+ (iEffectData & 0xf).toString( 16 ) + '" in Pattern: ' + iPatternNumber + ', Row: ' +  ((iNumberOfCells / iNoOfChannels)|0 ) + ', Channel: ' + ( (iNumberOfCells % iNoOfChannels ) +1) +'.';
1212 				return true;
1213 			}
1214 
1215 		}
1216 	}
1217 
1218 	return false;
1219 };
1220 
1221 // ---------------------------------------------------------------------------
1222 /**
1223  * Sniff for a Ulimate Soundtracker 1.21 module (v1.8 and v2.0 Ultimate Soundtracker modules are identical with the exception that the Song Speed many not be 120).
1224  * 
1225  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1226  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1227  * 				(allows playing of some slightly different formats that are not 
1228  * 				technically Ultimate Soundtracker 1.21 modules (different effect
1229  * 				numbers) or ones that have corrupted data/bad rips).
1230  * 
1231  * @private
1232  */
1233 weasel.ModuleSniffer.prototype.__sniffForUltimateSoundtracker121 = function( aModuleData, bNotSoStrict )
1234 {
1235 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1236 		return;
1237 	
1238 	// Ultimate Soundtracker 1.21 is expected to play at PAL Vertical Blank rate of 50hz, which is 125bpm.
1239 	// Most likely due to rounding errors Karsten calculated this as 120bpm.
1240 	//
1241 	{
1242 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1243 		
1244 		if( 120 != iSongSpeed && false == bNotSoStrict )
1245 		{
1246 			this.sReason = 'Song Speed is not 120 BPM.';
1247 			return;
1248 		}
1249 	}
1250 
1251 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1252 		return;
1253 
1254 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.UltimateSoundTracker121, bNotSoStrict ? 131070 : weasel.FormatUltimateSoundTracker121.MaxSampleSize, bNotSoStrict ) )
1255 		return;
1256 
1257 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__ultimateSoundtracker121EffectCheck, bNotSoStrict ) )
1258 		return;
1259 
1260 	this.sModuleType = this.SupportedModules.UltimateSoundTracker121;
1261 	this.sReason = 'ok';
1262 };
1263 
1264 // ---------------------------------------------------------------------------
1265 /**
1266  * Sniff for a Ulimate Soundtracker 1.8 module (v2.0 Ultimate Soundtracker modules are identical).
1267  * 
1268  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1269  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1270  * 				(allows playing of some slightly different formats that are not 
1271  * 				technically Ultimate Soundtracker 1.8 modules (different effect
1272  * 				numbers) or ones that have corrupted data/bad rips).
1273  * 
1274  * @private
1275  */
1276 weasel.ModuleSniffer.prototype.__sniffForUltimateSoundtracker18 = function( aModuleData, bNotSoStrict )
1277 {
1278 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1279 		return;
1280 	
1281 	// Check Song Speeds are within allowed values for CIA Timer.
1282 	// Original code uses "(240 - Song Speed) * 122", which can lead to division by zero & negative values for us.
1283 	// Ultimate Soundtracker 1.8 introduced the ability to change this value easily and that slaps a range of 0-220 on it.
1284 	//
1285 	{
1286 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1287 		
1288 		if( iSongSpeed < 0 || iSongSpeed > 220 )
1289 		{
1290 			this.sReason = 'Song Speed is out of range (expected 0-220).';
1291 			return;
1292 		}
1293 	}
1294 
1295 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1296 		return;
1297 
1298 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.UltimateSoundTracker18, bNotSoStrict ? 131070 : weasel.FormatUltimateSoundTracker121.MaxSampleSize, bNotSoStrict ) )
1299 		return;
1300 
1301 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__ultimateSoundtracker121EffectCheck, bNotSoStrict ) )
1302 		return;
1303 
1304 	this.sModuleType = this.SupportedModules.UltimateSoundTracker18;
1305 	this.sReason = 'ok';
1306 };
1307 
1308 
1309 // ---------------------------------------------------------------------------
1310 /**
1311  * Sniff for a DOC Soundtracker 9 module.
1312  * 
1313  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1314  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1315  * 				(allows playing of some slightly different formats that are not 
1316  * 				technically DOC Soundtracker 9 modules (additional effect
1317  * 				numbers) or ones that have corrupted data/bad rips).
1318  * 
1319  * @private
1320  */
1321 weasel.ModuleSniffer.prototype.__sniffForDOCSoundtracker9 = function( aModuleData, bNotSoStrict )
1322 {
1323 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1324 		return;
1325 	
1326 	// Check Song Speeds are within allowed values for CIA Timer.
1327 	// Original code uses "(240 - Song Speed) * 122", which can lead to division by zero & negative values for us.
1328 	// Ultimate Soundtracker 1.8 introduced the ability to change this value easily and that slaps a range of 0-220 on it.
1329 	//
1330 	{
1331 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1332 		
1333 		if( iSongSpeed < 0 || iSongSpeed > 220 )
1334 		{
1335 			this.sReason = 'Song Speed is out of range (expected 0-220).';
1336 			return;
1337 		}
1338 	}
1339 
1340 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1341 		return;
1342 
1343 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.DOCSoundTracker9, bNotSoStrict ? 131070 : weasel.FormatDOCSoundTracker9.MaxSampleSize, bNotSoStrict ) )
1344 		return;
1345 
1346 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__DOCSoundtracker9EffectCheck, bNotSoStrict ) )
1347 		return;
1348 
1349 	this.sModuleType = this.SupportedModules.DOCSoundTracker9;
1350 	this.sReason = 'ok';
1351 };
1352 
1353 // ---------------------------------------------------------------------------
1354 /**
1355  * Sniff for a DOC Soundtracker 2.0 & 2.2 module.
1356  * 
1357  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1358  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1359  * 				(allows playing of some slightly different formats that are not 
1360  * 				technically DOC Soundtracker 2.0/2.2 modules (additional effect
1361  * 				numbers) or ones that have corrupted data/bad rips).
1362  * 
1363  * @private
1364  */
1365 weasel.ModuleSniffer.prototype.__sniffForDOCSoundtracker22 = function( aModuleData, bNotSoStrict )
1366 {
1367 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1368 		return;
1369 	
1370 	// DOC Soundtracker 2.0 & 2.2 ignore Song Speed (BPM) and only playback at 50hz PAL VBL.
1371 	//
1372 	{
1373 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1374 		
1375 		if( iSongSpeed != 120 && false == bNotSoStrict )
1376 		{
1377 			this.sReason = 'Song Speed is out of range (expected 120).';
1378 			return;
1379 		}
1380 	}
1381 
1382 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1383 		return;
1384 
1385 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.DOCSoundTracker22, bNotSoStrict ? 131070 : weasel.FormatDOCSoundTracker9.MaxSampleSize, bNotSoStrict ) )
1386 		return;
1387 
1388 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__DOCSoundtracker22EffectCheck, bNotSoStrict ) )
1389 		return;
1390 
1391 	this.sModuleType = this.SupportedModules.DOCSoundTracker22;
1392 	this.sReason = 'ok';
1393 };
1394 
1395 // ---------------------------------------------------------------------------
1396 /**
1397  * Sniff for a TJC Soundtracker 2 module.
1398  * 
1399  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1400  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1401  * 				(allows playing of some slightly different formats that are not 
1402  * 				technically TJC Soundtracker 2 modules (additional effect
1403  * 				numbers) or ones that have corrupted data/bad rips).
1404  * 
1405  * @private
1406  */
1407 weasel.ModuleSniffer.prototype.__sniffForTJCSoundtracker2 = function( aModuleData, bNotSoStrict )
1408 {
1409 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1410 		return;
1411 	
1412 	// TJC Soundtracker 2 ignores Song Speed (BPM) and only playback at 50hz PAL VBL.
1413 	//
1414 	{
1415 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1416 		
1417 		if( iSongSpeed != 120 && false == bNotSoStrict )
1418 		{
1419 			this.sReason = 'Song Speed is out of range (expected 120).';
1420 			return;
1421 		}
1422 	}
1423 
1424 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1425 		return;
1426 
1427 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.TJCSoundTracker2, bNotSoStrict ? 131070 : weasel.FormatTJCSoundTracker2.MaxSampleSize, bNotSoStrict ) )
1428 		return;
1429 
1430 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__TJCSoundtracker2EffectCheck, bNotSoStrict ) )
1431 		return;
1432 
1433 	if( false == bNotSoStrict && false == this.bTJCSlideCommandsLookValid && false == this.bTJCSpecificCommandFound )
1434 	{
1435 		this.sReason = 'The VolumeSlide or AutoSlide commands have parameters which look more like DOC Soundtracker 9/2.2 PatternBreak and Filter commands.';
1436 		return;
1437 	}
1438 	
1439 	this.sModuleType = this.SupportedModules.TJCSoundTracker2;
1440 	this.sReason = 'ok';
1441 };
1442 
1443 // ---------------------------------------------------------------------------
1444 /**
1445  * Sniff for a Def Jam Soundtracker 3 module, along with:
1446  *    # Alpha Flight Soundtracker 4
1447  *    # DOC Soundtracker 4
1448  *    # DOC Soundtracker 6
1449  * 
1450  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1451  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1452  * 				(allows playing of some slightly different formats that are not 
1453  * 				technically Def Jam Soundtracker 3 modules (additional effect
1454  * 				numbers) or ones that have corrupted data/bad rips).
1455  * 
1456  * @private
1457  */
1458 weasel.ModuleSniffer.prototype.__sniffForDefJamSoundtracker3 = function( aModuleData, bNotSoStrict )
1459 {
1460 	if( this.__UltimateSoundtrackerLengthOfSong( aModuleData ) )
1461 		return;
1462 	
1463 	// TJC Soundtracker 2 ignores Song Speed (BPM) and only playback at 50hz PAL VBL.
1464 	//
1465 	{
1466 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed );
1467 		
1468 		if( iSongSpeed != 120 && false == bNotSoStrict )
1469 		{
1470 			this.sReason = 'Song Speed is out of range (expected 120).';
1471 			return;
1472 		}
1473 	}
1474 
1475 	if( this.__SoundtrackerCheckNumberOfPatterns( aModuleData ) )
1476 		return;
1477 
1478 	if( this.__UltimateSoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.TJCSoundTracker2, bNotSoStrict ? 131070 : weasel.FormatTJCSoundTracker2.MaxSampleSize, bNotSoStrict ) )
1479 		return;
1480 
1481 	if( this.__soundtrackerScanPatternEffects( aModuleData, this.__DefJamSoundtracker3EffectCheck, bNotSoStrict ) )
1482 		return;
1483 
1484 	if( false == bNotSoStrict && false == this.bTJCSlideCommandsLookValid && false == this.bTJCSpecificCommandFound )
1485 	{
1486 		this.sReason = 'The VolumeSlide or AutoSlide commands have parameters which look more like DOC Soundtracker 9/2.2 PatternBreak and Filter commands.';
1487 		return;
1488 	}
1489 	
1490 	this.sModuleType = this.SupportedModules.DefJamSoundTracker3;
1491 	this.sReason = 'ok';
1492 };
1493 
1494 // ---------------------------------------------------------------------------
1495 /**
1496  * Sniff for the Michael Kleps file marker, which indicates a 31 instrument 
1497  * module (Spreadpoint Soundtracker 2.3+, Noisetracker, Protracker etc).
1498  * 
1499  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1500  * 
1501  * @return {bool} = True M.K. marker found, false not found.
1502  * 
1503  * @private
1504  */
1505 weasel.ModuleSniffer.prototype.__sniffForMichaelKlepsID = function( aModuleData )
1506 {
1507 	if( undefined  == aModuleData )
1508 		return false;
1509 
1510 	if( aModuleData.length < weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize )
1511 		return false;
1512 
1513 	// Look for the Michael Kleps file marker.
1514 	//
1515 	if( -1 != weasel.Helper.searchArrayForString( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 4, 'M.K.' ) )
1516 	{
1517 		return true;
1518 	}
1519 
1520 	return false;
1521 };
1522 
1523 // ---------------------------------------------------------------------------
1524 /**
1525  * Sniff for the Mahoney & Kaktus alternate file marker, they apparently added
1526  * this to Noisetracker 2.0 so that when someone ripped their music a little train
1527  * would appear on the screen in Noisetracker 2.0. Values are "M&K!", "X:-K" for
1528  * Xolon (a friend from the demo crew Fairlight) and "GLUE" for Gluemaster (also in Fairlight).
1529  * This are just personalisations but cause identification issues.
1530  * Xolon also branched the Noisetracker 2 code base and created StarTrekker, which saves out
1531  * its 4 channel modules with the "FLT4" marker, these are identical to Noisetracker 2 modules.
1532  * 
1533  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1534  * 
1535  * @return {bool} = True [M.K.|M&K!|X:-K|GLUE|FLT4] marker found, false not found.
1536  * 
1537  * @private
1538  */
1539 weasel.ModuleSniffer.prototype.__sniffForMahoneyAndKaktusID = function( aModuleData )
1540 {
1541 	if( undefined  == aModuleData )
1542 		return false;
1543 
1544 	if( aModuleData.length < weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize )
1545 		return false;
1546 
1547 	// Look for the Mahoney & Kaktus file markers.
1548 	//
1549 	var aIDs = [ 'FLT4', 'GLUE', 'X:-K', 'M&K!', 'M.K.' ];
1550 	for( var iScanLength = aIDs.length; --iScanLength >= 0; )
1551 	{
1552 		if( -1 != weasel.Helper.searchArrayForString( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 4, aIDs[ iScanLength ] ) )
1553 		{
1554 			return true;
1555 		}
1556 	}
1557 
1558 	return false;
1559 };
1560 
1561 // ---------------------------------------------------------------------------
1562 /**
1563  * Sniff for the TakeTracker/FastTracker module file marker, which are created by TakeTracker/FastTracker/Digiboost.
1564  * Typically in the format xxCH where xx is the number of channels (02-32) or xCHN for 2-9 channel modules.
1565  * 
1566  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1567  * 
1568  * @return {bool} = True TakeTracker/FastTracker module marker found, false not found.
1569  * 
1570  * @private
1571  */
1572 weasel.ModuleSniffer.prototype.__sniffForFSTModuleID = function( aModuleData )
1573 {
1574 	if( undefined  == aModuleData )
1575 		return false;
1576 
1577 	if( aModuleData.length < weasel.FormatSpreadpointSoundTracker23.ModuleHeaderSize )
1578 	{
1579 		return false;
1580 	}
1581 
1582 	// Look for the TakeTracker/FastTracker file markers.
1583 	//
1584 	if( -1 != weasel.Helper.searchArrayForString( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 2, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 4, 'CH' ) )
1585 	{
1586 		var iHighChannelDigit = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint ) - 48;
1587 
1588 		if( iHighChannelDigit >= 0 && iHighChannelDigit <= 3 )
1589 		{
1590 			var iLowChannelDigit = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint +1 ) - 48;
1591 
1592 			if( iLowChannelDigit >= 0 && iLowChannelDigit <= 9 )
1593 			{
1594 				var iChannels = (iHighChannelDigit * 10) + iLowChannelDigit;
1595 
1596 				if( iChannels >= 2 && iChannels <= 32 )
1597 				{
1598 					return true;
1599 				}
1600 			}
1601 		}
1602 	}
1603 	else if( -1 != weasel.Helper.searchArrayForString( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 1, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint + 4, 'CHN' )  )
1604 	{
1605 		var iChannelDigit = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.MKFingerPrint ) - 48;
1606 
1607 		if( iChannelDigit >= 2 && iChannelDigit <= 9 )
1608 		{
1609 			return true;
1610 		}
1611 	}
1612 
1613 	return false;
1614 };
1615 
1616 // ---------------------------------------------------------------------------
1617 /**
1618  * Sniff for a Spreadpoint Soundtracker 2.3/2.4 module.
1619  * 
1620  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1621  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1622  * 				(allows playing of some slightly different formats that are not 
1623  * 				technically Spreadpoint Soundtracker 2.3 modules (additional effect
1624  * 				numbers) or ones that have corrupted data/bad rips).
1625  * 
1626  * @private
1627  */
1628 weasel.ModuleSniffer.prototype.__sniffForSpreadpointSoundtracker23 = function( aModuleData, bNotSoStrict )
1629 {
1630 	if( this.__MichaelKlepsSoundtrackerLengthOfSong( aModuleData ) )
1631 	{
1632 		return;
1633 	}
1634 
1635 	// Look for the Michael Kleps file marker.
1636 	//
1637 	if( false == this.__sniffForMichaelKlepsID( aModuleData ) )
1638 	{
1639 		this.sReason = 'No "M.K." ID mark found in module.';
1640 		return;
1641 	}
1642 
1643 	// Spreadpoint Soundtracker 2.3/2.4 is expected to play at PAL Vertical Blank rate of 50hz, which is 125bpm.
1644 	// The file will should be set to 120 via the Editor (residue from Ultimate Soundtracker 1.21).
1645 	// However due to "transitional" period a few modules that may have started their life as a DOC Soundtracker 9
1646 	// module may have a different speed set... Just to complicate things.
1647 	//
1648 	{
1649 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongSpeed );
1650 
1651 		if( 120 != iSongSpeed && false == bNotSoStrict )
1652 		{
1653 			this.sReason = 'Song Speed is not 120 BPM, its set to: ' + iSongSpeed;
1654 			return;
1655 		}
1656 	}
1657 
1658 	if( this.__MKSoundtrackerCheckNumberOfPatterns( aModuleData ) )
1659 		return;
1660 
1661 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.SpreadpointSoundTracker23, bNotSoStrict ? 131070 : weasel.FormatSpreadpointSoundTracker23.MaxSampleSize, false, bNotSoStrict, false, weasel.FormatUltimateSoundTracker121.PatternSize ) )
1662 		return;
1663 
1664 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__SpreadpointSoundtracker23EffectCheck, this.__checkBadNotePeriod, weasel.FormatUltimateSoundTracker121.PatternSize, bNotSoStrict ) )
1665 		return;
1666 
1667 	this.sModuleType = this.SupportedModules.SpreadpointSoundTracker23;
1668 	this.sReason = 'ok';
1669 };
1670 
1671 // ---------------------------------------------------------------------------
1672 /**
1673  * Sniff for a Spreadpoint Soundtracker 2.5 module.
1674  * 
1675  * @param {Array|Uint8Array} aModuleData = The suspected Noisetracker module in array format.
1676  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1677  * 				(allows playing of some slightly different formats that are not 
1678  * 				technically Spreadpoint Soundtracker 2.5 modules (additional effect
1679  * 				numbers) or ones that have corrupted data/bad rips).
1680  * 
1681  * @private
1682  */
1683 weasel.ModuleSniffer.prototype.__sniffForSpreadpointSoundtracker25 = function( aModuleData, bNotSoStrict )
1684 {
1685 	if( this.__MichaelKlepsSoundtrackerLengthOfSong( aModuleData ) )
1686 	{
1687 		return;
1688 	}
1689 
1690 	// Look for the Michael Kleps file marker.
1691 	//
1692 	if( false == this.__sniffForMichaelKlepsID( aModuleData ) )
1693 	{
1694 		this.sReason = 'No "M.K." ID mark found in module.';
1695 		return;
1696 	}
1697 
1698 	// Spreadpoint Soundtracker 2.5 is expected to play at PAL Vertical Blank rate of 50hz, which is 125bpm.
1699 	// The file will should be set to 120 via the Editor (residue from Ultimate Soundtracker 1.21).
1700 	// However due to "transitional" period a few modules that may have started their life as a DOC Soundtracker 9
1701 	// module may have a different speed set... Just to complicate things.
1702 	//
1703 	{
1704 		var iSongSpeed = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongSpeed );
1705 
1706 		if( 120 != iSongSpeed && false == bNotSoStrict )
1707 		{
1708 			this.sReason = 'Song Speed is not 120 BPM, its set to: ' + iSongSpeed;
1709 			return;
1710 		}
1711 	}
1712 
1713 	if( this.__MKSoundtrackerCheckNumberOfPatterns( aModuleData ) )
1714 		return;
1715 
1716 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.SpreadpointSoundTracker25, bNotSoStrict ? 131070 : weasel.FormatSpreadpointSoundTracker25.MaxSampleSize, true, bNotSoStrict, false, weasel.FormatUltimateSoundTracker121.PatternSize ) )
1717 		return;
1718 
1719 	// Spreadpoint Soundtracker 2.5 has all the effects from Noisetracker 1.1,
1720 	// so use Noisetracker 1.1 effect checker.
1721 	// NOTE: Tick Speed is implemented differently from Noisetracker 1.1, but
1722 	// has the same value ranges.
1723 	//
1724 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__Noisetracker11EffectCheck, this.__checkBadNotePeriod, weasel.FormatUltimateSoundTracker121.PatternSize, bNotSoStrict ) )
1725 		return;
1726 
1727 	this.sModuleType = this.SupportedModules.SpreadpointSoundTracker25;
1728 	this.sReason = 'ok';
1729 };
1730 
1731 // ---------------------------------------------------------------------------
1732 /**
1733  * Sniff for a Mahoney & Kaktus Noisetracker 1.1 module.
1734  * 
1735  * @param {Array|Uint8Array} aModuleData = The suspected Noisetracker module in array format.
1736  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1737  * 				(allows playing of some slightly different formats that are not 
1738  * 				technically Noisetracker 1.1 modules (additional effect
1739  * 				numbers) or ones that have corrupted data/bad rips).
1740  * 
1741  * @private
1742  */
1743 weasel.ModuleSniffer.prototype.__sniffForNoisetracker11 = function( aModuleData, bNotSoStrict )
1744 {
1745 	if( this.__MichaelKlepsSoundtrackerLengthOfSong( aModuleData ) )
1746 	{
1747 		return;
1748 	}
1749 
1750 	// Look for the Michael Kleps file marker, which is also present in Noisetracker modules.
1751 	//
1752 	if( false == this.__sniffForMichaelKlepsID( aModuleData ) )
1753 	{
1754 		this.sReason = 'No "M.K." ID mark found in module.';
1755 		return;
1756 	}
1757 
1758 	// Noisetracker 1.1 is expected to play at PAL Vertical Blank rate of 50hz, which is 125bpm.
1759 	// The traditional Song Speed (BPM) byte in the Soundtracker Module format is
1760 	// now used in Noisetracker as the Song Restart Position.
1761 	//
1762 	{
1763 		var iSongLength = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength );
1764 		var iSongRestartPosition = weasel.Helper.getByte( aModuleData, weasel.FormatNoiseTracker11.SongRestartPosition );
1765 
1766 		if( (iSongRestartPosition >= iSongLength) && false == bNotSoStrict )
1767 		{
1768 			this.sReason = 'Song Restart Position (' + iSongRestartPosition + ') is greater that Song Length (' + iSongLength + ')';
1769 			return;
1770 		}
1771 	}
1772 
1773 	if( this.__MKSoundtrackerCheckNumberOfPatterns( aModuleData ) )
1774 		return;
1775 
1776 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.NoiseTracker11, bNotSoStrict ? 131070 : weasel.FormatSpreadpointSoundTracker23.MaxSampleSize, true, bNotSoStrict, false, weasel.FormatUltimateSoundTracker121.PatternSize ) )
1777 		return;
1778 
1779 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__Noisetracker11EffectCheck, this.__checkBadNotePeriod, weasel.FormatUltimateSoundTracker121.PatternSize, bNotSoStrict ) )
1780 		return;
1781 
1782 	this.sModuleType = this.SupportedModules.NoiseTracker11;
1783 	this.sReason = 'ok';
1784 };
1785 
1786 
1787 // ---------------------------------------------------------------------------
1788 /**
1789  * Sniff for a Mahoney & Kaktus Noisetracker 2.0 module.
1790  * 
1791  * @param {Array|Uint8Array} aModuleData = The suspected Noisetracker module in array format.
1792  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1793  * 				(allows playing of some slightly different formats that are not 
1794  * 				technically Noisetracker 2.0 modules (additional effect
1795  * 				numbers) or ones that have corrupted data/bad rips).
1796  * 
1797  * @private
1798  */
1799 weasel.ModuleSniffer.prototype.__sniffForNoisetracker20 = function( aModuleData, bNotSoStrict )
1800 {
1801 	if( this.__MichaelKlepsSoundtrackerLengthOfSong( aModuleData ) )
1802 	{
1803 		return;
1804 	}
1805 
1806 	// Look for the Michael Kleps file marker, which is also present in Noisetracker modules.
1807 	//
1808 	if( false == this.__sniffForMahoneyAndKaktusID( aModuleData ) )
1809 	{
1810 		this.sReason = 'No "M.K." ID mark found in module (or "M&K!", "GLUE", "X:-K", "FLT4").';
1811 		return;
1812 	}
1813 
1814 	// Noisetracker 2.0 is expected to play at PAL Vertical Blank rate of 50hz, which is 125bpm.
1815 	// The traditional Song Speed (BPM) byte in the Soundtracker Module format is
1816 	// now used in Noisetracker as the Song Restart Position.
1817 	//
1818 	{
1819 		var iSongLength = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength );
1820 		var iSongRestartPosition = weasel.Helper.getByte( aModuleData, weasel.FormatNoiseTracker11.SongRestartPosition );
1821 
1822 		if( (iSongRestartPosition >= iSongLength) && false == bNotSoStrict )
1823 		{
1824 			this.sReason = 'Song Restart Position (' + iSongRestartPosition + ') is greater that Song Length (' + iSongLength + ')';
1825 			return;
1826 		}
1827 	}
1828 
1829 	if( this.__MKSoundtrackerCheckNumberOfPatterns( aModuleData ) )
1830 		return;
1831 
1832 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.NoiseTracker20, 131070, true, true, false, weasel.FormatUltimateSoundTracker121.PatternSize ) )
1833 		return;
1834 
1835 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__Noisetracker20EffectCheck, this.__checkBadNotePeriod, weasel.FormatUltimateSoundTracker121.PatternSize, bNotSoStrict ) )
1836 		return;
1837 
1838 	this.sModuleType = this.SupportedModules.NoiseTracker20;
1839 	this.sReason = 'ok';
1840 };
1841 
1842 // ---------------------------------------------------------------------------
1843 /**
1844  * Sniff for a Lars "Zap" Hamre Protracker module that uses the M.K. format.
1845  * 
1846  * @param {Array|Uint8Array} aModuleData = The suspected Protracker module in array format.
1847  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1848  * 				(allows playing of some slightly different formats that are not 
1849  * 				technically Protracker modules (additional effect
1850  * 				numbers) or ones that have corrupted data/bad rips).
1851  * 
1852  * @private
1853  */
1854 weasel.ModuleSniffer.prototype.__sniffForProtrackerMK = function( aModuleData, bNotSoStrict )
1855 {
1856 	if( this.__MichaelKlepsSoundtrackerLengthOfSong( aModuleData ) )
1857 	{
1858 		return;
1859 	}
1860 
1861 	// Look for the Michael Kleps file marker, which is also present in Protracker modules.
1862 	//
1863 	if( false == this.__sniffForMichaelKlepsID( aModuleData ) )
1864 	{
1865 		this.sReason = 'No "M.K." ID mark found in module.';
1866 		return;
1867 	}
1868 
1869 	// Spreadpoint Soundtracker 2.3 uses this byte for Song Speed, 
1870 	// Noisetracker 2.0 uses the same byte for Song Restart Position.
1871 	// Protracker always stores 127 in this byte and ignores it.
1872 	//
1873 	{
1874 		var iSongLength = weasel.Helper.getByte( aModuleData, weasel.FormatSpreadpointSoundTracker23.SongLength );
1875 		var iProtrackerMarker = weasel.Helper.getByte( aModuleData, weasel.FormatProTrackerMK.ProtrackerMarker );
1876 
1877 		if( (iProtrackerMarker != 127) && false == bNotSoStrict )
1878 		{
1879 			this.sReason = 'Protracker marker byte not found, should be 127 but was:' + iProtrackerMarker;
1880 			return;
1881 		}
1882 	}
1883 
1884 	if( this.__MKSoundtrackerCheckNumberOfPatterns( aModuleData ) )
1885 		return;
1886 
1887 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.ProTrackerMK, weasel.FormatProTrackerMK.MaxSampleSize, true, bNotSoStrict, true, weasel.FormatUltimateSoundTracker121.PatternSize ) )
1888 		return;
1889 
1890 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__ProtrackerEffectCheck, this.__checkBadNotePeriod, weasel.FormatUltimateSoundTracker121.PatternSize, bNotSoStrict ) )
1891 		return;
1892 
1893 	this.sModuleType = this.SupportedModules.ProTrackerMK;
1894 	this.sReason = 'ok';
1895 };
1896 
1897 
1898 // ---------------------------------------------------------------------------
1899 /**
1900  * Sniff for the TakeTracker/FastTracker module file marker, which are created by TakeTracker/FastTracker/Digiboost.
1901  * Typically in the format xxCH where xx is the number of channels (10-32) or xCHN for 2-9 channel modules.
1902  * 
1903  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1904  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1905  * 				(allows playing of some slightly different formats that are not 
1906  * 				technically TT/FST modules (additional effect
1907  * 				numbers) or ones that have corrupted data/bad rips).
1908  * 
1909  * @private
1910  */
1911 weasel.ModuleSniffer.prototype.__sniffForFSTModule = function( aModuleData, bNotSoStrict )
1912 {
1913 	if( this.__FSTLengthOfSong( aModuleData ) )
1914 	{
1915 		return;
1916 	}
1917 
1918 	if( false == this.__sniffForFSTModuleID( aModuleData ) )
1919 	{
1920 		this.sReason = 'No TakeTracker/FastTracker module [xxCH|xCHN] ID mark found in module.';
1921 		return;
1922 	}
1923 
1924 	var iChannels = this.__FSTGetNumberOfChannels( aModuleData );
1925 
1926 	if( this.__FSTCheckNumberOfPatterns( aModuleData ) )
1927 		return;
1928 
1929 	if( this.__Spreadpoint23SoundtrackerCheckInstruments( aModuleData, weasel.ModuleSniffer.prototype.SupportedModules.FSTModule, weasel.FormatProTrackerMK.MaxSampleSize, true, bNotSoStrict, true, iChannels * ( weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern * weasel.FormatUltimateSoundTracker121.BytesPerRowCell ) ) )
1930 		return;
1931 
1932 	if( this.__MKSoundtrackerScanPatternEffects( aModuleData, this.__FSTEffectCheck, this.__checkBadNotePeriodFST, iChannels * (weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern * weasel.FormatUltimateSoundTracker121.BytesPerRowCell ), bNotSoStrict ) )
1933 		return;
1934 
1935 
1936 	this.sModuleType = this.SupportedModules.FSTModule;
1937 	this.sReason = 'ok';
1938 };
1939 
1940 
1941 // ---------------------------------------------------------------------------
1942 /**
1943  * Sniff module data to find out what type of Soundtracker module it is.
1944  * 
1945  * @param {Array|Uint8Array} aModuleData = The suspected Soundtracker module in array format.
1946  * @param {bool}	bNotSoStrict = Don't be so strict about module data 
1947  * 				(allows playing of some slightly different formats that are not 
1948  * 				technically DOC Soundtracker 2.2/Protracker modules (different effect
1949  * 				numbers) or ones that have corrupted data/bad rips).
1950  */
1951 weasel.ModuleSniffer.prototype.sniff = function( aModuleData, bNotSoStrict )
1952 {
1953 	this.__reset();
1954 
1955 	if( !(( aModuleData instanceof Array ) || ( window.Uint8Array && aModuleData instanceof Uint8Array )) )
1956 	{
1957 		this.sReason = 'Passed Soundtracker module data is not an Array type.';
1958 		return;
1959 	}
1960 
1961 	if( this.__sniffForFSTModuleID( aModuleData ) )
1962 	{
1963 		this.__sniffForFSTModule( aModuleData, false );
1964 
1965 		if( 'ok' == this.sReason )
1966 		{
1967 			return;
1968 		}
1969 	}
1970 	else
1971 	{
1972 		this.sReason = 'No TakeTracker/FastTracker module [xxCH|xCHN] ID mark found in module.';
1973 	}
1974 
1975 	this.oSniffReasons.FSTModule = this.sReason;
1976 
1977 
1978 	if( this.__sniffForMahoneyAndKaktusID( aModuleData ) )
1979 	{
1980 		this.__sniffForSpreadpointSoundtracker23( aModuleData, false );
1981 
1982 		if( 'ok' != this.sReason )
1983 		{
1984 			this.oSniffReasons.SpreadpointSoundtracker23 = this.sReason;
1985 			this.__sniffForNoisetracker11( aModuleData, false );
1986 		}
1987 
1988 		if( 'ok' != this.sReason )
1989 		{
1990 			this.oSniffReasons.Noisetracker11 = this.sReason;
1991 			this.__sniffForNoisetracker20( aModuleData, false );
1992 		}
1993 
1994 		if( 'ok' != this.sReason )
1995 		{
1996 			this.oSniffReasons.Noisetracker20 = this.sReason;
1997 			this.__sniffForSpreadpointSoundtracker25( aModuleData, false );
1998 		}
1999 
2000 		if( 'ok' != this.sReason )
2001 		{
2002 			this.oSniffReasons.SpreadpointSoundtracker25 = this.sReason;
2003 			this.__sniffForProtrackerMK( aModuleData, bNotSoStrict );
2004 		}
2005 
2006 		if( 'ok' != this.sReason )
2007 		{
2008 			this.oSniffReasons.ProtrackerMK = this.sReason;
2009 		}
2010 
2011 		// Module Identified as a M.K. module, no need to scan for other types
2012 		// even if not a Spreadpoint Soundtracker 2.3+ as no other format
2013 		// can play it.
2014 		//
2015 		return;
2016 	}
2017 
2018 	this.oSniffReasons.MKModules = 'One of the "M.K." ID markers [M.K.|M&K!|X:-K|GLUE|FLT4] is missing from the file indicating it is not a 31 sample Soundtracker, Noisetracker or Protracker module.';
2019 
2020 	// M.K. pattern data in a different location so clear cache or we'll be using
2021 	// the wrong data.
2022 	//
2023 	this.iUniquePatterns = -1;
2024 
2025 	this.__sniffForUltimateSoundtracker121( aModuleData, false );
2026 
2027 	if( 'ok' != this.sReason )
2028 	{
2029 		this.oSniffReasons.UltimateSoundtracker121 = this.sReason;
2030 		this.__sniffForUltimateSoundtracker18( aModuleData, false );
2031 	}
2032 
2033 	if( 'ok' != this.sReason )
2034 	{
2035 		this.oSniffReasons.UltimateSoundtracker18 = this.sReason;
2036 		this.__sniffForDOCSoundtracker9( aModuleData, false );
2037 	}
2038 
2039 	if( 'ok' != this.sReason )
2040 	{
2041 		this.oSniffReasons.DOCSoundtracker9 = this.sReason;
2042 		this.bTJCSlideCommandsLookValid = false;
2043 		this.bTJCSpecificCommandFound = false;
2044 		this.__sniffForTJCSoundtracker2( aModuleData, false );
2045 	}
2046 
2047 	if( 'ok' != this.sReason )
2048 	{
2049 		this.oSniffReasons.TJCSoundtracker2 = this.sReason;
2050 		this.bTJCSlideCommandsLookValid = false;
2051 		this.bTJCSpecificCommandFound = false;
2052 		this.__sniffForDefJamSoundtracker3( aModuleData, false );
2053 	}
2054 
2055 	if( 'ok' != this.sReason )
2056 	{
2057 		this.oSniffReasons.DefJamSoundtracker3 = this.sReason;
2058 		this.__sniffForDOCSoundtracker22(aModuleData, bNotSoStrict);
2059 	}
2060 
2061 	if( 'ok' != this.sReason )
2062 	{
2063 		this.oSniffReasons.DOCSoundtracker22 = this.sReason;
2064 	}
2065 };
2066 
2067 // ---------------------------------------------------------------------------
2068 /**
2069  * Get the module type that the sniffer thinks its found.
2070  * 
2071  * @return {String} = The Soundtracker module type, 'Unknown' if unable to identify.
2072  */
2073 weasel.ModuleSniffer.prototype.getModuleType = function( )
2074 {
2075 	return this.sModuleType;
2076 };
2077 
2078 // ---------------------------------------------------------------------------
2079 /**
2080  * Has the sniffer found a Soundtracker module it recognises? Returns the reason if not.
2081  * 
2082  * @return {String} = 'ok' if Soundtracker module type is identified, else the reason for not identifying it (wrong format etc).
2083  */
2084 weasel.ModuleSniffer.prototype.getReason = function( )
2085 {
2086 	return this.sReason;
2087 };
2088 
2089 
2090 // ---------------------------------------------------------------------------
2091 /**
2092  * As support for more module types has been added the simple getReason() function
2093  * is not providing enough information about why the module type is unsupported, as it
2094  * ultimately returns the error message for either a Protracker module or a Doc Soundtracker 2.2
2095  * as these are the last sniffed module types.
2096  * This function returns all the Reasons for each module type sniffed for.
2097  * 
2098  * @return {Object} = Contains the reason for failure against each module type sniffed for.
2099  */
2100 weasel.ModuleSniffer.prototype.getAllReasons = function( )
2101 {
2102 	return this.oSniffReasons;
2103 };
2104 
2105 // ---------------------------------------------------------------------------
2106 /**
2107  * Make a Module object out of the provided data.
2108  * 
2109  * @param {Array|Uint8Array} aModuleData = The Soundtracker module in array format.
2110  * @param {int} iPlaybackFrequency = The playback frequency in Hertz to use (e.g. 44100 ).
2111  * @param {weasel.Sample.prototype.SampleScannerMode} iSampleScannerMode = Scan for IFF Header corruption residue?.
2112  * 
2113  * @return {weasel.UltimateSoundTracker121|weasel.UltimateSoundTracker18|weasel.DOCSoundTracker9|weasel.DOCSoundTracker22|weasel.TJCSoundTracker2|weasel.DefJamSoundTracker3|weasel.SpreadpointSoundTracker23|weasel.SpreadpointSoundTracker25|weasel.NoiseTracker11|weasel.NoiseTracker20|weasel.ProTrackerMK|weasel.FSTModule} = Soundtracker module object of the correct type, or NULL if not.
2114  */
2115 weasel.ModuleSniffer.prototype.createModule = function( aModuleData, iPlaybackFrequency, iSampleScannerMode )
2116 {
2117 	if( 'ok' == this.sReason )
2118 	{
2119 		switch( this.sModuleType )
2120 		{
2121 			case this.SupportedModules.UltimateSoundTracker121	:
2122 				return new weasel.UltimateSoundTracker121( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2123 			case this.SupportedModules.UltimateSoundTracker18	:
2124 				return new weasel.UltimateSoundTracker18( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2125 			case this.SupportedModules.DOCSoundTracker9	:
2126 				return new weasel.DOCSoundTracker9( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2127 			case this.SupportedModules.DOCSoundTracker22	:
2128 				return new weasel.DOCSoundTracker22( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2129 			case this.SupportedModules.TJCSoundTracker2	:
2130 				return new weasel.TJCSoundTracker2( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2131 			case this.SupportedModules.DefJamSoundTracker3	:
2132 				return new weasel.DefJamSoundTracker3( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2133 			case this.SupportedModules.SpreadpointSoundTracker23	:
2134 				return new weasel.SpreadpointSoundTracker23( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2135 			case this.SupportedModules.SpreadpointSoundTracker25	:
2136 				return new weasel.SpreadpointSoundTracker25( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2137 			case this.SupportedModules.NoiseTracker11	:
2138 				return new weasel.NoiseTracker11( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2139 			case this.SupportedModules.NoiseTracker20	:
2140 				return new weasel.NoiseTracker20( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2141 			case this.SupportedModules.ProTrackerMK	:
2142 				return new weasel.ProTrackerMK( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2143 			case this.SupportedModules.FSTModule	:
2144 
2145 				var oModule = new weasel.FSTModule( aModuleData, iPlaybackFrequency, iSampleScannerMode );
2146 				this.fPanningMean = this.fPanningMean / (this.iPanningEffectTotal == 0 ? 1 : this.iPanningEffectTotal );
2147 
2148 				for( var iLength = this.aPanningPositions.length, iIndex = 0; iIndex < iLength; iIndex++ )
2149 				{
2150 					this.fPanningVariance += Math.pow( this.aPanningPositions[ iIndex ] - this.fPanningMean, 2 );
2151 				}
2152 
2153 				this.fPanningVariance = this.fPanningVariance / (this.iPanningEffectTotal == 0 ? 1 : this.iPanningEffectTotal );
2154 				this.fPanningStandardDeviation = Math.sqrt( this.fPanningVariance );
2155 
2156 				if( this.fPanningStandardDeviation + this.fPanningMean < 128 && this.iMaxPanningPosition != 0 )
2157 				{
2158 					oModule.setFST7BitPanningMode( true );
2159 
2160 					// Set panning position to centre.
2161 					//
2162 					for( var iChannels = oModule.getNumberOfChannels(), iChannel = 0; iChannel < iChannels; iChannel++ )
2163 					{
2164 						oModule.getChannel( iChannel ).setPanningPosition( 128 );
2165 					}
2166 				}
2167 				else if( 0 == this.iMaxPanningPosition && !this.bCoarsePanningUsed )
2168 				{
2169 					// No Panning used in Module, set LRRL format.
2170 					//
2171 					for( var iChannels = oModule.getNumberOfChannels(), iChannel = 0; iChannel < iChannels; iChannel++ )
2172 					{
2173 						var iPos = iChannel & 3;
2174 						var iPanning = iPos == 0 || iPos == 3 ? 64 : 192;
2175 						oModule.getChannel( iChannel ).setPanningPosition( iPanning );
2176 					}
2177 				}
2178 
2179 				return oModule;
2180 			default:
2181 				break;
2182 		}
2183 	}
2184 
2185 	return null;
2186 };
2187