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 };