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