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 if( undefined == window.weasel ) window.weasel = {}; 5 6 // --------------------------------------------------------------------------- 7 /** Create a Ultimate Soundtracker 1.21 module out of the provided data (which has already passed the module sniffer test). 8 * 9 * @constructor 10 * @param {Array|Uint8Array} aModuleData = The ultimate Soundtracker 1.21 module as a byte array that MUST have passed the module sniffer test. 11 * @param {int} iPlaybackFrequency = The playback frequency in hertz to use (e.g. 44100 ). 12 * @param {weasel.Sample.prototype.SampleScannerMode} iSampleScannerMode = Scan for IFF Header corruption residue?. 13 * 14 * @author Warren Willmey 2011 15 */ 16 weasel.UltimateSoundTracker121 = function( aModuleData, iPlaybackFrequency, iSampleScannerMode ) 17 { 18 // Needed for prototype Inheritance. 19 if( aModuleData === undefined || !(( aModuleData instanceof Array ) || ( window.Uint8Array && aModuleData instanceof Uint8Array )) ) 20 return; 21 22 this.sModuleType = weasel.ModuleSniffer.prototype.SupportedModules.UltimateSoundTracker121; 23 this.aModuleData = aModuleData; 24 this.aSequenceTable = this.getSequenceTable(); 25 this.iMaxPattern = 0; 26 this.iSongRestartSequence = 0; // Ultimate Soundtracker always restarts its song at pattern 0. This is same for all other Soundtrackers with the exception of the Noisetracker series and its derivatives. 27 this.bUsePALClockConstant = true; 28 this.iPlaybackFrequency = iPlaybackFrequency; 29 this.fMasterVolume = 1.0; 30 this.bFilterOn = false; 31 this.iSongSpeed = 0; 32 this.bPatternBreak = false; // DOC Soundtracker 2.0 & 2.2 "Pattern Break" Effect Command. 33 this.iSequencePositionJump = -1; // DOC Soundtracker 2.0 & 2.2 "Sequence Position Jump" Effect Command. 34 this.iPatternBreakToRow = 0; // Protracker's Pattern Break changes to a provided row number. 35 this.bNoisetracker20VibratoMode = false; // Noisetracker 1.1 and Noisetracker 2.0 have a different Vibrato divider. 36 this.iRowDelay = 0; // Protracker Pattern Row Delay command. 37 this.bProtrackerTremoloSawtoothBug = true; // Protracker Tremolo has a bug when using the sawtooth waveform, turning off makes it behave as it was intended. 38 this.bProtrackerRowDelayQuirk = false; // Protracker Quirk, Row Delay command (EEx) and Pattern Break (Dxx) causes row skip, so Pattern Breaks to Dxx +1. 39 this.fWaitForDMAMultiplier = 1.0; // Protracker ED0 command causes software delays, some 6000 clock cycles for each ED0. 40 this.bProtracker3SampleOffsetMode = false; // Set Sample Offset command (9xx) behaves differently in Protracker 3, they removed some of the Quirks. 41 this.iTimingOverride = this.TimingOverrides.PAL; 42 43 44 this.aInstruments = new Array( this.FormatInstrumentTotal() ); 45 46 this.bSampleLoopOffsetInWords = undefined == this.bSampleLoopOffsetInWords ? false : this.bSampleLoopOffsetInWords; 47 this.bNoiseTrackerLoopQuirk = undefined == this.bNoiseTrackerLoopQuirk ? false : this.bNoiseTrackerLoopQuirk; 48 this.bUseFineTuning = undefined == this.bUseFineTuning ? false : this.bUseFineTuning; 49 this.bFSTClearSampleOffsetAfterUse = undefined == this.bFSTClearSampleOffsetAfterUse ? false : this.bFSTClearSampleOffsetAfterUse; 50 51 for( var iInstrumentCount = 0, iTotalInstruments = this.aInstruments.length; iInstrumentCount <= iTotalInstruments; iInstrumentCount++ ) 52 { 53 this.aInstruments[ iInstrumentCount ] = new weasel.Instrument( this, iInstrumentCount, iSampleScannerMode, this.bSampleLoopOffsetInWords, this.bNoiseTrackerLoopQuirk, this.bUseFineTuning ); 54 } 55 56 this.aPatterns = new Array( this.numberOfUniquePatternsInSong() ); 57 this.extractPatterns(); 58 59 this._extractSongSpeed(); 60 61 62 var iNumberOfChannels = this.getNumberOfChannels(); 63 this.aSoundChannels = new Array( this.getNumberOfChannels() ); 64 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 65 this.aSoundChannels[ iChannel ] = new weasel.Channel( this, iPlaybackFrequency, 1.0 ); 66 this.bStartProcessingPatterns = false; // Force Module to fetch row 0 of first pattern, or instruments wont play. 67 this.iCurrentSequencePosition = 0; 68 this.iCurrentTick = 0; 69 this.iTickSpeed = weasel.FormatUltimateSoundTracker121.TicksPerRow; // Ultimate Soundtracker modules do NOT change their tick speed. 70 this.iTotalPatternTicks = 0; 71 this.iCurrentPatternRowPosition = 0; 72 this.iSamplesPerTick = 0; 73 this.setSamplesPerTick(); 74 this.iSamplesRemaining = this.iSamplesPerTick; 75 76 this.bSongEnded = false; 77 this.b7BitPanning = false; 78 79 // Try to reduce heap activity with pre-allocation of Filter state changes 80 // during play back. Its unlikely more that 4 elements could be used 81 // (BPM speed of 220 (292Hz) and Tick speed of 2). 82 // 83 this.aFilterStateChange = [ 0|0, 0|0, 0|0, 0|0 ]; 84 this.aFilterStateForMixer = [ false, false, false, false ]; 85 }; 86 87 // --------------------------------------------------------------------------- 88 /** Override the default song speed and use the VBL timer instead. 89 * @const 90 * @enum {int} 91 */ 92 weasel.UltimateSoundTracker121.prototype.TimingOverrides = { 93 UseBPM: 0 94 , PAL : 1 95 , NTSC : 2 }; 96 97 // --------------------------------------------------------------------------- 98 /** PAL vertical refresh rate in Hertz. 99 * @const 100 * @type {float} 101 */ 102 weasel.UltimateSoundTracker121.prototype.PAL = 50.0; 103 104 // --------------------------------------------------------------------------- 105 /** NTSC vertical refresh rate in Hertz. 106 * @const 107 * @type {float} 108 */ 109 weasel.UltimateSoundTracker121.prototype.NTSC = 60.0 / 1.001; 110 111 // --------------------------------------------------------------------------- 112 /** Get the number of instruments supported by the module format. 113 * 114 * @return {int} The total number of instruments this format supports (15 for Ultimate Soundtracker etc). 115 */ 116 weasel.UltimateSoundTracker121.prototype.FormatInstrumentTotal = function( ) 117 { 118 return weasel.FormatUltimateSoundTracker121.NumberOfInstruments; 119 }; 120 121 // --------------------------------------------------------------------------- 122 /** Get the Module Header Size used by the module format, it is also the starting offset for the pattern data. 123 * 124 * @return {int} the Module Header Size used by the module format, it is also the starting offset for the pattern data. 125 */ 126 weasel.UltimateSoundTracker121.prototype.FormatModuleHeaderSize = function( ) 127 { 128 return weasel.FormatUltimateSoundTracker121.ModuleHeaderSize; 129 }; 130 131 // --------------------------------------------------------------------------- 132 /** Get the number of channels in this module. 133 * 134 * @return {int} The number of channels in this module. 135 */ 136 weasel.UltimateSoundTracker121.prototype.getNumberOfChannels = function( ) 137 { 138 return weasel.FormatUltimateSoundTracker121.NumberOfChannels; 139 }; 140 141 142 // --------------------------------------------------------------------------- 143 /** Get the pattern size in bytes of this module. 144 * 145 * @return {int} The size of each pattern in bytes for this module type. 146 */ 147 weasel.UltimateSoundTracker121.prototype.getPatternSizeInBytes = function( ) 148 { 149 return weasel.FormatUltimateSoundTracker121.PatternSize; 150 }; 151 152 // --------------------------------------------------------------------------- 153 /** Set the overriding master volume for this module. 154 * 155 * @param {float} fVolume = The volume range ( 0.0 - 1.0 ) although a range of 156 * 0.0 to 2.0 is accepted, the volume levels may not be clamped by the mixer causing clicks/pops etc. 157 */ 158 weasel.UltimateSoundTracker121.prototype.setMasterVolume = function( fVolume ) 159 { 160 if( fVolume < 0 ) 161 { 162 fVolume = 0; 163 } 164 165 if( fVolume > 2 ) 166 { 167 fVolume = 2; 168 } 169 170 this.fMasterVolume = fVolume; 171 172 173 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 174 { 175 this.aSoundChannels[ iChannel ].setMasterVolume( fVolume ); 176 } 177 }; 178 179 // --------------------------------------------------------------------------- 180 /** Get the current master volume of the module. 181 * 182 * @return {float} The current master volume. 183 */ 184 weasel.UltimateSoundTracker121.prototype.getMasterVolume = function( ) 185 { 186 return this.fMasterVolume; 187 }; 188 189 // --------------------------------------------------------------------------- 190 /** 191 * Set the number of samples needed between each song "tick" is processed (when new note data & sound fx are process etc), 192 * the current samples remaining does not change, until the tick occurs. 193 * 194 */ 195 weasel.UltimateSoundTracker121.prototype.setSamplesPerTick = function( ) 196 { 197 this.iSamplesPerTick = ( this.iPlaybackFrequency / this.tickPlaybackRateInHz() ) | 0; 198 }; 199 200 // --------------------------------------------------------------------------- 201 /** 202 * Set new output play back frequency, which requires the resizing of various buffers etc. 203 * 204 * @param {int} iPlaybackFrequency = new replay play back frequency. 205 * 206 */ 207 weasel.UltimateSoundTracker121.prototype.changePlaybackFrequency = function( iPlaybackFrequency ) 208 { 209 this.iPlaybackFrequency = iPlaybackFrequency; 210 211 this.setSamplesPerTick(); 212 213 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 214 this.aSoundChannels[ iChannel ].changePlaybackFrequency( iPlaybackFrequency ); 215 }; 216 217 // --------------------------------------------------------------------------- 218 /** Get the current moduel playback frequency. 219 * 220 * @return {int} The current module playback frequency. 221 */ 222 weasel.UltimateSoundTracker121.prototype.getPlaybackFrequency = function( ) 223 { 224 return this.iPlaybackFrequency; 225 }; 226 227 // --------------------------------------------------------------------------- 228 /** 229 * Get the name of the Ultimate Soundtracker 1.21 module as a String. 230 * 231 * @return {String} = Name of Sound Tracker Module, always character escape this string before display. 232 */ 233 weasel.UltimateSoundTracker121.prototype.getTitle = function( ) 234 { 235 var sTitle = ''; 236 237 try 238 { 239 // The first 20 bytes are the module title, which is padded with NULLs. 240 // 241 sTitle = weasel.Helper.getNullTerminatedString( this.aModuleData, weasel.FormatUltimateSoundTracker121.Title, weasel.FormatUltimateSoundTracker121.MaxTitleLength ); 242 243 }catch( oException ) 244 { 245 } 246 247 return sTitle; 248 }; 249 250 // --------------------------------------------------------------------------- 251 /** 252 * Get the length of the Ultimate Soundtracker 1.21 module in patterns. 253 * 254 * @return {int} = Length of song in patterns. 255 */ 256 weasel.UltimateSoundTracker121.prototype.getSongLengthInPatterns = function() 257 { 258 try 259 { 260 return weasel.Helper.getByte( this.aModuleData, weasel.FormatUltimateSoundTracker121.SongLength ); 261 }catch ( oException ) 262 { 263 return 0; 264 } 265 }; 266 267 // --------------------------------------------------------------------------- 268 /** 269 * Get the song speed of the Ultimate Soundtracker 1.21 module. 270 * 271 * @return {int} = Song speed from module, which is in BPM (although its really an approximation, to get an accurate BPM use: (((tickPlaybackRateInHz() * 60Seconds) / 6TicksPerRow) / 4RowsPerQuarterNote) ). 272 */ 273 weasel.UltimateSoundTracker121.prototype.getSongSpeed = function() 274 { 275 return this.iSongSpeed; 276 }; 277 278 // --------------------------------------------------------------------------- 279 /** 280 * Set the song speed. 281 * 282 * @param {int} iModuleBPMSpeed = The song speed as set in the module file (BPM) [0-220] which is the limits set down in Ultimate Soundtracker 1.8. 283 */ 284 weasel.UltimateSoundTracker121.prototype.setSongSpeed = function( iModuleBPMSpeed ) 285 { 286 this.iSongSpeed = iModuleBPMSpeed > 220 ? 220 : iModuleBPMSpeed < 0 ? 0 : iModuleBPMSpeed; 287 this.setSamplesPerTick(); 288 }; 289 290 // --------------------------------------------------------------------------- 291 /** 292 * Get the maximum pattern number in Ultimate Soundtracker 1.21 module (this includes patterns not played). 293 * 294 * @return {int} = Maximum Pattern number. 295 */ 296 weasel.UltimateSoundTracker121.prototype.findMaximumPatternNumber = function() 297 { 298 if( 0 != this.iMaxPattern ) 299 return this.iMaxPattern; 300 301 try 302 { 303 for( var iSequenceNumber = 0, iNumberOfPatterns = weasel.FormatUltimateSoundTracker121.SequenceTableLength; iSequenceNumber < iNumberOfPatterns; iSequenceNumber++ ) 304 { 305 var iPatternNumber = this.getPatternNumber( iSequenceNumber ); 306 307 if( iPatternNumber > this.iMaxPattern ) 308 { 309 this.iMaxPattern = iPatternNumber; 310 } 311 } 312 }catch (oException ) {} 313 314 return this.iMaxPattern; 315 }; 316 317 // --------------------------------------------------------------------------- 318 /** 319 * Get the pattern sequence table in Ultimate Soundtracker 1.21 module. 320 * 321 * @return {Array} = The Pattern Sequence Table. 322 */ 323 weasel.UltimateSoundTracker121.prototype.getSequenceTable = function() 324 { 325 if( null != this.aSequenceTable ) 326 return this.aSequenceTable; 327 328 var aPatternSequenceTable = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength ); 329 330 for( var iLength = aPatternSequenceTable.length; --iLength >= 0; ) 331 aPatternSequenceTable[ iLength ] = 0; 332 333 for( var iPatternSequenceTable = weasel.FormatUltimateSoundTracker121.PatternSequenceTable, iNumberOfPatterns = weasel.FormatUltimateSoundTracker121.SequenceTableLength, iPattern = 0; iPattern < iNumberOfPatterns; iPattern++ ) 334 { 335 try 336 { 337 var iPatternNumber = weasel.Helper.getByte( this.aModuleData, iPatternSequenceTable++ ); 338 339 aPatternSequenceTable[ iPattern ] = iPatternNumber; 340 341 }catch (oException ) {} 342 } 343 344 return aPatternSequenceTable; 345 }; 346 347 348 // --------------------------------------------------------------------------- 349 /** 350 * Get the number of unique patterns in the Ultimate Soundtracker 1.21 module. 351 * 352 * @return {int} = Number of unique number. 353 */ 354 weasel.UltimateSoundTracker121.prototype.numberOfUniquePatternsInSong = function() 355 { 356 var iMaxPatternNumber = this.findMaximumPatternNumber(); 357 358 return iMaxPatternNumber +1; 359 }; 360 361 // --------------------------------------------------------------------------- 362 /** 363 * Get the instrument object. 364 * 365 * @param {int} iInstrumentNumber = The instrument number (Range 1-15). 366 * 367 * @return {weasel.Instrument} = The instrument object, or null if not found (instrument number out of range). 368 */ 369 weasel.UltimateSoundTracker121.prototype.getInstrument = function( iInstrumentNumber ) 370 { 371 if( iInstrumentNumber >= 0 && iInstrumentNumber <= this.FormatInstrumentTotal() ) 372 return this.aInstruments[ iInstrumentNumber ]; 373 374 return null; 375 }; 376 377 // --------------------------------------------------------------------------- 378 /** 379 * Get the Module Data. 380 * 381 * @return {Array|Uint8Array} = Array containing the original module data. 382 */ 383 weasel.UltimateSoundTracker121.prototype.getModuleData = function() 384 { 385 return this.aModuleData; 386 }; 387 388 389 // --------------------------------------------------------------------------- 390 /** 391 * Get the note and octave from the period value (which is stored in the pattern). 392 * 393 * @param {int} iAmigaPeriod = The Period value of the note. 394 * 395 * @return {String} = The note and octave (in the format note octave e.g. 'C-2' or 'G#1'), or '???' if not found. 396 * 397 * @TODO Should we be returning the nearest note if not found?? 398 */ 399 weasel.UltimateSoundTracker121.prototype.getNoteFromPeriod = function( iAmigaPeriod ) 400 { 401 if( iAmigaPeriod < 113 || iAmigaPeriod > 856 ) 402 return '???'; 403 404 if( iAmigaPeriod in weasel.FormatUltimateSoundTracker121.NoteTable ) 405 { 406 return weasel.FormatUltimateSoundTracker121.NoteTable[ iAmigaPeriod ]; 407 } 408 409 // Should we be returning the nearest note if not found?? 410 // 411 return '???'; 412 }; 413 414 // --------------------------------------------------------------------------- 415 /** 416 * Get the pattern number from the sequence table at the given offset. 417 * 418 * @param {int} iSequenceNumber = The offset into the sequence table of the wanted pattern number. 419 * 420 * @return {int} = Pattern number. 421 */ 422 weasel.UltimateSoundTracker121.prototype.getPatternNumber = function( iSequenceNumber ) 423 { 424 if( iSequenceNumber < 0 || iSequenceNumber >= weasel.FormatUltimateSoundTracker121.SequenceTableLength ) 425 return 0; 426 427 var iPatternNumber = 0; 428 429 try 430 { 431 iPatternNumber = this.aSequenceTable[ iSequenceNumber ]; 432 }catch (oException ) 433 {} 434 435 return iPatternNumber; 436 }; 437 438 // --------------------------------------------------------------------------- 439 /** 440 * Extract all patterns within module data into Pattern objects. 441 */ 442 weasel.UltimateSoundTracker121.prototype.extractPatterns = function( ) 443 { 444 { 445 for( var iPattern = 0, iMaxPattern = this.numberOfUniquePatternsInSong(); iPattern < iMaxPattern; iPattern++ ) 446 { 447 this.aPatterns[ iPattern ] = new weasel.Pattern( this, iPattern ); 448 } 449 } 450 }; 451 452 // --------------------------------------------------------------------------- 453 /** 454 * Extract song speed from module. 455 * 456 * @protected 457 */ 458 weasel.UltimateSoundTracker121.prototype._extractSongSpeed = function( ) 459 { 460 try 461 { 462 var iSongSpeed = weasel.Helper.getByte( this.aModuleData, weasel.FormatUltimateSoundTracker121.SongSpeed ); 463 this.setSongSpeed( iSongSpeed ); 464 }catch ( oException ) 465 { 466 } 467 }; 468 469 470 // --------------------------------------------------------------------------- 471 /** 472 * Get Pattern object from the pattern number. 473 * 474 * @param {int} iPatternNumber = The number of the pattern to fetch (NOT THE SEQUENCE NUMBER!). 475 * 476 * @return Pattern = The request Pattern object or null if non-existent. 477 */ 478 weasel.UltimateSoundTracker121.prototype.getPattern = function( iPatternNumber ) 479 { 480 if( iPatternNumber < 0 || iPatternNumber >= this.aPatterns.length ) 481 return null; 482 483 return this.aPatterns[ iPatternNumber ]; 484 }; 485 486 // --------------------------------------------------------------------------- 487 /** 488 * Get the CIA Timer Clock Constant, which will be either PAL or NTSC CPU frequency divided by 10. 489 * 490 * @return {float} = The CIA Timer Clock Constant. 491 */ 492 weasel.UltimateSoundTracker121.prototype.getCIATimerConstant = function( ) 493 { 494 return this.bUsePALClockConstant == true ? (weasel.FormatUltimateSoundTracker121.ClockConstantPAL / 5) : (weasel.FormatUltimateSoundTracker121.ClockConstantNTSC / 5); 495 }; 496 497 498 // --------------------------------------------------------------------------- 499 /** 500 * Get tick playback rate in hz, this is speed at which the module processes each tick 501 * (there are 6 ticks per row), used to set the (CIA) interrupt rate 502 * , Ultimate Soundtracker allows you to adjust the song speed to values other than 503 * the normal 50hz PAL vertical refresh synchronization. Notice that this value is 504 * calculated from the CIA Timer Clock Constant value and that the default song speed of 120 (bpm) 505 * does not result in a 50hz timer, instead its ~48.45hz (PAL). 120bpm is a established music speed, 506 * 125bpm is a traditional Amiga song speed as it allows the song to be synced with a 50hz PAL TV. 507 * 508 * @return {float} = The playback rate in hz. 509 */ 510 weasel.UltimateSoundTracker121.prototype.tickPlaybackRateInHz = function( ) 511 { 512 var fPlaybackRateInHz = this.getCIATimerConstant() / ((240 - this.getSongSpeed()) * 122 ); 513 514 if( this.iTimingOverride == this.TimingOverrides.PAL ) 515 { 516 fPlaybackRateInHz = this.PAL; 517 } 518 else if( this.iTimingOverride == this.TimingOverrides.NTSC ) 519 { 520 fPlaybackRateInHz = this.NTSC; 521 } 522 523 return fPlaybackRateInHz; 524 }; 525 526 527 // --------------------------------------------------------------------------- 528 /** 529 * Get length of song in milliseconds. 530 * 531 * @return {float} = The length of the song in ms. 532 */ 533 weasel.UltimateSoundTracker121.prototype.getLengthOfSongInMilliSeconds = function( ) 534 { 535 return this.getSongLengthInPatterns() * weasel.FormatUltimateSoundTracker121.TicksPerRow * weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern / this.tickPlaybackRateInHz() * 1000.0; 536 537 }; 538 539 // --------------------------------------------------------------------------- 540 /** 541 * Get song position in milliseconds. 542 * 543 * @return {float} = The song position from its beginning in ms. 544 */ 545 weasel.UltimateSoundTracker121.prototype.getSongPositionInMilliSeconds = function( ) 546 { 547 var iCurrentPosition = (this.iCurrentSequencePosition * weasel.FormatUltimateSoundTracker121.TicksPerRow * weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern) 548 + ( this.iCurrentPatternRowPosition * weasel.FormatUltimateSoundTracker121.TicksPerRow ) 549 + ( this.iCurrentTick ); 550 551 return iCurrentPosition / this.tickPlaybackRateInHz() * 1000.0; 552 }; 553 554 555 // --------------------------------------------------------------------------- 556 /** Some Soundtrackers have effects that need processing out of the normal order, 557 * such as the Note Portamento command in Noisetracker. 558 * 559 * @param {weasel.Channel} oChannel = The Channel to process for effects. 560 * @param {int} iNotePeriod = The note period yet to be set for this oChannel object. 561 * 562 * @return {bool} = false : the fetch row process should continue for this oChannel object, 563 * true : stop the fetch row process for this oChannel object but continue for the other channels.. 564 * 565 * @protected 566 */ 567 weasel.UltimateSoundTracker121.prototype._exceptionPreprocessEffects = function( oChannel, iNotePeriod ) 568 { 569 return false; 570 }; 571 572 // --------------------------------------------------------------------------- 573 /** Process a channel's effects. 574 * 575 * @param {weasel.Channel} oChannel = The Channel to process for effects. 576 * 577 * @protected 578 */ 579 weasel.UltimateSoundTracker121.prototype._processChannelEffect = function( oChannel ) 580 { 581 switch( oChannel.getEffectNumber() ) 582 { 583 case weasel.FormatUltimateSoundTracker121.Effects.Arpeggio : 584 oChannel.arpeggio( this.iCurrentTick, weasel.Channel.prototype.ArpeggioMode.UltimateSoundtracker ); 585 break; 586 587 case weasel.FormatUltimateSoundTracker121.Effects.Pitchbend : 588 var iEffectParameter= oChannel.getEffectParameter(); 589 var iPitchBendDown = (iEffectParameter >>> 4) & 0x0f; 590 var iPitchBendUp = iEffectParameter & 0x0f; 591 592 oChannel.pitchBend( this.iCurrentTick, iPitchBendDown, iPitchBendUp ); 593 break; 594 595 default : 596 break; 597 } 598 }; 599 600 // --------------------------------------------------------------------------- 601 /** 602 * Process the effects column for all channels. 603 */ 604 weasel.UltimateSoundTracker121.prototype.processEffects = function( ) 605 { 606 if( 0 != this.iCurrentTick ) 607 { 608 for( var iChannels = this.aSoundChannels.length, iChannel = -1; ++iChannel < iChannels; ) 609 { 610 var oChannel = this.aSoundChannels[ iChannel ]; 611 612 this._processChannelEffect( oChannel ); 613 } 614 } 615 }; 616 617 // --------------------------------------------------------------------------- 618 /** Process a channel's effects that occur on Tick Zero, there aren't any for Ultimate Soundtracker. 619 * 620 * @param {weasel.Channel} oChannel = The Channel to process for effects. 621 * 622 * @protected 623 */ 624 weasel.UltimateSoundTracker121.prototype._processChannelTick0Effect = function( oChannel ) 625 { 626 }; 627 628 // --------------------------------------------------------------------------- 629 /** 630 * Process the effects column for all channels when current tick is zero (a new row has been fetched) 631 * Ultimate Soundtracker does not process effects on this tick, however other Soundtrackers might. 632 */ 633 weasel.UltimateSoundTracker121.prototype.processTick0Effects = function( ) 634 { 635 636 }; 637 638 // --------------------------------------------------------------------------- 639 /** Protracker allows fining tuning of its samples, Ultimate Soundtracker and others 640 * do not. 641 * 642 * @param {weasel.Channel} oChannel = The channel object to start its pending sample. 643 * @param {int} iNotePeriod = The note period yet to be set for this oChannel object. 644 * 645 * @return {int} = The note period corrected for fine tuning. 646 * 647 * @protected 648 */ 649 weasel.UltimateSoundTracker121.prototype._fineTune = function( oChannel, iNotePeriod ) 650 { 651 return iNotePeriod; 652 }; 653 654 // --------------------------------------------------------------------------- 655 /** Apply instrument volume to channel immediately, this is because Ultimate soundtracker applies 656 * the new volume level of the pending instrument whether a note is present or not, 657 * allowing you to change the volume of the sample without actually changing the 658 * instrument or note period. 659 * 660 * @param {weasel.Channel} oChannel = The channel object to apply volume to immediately. 661 * 662 * @protected 663 */ 664 weasel.UltimateSoundTracker121.prototype._applyVolumeImmediately = function( oChannel ) 665 { 666 // Volume change is applied immediately even if there is no new note. 667 // 668 oChannel.setVolume( this.getInstrument( oChannel.getPendingInstrumentNumber() ).getVolume() ); 669 670 }; 671 672 // --------------------------------------------------------------------------- 673 /** 674 * Fetch the next pattern row into the channels for processing. 675 */ 676 weasel.UltimateSoundTracker121.prototype.fetchRow = function( ) 677 { 678 for( var iChannel = 0, iTotalChannels = this.aSoundChannels.length; iChannel < iTotalChannels; iChannel++ ) 679 { 680 var oChannel = this.aSoundChannels[ iChannel ]; 681 var oPatternCell = this.getCurrentPatternCell( iChannel ); 682 683 this._applyPatternCell( oChannel, oPatternCell ); 684 this._processChannelTick0Effect( oChannel ); 685 } 686 687 for( var iChannel = 0, iTotalChannels = this.aSoundChannels.length; iChannel < iTotalChannels; iChannel++ ) 688 { 689 var oChannel = this.aSoundChannels[ iChannel ]; 690 691 if( true == oChannel.getNoNoteDelayQuirk() ) 692 { 693 // Update ALL normal channels playback as one or more ED0 or E9x command was used on this row and so normal channels get delayed by the DMA Wait pause. 694 // 695 oChannel.setNoNoteDelayQuirk( false ); 696 oChannel.delaySampleStart( true, this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ); 697 } 698 } 699 }; 700 701 // --------------------------------------------------------------------------- 702 /** Apply the Channel's pattern cell to the channel. 703 * 704 * @param {weasel.Channel} oChannel = The channel object to apply volume to immediately. 705 * @return {weasel.PatternCell} = The current PatternCell object for the provided channel number. 706 * 707 * @protected 708 */ 709 weasel.UltimateSoundTracker121.prototype._applyPatternCell = function( oChannel, oPatternCell ) 710 { 711 // Always set effect command and effect parameter. 712 // 713 oChannel.setEffectNumber( oPatternCell.getEffectNumber() ); 714 oChannel.setEffectParameter( oPatternCell.getEffectParameter() ); 715 oChannel.setCurrentPatternCell( oPatternCell ); 716 717 var iInstrumentNumber = oPatternCell.getInstrumentNumber(); 718 719 // Only change instrument number if set in pattern. 720 // 721 if( iInstrumentNumber > 0 ) 722 { 723 oChannel.setPendingInstrumentNumber( iInstrumentNumber, null ); 724 725 // Volume change is applied immediately even if there is no new note. 726 // 727 this._applyVolumeImmediately( oChannel ); 728 } 729 730 var iNotePeriod = oPatternCell.getNotePeriod(); 731 732 // Only change note if set in pattern. 733 // 734 if( 0 != iNotePeriod ) 735 { 736 if( this._exceptionPreprocessEffects( oChannel, iNotePeriod ) ) 737 { 738 return; 739 } 740 741 // Noisetracker 1.1 Vibrato command. 742 // 743 oChannel.setVibratoTablePosition( 0 ); 744 745 // If period value is already too low (like 2604hz) then do not change the instrument. 746 // Due to the Amiga DMA not being given enough time to stop, so it does not play the new sample (continues with old sample). 747 // 748 if( oChannel.getNotePeriod() >= 1374 ) 749 { 750 oChannel.setNotePeriod( iNotePeriod ); 751 oChannel.setLastSavedNotePeriod( iNotePeriod ); 752 oChannel.setShadowNotePeriod( iNotePeriod ); 753 754 return; 755 } 756 757 this.startPendingSample( oChannel, iNotePeriod, this.WaitForDMAToStop() ); 758 } 759 }; 760 761 // --------------------------------------------------------------------------- 762 /** 763 * Start pending sample on a channel at a given note period. 764 * 765 * @param {weasel.Channel} oChannel = The channel object to start its pending sample. 766 * @param {int} iNotePeriod = The note period yet to be set for this oChannel object. 767 * @param {float} fWaitForDMAToStop = The value to use (in milliseconds) for the pause between samples, typically weasel.FormatUltimateSoundTracker121.WaitForDMAToStop but Noisetracker/Protracker use different values. 768 */ 769 weasel.UltimateSoundTracker121.prototype.startPendingSample = function( oChannel, iNotePeriod, fWaitForDMAToStop ) 770 { 771 var iInstrumentNumber = oChannel.getPendingInstrumentNumber(); 772 oChannel.setNotePeriod( iNotePeriod ); 773 oChannel.setLastSavedNotePeriod( iNotePeriod ); 774 oChannel.setShadowNotePeriod( iNotePeriod ); 775 oChannel.setInstrumentNumber( iInstrumentNumber ); 776 oChannel.delaySampleStart( true, fWaitForDMAToStop ); 777 778 var oInstrument = this.getInstrument( iInstrumentNumber ); 779 var oSample = oInstrument.getSample(); 780 781 oSample.setLoopedYet( false ); 782 oChannel.setSample( oSample ); 783 oChannel.setSamplePosition( 0.0 ); 784 oChannel.setProtrackerInvertLoopOffset( oInstrument ); 785 786 if( 0 != oChannel.playFromOffset() ) 787 { 788 // Protracker Set SampleOffset Command. 789 // 790 var iOffset = oChannel.playFromOffset(); 791 var iLength = oSample.getLength(); 792 var iLoopEnd= oSample.getLoopStart() + oSample.getLoopLength(); 793 794 if( this.bFSTClearSampleOffsetAfterUse ) 795 { 796 // Fasttracker does not apply the SampleOffset to notes without the 797 // 9xx command. 798 // So clear after use. 799 // 800 oChannel.setPlayFromOffset( 0 ); 801 } 802 803 if( !oSample.isLooped() && iOffset > iLength ) 804 { 805 iOffset = iLength; 806 } 807 else if( oSample.isLooped() && oInstrument.isNoisetrackerInstrument() && iOffset > iLoopEnd ) 808 { 809 iOffset = iLoopEnd; 810 } 811 812 oChannel.setSamplePosition( iOffset ); 813 } 814 815 if( oSample.isLooped() && !oInstrument.isNoisetrackerInstrument() ) 816 { 817 oChannel.setSamplePosition( oSample.getLoopStart() ); 818 } 819 820 oChannel.setSampleAccumulator(); 821 }; 822 823 // --------------------------------------------------------------------------- 824 /** 825 * Process this modules pattern data, fetch the next pattern row into the channels for processing etc. 826 */ 827 weasel.UltimateSoundTracker121.prototype.processPattern = function( ) 828 { 829 if( this.bStartProcessingPatterns ) 830 { 831 this.iCurrentTick++; 832 this.iTotalPatternTicks++; 833 } 834 else 835 { 836 this.bStartProcessingPatterns = true; 837 } 838 839 // Next row? 840 // 841 if( this.iCurrentTick >= this.iTickSpeed ) 842 { 843 this.iCurrentTick = 0; 844 845 if( --this.iRowDelay <= 0 ) 846 { 847 this.iRowDelay = 0; 848 this.iCurrentPatternRowPosition += 1; 849 850 // Next Pattern? 851 // 852 if( this.getCurrentPatternRowPosition() >= weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern || this.bPatternBreak ) 853 { 854 if( this.bProtrackerRowDelayQuirk && this.bPatternBreak ) 855 { 856 // Protracker Quirk, Row Delay command (EEx) and Pattern Break (Dxx) causes row skip, so Pattern Breaks to Dxx +1. 857 // 858 this.iPatternBreakToRow += 1; 859 860 if( this.iPatternBreakToRow >= weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern ) 861 { 862 // Stop from visiting Row 65.. 863 // Bump sequence position along too. 864 // 865 this.iPatternBreakToRow = 0; 866 this.iCurrentSequencePosition += 1; 867 } 868 } 869 870 this.iCurrentPatternRowPosition = this.iPatternBreakToRow; 871 this.iCurrentSequencePosition += 1; 872 873 if( -1 != this.iSequencePositionJump && this.bPatternBreak ) 874 { 875 this.iCurrentSequencePosition = this.iSequencePositionJump; 876 } 877 878 this.bPatternBreak = false; 879 this.iPatternBreakToRow = 0; 880 this.iSequencePositionJump = -1; 881 882 // End of song? 883 // 884 if( this.iCurrentSequencePosition >= this.getSongLengthInPatterns() ) 885 { 886 this.iCurrentSequencePosition = this.iSongRestartSequence; 887 this.bSongEnded = true; 888 } 889 890 this._songSequenceChange(); 891 } 892 893 this.bProtrackerRowDelayQuirk = false; 894 } 895 } 896 897 this.fWaitForDMAMultiplier = 1.0; // Protracker ED0 command causes software delays (some 6000 clock cycles for each ED0). 898 899 // Fetch current pattern row. 900 // 901 if( 0 == this.iCurrentTick ) 902 { 903 if( this.iRowDelay <= 0 ) 904 { 905 this.fetchRow(); 906 } 907 else 908 { 909 this.processTick0Effects(); 910 } 911 } 912 else 913 { 914 this.processEffects(); 915 } 916 }; 917 918 // --------------------------------------------------------------------------- 919 /** 920 * Song Sequence Position changed. 921 * 922 * @protected 923 */ 924 weasel.UltimateSoundTracker121.prototype._songSequenceChange = function( ) 925 { 926 this.iTotalPatternTicks = 0; 927 }; 928 929 // --------------------------------------------------------------------------- 930 /** 931 * Restart song from beginning of module. 932 */ 933 weasel.UltimateSoundTracker121.prototype.restartSong = function( ) 934 { 935 this.iCurrentTick = 0; 936 this.iTotalPatternTicks = 0; 937 this.iCurrentPatternRowPosition = 0; 938 this.iCurrentSequencePosition = 0; 939 this.bSongEnded = false; 940 this.bPatternBreak = false; 941 this.iPatternBreakToRow = 0; 942 this.bStartProcessingPatterns = false; 943 this.iSequencePositionJump = -1; 944 this.iRowDelay = 0; 945 this.fWaitForDMAMultiplier = 1.0; 946 this.bProtrackerRowDelayQuirk = false; 947 948 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 949 { 950 this.aSoundChannels[ iChannel ].clearChannel(); 951 } 952 }; 953 954 // --------------------------------------------------------------------------- 955 /** 956 * Get the current tick speed. 957 * 958 * @return {int} = The tick speed. 959 */ 960 weasel.UltimateSoundTracker121.prototype.getTickSpeed = function( ) 961 { 962 return this.iTickSpeed; 963 }; 964 965 // --------------------------------------------------------------------------- 966 /** 967 * Get the requested audio channel object. 968 * 969 * @param {int} iChannelNumber = The number of the channel to fetch. 970 * 971 * @return {weasel.Channel} = The requested audio channel object. 972 */ 973 weasel.UltimateSoundTracker121.prototype.getChannel = function( iChannelNumber ) 974 { 975 if( iChannelNumber < 0 || iChannelNumber >= this.aSoundChannels.length ) 976 { 977 return null; 978 } 979 980 return this.aSoundChannels[ iChannelNumber ]; 981 }; 982 983 984 // --------------------------------------------------------------------------- 985 /** 986 * Get the current row tick. 987 * 988 * @return {int} = The current row tick (0-5 range, when a 0 occurs a new pattern row has been fetched). 989 */ 990 weasel.UltimateSoundTracker121.prototype.getCurrentTick = function() 991 { 992 return this.iCurrentTick; 993 }; 994 995 // --------------------------------------------------------------------------- 996 /** 997 * Get the current pattern cell object from the selected channel. 998 * 999 * @param {int} iChannelNumber = The Channel containing the pattern cell to fetch. 1000 * 1001 * @return {weasel.PatternCell} = The current PatternCell object for the provided channel number. 1002 */ 1003 weasel.UltimateSoundTracker121.prototype.getCurrentPatternCell = function( iChannelNumber ) 1004 { 1005 if( iChannelNumber < 0 || iChannelNumber >= this.getNumberOfChannels() ) 1006 return null; 1007 1008 return this.getPattern( this.getPatternNumber( this.getCurrentSequencePosition() ) ).getColumn( iChannelNumber ).getCell( this.getCurrentPatternRowPosition() ); 1009 }; 1010 1011 // --------------------------------------------------------------------------- 1012 /** 1013 * Play this module into the given AudioBuffer object. 1014 * 1015 * @param {weasel.AudioBuffer} oAudioBuffer = The AudioBuffer object to render to. 1016 * @param {bool} bIgnoreFilter = Ignore using the Amiga Filter (only a very few modules actually use it). 1017 * @param {int} iSamples = [optional] The number of samples to make this frame, usually the remaining samples to fill in the oAudioBuffer object. 1018 */ 1019 weasel.UltimateSoundTracker121.prototype.play = function( oAudioBuffer, bIgnoreFilter, iSamples ) 1020 { 1021 var iSamplesToFill = oAudioBuffer.samplesToFill(); 1022 var oChannel1 = this.getChannel( 0 ); 1023 var oChannel2 = this.getChannel( 1 ); 1024 var oChannel3 = this.getChannel( 2 ); 1025 var oChannel4 = this.getChannel( 3 ); 1026 1027 if( undefined !== iSamples ) 1028 { 1029 if( iSamples < iSamplesToFill && iSamples >= 0 ) 1030 { 1031 iSamplesToFill = iSamples; 1032 } 1033 } 1034 1035 var bFilterStartingState = this.bFilterOn; 1036 var bFilterStateChange = bFilterStartingState; 1037 var iFilterStateChanges = 0; 1038 var aFilterStateChange = this.aFilterStateChange; 1039 var aFilterStateForMixer = this.aFilterStateForMixer; 1040 1041 var iSamplesToMix = iSamplesToFill; 1042 var iStartingCircularBufferOffset = oChannel1.getCircularBufferPosition(); 1043 1044 for( ; iSamplesToFill > 0; ) 1045 { 1046 var iSamplesTillPatternProcess = this.iSamplesRemaining; 1047 1048 if( iSamplesTillPatternProcess > iSamplesToFill ) 1049 { 1050 iSamplesTillPatternProcess = iSamplesToFill; 1051 } 1052 1053 oChannel1.make( iSamplesTillPatternProcess ); 1054 oChannel2.make( iSamplesTillPatternProcess ); 1055 oChannel3.make( iSamplesTillPatternProcess ); 1056 oChannel4.make( iSamplesTillPatternProcess ); 1057 1058 1059 this.iSamplesRemaining -= iSamplesTillPatternProcess; 1060 iSamplesToFill -= iSamplesTillPatternProcess; 1061 1062 // Time to process Pattern data? 1063 // 1064 if( this.iSamplesRemaining <= 0 ) 1065 { 1066 this.processPattern(); 1067 this.iSamplesRemaining = this.iSamplesPerTick; 1068 1069 if( bFilterStateChange != this.bFilterOn ) 1070 { 1071 // Filter has been toggled on or off, record sample offset 1072 // when this occurred for mixer. 1073 // 1074 bFilterStateChange = this.bFilterOn; 1075 aFilterStateChange[ iFilterStateChanges ] = iSamplesTillPatternProcess; 1076 aFilterStateForMixer[ iFilterStateChanges ] = bFilterStateChange; 1077 iFilterStateChanges++; 1078 } 1079 } 1080 } 1081 1082 if( iSamplesToMix > 0 ) 1083 { 1084 var aSamples1 = oChannel1.getCircularAudioBuffer(); 1085 var aSamples2 = oChannel2.getCircularAudioBuffer(); 1086 var aSamples3 = oChannel3.getCircularAudioBuffer(); 1087 var aSamples4 = oChannel4.getCircularAudioBuffer(); 1088 1089 if( 0 == iFilterStateChanges || bIgnoreFilter ) 1090 { 1091 // Filter state did not change, can mix channels in one go. 1092 // 1093 oAudioBuffer.mix( iSamplesToMix, aSamples1, aSamples2, aSamples3, aSamples4, iStartingCircularBufferOffset, bFilterStartingState && !bIgnoreFilter ); 1094 } 1095 else 1096 { 1097 // Filters state changed, mix in chunks before and after change(s). 1098 // 1099 for( var iFilterChanges = 0; iFilterChanges < iFilterStateChanges; iFilterChanges++ ) 1100 { 1101 var iSamplesTillChange = aFilterStateChange[ iFilterChanges ]; 1102 1103 oAudioBuffer.mix( iSamplesTillChange, aSamples1, aSamples2, aSamples3, aSamples4, iStartingCircularBufferOffset, bFilterStartingState ); 1104 1105 bFilterStartingState = aFilterStateForMixer[ iFilterChanges ]; 1106 iStartingCircularBufferOffset = (iStartingCircularBufferOffset + iSamplesTillChange) % aSamples1.length; 1107 iSamplesToMix -= iSamplesTillChange; 1108 } 1109 1110 if( iSamplesToMix > 0 ) 1111 { 1112 oAudioBuffer.mix( iSamplesToMix, aSamples1, aSamples2, aSamples3, aSamples4, iStartingCircularBufferOffset, bFilterStartingState ); 1113 } 1114 } 1115 } 1116 }; 1117 1118 // --------------------------------------------------------------------------- 1119 /** 1120 * Use to see if playing module has finished (modules naturally loop back to beginning). 1121 * 1122 * @return {bool} = TRUE means Song has ended , FALSE mean Song not finished. 1123 */ 1124 weasel.UltimateSoundTracker121.prototype.hasSongEnded = function( ) 1125 { 1126 return this.bSongEnded; 1127 }; 1128 1129 // --------------------------------------------------------------------------- 1130 /** 1131 * Get the current sequence position in the song. 1132 * 1133 * @return {int} = The current sequence position. 1134 */ 1135 weasel.UltimateSoundTracker121.prototype.getCurrentSequencePosition = function( ) 1136 { 1137 return this.iCurrentSequencePosition; 1138 }; 1139 1140 // --------------------------------------------------------------------------- 1141 /** 1142 * Set the current sequence position in the song. 1143 * 1144 * @param {int} iSequencePosition = The position in the sequence table you want. 1145 */ 1146 weasel.UltimateSoundTracker121.prototype.setCurrentSequencePosition = function( iSequencePosition ) 1147 { 1148 var iSongLength = this.getSongLengthInPatterns(); 1149 1150 if( iSequencePosition >= this.getSongLengthInPatterns() ) 1151 { 1152 iSequencePosition = iSongLength -1; 1153 } 1154 1155 if( iSequencePosition < 0 ) 1156 { 1157 iSequencePosition = 0; 1158 } 1159 1160 this.iCurrentSequencePosition = iSequencePosition; 1161 }; 1162 1163 // --------------------------------------------------------------------------- 1164 /** 1165 * Get the Current Pattern Row Position in the song. 1166 * 1167 * @return {int} = The Current Pattern Row Position. 1168 */ 1169 weasel.UltimateSoundTracker121.prototype.getCurrentPatternRowPosition = function( ) 1170 { 1171 return this.iCurrentPatternRowPosition; 1172 }; 1173 1174 1175 // --------------------------------------------------------------------------- 1176 /** 1177 * Set Clock Constant to either PAL or NTSC. 1178 * 1179 * @param {bool} bClockConstant = true : PAL, false : NTSC. 1180 */ 1181 weasel.UltimateSoundTracker121.prototype.setClockConstant = function( bClockConstant ) 1182 { 1183 this.bUsePALClockConstant = bClockConstant == true ? true : false; 1184 this.setSamplesPerTick(); 1185 1186 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 1187 this.aSoundChannels[ iChannel ].setClockConstant( this.bUsePALClockConstant ); 1188 }; 1189 1190 // --------------------------------------------------------------------------- 1191 /** 1192 * Set Clock Constant to either PAL or NTSC. 1193 * 1194 * @param {weasel.UltimateSoundTracker121.TimingOverrides} iBPMPALNTSC = Use the BPM set in mod | PAL 50hz | NTSC ~59.94hz (actually 60/1.001). 1195 */ 1196 weasel.UltimateSoundTracker121.prototype.timingOverride = function( iBPMPALNTSC ) 1197 { 1198 switch( iBPMPALNTSC ) 1199 { 1200 1201 case this.TimingOverrides.PAL : 1202 this.iTimingOverride = this.TimingOverrides.PAL; 1203 this.setClockConstant( true ); 1204 break; 1205 1206 case this.TimingOverrides.NTSC : 1207 this.iTimingOverride = this.TimingOverrides.NTSC; 1208 this.setClockConstant( false ); 1209 break; 1210 1211 1212 case this.TimingOverrides.UseBPM : 1213 this.iTimingOverride = this.TimingOverrides.UseBPM; 1214 this.setSamplesPerTick(); 1215 default: 1216 break; 1217 }; 1218 }; 1219 1220 // --------------------------------------------------------------------------- 1221 /** 1222 * Get the timing override. 1223 * 1224 * @return {weasel.UltimateSoundTracker121.TimingOverrides} = The timing override. 1225 */ 1226 weasel.UltimateSoundTracker121.prototype.getTimingOverride = function() 1227 { 1228 return this.iTimingOverride; 1229 }; 1230 1231 // --------------------------------------------------------------------------- 1232 /** 1233 * Get the type of Soundtracker Module. 1234 * 1235 * @return {String} = The type of module. 1236 */ 1237 weasel.UltimateSoundTracker121.prototype.getModuleType = function() 1238 { 1239 return this.sModuleType; 1240 }; 1241 1242 // --------------------------------------------------------------------------- 1243 /** 1244 * Get the mode of the Vibrato (Noisetracker 1.1 & 2.0 have a different divider, 1245 * unfortunately its impossible to 100% id a Noisetracker 2.0 module so need to 1246 * give the user the ability to switch between Vibrato Modes). 1247 * 1248 * @return {bool} = True : Noisetracker 2.0 Vibrato mode, False : Noisetracker 1.1 Vibrato Mode. 1249 */ 1250 weasel.UltimateSoundTracker121.prototype.getVibratoMode = function() 1251 { 1252 return this.bNoisetracker20VibratoMode; 1253 }; 1254 1255 // --------------------------------------------------------------------------- 1256 /** 1257 * Set the mode of the Vibrato (Noisetracker 1.1 & 2.0 have a different divider, 1258 * unfortunately its impossible to 100% id a Noisetracker 2.0 module so need to 1259 * give the user the ability to switch between Vibrato Modes). 1260 * 1261 * @param {bool} bNoisetracker20Mode = True : Noisetracker 2.0 Vibrato mode, False : Noisetracker 1.1 Vibrato Mode. 1262 */ 1263 weasel.UltimateSoundTracker121.prototype.setVibratoMode = function( bNoisetracker20Mode ) 1264 { 1265 this.bNoisetracker20VibratoMode = bNoisetracker20Mode == true ? true : false; 1266 }; 1267 1268 // --------------------------------------------------------------------------- 1269 /** 1270 * The Protracker series has a bug when using the Ramp Down Sawtooth waveform 1271 * during the Tremolo command (the other waveform types are fine). The bug 1272 * is caused by using the Vibrato Table Position instead of the Tremolo Table 1273 * Position during construction of the sawtooth resulting in either a lop sided 1274 * sawooth (non centered) if the Vibrato command is not used or a triangle-ish waveform 1275 * that almost randomly changes direction if the Vibrato command has been used on 1276 * the same channel. 1277 * 1278 * @param {bool} bBugEnabled = True : Bug enabled (normal behaviour), False : bug disable. 1279 */ 1280 weasel.UltimateSoundTracker121.prototype.setProtrackerTremoloSawtoothBugMode = function( bBugEnabled ) 1281 { 1282 this.bProtrackerTremoloSawtoothBug = bBugEnabled == true ? true : false; 1283 }; 1284 1285 // --------------------------------------------------------------------------- 1286 /** 1287 * Get the mode of the Protracker Tremolo Sawtooth bug. 1288 * 1289 * @return {bool} = True : Bug enabled (normal behaviour), False : bug disable. 1290 */ 1291 weasel.UltimateSoundTracker121.prototype.getProtrackerTremoloSawtoothBugMode = function() 1292 { 1293 return this.bProtrackerTremoloSawtoothBug; 1294 }; 1295 1296 // --------------------------------------------------------------------------- 1297 /** 1298 * Get the constant used for waiting for the DMA to stop in milliseconds. 1299 * 1300 * @return {float} = The millisecond pause used by this module type. 1301 */ 1302 weasel.UltimateSoundTracker121.prototype.WaitForDMAToStop = function() 1303 { 1304 return weasel.FormatUltimateSoundTracker121.WaitForDMAToStop; 1305 }; 1306 1307 1308 // --------------------------------------------------------------------------- 1309 /** 1310 * Protracker 3 has different Set Sample Offset command (9xx) behaviour, in that 1311 * it behaves as expected and only applies the Sample Offset before the sample 1312 * starts and not after, neither does it keep adding the Offset to the sample start 1313 * each call. 1314 * 1315 * @param {bool} bEnabled = True : Enable Protracker 3 mode, False : Enable Protracker 1 & 2 mode. 1316 */ 1317 weasel.UltimateSoundTracker121.prototype.setProtracker3SampleOffsetMode = function( bEnabled ) 1318 { 1319 this.bProtracker3SampleOffsetMode = bEnabled == true ? true : false; 1320 }; 1321 1322 // --------------------------------------------------------------------------- 1323 /** 1324 * Get the mode of the Protracker 3 mode, Set Sample Offset behaves differently from Protracker 1 & 2. 1325 * 1326 * @return {bool} = True : Bug enabled (normal behaviour), False : bug disable. 1327 */ 1328 weasel.UltimateSoundTracker121.prototype.getProtracker3SampleOffsetMode = function() 1329 { 1330 return this.bProtracker3SampleOffsetMode; 1331 }; 1332 1333 1334 // --------------------------------------------------------------------------- 1335 /** 1336 * Some Fasttracker/Taketracker use a 7 Bit panning position value in the 0x8xx 1337 * command. The Module Sniffer is able to detect this and sets the module accordingly, 1338 * but will sometimes get it wrong as composers enter the wrong values (or reperpose 1339 * in the case of a few of Necros & Basehead FST modules for 3D stereo using additional channels on the GUS). 1340 * 1341 * @param {bool} bEnabled = True : Enable 7 Bit Panning mode, False : Enable 8 Bit Panning mode. 1342 */ 1343 weasel.UltimateSoundTracker121.prototype.setFST7BitPanningMode = function( b7BitPanning ) 1344 { 1345 if( !this.b7BitPanning && b7BitPanning ) 1346 { 1347 // Switch current 8bit panning positions on channels to where they 1348 // would be as if they were set in 7bit panning mode. 1349 // 1350 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 1351 { 1352 var oChannel = this.aSoundChannels[ iChannel ]; 1353 oChannel.setPanningPosition( ( oChannel.getPanningPosition() * 2 ) |0 ); 1354 } 1355 } 1356 else if( this.b7BitPanning && !b7BitPanning ) 1357 { 1358 // Switch current 7bit panning positions on channels to where they 1359 // would be as if they were set in 8bit panning mode. 1360 // 1361 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 1362 { 1363 var oChannel = this.aSoundChannels[ iChannel ]; 1364 oChannel.setPanningPosition( ( oChannel.getPanningPosition() / 2 ) |0 ); 1365 } 1366 } 1367 1368 this.b7BitPanning = b7BitPanning == true ? true : false; 1369 }; 1370 1371 // --------------------------------------------------------------------------- 1372 /** 1373 * Get the Fasttracker/Taketracker 7 Bit Panning Mode. 1374 * 1375 * @return {bool} = True : 7 Bit Panning in use, FALSE : 8 Bit Panning in use. 1376 */ 1377 weasel.UltimateSoundTracker121.prototype.getFSTPanningMode = function() 1378 { 1379 return this.b7BitPanning; 1380 }; 1381 1382 1383 //--------------------------------------------------------------------------- 1384 /** 1385 * Noisetracker/Protracker loop quirks mode active (Taketracker/Fasttracker does not implement them). 1386 * 1387 * @return {bool} = True : yes, FALSE : no. 1388 */ 1389 weasel.UltimateSoundTracker121.prototype.getNoiseTrackerLoopQuirkEnabled = function() 1390 { 1391 return this.bNoiseTrackerLoopQuirk; 1392 }; 1393