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 /** Create a Lars "Zap" Hamre Protracker module that uses the M.K. format out of the provided data (which has already passed the module sniffer test). 9 * 10 * @constructor 11 * @extends weasel.NoiseTracker20 12 * 13 * @param {Array|Uint8Array} aModuleData = The Lars "Zap" Hamre Protracker module as a byte array that MUST have passed the module sniffer test. 14 * @param {int} iPlaybackFrequency = The playback frequency in hertz to use (e.g. 44100 ). 15 * @param {weasel.Sample.prototype.SampleScannerMode} iSampleScannerMode = Scan for IFF Header corruption residue?. 16 * 17 * @author Warren Willmey 2013 18 */ 19 weasel.ProTrackerMK = function( aModuleData, iPlaybackFrequency, iSampleScannerMode ) 20 { 21 this.parent = weasel.NoiseTracker20; 22 23 // Needed for prototype Inheritance. 24 // 25 if( aModuleData === undefined || !(( aModuleData instanceof Array ) || ( window.Uint8Array && aModuleData instanceof Uint8Array )) ) 26 return; 27 28 // To simplify the process of keeping track of the Song Position In MilliSeconds, 29 // Keep track of all BPM changes in the song sequence. 30 // All Protracker modules start of at 125 BPM. 31 // 32 this.aBPMChangedTimeTick = []; 33 this.aBPMSpeeds = []; 34 this.bIgnorePatternScan = true; 35 this.bUseFineTuning = true; 36 37 this.parent( aModuleData, iPlaybackFrequency, iSampleScannerMode ); 38 39 // When the Note Pitch is low enough (~2604hz) there is not enough time to stop the 40 // DMA, so the current sample continues playing instead of the new one. 41 // This attribute is present so that FST Modules can turn it off as: 42 // a) Doesn't use the Protracker source code 43 // b) Doesn't emulate waiting for the Amiga's DMA. 44 // 45 this.bDontStopDMAQuirk = true; 46 47 // Set Noisetracker 2.0 Vibrato mode, although it should be noted that 48 // Protracker 1.0c modules use the same Vibrato as Noisetracker 1.1. 49 // From Protracker 1.1a onwards the Vibrato was changed to match Noisetracker 2.0. 50 // 51 this.setVibratoMode( true ); 52 53 // Protracker did not include the Song Restart Position from Noisetracker 1.1. 54 // 55 this.iSongRestartSequence = 0; 56 57 // Protracker introduces a more accurate BPM Song Speed setting than that 58 // used from Ultimate Soundtracker 1.8, so that 59 // a BPM of 125 is actually correct at 50hz and a 250BPM is 100Hz. 60 // ALL modules start off at 125bpm (PAL VBL timing) and then can be changed 61 // via the Set Speed Effect Command. 62 // 63 this.setSongSpeed( 125 ); 64 this.timingOverride( weasel.UltimateSoundTracker121.prototype.TimingOverrides.UseBPM ); 65 66 this.sModuleType = weasel.ModuleSniffer.prototype.SupportedModules.ProTrackerMK; 67 68 // Due to the Pattern Row Loop effect command in Protracker we need to track 69 // which pattern rows have already been visited to see if the song has ended. 70 // 71 this.aVisitedSequence = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength ); 72 73 for( var aVisited = this.aVisitedSequence, iSequence = aVisited.length; --iSequence >= 0; ) 74 { 75 aVisited[ iSequence ] = new Array( weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern ); 76 77 for( var aRows = aVisited[ iSequence ], iRow = aRows.length; --iRow >= 0; ) 78 { 79 aRows[ iRow ] = false; 80 } 81 } 82 83 this.bIgnorePatternScan = false; 84 this._computeSequenceTableTicks(); 85 }; 86 87 weasel.ProTrackerMK.prototype = new weasel.NoiseTracker20; 88 89 90 // --------------------------------------------------------------------------- 91 /** Scan all used patterns in sequence order to work out the length of the song 92 * in ticks. Due to: 93 * - Sequence Position Jump 94 * - Pattern Break 95 * - Pattern Row Loop 96 * - Pattern Row Delay 97 * - Tick Speed Changes 98 * - BPM Speed Changes 99 * commands, it may result in certain sequence positions never actually playing. 100 * Not to sure about this implementation being wacked out on Amoxicillin antibiotics 101 * (take a look at the side effects!) for an ear infection (currently I can only 102 * hear out of one ear!) and its 30*C indoors... Perhaps it could be better. 103 * 104 * @protected 105 * @override 106 */ 107 weasel.ProTrackerMK.prototype._computeSequenceTableTicks = function( ) 108 { 109 if( this.bIgnorePatternScan ) 110 { 111 return; 112 } 113 114 var aAlreadyVisitedSequence = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength ); 115 this.aSequenceTableAccumulatedTicks = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength ); 116 this.aBPMChangedTimeTick = []; 117 this.aBPMSpeeds = []; 118 119 for( var iSequence = aAlreadyVisitedSequence.length; --iSequence >= 0; ) 120 { 121 aAlreadyVisitedSequence[ iSequence ] = new Array( weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern ); 122 123 for( var aRows = aAlreadyVisitedSequence[ iSequence ], iRow = aRows.length; --iRow >= 0; ) 124 { 125 aRows[ iRow ] = false; 126 } 127 } 128 129 for( var iLength = this.aSequenceTableAccumulatedTicks.length; --iLength >= 0; ) 130 { 131 this.aSequenceTableAccumulatedTicks[ iLength ] = 0; 132 } 133 134 var iNumberOfSoundChannels = this.getNumberOfChannels(); 135 var aRowLoopsPoints = new Array( iNumberOfSoundChannels ); 136 var aRowLoopCounters = new Array( iNumberOfSoundChannels ); 137 var iTickSpeed = weasel.FormatUltimateSoundTracker121.TicksPerRow; 138 var iBPMSpeed = 125; 139 var iWantedBPM = 125; 140 var iTickTotal = 0; 141 var iSequencePositionJump = -1; 142 var bSongEnded = false; 143 var iPatternBreakToRow = 0; 144 var iRowLoopTo = -1; 145 var bRowDelayQuirk = false; 146 147 for( var iSongSequenceLength = this.getSongLengthInPatterns(), iSongPosition = 0; 148 iSongPosition < weasel.FormatUltimateSoundTracker121.SequenceTableLength 149 && iSongPosition < iSongSequenceLength 150 && !aAlreadyVisitedSequence[ iSongPosition ][ iPatternBreakToRow ] 151 && !bSongEnded; ) 152 { 153 // Record the Total Ticks needed to get to the beginning of each pattern sequence. 154 // 155 if( this.aSequenceTableAccumulatedTicks[ iSongPosition ] == 0 && iTickTotal != 0 && iSongPosition != 0 ) 156 { 157 this.aSequenceTableAccumulatedTicks[ iSongPosition ] = iTickTotal; 158 } 159 160 iSequencePositionJump = -1; 161 162 for( var iClearRowLoops = this.aSoundChannels.length; --iClearRowLoops >= 0; ) 163 { 164 aRowLoopsPoints[ iClearRowLoops ] = 0; 165 aRowLoopCounters[ iClearRowLoops ] = -1; 166 } 167 iRowLoopTo = -1; 168 169 var iPatternNumber = this.getPatternNumber( iSongPosition ); 170 var oPattern = this.getPattern( iPatternNumber ); 171 172 for( var iRowsPerPattern = weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern, iRow = iPatternBreakToRow, bPatternBreak = false, iPatternBreakToRow = 0; (iRow < iRowsPerPattern) && !bPatternBreak && !bSongEnded; iRow++ ) 173 { 174 var iRowDelay = 0; 175 bRowDelayQuirk = false; 176 // Check to see if we're within a Pattern Row Loop command, in which case its OK to have already visited it. 177 // 178 var bInPatternRowLoop = false; 179 for( var iChannel = iNumberOfSoundChannels; --iChannel >= 0; ) 180 { 181 if( aRowLoopCounters[ iChannel ] >= 0 ) 182 { 183 bInPatternRowLoop = true; 184 break; 185 } 186 } 187 188 if( true == aAlreadyVisitedSequence[ iSongPosition ][ iRow ] && !bInPatternRowLoop ) 189 { 190 bSongEnded = true; 191 continue; 192 } 193 194 aAlreadyVisitedSequence[ iSongPosition ][ iRow ] = true; 195 196 // Order of channels important, there may be more than one Tick Speed effect in a single row. 197 // 198 for( var iChannels = iNumberOfSoundChannels, iChannel = -1; ++iChannel < iChannels; ) 199 { 200 var oColumn = oPattern.getColumn( iChannel ); 201 var oPatternCell = oColumn.getCell( iRow ); 202 var iEffectParameter = oPatternCell.getEffectParameter(); 203 204 switch( oPatternCell.getEffectNumber() ) 205 { 206 case weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump : 207 208 // If Pattern Break command occurs in any other channel, this command 209 // turns it into a Sequence Position Jump command instead. 210 // 211 bPatternBreak = true; 212 iSequencePositionJump = iEffectParameter; 213 iPatternBreakToRow = 0; 214 break; 215 216 case weasel.FormatDOCSoundTracker22.Effects.PatternBreak : 217 218 // Pattern break. 219 // 220 var iRowJump = ((iEffectParameter >>> 4) * 10) + (iEffectParameter & 0xf ); 221 222 if( iRowJump > 63 ) 223 { 224 iRowJump = 0; 225 } 226 227 bPatternBreak = true; 228 iPatternBreakToRow = iRowJump; 229 break; 230 231 case weasel.FormatDOCSoundTracker9.Effects.TickSpeed : 232 233 if( this.getTimingOverride() == this.TimingOverrides.UseBPM ) 234 { 235 // BPM/CIA replay mode. 236 // 237 238 // Tick Speed has changed. 239 // 240 if( 0 == iEffectParameter ) 241 { 242 bSongEnded = true; 243 iTickSpeed = 0; 244 break; 245 } 246 247 if( iEffectParameter < 32 ) 248 { 249 // Change Tick Speed. 250 // 251 iTickSpeed = iEffectParameter; 252 } 253 else 254 { 255 // Change tempo/BPM speed. 256 // 257 if( iWantedBPM != iEffectParameter ) 258 { 259 iWantedBPM = iEffectParameter; 260 } 261 } 262 } 263 else 264 { 265 // VBL replay mode. 266 // There is an undocumented side of Protracker in VBL mode 267 // which allows setting the Tick Speed in the range 1-255! 268 // 0 is ignored. 269 if( iEffectParameter > 0 ) 270 { 271 // Change Tick Speed to 1-255 range. 272 // 273 iTickSpeed = iEffectParameter; 274 } 275 } 276 277 break; 278 279 case weasel.FormatProTrackerMK.Effects.ExtendedCommands : 280 var iExtendedCommand = iEffectParameter & 0xf0; 281 var iExtendedEffectParameter = iEffectParameter & 0xf; 282 283 if( iExtendedCommand == weasel.FormatProTrackerMK.Effects.PatternLoop ) 284 { 285 // Pattern Row Loop command. 286 // 287 if( 0 == iExtendedEffectParameter ) 288 { 289 // Set pattern Loop starting Point. 290 // 291 aRowLoopsPoints[ iChannel ] = iRow; 292 } 293 else 294 { 295 if( aRowLoopCounters[ iChannel ] <= 0 ) 296 { 297 aRowLoopCounters[ iChannel ] = iExtendedEffectParameter; 298 } 299 else 300 { 301 if( --aRowLoopCounters[ iChannel ] <= 0 ) 302 { 303 // Pattern Row Loop ended, continue pattern as normal. 304 // 305 306 continue; 307 } 308 } 309 310 // Loop to row in current pattern. 311 // 312 iRowLoopTo = aRowLoopsPoints[ iChannel ]; 313 } 314 } 315 else if( iExtendedCommand == weasel.FormatProTrackerMK.Effects.PatternDelay ) 316 { 317 bRowDelayQuirk = true; // Row Delay (EEx) + Pattern Break (Dxx) causes jump to Dxx +1 NOT Dxx. 318 iRowDelay = iExtendedEffectParameter; 319 } 320 321 break; 322 323 default: 324 break; 325 } 326 } 327 328 // Log BPM tempo changes. 329 // 330 if( iWantedBPM != iBPMSpeed ) 331 { 332 iBPMSpeed = iWantedBPM; 333 this.aBPMSpeeds.push( iBPMSpeed ); 334 this.aBPMChangedTimeTick.push( iTickTotal ); 335 } 336 337 if( iRowLoopTo >= 0 ) 338 { 339 iRow = iRowLoopTo -1; 340 iRowLoopTo = -1; 341 } 342 343 iTickTotal += iTickSpeed + (iRowDelay * iTickSpeed); 344 } 345 346 if( bRowDelayQuirk && bPatternBreak ) 347 { 348 // Row Delay + Pattern Break Quirk causing row skip. 349 // 350 iPatternBreakToRow++; 351 352 if( iPatternBreakToRow >= weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern ) 353 { 354 // Skip sequence as row now out of range of pattern. 355 // 356 iPatternBreakToRow = 0; 357 iSongPosition++; 358 } 359 } 360 361 // Move onto next Song Position in Sequence List. 362 // 363 if( -1 == iSequencePositionJump ) 364 { 365 iSongPosition++; 366 } 367 else 368 { 369 iSongPosition = iSequencePositionJump; 370 } 371 } 372 373 this.iSongLengthInTicks = iTickTotal; 374 }; 375 376 // --------------------------------------------------------------------------- 377 /** 378 * Get song position in milliseconds, which is more complicated with Protracker 379 * as the BPM speed can change within the song on any row. 380 * 381 * @param {int} iTickTotal = The number of ticks into the current song (or the total ticks of the song to get the song length). 382 * @param {bool} bLengthOfSong = use this function to compute the length of song instead of the current position. 383 * 384 * @return {float} = The song position from its beginning to the desired position (or end of song) in ms. 385 * 386 * @private 387 */ 388 weasel.ProTrackerMK.prototype._getSongPositionInMilliSeconds = function( iTickTotal, bLengthOfSong ) 389 { 390 var aTickBPMChanged = this.aBPMChangedTimeTick; 391 var aBPMSpeeds = this.aBPMSpeeds; 392 393 if( aBPMSpeeds.length == 0 ) 394 { 395 // No BPM changes within song. 396 // 397 return iTickTotal * (1000.0 / this.tickPlaybackRateInHz()); 398 } 399 400 var fBPMTotal = 0.0; 401 var iPreviousTickChange = 0; 402 var iPreviousBPM = 125; 403 var iChange = 0; 404 405 for( var iLength = aTickBPMChanged.length; iChange < iLength && aTickBPMChanged[ iChange ] < iTickTotal; iChange++ ) 406 { 407 fBPMTotal += (aTickBPMChanged[ iChange ] - iPreviousTickChange) * (2500 / iPreviousBPM); 408 iPreviousTickChange = aTickBPMChanged[ iChange ]; 409 iPreviousBPM = aBPMSpeeds[ iChange ]; 410 } 411 412 // Song position somewhere in between BPM changes or end of song, so 413 // add in remainder at current BPM. 414 // 415 if( bLengthOfSong ) 416 { 417 fBPMTotal += (iTickTotal - iPreviousTickChange) * (2500 / iPreviousBPM); 418 } 419 else 420 { 421 fBPMTotal += (iTickTotal - iPreviousTickChange) * (1000.0 / this.tickPlaybackRateInHz()); 422 } 423 424 return fBPMTotal; 425 }; 426 427 // --------------------------------------------------------------------------- 428 /** 429 * Get song position in milliseconds, which is more complicated with Protracker 430 * as the BPM speed can change within the song on any row. 431 * 432 * @return {float} = The song position from its beginning in ms. 433 * 434 * @override 435 */ 436 weasel.ProTrackerMK.prototype.getSongPositionInMilliSeconds = function( ) 437 { 438 var iTickTotal = this.aSequenceTableAccumulatedTicks[ this.getCurrentSequencePosition() ] + this.iTotalPatternTicks; 439 440 return this._getSongPositionInMilliSeconds( iTickTotal, false ); 441 }; 442 443 // --------------------------------------------------------------------------- 444 /** 445 * Get length of song in milliseconds. 446 * 447 * @return {float} = The length of the song in ms. 448 * 449 * @override 450 */ 451 weasel.ProTrackerMK.prototype.getLengthOfSongInMilliSeconds = function( ) 452 { 453 return this._getSongPositionInMilliSeconds( this.iSongLengthInTicks, true ); 454 455 }; 456 457 // --------------------------------------------------------------------------- 458 /** 459 * Set the song speed as Protracker has a different and more accurate BPM mode. 460 * 461 * @param {int} iModuleBPMSpeed = The song speed in BPM (BPM) [32-255]. 462 * 463 * @override 464 */ 465 weasel.ProTrackerMK.prototype.setSongSpeed = function( iModuleBPMSpeed ) 466 { 467 if( iModuleBPMSpeed < 32 ) 468 { 469 iModuleBPMSpeed = 32; 470 } 471 else if( iModuleBPMSpeed > 255 ) 472 { 473 iModuleBPMSpeed = 255; 474 } 475 476 this.iSongSpeed = iModuleBPMSpeed; 477 this.setSamplesPerTick(); 478 }; 479 480 // --------------------------------------------------------------------------- 481 /** 482 * Extract song speed from module, all Protracker Modules start at a BPM of 125 (50hz), 483 * but this value is not stored in the file. 484 * 485 * @protected 486 * @override 487 */ 488 weasel.ProTrackerMK.prototype._extractSongSpeed = function( ) 489 { 490 this.setSongSpeed( 125 ); 491 }; 492 493 // --------------------------------------------------------------------------- 494 /** 495 * Protracker uses a more accurate Tempo Timer than Ultimate Soundtracker. 496 * 497 * @return {float} = The playback rate in hz. 498 * 499 * @override 500 */ 501 weasel.ProTrackerMK.prototype.tickPlaybackRateInHz = function( ) 502 { 503 var fPlaybackRateInHz = this.getCIATimerConstant() / ((this.getCIATimerConstant() / 50.0) * (125 / this.getSongSpeed())); 504 505 if( this.iTimingOverride == this.TimingOverrides.PAL ) 506 { 507 fPlaybackRateInHz = this.PAL; 508 } 509 else if( this.iTimingOverride == this.TimingOverrides.NTSC ) 510 { 511 fPlaybackRateInHz = this.NTSC; 512 } 513 514 return fPlaybackRateInHz; 515 }; 516 517 // --------------------------------------------------------------------------- 518 /** Set the row tick speed. 519 * 520 * @param {int} iTickSpeed = The new row tick speed to use (0-255), a value of 0 in Protracker indicates the song has ended. 521 * A range of 0-31 is used during CIA/BPM playback, a range of 1-255 is allowed during VBL playback. 522 * 523 * @override 524 */ 525 weasel.ProTrackerMK.prototype.setTickSpeed = function( iTickSpeed ) 526 { 527 if( iTickSpeed <= 0 ) 528 { 529 this.bSongEnded = true; 530 return; 531 } 532 533 this.iTickSpeed = iTickSpeed > 255 ? 255 : iTickSpeed; 534 }; 535 536 // --------------------------------------------------------------------------- 537 /** Process a channel's effects. 538 * 539 * @param {weasel.Channel} oChannel = The Channel to process for effects. 540 * 541 * @protected 542 * @override 543 */ 544 weasel.ProTrackerMK.prototype._processChannelEffect = function( oChannel ) 545 { 546 oChannel.updateProtrackerInvertLoop(); 547 548 switch( oChannel.getEffectNumber() ) 549 { 550 case weasel.FormatDOCSoundTracker9.Effects.Arpeggio : 551 552 this._arpeggio( oChannel ); 553 554 break; 555 556 case weasel.FormatDOCSoundTracker9.Effects.PitchbendUp : 557 558 oChannel.pitchBend( this.iCurrentTick, 0, oChannel.getEffectParameter() ); 559 oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) ); 560 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 561 562 break; 563 564 case weasel.FormatDOCSoundTracker9.Effects.PitchbendDown : 565 566 oChannel.pitchBend( this.iCurrentTick, oChannel.getEffectParameter(), 0 ); 567 oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) ); 568 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 569 570 break; 571 572 case weasel.FormatNoiseTracker11.Effects.TonePortamento : 573 574 oChannel.setNotePortamentoSpeed( oChannel.getEffectParameter() ); 575 oChannel.notePortamento( this.iCurrentTick ); 576 oChannel.setNotePeriod( this._clampNotePeriod( oChannel.getNotePeriod() ) ); 577 break; 578 579 case weasel.FormatNoiseTracker11.Effects.Vibrato : 580 581 oChannel.protrackerVibrato( this.iCurrentTick, true, this.getVibratoMode() ); 582 break; 583 584 585 case weasel.FormatNoiseTracker20.Effects.TonePortamentoAndVolumeSlide : 586 587 oChannel.notePortamento( this.iCurrentTick ); 588 oChannel.setNotePeriod( this._clampNotePeriod( oChannel.getNotePeriod() ) ); 589 oChannel.volumeSlide( this.iCurrentTick ); 590 break; 591 592 case weasel.FormatNoiseTracker20.Effects.VibratoAndVolumeSlide : 593 594 oChannel.protrackerVibrato( this.iCurrentTick, false, this.getVibratoMode() ); 595 oChannel.volumeSlide( this.iCurrentTick ); 596 break; 597 598 case weasel.FormatProTrackerMK.Effects.Tremolo : 599 600 // Restore (shadow) note period when a Effect Command 7 occurs. 601 // 602 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 603 604 oChannel.tremolo( this.iCurrentTick, this.getProtrackerTremoloSawtoothBugMode() ); 605 break; 606 607 case weasel.FormatSpreadpointSoundTracker23.Effects.VolumeSlide : 608 609 // Restore (shadow) note period when a Effect Command 10 occurs. 610 // 611 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 612 613 oChannel.volumeSlide( this.iCurrentTick ); 614 615 break; 616 617 case weasel.FormatProTrackerMK.Effects.ExtendedCommands : 618 619 // Process Extended Commands that work on non tick zero state. 620 // 621 var iExtendedEffectParameter = oChannel.getEffectParameter() & 0xf; 622 switch( oChannel.getEffectParameter() & 0xf0 ) 623 { 624 case weasel.FormatProTrackerMK.Effects.RetriggerNote: 625 // Re-trigger Note can also occur on Tick 0. 626 // 627 if( oChannel.retriggerNote( this.getCurrentTick(), iExtendedEffectParameter, this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ) ) 628 { 629 this.fWaitForDMAMultiplier += 2.0; // Each E9x command causes a software pause which may effect subsequent channels (if used). 630 } 631 632 break; 633 634 case weasel.FormatProTrackerMK.Effects.NoteCut: 635 // Note Cut can also occur on Tick 0. 636 // 637 if( this.getCurrentTick() == iExtendedEffectParameter ) 638 { 639 oChannel.setVolume( 0 ); 640 } 641 break; 642 643 case weasel.FormatProTrackerMK.Effects.NoteDelay: 644 if( this.getCurrentTick() == iExtendedEffectParameter && oChannel.getDelayedNotePeriod() != 0 ) 645 { 646 var oPatternCell = oChannel.getCurrentPatternCell(); 647 648 // Only apply Note Delay Command IF there is actually a new note.. 649 // 650 if( oPatternCell != null && 0 != oPatternCell.getNotePeriod() ) 651 { 652 this.startPendingSample( oChannel, oChannel.getDelayedNotePeriod(), this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ); 653 654 this.fWaitForDMAMultiplier += 2.0; // Each EDx command causes a software pause which may effect subsequent channels (if used). 655 } 656 } 657 break; 658 } 659 break; 660 661 default : 662 // Restore the note period when a Effect Command 663 // that is not processed on a non tick 0 occurs. Such as 664 // Set Volume Command. 665 // Commands 7, 8, 9, 10, 11, 12, 13, 15 restore the 666 // period value. This value is taken from the Shadow Note Period 667 // as the pitch bend/Portamento period value is maintained. 668 // 669 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 670 break; 671 } 672 }; 673 674 //--------------------------------------------------------------------------- 675 /** Process arpeggio for Protracker. 676 * 677 * @param {weasel.Channel} oChannel = The Channel to process. 678 * 679 * @protected 680 * @override 681 */ 682 weasel.ProTrackerMK.prototype._arpeggio = function( oChannel ) 683 { 684 if( oChannel.getEffectParameter() == 0 ) 685 { 686 // Restore the (shadow) note period when no effect occurs. 687 // 688 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 689 return; 690 } 691 692 oChannel.arpeggio( this.iCurrentTick, weasel.Channel.prototype.ArpeggioMode.Protracker ); 693 }; 694 695 696 // --------------------------------------------------------------------------- 697 /** Process a channel's effects that occur on Tick Zero. 698 * 699 * @param {weasel.Channel} oChannel = The Channel to process for effects. 700 * 701 * @protected 702 * @override 703 */ 704 weasel.ProTrackerMK.prototype._processChannelTick0Effect = function( oChannel ) 705 { 706 var iEffectParameter = oChannel.getEffectParameter(); 707 708 switch( oChannel.getEffectNumber() ) 709 { 710 case weasel.FormatProTrackerMK.Effects.SetSampleOffset: 711 // Set Sample Offset is in fact called twice in Protracker, Once before 712 // the sample is played and once after. 713 // This deals with changing the offset AFTER the sample has start, so 714 // that although it appears that it has no affect, it will affect 715 // subsequent uses (such as 900). 716 // 717 // Protracker 3 series does not apply Sample Offset AFTER sample has started. 718 // 719 if( this.bProtracker3SampleOffsetMode ) 720 { 721 break; 722 } 723 724 var iSampleOffset = iEffectParameter; 725 if( 0 == iSampleOffset) 726 { 727 iSampleOffset = oChannel.getLastSampleOffsetParameter(); 728 } 729 else 730 { 731 // Save it for reuse when 9-00 is used. 732 // 733 oChannel.setLastSampleOffsetParameter( iSampleOffset ); 734 } 735 736 oChannel.setPlayFromOffset( oChannel.playFromOffset() + (iSampleOffset << 8) ); 737 break; 738 739 case weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump : 740 741 // If Pattern Break command occurs in any other channel, this command 742 // turns it into a Sequence Position Jump command instead. 743 // 744 var iSequenceJump = iEffectParameter; 745 746 this.bPatternBreak = true; 747 this.iPatternBreakToRow = 0; 748 this.iSequencePositionJump = iSequenceJump; 749 break; 750 751 case weasel.FormatDOCSoundTracker9.Effects.Volume : 752 753 var iVolume = iEffectParameter; 754 755 if( iVolume > 64 ) 756 { 757 iVolume = 64; 758 } 759 760 oChannel.setVolume( iVolume ); 761 762 break; 763 764 case weasel.FormatDOCSoundTracker22.Effects.PatternBreak : 765 766 // Pattern break. 767 // 768 var iRowJump = ((iEffectParameter >>> 4) * 10) + (iEffectParameter & 0xf ); 769 770 if( iRowJump > 63 ) 771 { 772 iRowJump = 0; 773 } 774 775 this.bPatternBreak = true; 776 this.iPatternBreakToRow = iRowJump; 777 break; 778 779 case weasel.FormatProTrackerMK.Effects.ExtendedCommands : 780 781 this._processChannelTick0ExtendedEffect( oChannel ); 782 break; 783 784 case weasel.FormatDOCSoundTracker9.Effects.TickSpeed : 785 786 if( this.getTimingOverride() == this.TimingOverrides.UseBPM ) 787 { 788 // BPM/CIA replay mode. 789 // 790 if( iEffectParameter < 32 ) 791 { 792 // Change Tick Speed. 793 // 794 this.setTickSpeed( iEffectParameter ); 795 } 796 else 797 { 798 // Change tempo/BPM speed. 799 // 800 this.setSongSpeed( iEffectParameter ); 801 } 802 } 803 else 804 { 805 // VBL replay mode. 806 // There is an undocumented side of Protracker in VBL mode 807 // which allows setting the Tick Speed in the range 1-255! 808 // 0 is ignored. 809 if( iEffectParameter > 0 ) 810 { 811 // Change Tick Speed to 1-255 range. 812 // 813 this.setTickSpeed( iEffectParameter ); 814 } 815 } 816 break; 817 818 default : 819 // Restore the (shadow) note period for all other effects which are not processed on this tick. 820 // 821 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 822 break; 823 } 824 }; 825 826 // --------------------------------------------------------------------------- 827 /** Process a channel's Extended Effect Commands that occur on Tick Zero. 828 * 829 * @param {weasel.Channel} oChannel = The Channel to process for effects. 830 * 831 * @protected 832 */ 833 weasel.ProTrackerMK.prototype._processChannelTick0ExtendedEffect = function( oChannel ) 834 { 835 836 var iExtendedEffectParameter = oChannel.getEffectParameter() & 0xf; 837 838 switch( oChannel.getEffectParameter() & 0xf0 ) 839 { 840 case weasel.FormatProTrackerMK.Effects.Filter: 841 var iFilter = iExtendedEffectParameter & 1; 842 843 this.bFilterOn = iFilter == 0; // Filter On = 0 (Amiga Power LED On), Filter Off = 1 (Amiga Power LED Off). 844 break; 845 846 case weasel.FormatProTrackerMK.Effects.FineSlideUp: 847 oChannel.pitchBend( 1, 0, iExtendedEffectParameter ); 848 oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) ); 849 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 850 break; 851 852 case weasel.FormatProTrackerMK.Effects.FineSlideDown: 853 oChannel.pitchBend( 1, iExtendedEffectParameter, 0 ); 854 oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) ); 855 oChannel.setNotePeriod( oChannel.getShadowNotePeriod() ); 856 break; 857 858 case weasel.FormatProTrackerMK.Effects.GlissandoControl: 859 oChannel.setGlissando( iExtendedEffectParameter != 0 ); 860 break; 861 862 case weasel.FormatProTrackerMK.Effects.SetVibratoWaveform: 863 var bContinueVibratoWaveform = (iExtendedEffectParameter & 0x4) != 0 ? true : false; 864 var iVibratoWaveformType = 0; 865 866 switch( iExtendedEffectParameter & 0x3 ) 867 { 868 case weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave: 869 iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave; 870 break; 871 872 case weasel.Channel.prototype.ProtrackerVibratoWaveform.RampDownSawTooth: 873 iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.RampDownSawTooth; 874 break; 875 876 default: 877 iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.Square; 878 break; 879 880 } 881 882 oChannel.setProtrackerContinueVibratoWaveform( bContinueVibratoWaveform ); 883 oChannel.setProtrackerVibratoWaveformType( iVibratoWaveformType ); 884 break; 885 886 case weasel.FormatProTrackerMK.Effects.PatternLoop: 887 if( 0 == iExtendedEffectParameter ) 888 { 889 // Set the pattern start loop point. 890 // 891 oChannel.setPatternRowLoopStart( this.getCurrentPatternRowPosition() ); 892 } 893 else 894 { 895 if( oChannel.getPatternRowLoopCounter() <= 0 ) 896 { 897 oChannel.setPatternRowLoopCounter( iExtendedEffectParameter ); 898 } 899 else 900 { 901 oChannel.decPatternRowLoopCounter(); 902 903 if( oChannel.getPatternRowLoopCounter() <= 0 ) 904 { 905 // Pattern Row Loop ended, continue pattern as normal. 906 // 907 908 return; 909 } 910 } 911 912 // Loop to row in current pattern. 913 // 914 this.bPatternBreak = true; 915 this.iPatternBreakToRow = oChannel.getPatternRowLoopStart(); 916 this.iSequencePositionJump = this.getCurrentSequencePosition(); 917 } 918 break; 919 920 case weasel.FormatProTrackerMK.Effects.SetTremoloWaveform: 921 var bContinueTremoloWaveform = (iExtendedEffectParameter & 0x4) != 0 ? true : false; 922 var iTremoloWaveformType = 0; 923 924 switch( iExtendedEffectParameter & 0x3 ) 925 { 926 case weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave: 927 iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave; 928 break; 929 930 case weasel.Channel.prototype.ProtrackerTremoloWaveform.RampDownSawTooth: 931 iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.RampDownSawTooth; 932 break; 933 934 default: 935 iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.Square; 936 break; 937 938 } 939 940 oChannel.setProtrackerContinueTremoloWaveform( bContinueTremoloWaveform ); 941 oChannel.setProtrackerTremoloWaveformType( iTremoloWaveformType ); 942 break; 943 944 case weasel.FormatProTrackerMK.Effects.RetriggerNote: 945 // Re-trigger Note can also occur on Tick 0, but only if there is NO note! 946 // 947 if( 0 == oChannel.getCurrentPatternCell().getNotePeriod() ) 948 { 949 if( oChannel.retriggerNote( 0, iExtendedEffectParameter, this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ) ) 950 { 951 this.fWaitForDMAMultiplier += 2.0; // Each E9x command causes a software pause which may effect subsequent channels (if used). 952 953 } 954 } 955 break; 956 957 case weasel.FormatProTrackerMK.Effects.FineVolumeSlideUp: 958 var iVolume = oChannel.getShadowVolume()+ iExtendedEffectParameter; 959 960 if( iVolume > 64 ) 961 { 962 iVolume = 64; 963 } 964 oChannel.setVolume( iVolume ); 965 break; 966 967 case weasel.FormatProTrackerMK.Effects.FineVolumeSlideDown: 968 var iVolume = oChannel.getShadowVolume() - iExtendedEffectParameter; 969 970 if( iVolume < 0 ) 971 { 972 iVolume = 0; 973 } 974 oChannel.setVolume( iVolume ); 975 break; 976 977 case weasel.FormatProTrackerMK.Effects.NoteCut: 978 // Note Cut can also occur on Tick 0. 979 // 980 if( this.getCurrentTick() == iExtendedEffectParameter ) 981 { 982 oChannel.setVolume( 0 ); 983 } 984 break; 985 986 case weasel.FormatProTrackerMK.Effects.PatternDelay: 987 if( this.iRowDelay == 0 ) 988 { 989 this.bProtrackerRowDelayQuirk = true; 990 this.iRowDelay = iExtendedEffectParameter + 1; 991 } 992 break; 993 994 case weasel.FormatProTrackerMK.Effects.InvertLoop: 995 996 oChannel.setProtrackerInvertLoopSpeed( iExtendedEffectParameter ); 997 998 // Notice that on Tick Zero updateProtrackerInvertLoop() can get 999 // called twice IF the Extended Command is present in the current cell. 1000 // BUT on further inspection it transpires that the Protracker (2.3a) 1001 // Editor does not do this. There appears to be a difference between 1002 // the Editor code and the Stand Alone Replay source code, with the 1003 // Stand Alone Replay code calls updateProtrackerInvertLoop() twice. 1004 // So decided to go with the Editor. 1005 // 1006 oChannel.updateProtrackerInvertLoop(); 1007 break; 1008 1009 } 1010 }; 1011 // --------------------------------------------------------------------------- 1012 /** Some Soundtrackers have effects that need processing out of the normal order, 1013 * such as the Note Portamento command in Noisetracker, Note Delay in Protracker. 1014 * 1015 * @param {weasel.Channel} oChannel = The Channel to process for effects. 1016 * @param {int} iNotePeriod = The note period yet to be set for this oChannel object. 1017 * 1018 * @return {bool} = false : the fetch row process should continue for this oChannel object, 1019 * true : stop the fetch row process for this oChannel object but continue for the other channels.. 1020 * 1021 * @protected 1022 * @override 1023 */ 1024 weasel.ProTrackerMK.prototype._exceptionPreprocessEffects = function( oChannel, iNotePeriod ) 1025 { 1026 var iEffectNumber = oChannel.getEffectNumber(); 1027 var iEffectParameter = oChannel.getEffectParameter(); 1028 1029 switch( iEffectNumber ) 1030 { 1031 case weasel.FormatNoiseTracker11.Effects.TonePortamento : 1032 // Command Fall through! 1033 // 1034 case weasel.FormatNoiseTracker20.Effects.TonePortamentoAndVolumeSlide : 1035 oChannel.setNotePortamentoTarget( iNotePeriod ); 1036 return true; 1037 1038 case weasel.FormatProTrackerMK.Effects.SetSampleOffset: 1039 // Set Sample Offset is in fact called twice in Protracker, Once before 1040 // the sample is played and once after. 1041 // This deals with changing the offset BEFORE the sample has start. 1042 // Protracker 3 only adds the Set Sample Offset to the starting address 1043 // of the sample once BEFORE the sample is played, it does not update the 1044 // sample starting address again (unlike Protracker 2). 1045 // 1046 1047 var iSampleOffset = iEffectParameter; 1048 if( 0 == iSampleOffset) 1049 { 1050 iSampleOffset = oChannel.getLastSampleOffsetParameter(); 1051 } 1052 else 1053 { 1054 // Save it for reuse when 9-00 is used. 1055 // 1056 oChannel.setLastSampleOffsetParameter( iSampleOffset ); 1057 } 1058 1059 if( this.bProtracker3SampleOffsetMode ) 1060 { 1061 iSampleOffset = (iSampleOffset << 8); 1062 } 1063 else 1064 { 1065 iSampleOffset = oChannel.playFromOffset() + (iSampleOffset << 8); 1066 } 1067 1068 oChannel.setPlayFromOffset( iSampleOffset ); 1069 1070 return false; 1071 1072 case weasel.FormatProTrackerMK.Effects.ExtendedCommands : 1073 1074 // Process Extended Commands. 1075 // 1076 var iExtendedEffectParameter = oChannel.getEffectParameter() & 0xf; 1077 var iExtendedEffectCommand = oChannel.getEffectParameter() & 0xf0; 1078 1079 if( weasel.FormatProTrackerMK.Effects.SetFineTune == iExtendedEffectCommand ) 1080 { 1081 oChannel.setFineTune( iExtendedEffectParameter ); 1082 return false; 1083 } 1084 else if(weasel.FormatProTrackerMK.Effects.NoteDelay == iExtendedEffectCommand ) 1085 { 1086 oChannel.setDelayedNotePeriod( iNotePeriod ); 1087 1088 if( this.getCurrentTick() == iExtendedEffectParameter && iNotePeriod != 0 ) 1089 { 1090 // Delayed note is for this tick, which is tick 0 so play note. 1091 // 1092 this.startPendingSample( oChannel, oChannel.getDelayedNotePeriod(), this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ); 1093 1094 this.fWaitForDMAMultiplier += 2.0; // Each EDx command causes a software pause which may effect subsequent channels (if used). 1095 } 1096 1097 // Note is delayed. 1098 // 1099 return true; 1100 } 1101 1102 break; 1103 1104 default: 1105 break; 1106 } 1107 1108 return false; 1109 }; 1110 1111 // --------------------------------------------------------------------------- 1112 /** Protracker allows fining tuning of its samples, Ultimate Soundtracker and others 1113 * do not. 1114 * 1115 * @param {weasel.Channel} oChannel = The channel object to start its pending sample. 1116 * @param {int} iNotePeriod = The note period yet to be set for this oChannel object. 1117 * 1118 * @return {int} = The note period corrected for fine tuning. 1119 * 1120 * @protected 1121 * @override 1122 */ 1123 weasel.ProTrackerMK.prototype._fineTune = function( oChannel, iNotePeriod ) 1124 { 1125 var iFineTune = oChannel.getFineTune(); 1126 1127 if( 0 == iFineTune ) 1128 { 1129 return iNotePeriod; 1130 } 1131 1132 // Clamp Fine Tuning, just in case. 1133 // 1134 if( iFineTune < 0 ) 1135 { 1136 iFineTune = 0; 1137 } 1138 else if( iFineTune > 15 ) 1139 { 1140 iFineTune = 15; 1141 } 1142 1143 var aFineTuneTables = weasel.FormatProTrackerMK.FineTunePeriodTables; 1144 var iTableLength = 36; 1145 var iNote = 0; 1146 1147 for( ; iNote < iTableLength; iNote++ ) 1148 { 1149 if( iNotePeriod >= aFineTuneTables[ iNote ] ) 1150 { 1151 break; 1152 } 1153 } 1154 1155 1156 iNotePeriod = aFineTuneTables[ (iFineTune * 37) + iNote ]; 1157 1158 return iNotePeriod; 1159 }; 1160 1161 // --------------------------------------------------------------------------- 1162 /** Apply the Channel's Protracker pattern cell to the channel, only needed due to 1163 * sample fine tuning being applied. 1164 * 1165 * @param {weasel.Channel} oChannel = The channel object to apply volume to immediately. 1166 * @return {weasel.PatternCell} = The current PatternCell object for the provided channel number. 1167 * 1168 * @protected 1169 * @override 1170 * 1171 * TODO Possible refactor, this function is similar to NoiseTracker11._applyPatternCell() can they be merged? 1172 */ 1173 weasel.ProTrackerMK.prototype._applyPatternCell = function( oChannel, oPatternCell ) 1174 { 1175 // Always set effect command and effect parameter. 1176 // 1177 var iEffectNumber = oPatternCell.getEffectNumber(); 1178 var iEffectParameter = oPatternCell.getEffectParameter(); 1179 var iInstrumentNumber = oPatternCell.getInstrumentNumber(); 1180 1181 oChannel.setEffectNumber( iEffectNumber ); 1182 oChannel.setEffectParameter( iEffectParameter ); 1183 oChannel.setCurrentPatternCell( oPatternCell ); 1184 // Mark Pattern row as visited for song end/song loop detection. 1185 // 1186 this.aVisitedSequence[ this.getCurrentSequencePosition() ][ this.getCurrentPatternRowPosition() ] = true; 1187 1188 // Only change instrument number if set in pattern. 1189 // 1190 if( iInstrumentNumber > 0 ) 1191 { 1192 var oInstrument = this.getInstrument( iInstrumentNumber ); 1193 1194 // Allow for Noisetracker sample loop chaining. 1195 // 1196 oChannel.setPendingInstrumentNumber( iInstrumentNumber, oInstrument ); 1197 1198 // Volume change is applied immediately even if there is no new note. 1199 // 1200 this._applyVolumeImmediately( oChannel ); 1201 1202 // Set Sample Offset command needs resetting, but only if there is a instrument number. 1203 // 1204 oChannel.setPlayFromOffset( 0 ); 1205 1206 // Protracker set pending instruments fine tuning, this value may be overridden 1207 // immediately by the Set Fine Tune Extended Effect Command. 1208 // 1209 oChannel.setFineTune( oInstrument.getFineTuning() ); 1210 } 1211 1212 var iNotePeriod = oPatternCell.getNotePeriod(); 1213 1214 // Only change note if set in pattern. 1215 // 1216 if( 0 != iNotePeriod ) 1217 { 1218 iNotePeriod = this._fineTune( oChannel, iNotePeriod ); 1219 1220 if( this._exceptionPreprocessEffects( oChannel, iNotePeriod ) ) 1221 { 1222 return; 1223 } 1224 1225 // Have to re-apply Fine Tune just in case the Set Fine Tune Effect Command 1226 // is used, grrr. 1227 // 1228 if( weasel.FormatProTrackerMK.Effects.ExtendedCommands == iEffectNumber && weasel.FormatProTrackerMK.Effects.SetFineTune == (iEffectParameter & 0xf0) ) 1229 { 1230 iNotePeriod = this._fineTune( oChannel, oPatternCell.getNotePeriod() ); 1231 } 1232 1233 // Protracker Vibrato wave control determines if the Vibrato is reset 1234 // upon a new note or not. 1235 // 1236 if( !oChannel.getProtrackerContinueVibratoWaveform() ) 1237 { 1238 oChannel.setVibratoTablePosition( 0 ); 1239 } 1240 1241 // Protracker Tremolo wave control determines if the Tremolo is reset 1242 // upon a new note or not. 1243 // 1244 if( !oChannel.getProtrackerContinueTremoloWaveform() ) 1245 { 1246 oChannel.setTremoloTablePosition( 0 ); 1247 } 1248 1249 // If period value is already too low (like 2604hz) then do not change the instrument. 1250 // Due to the Amiga DMA not being given enough time to stop, so it does not play the new sample (continues with old sample). 1251 // 1252 if( oChannel.getNotePeriod() >= 1374 && this.bDontStopDMAQuirk ) 1253 { 1254 oChannel.setNotePeriod( iNotePeriod ); 1255 oChannel.setLastSavedNotePeriod( iNotePeriod ); 1256 oChannel.setShadowNotePeriod( iNotePeriod ); 1257 1258 return; 1259 } 1260 1261 this.startPendingSample( oChannel, iNotePeriod, this.WaitForDMAToStop() * this.fWaitForDMAMultiplier ); 1262 oChannel.setNoNoteDelayQuirk( true ); 1263 } 1264 }; 1265 1266 // --------------------------------------------------------------------------- 1267 /** 1268 * Song Sequence Position changed, due to Pattern Row Loop command handle with care!. 1269 * 1270 * @protected 1271 * @override 1272 */ 1273 weasel.ProTrackerMK.prototype._songSequenceChange = function( ) 1274 { 1275 for( var iChannel = this.aSoundChannels.length; --iChannel >= 0; ) 1276 { 1277 // Check to see if the Extended Command Pattern Loop is in use, 1278 // if so ignore song sequence change. 1279 // 1280 if( this.aSoundChannels[ iChannel ].getPatternRowLoopCounter() > 0 ) 1281 { 1282 // Ignore song sequence change. 1283 // 1284 return; 1285 } 1286 } 1287 1288 var iCurrentSequencePosition = this.getCurrentSequencePosition(); 1289 var iCurrentRow = this.getCurrentPatternRowPosition(); 1290 1291 if( true == this.aVisitedSequence[ iCurrentSequencePosition ][ iCurrentRow ] ) 1292 { 1293 this.bSongEnded = true; 1294 } 1295 1296 this.aVisitedSequence[ iCurrentSequencePosition ][ iCurrentRow ] = true; 1297 this.iTotalPatternTicks = 0; 1298 }; 1299 1300 // --------------------------------------------------------------------------- 1301 /** Clear the Sequence Position Tracking table as Protracker modules need 1302 * their Sequence Positions and ROW tracked to find out if a song has looped. 1303 * 1304 * @override 1305 */ 1306 weasel.ProTrackerMK.prototype.clearVisitedSequenceTable = function( ) 1307 { 1308 if( undefined == this.aVisitedSequence[ 0 ] ) 1309 { 1310 return; 1311 } 1312 1313 for( var aVisited = this.aVisitedSequence, iSequence = aVisited.length; --iSequence >= 0; ) 1314 { 1315 for( var aRows = aVisited[ iSequence ], iRow = aRows.length; --iRow >= 0; ) 1316 { 1317 aRows[ iRow ] = false; 1318 } 1319 } 1320 }; 1321 1322 // --------------------------------------------------------------------------- 1323 /** 1324 * Set Clock Constant to BPM, PAL or NTSC Protracker has a wrapper as it needs 1325 * to recalculate the duration of the module (Protracker has a VBL mode which 1326 * allows a Tick Speed of 1-255 where as in CIA mode its 1-31). 1327 * 1328 * @param {weasel.UltimateSoundTracker121.TimingOverrides} iBPMPALNTSC = Use the BPM set in mod | PAL 50hz | NTSC ~59.94hz (actually 60/1.001). 1329 * 1330 * @override 1331 */ 1332 weasel.ProTrackerMK.prototype.timingOverride = function( iBPMPALNTSC ) 1333 { 1334 var iOldTiming = this.getTimingOverride(); 1335 // Call normal function on parent. 1336 // 1337 this.parent.prototype.timingOverride.call( this, iBPMPALNTSC ); 1338 1339 var iNewTiming = this.getTimingOverride(); 1340 1341 if( (iOldTiming != iNewTiming) && ( iOldTiming == this.TimingOverrides.UseBPM || iNewTiming == this.TimingOverrides.UseBPM ) ) 1342 { 1343 // Recalculate song play time :( 1344 // 1345 this._computeSequenceTableTicks(); 1346 } 1347 }; 1348 1349 1350 // --------------------------------------------------------------------------- 1351 /** 1352 * Get the constant used for waiting for the DMA to stop in milliseconds used in 1353 * Protracker which is the same as Noisetracker 1.1. The Protracker 3 Editor 1354 * does a very good job of eliminating this delay (although the stand alone replay 1355 * is different). 1356 * 1357 * @return {float} = The millisecond pause used by this module type. 1358 * 1359 * @override 1360 */ 1361 weasel.ProTrackerMK.prototype.WaitForDMAToStop = function() 1362 { 1363 if( this.bProtracker3SampleOffsetMode ) 1364 { 1365 // No pause in Protracker 3 mode. 1366 // 1367 return 0.0; 1368 } 1369 1370 return weasel.FormatNoiseTracker11.WaitForDMAToStop; 1371 };