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 /** Object to contain all the details needed for creating audio data associated 
  9  * with a soundtracker channel.
 10  *  
 11  * @constructor
 12  * @param {weasel.UltimateSoundTracker121|weasel.UltimateSoundTracker18|weasel.DOCSoundTracker9|weasel.DOCSoundTracker22|weasel.TJCSoundTracker2|weasel.DefJamSoundTracker3|weasel.SpreadpointSoundTracker23|weasel.SpreadpointSoundTracker25|weasel.NoiseTracker11|weasel.NoiseTracker20|weasel.ProTrackerMK|weasel.FSTModule} oModule = The current module.
 13  * @param {int} iPlaybackFrequency = The playback frequency in hertz to use.
 14  * @param {float} fMasterVolume = The master volume for this channel (0.0 to 1.0 range)..
 15  * 
 16  * @author Warren Willmey 2011
 17  */
 18 weasel.Channel = function( oModule, iPlaybackFrequency, fMasterVolume )
 19 {
 20 	this.iCurrentNotePeriod = 0;
 21 	this.iCurrentInstrument = 0;
 22 	this.iCurrentEffect = 0;
 23 	this.iCurrentEffectParameter = 0;
 24 	this.iCurrentVolume = 0;
 25 	this.iShadowVolume = 0;
 26 	this.fMasterVolume = 1.0; 
 27 	this.setMasterVolume( fMasterVolume );
 28 	this.iLastSavedNotePeriod = 0;
 29 	this.iShadowNotePeriod = 0;			// Soundtrackers prior to Noisetracker 1.0/1.1 had a separate Note Period used for pitchbends.
 30 	this.fCurrentSamplePosition = 0.0;
 31 	this.iPendingInstrument = 0;
 32 	this.oPendingInstrument = null;
 33 	this.iDelayedNotePeriod = 0;			// Used for Protracker Delay Note command.
 34 	this.iLastSampleOffsetParameter = 0;	// Protracker Sample Offset Command.
 35 	this.iSampleStartOffset = 0;			// Protracker Sample Offset Command.
 36 	this.iFineTune = 0;						// Protracker Fine Tuning of samples.
 37 	this.iPatternRowLoopStart = 0;			// Protracker Pattern Loop Command.
 38 	this.iPatternRowLoopCounter = 0;		// Protracker Pattern Loop Command.
 39 	this.iPlaybackFrequency = iPlaybackFrequency;
 40 	this.fCurrentFrequencyStep = 0.0;
 41 	this.bDelaySampleStart = true;
 42 	this.iDelayInSamples = 0;
 43 	this.bNoNoteDelayQuirk = false;			// Protracker ED0 and E9x commands cause normal channels to delay by the Wait for DMA software pause. 
 44 	this.bUsePALClockConstant = true;
 45 	this.oModule = oModule;
 46 
 47 	this.aCircularAudioBuffer = weasel.Helper.getFloat32Array( iPlaybackFrequency );
 48 	for( var iLength = this.aCircularAudioBuffer.length, aCircularAudioBuffer = this.aCircularAudioBuffer; --iLength >= 0; )
 49 		aCircularAudioBuffer[ iLength ] = 0.0;
 50 
 51 	this.iCircularBufferPosition = 0;
 52 	this.fSampleAccumulator = 0.0;
 53 this.fSampleAccumulator1 = 0.0;
 54 this.fSampleAccumulator2 = 0.0;
 55 this.fSampleAccumulator3 = 0.0;
 56 this.fSampleAccumulator4 = 0.0;
 57 	this.oSample = null;
 58 	this.iChannelInterpolation = 0;
 59 	
 60 	this.bMute = false;
 61 	
 62 	this.bAutoSlide = false;			// Jungle Command Soundtracker 2 command.
 63 	this.iAutoSlideValue = 0;			// Jungle Command Soundtracker 2 command.
 64 	this.iNotePortamentoTargetPeriod=0;	// Noisetracker 1.0/1.1 command.
 65 	this.iNotePoramentoSpeed = 0;		// Noisetracker 1.0/1.1 command.
 66 	this.iVibratoTablePosition = 0;		// Noisetracker 1.0/1.1 command.
 67 	this.iLastVibratoParameter = 0;		// Noisetracker 1.0/1.1 command.
 68 	this.bVibratoContinueWaveform = false;	// Protracker Vibrato wave control parameter.
 69 	this.iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave;	// Protracker Vibrato waveform type.
 70 	this.iLastTremoloParameter = 0;		// Protracker Tremolo command parameter, updates nibbles separately.
 71 	this.iTremoloTablePosition = 0;		// Protracker Tremolo command.
 72 	this.bTremoloContinueWaveform = false;	// Protracker Tremolo wave control parameter.
 73 	this.iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave;	// Protracker Tremolo waveform type.
 74 	this.iInvertLoopSpeed = 0;				// Protracker Invert Loop control parameter.
 75 	this.iInvertLoopTimeDelay = 0;			// Protracker Invert Loop time delay.
 76 	this.iInvertLoopSampleOffset = 0;		// Protracker Invert Loop byte to modify within sample loop.
 77 	this.iInvertLoopSampleLoopStart = 0;	// Protracker Invert Loop keep tracker of the original loop starting point.
 78 	this.bGlissandoMode = false;			// Protracker Glissando Mode for Toneportamento command.
 79 	this.iPanningPosition = 128;			// Taketracker/Fasttracker Panning Position command 8.
 80 	this.oPatternCell = null;
 81 };
 82 
 83 
 84 // ---------------------------------------------------------------------------
 85 /** Supported interpolation methods (that may change in future), also used for 
 86  * display names where '$' is converted to '-' and '_' becomes ' '.
 87  * @enum
 88  * @type {string}
 89  * @const
 90  */
 91 weasel.Channel.prototype.SupportedInterpolationTypes = {
 92 	  None : 0
 93 	, RLM$D_Alias_Reduction : 1
 94 	, Linear_Interpolation : 2
 95 	, Cubic_Spline_4_Point_Interpolation : 3
 96 	, Catmull$Rom_Spline_Interpolation : 4
 97 	, Spline_6_Point_Interpolation : 5
 98 };
 99 
100 // ---------------------------------------------------------------------------
101 /** Supported arpeggio modes (different trackers behave slightly differently).
102  * @enum
103  * @type {int}
104  * @const
105  */
106 weasel.Channel.prototype.ArpeggioMode = {
107 	  UltimateSoundtracker	: 0
108 	, Noisetracker			: 1
109 	, Protracker			: 2
110 	, FSTModule				: 3
111 };
112 
113 // ---------------------------------------------------------------------------
114 /** Supported Protracker Vibrato waveforms, note that Random does not actually 
115  * exist, its defined in the help docs (see PT 2.3a) but not in the code and just
116  * results in a Square wave being used.
117  * @enum
118  * @type {int}
119  * @const
120  */
121 weasel.Channel.prototype.ProtrackerVibratoWaveform = {
122 	  SineWave			: 0
123 	, RampDownSawTooth	: 1
124 	, Square			: 2
125 	, Random			: 3
126 };
127 
128 // ---------------------------------------------------------------------------
129 /** Supported Protracker Tremolo waveforms, note that Random does not actually 
130  * exist, its defined in the help docs (see PT 2.3a) but not in the code and just
131  * results in a Square wave being used.
132  * @enum
133  * @type {int}
134  * @const
135  */
136 weasel.Channel.prototype.ProtrackerTremoloWaveform = {
137 	  SineWave			: 0
138 	, RampDownSawTooth	: 1
139 	, Square			: 2
140 	, Random			: 3
141 };
142 
143 // ---------------------------------------------------------------------------
144 /**
145  * Set new output play back frequency, which requires the resizing of various buffers.
146  * 
147  * @param {int} iPlaybackFrequency = new replay play back frequency.
148  * 
149  */
150 weasel.Channel.prototype.changePlaybackFrequency = function( iPlaybackFrequency )
151 {
152 	var aNewCircularBuffer = weasel.Helper.getFloat32Array( iPlaybackFrequency );
153 	for( var iLength = aNewCircularBuffer.length; --iLength >= 0; )
154 		aNewCircularBuffer[ iLength ] = 0.0;
155 
156 	this.iPlaybackFrequency = iPlaybackFrequency;
157 	this.setNotePeriod( this.getNotePeriod() );
158 	this.aCircularAudioBuffer = aNewCircularBuffer;
159 	this.setCircularBufferPosition( 0 );
160 };
161 
162 
163 // ---------------------------------------------------------------------------
164 /**
165  * Set the Sample Object for this channel.
166  * 
167  * @param {weasel.Sample} oSample = The Sample object to use.
168  */
169 weasel.Channel.prototype.setSample = function( oSample )
170 {
171 	this.oSample = oSample;
172 };
173 
174 // ---------------------------------------------------------------------------
175 /**
176  * Get the Sample Object for this channel.
177  * 
178  * @return {weasel.Sample} = The Sample object to use.
179  */
180 weasel.Channel.prototype.getSample = function()
181 {
182 	return this.oSample;
183 };
184 
185 // ---------------------------------------------------------------------------
186 /**
187  * Indicate that the current sample should not start immediately, and should be
188  * delayed by 0.768ms (12 scanlines) as this is what Karstens' Amiga replay routines does,
189  * the delay is actually to allow the Amiga DMA to stop playing its current sample.
190  * 
191  * @param {bool} bDelay = true : delay the sample.
192  * @param {float} fDelayInMs = The delay, in milliseconds, to use.
193  */
194 weasel.Channel.prototype.delaySampleStart = function( bDelay, fDelayInMs )
195 {
196 	this.bDelaySampleStart = bDelay == true? true : false;
197 
198 	if( fDelayInMs <= 0.0 )
199 	{
200 		// Don't use delay.
201 		//
202 		this.iDelayInSamples = 0;
203 	}
204 	else
205 	{
206 		this.iDelayInSamples = (this.iPlaybackFrequency / ( 1000.0 / fDelayInMs))|0;
207 	}
208 };
209 
210 // ---------------------------------------------------------------------------
211 /**
212  * Get the current state of the delayed sample start.
213  * 
214  * @return {bool} = The delayed start.
215  */
216 weasel.Channel.prototype.getDelaySampleStart = function()
217 {
218 	return this.bDelaySampleStart;
219 };
220 
221 
222 // ---------------------------------------------------------------------------
223 /**
224  * Get list of supported interpolation type, used for populating a drop down list for user selection.
225  * 
226  * @return {weasel.Channel.prototype.SupportedInterpolationTypes} = The list of supported interpolation types.
227  */
228 weasel.Channel.prototype.getSupportedInterpolationTypes = function()
229 {
230 	return weasel.Channel.prototype.SupportedInterpolationTypes;
231 };
232 
233 // ---------------------------------------------------------------------------
234 /**
235  * get the note period for this Channel object.
236  * 
237  * @return {int} = The current note period.
238  */
239 weasel.Channel.prototype.getNotePeriod = function()
240 {
241 	return this.iCurrentNotePeriod;
242 };
243 
244 
245 // ---------------------------------------------------------------------------
246 /**
247  * get the NTSC or PAL Amiga Clock Constant.
248  * 
249  * @return {int} = The Amiga Clock Constant.
250  */
251 weasel.Channel.prototype.getClockConstant = function()
252 {
253 	return  this.bUsePALClockConstant == true ? weasel.FormatUltimateSoundTracker121.ClockConstantPAL : weasel.FormatUltimateSoundTracker121.ClockConstantNTSC;
254 };
255 
256 // ---------------------------------------------------------------------------
257 /**
258  * get the NTSC or PAL Amiga Clock Constant in human readable form.
259  * 
260  * @return string = The Amiga Clock Constant in human readable form e.g. 'PAL' or 'NTSC'.
261  */
262 weasel.Channel.prototype.getClockConstantType = function()
263 {
264 	return  this.bUsePALClockConstant == true ? 'PAL' : 'NTSC';
265 };
266 
267 
268 // ---------------------------------------------------------------------------
269 /**
270  * Set Clock Constant to either PAL or NTSC.
271  * 
272  * @param bool bClockConstant = true : PAL, false : NTSC.
273  */
274 weasel.Channel.prototype.setClockConstant = function( bClockConstant )
275 {
276 	this.bUsePALClockConstant = bClockConstant == true ? true : false;
277 	
278 	// Reset the current note period now or Clock Constant wont have effect 
279 	// until new note is played!
280 	//
281 	this.setNotePeriod( this.getNotePeriod() );
282 };
283 
284 // ---------------------------------------------------------------------------
285 /**
286  * Set the note period for this Channel object.
287  * 
288  * @param {int} iNotePeriod = The note period.
289  */
290 weasel.Channel.prototype.setNotePeriod = function( iNotePeriod )
291 {
292 	this.iCurrentNotePeriod = iNotePeriod;
293 
294 	if( iNotePeriod  <= 0 || this.iPlaybackFrequency <= 0  )
295 	{
296 		this.fCurrentFrequencyStep = 0.0;
297 		return;
298 	}
299 	this.fCurrentFrequencyStep = this.getClockConstant() / iNotePeriod / this.iPlaybackFrequency;
300 };
301 
302 // ---------------------------------------------------------------------------
303 /**
304  * Get the Frequency Step rate for the current note period at the current Playback Frequency Rate.
305  * 
306  * @return float = The frequency step rate of the current note period at the current Playback Frequency Rate.
307  */
308 weasel.Channel.prototype.getFrequencyStepRate = function()
309 {
310 	return this.fCurrentFrequencyStep;
311 };
312 
313 // ---------------------------------------------------------------------------
314 /**
315  * Get the last saved note period for this Channel object.
316  * 
317  * @return {int} = The last saved note period.
318  */
319 weasel.Channel.prototype.getLastSavedNotePeriod = function()
320 {
321 	return this.iLastSavedNotePeriod;
322 };
323 
324 // ---------------------------------------------------------------------------
325 /**
326  * Set the last saved period for this Channel object.
327  * 
328  * @param {int} iNotePeriod = The note period.
329  */
330 weasel.Channel.prototype.setLastSavedNotePeriod = function( iNotePeriod )
331 {
332 	this.iLastSavedNotePeriod = iNotePeriod;
333 };
334 
335 // ---------------------------------------------------------------------------
336 /**
337  * Get the last saved note period used for Pitch Bend Command for this Channel object.
338  * Soundtrackers prior to Noisetracker 1.0/1.1 used a separate note period for
339  * Pitch Bend Effect Command. Resulting in different note period when switching
340  * between Arpeggio and Pitch Bend.
341  * 
342  * @return {int} = The last saved note period.
343  */
344 weasel.Channel.prototype.getShadowNotePeriod = function()
345 {
346 	return this.iShadowNotePeriod;
347 };
348 
349 // ---------------------------------------------------------------------------
350 /**
351  * Set the last saved period used for Pitch Bend Command for this Channel object.
352  * Soundtrackers prior to Noisetracker 1.0/1.1 used a separate note period for
353  * Pitch Bend Effect Command. Resulting in different note period when switching
354  * between Arpeggio and Pitch Bend.
355  * 
356  * @param {int} iNotePeriod = The note period.
357  */
358 weasel.Channel.prototype.setShadowNotePeriod = function( iNotePeriod )
359 {
360 	this.iShadowNotePeriod = iNotePeriod;
361 };
362 
363 
364 // ---------------------------------------------------------------------------
365 /**
366  * Get the instrument number for this Channel object.
367  * 
368  * @return {int} = The instrument number.
369  */
370 weasel.Channel.prototype.getInstrumentNumber = function()
371 {
372 	return this.iCurrentInstrument;
373 };
374 
375 // ---------------------------------------------------------------------------
376 /**
377  * Set the instrument number for this Channel object.
378  * 
379  * @param {int} iInstrumentNumber = The instrument number.
380  */
381 weasel.Channel.prototype.setInstrumentNumber = function( iInstrumentNumber )
382 {
383 	this.iCurrentInstrument = iInstrumentNumber;
384 };
385 
386 // ---------------------------------------------------------------------------
387 /**
388  * get the pending instrument number for this Channel object, pending in that 
389  * once a new note period fires the instrument will have this number.
390  * 
391  * @return {int} = The pending instrument number.
392  */
393 weasel.Channel.prototype.getPendingInstrumentNumber = function()
394 {
395 	return this.iPendingInstrument;
396 };
397 
398 // ---------------------------------------------------------------------------
399 /**
400  * Set the pending instrument number for this Channel object, pending in that 
401  * once a new note period fires the instrument will have this number, the pending
402  * instrument (oInstrument) is needed for Noisetracker/Soundtracker2.5/Protracker which
403  * inherit the ability to seamlessly chain samples together (when one loop point has
404  * finished it starts playing the sample loop of the pending instrument, without a pause).
405  * 
406  * @param {int} iPendingInstrumentNumber = The pending instrument number.
407  * @param {weasel.Instrument} oInstrument = The pending instrument, which is used for Noisetracker Loop Chaining of samples together.
408  */
409 weasel.Channel.prototype.setPendingInstrumentNumber = function( iPendingInstrumentNumber, oInstrument )
410 {
411 	this.iPendingInstrument = iPendingInstrumentNumber;
412 	this.oPendingInstrument = oInstrument;
413 };
414 
415 // ---------------------------------------------------------------------------
416 /**
417  * Get the Delayed Note Period, used by the Protracker Note Delay command..
418  * 
419  * @return {int} = The note period to be used by the Note Delay command.
420  */
421 weasel.Channel.prototype.getDelayedNotePeriod = function()
422 {
423 	return this.iDelayedNotePeriod;
424 };
425 
426 // ---------------------------------------------------------------------------
427 /**
428  * Set the Delayed Note Period, used by the Protracker Note Delay command.
429  * 
430  * @param {int} iNotePeriod = The note period to be used by the Note Delay command.
431  */
432 weasel.Channel.prototype.setDelayedNotePeriod = function( iNotePeriod )
433 {
434 	this.iDelayedNotePeriod = iNotePeriod;
435 };
436 
437 // ---------------------------------------------------------------------------
438 /**
439  * get the effect number for this Channel object.
440  * 
441  * @return {int} = The effect number.
442  */
443 weasel.Channel.prototype.getEffectNumber = function()
444 {
445 	return this.iCurrentEffect;
446 };
447 
448 // ---------------------------------------------------------------------------
449 /**
450  * Set the effect number for this Channel object.
451  * 
452  * @param {int} iEffectNumber = The effect number.
453  */
454 weasel.Channel.prototype.setEffectNumber = function( iEffectNumber )
455 {
456 	this.iCurrentEffect = iEffectNumber;
457 };
458 
459 // ---------------------------------------------------------------------------
460 /**
461  * get the effect parameter for this Channel object.
462  * 
463  * @return {int} = The effect parameter.
464  */
465 weasel.Channel.prototype.getEffectParameter = function()
466 {
467 	return this.iCurrentEffectParameter;
468 };
469 
470 // ---------------------------------------------------------------------------
471 /**
472  * Set the effect parameter for this Channel object.
473  * 
474  * @param {int} iEffectParameter = The effect parameter.
475  */
476 weasel.Channel.prototype.setEffectParameter = function( iEffectParameter )
477 {
478 	this.iCurrentEffectParameter = iEffectParameter;
479 };
480 
481 
482 // ---------------------------------------------------------------------------
483 /** Set the overriding master volume for this Channel.
484  * 
485  * @param {float} fVolume = The volume range ( 0.0 - 1.0 ) although a range of 
486  * 0.0 to 2.0 is accepted, the volume levels may not be clamped by the mixer causing clicks/pops etc.
487  */
488 weasel.Channel.prototype.setMasterVolume = function( fVolume )
489 {
490 	if( undefined == fVolume )
491 	{
492 		fVolume = 1.0;
493 	}
494 
495 	if( fVolume < 0.0 )
496 	{
497 		fVolume = 0.0;
498 	}
499 
500 	if( fVolume > 2.0 )
501 	{
502 		fVolume = 2.0;
503 	}
504 
505 	this.fMasterVolume = fVolume;
506 };
507 
508 // ---------------------------------------------------------------------------
509 /** Get the current master volume of this Channel.
510  * 
511  * @return {float} The current master volume.
512  */
513 weasel.Channel.prototype.getMasterVolume = function( )
514 {
515 	return this.fMasterVolume;
516 };
517 
518 // ---------------------------------------------------------------------------
519 /**
520  * get the volume for this Channel object, when the audio is created it uses this
521  * volume level.
522  * 
523  * @return {int} = The current volume.
524  */
525 weasel.Channel.prototype.getVolume = function()
526 {
527 	return this.bMute == true ? 0 : this.iCurrentVolume;
528 };
529 
530 // ---------------------------------------------------------------------------
531 /**
532  * get the shadow volume for this Channel object, which represent the volume
533  * set in the pattern (not in the Volume register of the Amiga, the Protracker
534  * Tremolo command needs this separation).
535  * 
536  * @return {int} = The shadow volume.
537  */
538 weasel.Channel.prototype.getShadowVolume = function()
539 {
540 	return this.iShadowVolume;
541 };
542 
543 // ---------------------------------------------------------------------------
544 /**
545  * Mute/un mute this channel.
546  * 
547  * @param {boolean} bMute = TRUE: silence this channel, FALSE: un-silence this channel.
548  */
549 weasel.Channel.prototype.setMute = function( bMute )
550 {
551 	this.bMute = bMute == true ? true : false;
552 };
553 
554 // ---------------------------------------------------------------------------
555 /**
556  * Set the volume for this Channel object.
557  * 
558  * @param {int} iVolume = The volume (0-64).
559  */
560 weasel.Channel.prototype.setVolume = function( iVolume )
561 {
562 	this.iShadowVolume = this.iCurrentVolume = iVolume;
563 };
564 
565 // ---------------------------------------------------------------------------
566 /**
567  * get the position within the sample that is being played (the sample offset) for this Channel object.
568  * 
569  * @return {float} = The current sample offset (in samples).
570  */
571 weasel.Channel.prototype.getSamplePosition = function()
572 {
573 	return this.fCurrentSamplePosition;
574 };
575 
576 // ---------------------------------------------------------------------------
577 /**
578  * get the position within the sample that is being played (the sample offset) for this Channel object.
579  * 
580  * @param {float} fSamplePosition = The sample offset (in samples).
581  */
582 weasel.Channel.prototype.setSamplePosition = function( fSamplePosition )
583 {
584 	this.fCurrentSamplePosition = fSamplePosition;
585 };
586 
587 
588 // ---------------------------------------------------------------------------
589 /**
590  * Apply arpeggio to the current note.
591  * 
592  * @param {int} iCurrentTick = The current row tick value (0-5 range).
593  * @param {weasel.Channel.prototype.ArpeggioMode} iArpeggioMode = Some Soundtracker Arpeggio's were bugged 
594  * prior to Noisetracker 1.0/1.1 if they have used the SetSpeed Command they where
595  * unable to handle a current row tick greater than 5 (they just ignored Arpeggio).
596  * And they cannot be combined with Pitch Bend command as they use their own
597  * Note Period for the Arpeggio which does not change with the Pitch Bend (this was
598  * corected in Noisetracker), Protracker has Fine Tune applied.
599  */
600 weasel.Channel.prototype.arpeggio = function( iCurrentTick, iArpeggioMode )
601 {
602 	var iBasePeriod = 0;
603 
604 	if( iArpeggioMode == this.ArpeggioMode.UltimateSoundtracker )
605 	{
606 		iBasePeriod = this.getLastSavedNotePeriod();
607 	}
608 	else
609 	{
610 		iBasePeriod = this.getShadowNotePeriod();
611 	}
612 
613 	if( 0 == iBasePeriod )
614 	{
615 		this.setNotePeriod( 0 );
616 		return;
617 	}
618 
619 	if( iArpeggioMode != this.ArpeggioMode.UltimateSoundtracker )
620 	{
621 		// Noisetracker clamps the Arpeggio tick to the 0-2 range.
622 		//
623 		iCurrentTick %= 3;
624 	}
625 
626 	if( 0 == iCurrentTick || 3 == iCurrentTick )
627 	{
628 		this.setNotePeriod( iBasePeriod );
629 		return;
630 	}
631 
632 	var iNoteOffset = this.getEffectParameter() & 0xf;
633 
634 	if( 1 == iCurrentTick || 4 == iCurrentTick )
635 		iNoteOffset = (this.getEffectParameter() >>> 4 ) & 0xf;
636 
637 	var aArpeggioTable = weasel.FormatUltimateSoundTracker121.PeriodTable;
638 	var iTableLength = aArpeggioTable.length;
639 	var iNote = 0;
640 	var iFineTuneOffset = 0;
641 
642 	if( iArpeggioMode != this.ArpeggioMode.UltimateSoundtracker )
643 	{
644 		iTableLength = 36;
645 	}
646 
647 	if( iArpeggioMode == this.ArpeggioMode.Protracker )
648 	{
649 		aArpeggioTable = weasel.FormatProTrackerMK.FineTunePeriodTables;
650 		iFineTuneOffset = this.getFineTune() * 37;
651 	}
652 
653 
654 	if( iArpeggioMode == this.ArpeggioMode.FSTModule )
655 	{
656 		aArpeggioTable = weasel.FormatFSTModule.FSTPeriodTable;
657 		iTableLength = aArpeggioTable.length;
658 		iFineTuneOffset = this.getFineTune();
659 	}
660 
661 	// Linear search for note in period table.
662 	//
663 	if( iArpeggioMode == this.ArpeggioMode.UltimateSoundtracker )
664 	{
665 		for( ; iNote < iTableLength; iNote++ )
666 		{
667 			if( iBasePeriod == aArpeggioTable[ iNote ] )
668 			{
669 				break;
670 			}
671 		}
672 	}
673 	else if( iArpeggioMode == this.ArpeggioMode.FSTModule )
674 	{
675 		for( ; iNote < iTableLength; iNote++ )
676 		{
677 			if( iBasePeriod >= aArpeggioTable[ iNote ] )
678 			{
679 				break;
680 			}
681 		}
682 	}
683 	else
684 	{
685 		for( ; iNote < iTableLength; iNote++ )
686 		{
687 			if( iBasePeriod >= aArpeggioTable[ iNote + iFineTuneOffset ] )
688 			{
689 				// Noisetracker allows for note periods that may not be in the arpeggio table.
690 				// In which case it it uses the next lowest note as the base of the arpeggio.
691 				//
692 				break;
693 			}
694 		}
695 	}
696 
697 	iNote += iNoteOffset;
698 
699 	if( iNote >= iTableLength )
700 	{
701 		if( iArpeggioMode == this.ArpeggioMode.Noisetracker )
702 		{
703 			// Into the Random Data zone, which for the Noisetracker 2.0 Editor
704 			// is just a bank of zeros.
705 			//
706 			this.setNotePeriod( 0 );
707 			return;
708 		}
709 		if( iArpeggioMode == this.ArpeggioMode.Protracker )
710 		{
711 			// Do nothing for protracker, allow note to fall into next
712 			// fine tune table.
713 		}
714 		else if( iArpeggioMode == this.ArpeggioMode.FSTModule )
715 		{
716 			// Clamp note to maximum period in table.
717 			//
718 			var iNotePeriod = this.oModule.calcFineTune( this, aArpeggioTable[ iTableLength - 1 ] );
719 			this.setNotePeriod( iNotePeriod );
720 			return;
721 		}
722 		else
723 		{
724 			// Ultimate Sountracker only stop note lookup from leaving the 
725 			// Period Table (including the "random data" section).
726 			// Max note playable.
727 			//
728 			iNote = iTableLength -1;
729 		}
730 	}
731 
732 	if( iArpeggioMode == this.ArpeggioMode.FSTModule )
733 	{
734 		var iNotePeriod = this.oModule.calcFineTune( this, aArpeggioTable[ iNote ] );
735 		this.setNotePeriod( iNotePeriod );
736 		return;
737 	}
738 
739 	this.setNotePeriod( aArpeggioTable[ iNote + iFineTuneOffset ] );
740 };
741 
742 // ---------------------------------------------------------------------------
743 /**
744  * Apply pitch bend to the current note, Soundtrackers prior to Noisetracker 1.0/1,1
745  * store separate note periods for Arpeggio and Pitch Bend Commands (Arpeggio 
746  * after a Pitchbend with be done on the original note period NOT the final Pitch Bend period). 
747  * 
748  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores pitch bend).
749  * @param {int} iPitchBendDown = The pitch bend down effect parameter.
750  * @param {int} iPitchBendUp = The pitch bend up effect parameter.
751  */
752 weasel.Channel.prototype.pitchBend = function( iCurrentTick, iPitchBendDown, iPitchBendUp )
753 {
754 	var iBasePeriod = this.getShadowNotePeriod();
755 
756 	// Do not apply effect at the beginning of a new row fetch.
757 	//
758 	if( 0 == iCurrentTick )
759 		return;
760 
761 	if( 0 != iPitchBendDown )
762 	{
763 		// Pitch bend down note scale (the larger the note period the lower the note pitch).
764 		//
765 		iBasePeriod += iPitchBendDown;
766 
767 		if( iBasePeriod > 65535 )
768 			iBasePeriod = 65535;
769 	}
770 	else if( 0 != iPitchBendUp )
771 	{
772 		// Pitch bend up note scale (the smaller the note period the high the note pitch).
773 		//
774 		iBasePeriod -= iPitchBendUp;
775 
776 		if( iBasePeriod < 0 )
777 			iBasePeriod = 0;
778 	}
779 
780 	this.setShadowNotePeriod( iBasePeriod );
781 	this.setNotePeriod( iBasePeriod );
782 };
783 
784 // ---------------------------------------------------------------------------
785 /**
786  * Apply volume slide to current channel, as used by TJC Soundktracker 2, Spreadpoint Soundtracker 2.3, Noisetracker 1.1/2.0. 
787  * 
788  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores volumeSlide).
789  */
790 weasel.Channel.prototype.volumeSlide = function( iCurrentTick )
791 {
792 	// Do not apply effect at the beginning of a new row fetch.
793 	//
794 	if( 0 == iCurrentTick )
795 		return;
796 
797 	var iSlideVolume = this.getEffectParameter();
798 
799 	if( 0 == (iSlideVolume >>> 4) & 0xf )
800 	{
801 		// Slide volume down.
802 		//
803 		iSlideVolume = 0 - (iSlideVolume & 0xf);
804 	}
805 	else
806 	{
807 		// Slide Volume up.
808 		//
809 		iSlideVolume = (iSlideVolume >>> 4) & 0xf;
810 	}
811 
812 	var iVolume = this.getShadowVolume() + iSlideVolume;
813 	
814 	if( iVolume > 64 )
815 	{
816 		iVolume = 64;
817 	}
818 	else if( iVolume < 0 )
819 	{
820 		iVolume = 0;
821 	}
822 
823 	this.setVolume( iVolume );
824 };
825 
826 // ---------------------------------------------------------------------------
827 /**
828  * Apply Noisetracker Note Portamento to current note period, Protracker adds
829  * a Glissando Mode on top.
830  * 
831  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento).
832  */
833 weasel.Channel.prototype.notePortamento = function( iCurrentTick )
834 {
835 	// Do not apply effect at the beginning of a new row fetch.
836 	//
837 	if( 0 == iCurrentTick )
838 		return;
839 
840 	var iTargetPeriod  = this.iNotePortamentoTargetPeriod;
841 
842 	if( 0 == iTargetPeriod )
843 	{
844 		// If no Note Portamento Target is set then don't pitch bend.
845 		//
846 		return;
847 	}
848 
849 	var iCurrentPeriod = this.getShadowNotePeriod();
850 
851 	if( iCurrentPeriod < iTargetPeriod )
852 	{
853 		// Pitch bend down note scale (the larger the note period the lower the note pitch).
854 		//
855 		iCurrentPeriod += this.iNotePoramentoSpeed;
856 
857 		if( iCurrentPeriod > iTargetPeriod )
858 		{
859 			iCurrentPeriod = iTargetPeriod;
860 			this.iNotePortamentoTargetPeriod = 0;
861 		}
862 	}
863 	else if( iCurrentPeriod > iTargetPeriod )
864 	{
865 		// Pitch bend up note scale (the smaller the note period the high the note pitch).
866 		//
867 		iCurrentPeriod -= this.iNotePoramentoSpeed;
868 
869 		if( iCurrentPeriod < iTargetPeriod )
870 		{
871 			iCurrentPeriod = iTargetPeriod;
872 			this.iNotePortamentoTargetPeriod = 0;
873 		}
874 	}
875 
876 	this.setShadowNotePeriod( iCurrentPeriod );
877 
878 	if( this.bGlissandoMode )
879 	{
880 		// Protracker Glissando basically rounds the Period value
881 		// to the nearest note.
882 		//
883 		var aFineTunePeriodTables = weasel.FormatProTrackerMK.FineTunePeriodTables;
884 		var iFineTuneOffset = this.getFineTune() * 37;
885 		var iTableLength = weasel.FormatUltimateSoundTracker121.PeriodTable.length;
886 		var iNote = 0;
887 
888 		for( ; iNote < iTableLength; iNote++ )
889 		{
890 			if( iCurrentPeriod >= aFineTunePeriodTables[ iNote + iFineTuneOffset ] )
891 			{
892 				break;
893 			}
894 		}
895 
896 		iCurrentPeriod = aFineTunePeriodTables[ iNote + iFineTuneOffset ];
897 	}
898 
899 	this.setNotePeriod( iCurrentPeriod );
900 };
901 
902 // ---------------------------------------------------------------------------
903 /**
904  * Apply Noisetracker Vibrato Command to current note period.
905  * 
906  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento).
907  * @param {bool} bRememberLastVibrato = Remember last Vibrato Effect Parameter (used for VibratoAndVolumeSlide Command).
908  * @param {bool} bNoisetracker20Vibrato = True : Noisetracker 2.0 applies a smaller Vibrato that Noisetracker 1.1.
909  * 
910  */
911 weasel.Channel.prototype.vibrato = function( iCurrentTick, bRememberLastVibrato, bNoisetracker20Vibrato )
912 {
913 	// Do not apply effect at the beginning of a new row fetch.
914 	//
915 	if( 0 == iCurrentTick )
916 		return;
917 
918 	if( 0 != this.getEffectParameter() && bRememberLastVibrato )
919 	{
920 		// Store the Vibrato Effect Parameter so it can be used if its simplified form
921 		//
922 		this.iLastVibratoParameter = this.getEffectParameter();
923 	}
924 
925 	var iVibratoPeriodDivider = bNoisetracker20Vibrato ? 7 : 6;
926 
927 	var iVibratoSpeed = ( this.iLastVibratoParameter & 0xf0 ) >>> 4;
928 	var iVibratoSize = this.iLastVibratoParameter & 0xf;
929 	var iVibratoTablePosition = ( this.iVibratoTablePosition >>> 2 ) & 0x1f;
930 	var iVibratoPeriod = ( weasel.FormatNoiseTracker11.VibratoTable[ iVibratoTablePosition ] * iVibratoSize ) >>> iVibratoPeriodDivider;
931 	var iCurrentPeriod = this.getShadowNotePeriod();
932 
933 	if( 0 == (this.iVibratoTablePosition & 0x80) )
934 	{
935 		// Positive half of sine wave, so add it to the note period.
936 		//
937 		iCurrentPeriod += iVibratoPeriod;
938 	}
939 	else
940 	{
941 		// Negative half of sine wave, so subtract from note period.
942 		//
943 		iCurrentPeriod -= iVibratoPeriod;
944 	}
945 
946 	this.iVibratoTablePosition = ( this.iVibratoTablePosition + ( iVibratoSpeed << 2 ) ) & 0xff;
947 
948 	this.setNotePeriod( iCurrentPeriod );
949 };
950 
951 // ---------------------------------------------------------------------------
952 /**
953  * Apply Protracker Vibrato Command to current note period, different from
954  * Noisetracker Vibrato in that the parameters (Vibrato rate and depth) get stored
955  * independently and there are 3 different Vibrato Waveforms to choose from.
956  * 
957  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento).
958  * @param {bool} bRememberLastVibrato = Remember last Vibrato Effect Parameter (used for VibratoAndVolumeSlide Command).
959  * @param {bool} bNoisetracker20Vibrato = True : Noisetracker 2.0 applies a smaller Vibrato that Noisetracker 1.1, this also applies to Protracker 1.0c and below, Protracker 1.1a and above adopted the same Vibrato Depth as Noisetracker 2.0.
960  * 
961  */
962 weasel.Channel.prototype.protrackerVibrato = function( iCurrentTick, bRememberLastVibrato, bNoisetracker20Vibrato )
963 {
964 	// Do not apply effect at the beginning of a new row fetch.
965 	//
966 	if( 0 == iCurrentTick )
967 		return;
968 
969 	if( 0 != this.getEffectParameter() && bRememberLastVibrato )
970 	{
971 		// Store the Vibrato Effect Parameter so it can be used if its simplified form
972 		//
973 		// Protracker Vibrato mode.
974 		//
975 		var iNibble = this.getEffectParameter() & 0xf0;
976 
977 		if( iNibble != 0 )
978 		{
979 			this.iLastVibratoParameter = this.iLastVibratoParameter & 0xf | iNibble;
980 		}
981 
982 		var iNibble = this.getEffectParameter() & 0xf;
983 
984 		if( iNibble != 0 )
985 		{
986 			this.iLastVibratoParameter = this.iLastVibratoParameter & 0xf0 | iNibble;
987 		}
988 	}
989 
990 	var iVibratoTablePosition = ( this.iVibratoTablePosition >>> 2 ) & 0x1f;
991 	var iVibratoData = 255;	// Square waveform.
992 
993 	if( this.iVibratoWaveformType == weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave )
994 	{
995 		iVibratoData = weasel.FormatNoiseTracker11.VibratoTable[ iVibratoTablePosition ];
996 	}
997 	else if( this.iVibratoWaveformType == weasel.Channel.prototype.ProtrackerVibratoWaveform.RampDownSawTooth )
998 	{
999 		iVibratoData = iVibratoTablePosition << 3;
1000 
1001 		if( 0 != (this.iVibratoTablePosition & 0x80) )
1002 		{
1003 			iVibratoData = 255 - iVibratoData;
1004 		}
1005 	}
1006 
1007 	var iVibratoPeriodDivider = bNoisetracker20Vibrato ? 7 : 6;
1008 
1009 	var iVibratoSpeed = ( this.iLastVibratoParameter & 0xf0 ) >>> 4;
1010 	var iVibratoSize = this.iLastVibratoParameter & 0xf;
1011 	var iVibratoPeriod = ( iVibratoData * iVibratoSize ) >>> iVibratoPeriodDivider;
1012 	var iCurrentPeriod = this.getShadowNotePeriod();
1013 
1014 	if( 0 == (this.iVibratoTablePosition & 0x80) )
1015 	{
1016 		// Positive half of sine wave, so add it to the note period.
1017 		//
1018 		iCurrentPeriod += iVibratoPeriod;
1019 	}
1020 	else
1021 	{
1022 		// Negative half of sine wave, so subtract from note period.
1023 		//
1024 		iCurrentPeriod -= iVibratoPeriod;
1025 	}
1026 
1027 	this.iVibratoTablePosition = ( this.iVibratoTablePosition + ( iVibratoSpeed << 2 ) ) & 0xff;
1028 
1029 	this.setNotePeriod( iCurrentPeriod );
1030 };
1031 
1032 // ---------------------------------------------------------------------------
1033 /**
1034  * Apply Protracker Tremolo Command.
1035  * 
1036  * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento).
1037  * @param {bool} bProtrackerBugMode = true : The Protracker series has a bug
1038  *  when using the Ramp Down Sawtooth waveform
1039  * during the Tremolo command (the other waveform types are fine). The bug
1040  * is caused by using the Vibrato Table Position instead of the Tremolo Table
1041  * Position during construction of the sawtooth resulting in a Ramp Up (not Ramp Down)
1042  * sawooth if the Vibrato command is not used or a triangle-ish waveform
1043  * that almost randomly changes direction if the Vibrato command has been used on
1044  * the same channel. Even more weird is that I've been sitting on this bug for
1045  * ~20 years! :-O Good to finally get it off the chest so to speak! Especially
1046  * as its obviously just a cut and paste error (the code for the Vibrato is 95% the
1047  * same). False : Behave as expected (without the bug). The Protracker series also
1048  * contains another bug in the Editor (but NOT in th replay.s) in that it resets
1049  * the volume to that of the sample on every tick 0, so for Ramp Down it creates 
1050  * an annoying clicking noise.
1051  * 
1052  */
1053 weasel.Channel.prototype.tremolo = function( iCurrentTick, bProtrackerBugMode )
1054 {
1055 	// Do not apply effect at the beginning of a new row fetch.
1056 	//
1057 	if( 0 == iCurrentTick )
1058 		return;
1059 
1060 	if( 0 != this.getEffectParameter() )
1061 	{
1062 		// Store the Tremolo Effect Parameter so it can be used if its simplified form
1063 		//
1064 		var iNibble = this.getEffectParameter() & 0xf0;
1065 
1066 		if( iNibble != 0 )
1067 		{
1068 			this.iLastTremoloParameter = this.iLastTremoloParameter & 0xf | iNibble;
1069 		}
1070 
1071 		var iNibble = this.getEffectParameter() & 0xf;
1072 
1073 		if( iNibble != 0 )
1074 		{
1075 			this.iLastTremoloParameter = this.iLastTremoloParameter & 0xf0 | iNibble;
1076 		}
1077 	}
1078 
1079 	var iTremoloTablePosition = ( this.iTremoloTablePosition >>> 2 ) & 0x1f;
1080 	var iTremoloData = 255;	// Square waveform.
1081 
1082 	if( this.iTremoloWaveformType == weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave )
1083 	{
1084 		iTremoloData = weasel.FormatNoiseTracker11.VibratoTable[ iTremoloTablePosition ];
1085 	}
1086 	else if( this.iTremoloWaveformType == weasel.Channel.prototype.ProtrackerTremoloWaveform.RampDownSawTooth )
1087 	{
1088 		iTremoloData = iTremoloTablePosition << 3;
1089 
1090 		// Protrackers replay routine accidentally uses the Vibrato Table Position
1091 		// NOT the Tremolo Table Position.
1092 		// Resulting in a more erratic Saw Tooth.
1093 		//
1094 		var iTablePosition = true == bProtrackerBugMode ? this.iVibratoTablePosition : this.iTremoloTablePosition;
1095 
1096 		if( 0 != (iTablePosition & 0x80) )
1097 		{
1098 			iTremoloData = 255 - iTremoloData;
1099 		}
1100 	}
1101 
1102 	var iTremoloSpeed = ( this.iLastTremoloParameter & 0xf0 ) >>> 4;
1103 	var iTremoloSize = this.iLastTremoloParameter & 0xf;
1104 	var iTremolo = ( iTremoloData * iTremoloSize ) >>> 6;
1105 	var iShadowVolume = this.getShadowVolume();
1106 
1107 	if( 0 == (this.iTremoloTablePosition & 0x80) )
1108 	{
1109 		// Positive half of sine wave, so add Tremolo to Volume level.
1110 		//
1111 		iShadowVolume += iTremolo;
1112 	}
1113 	else
1114 	{
1115 		// Negative half of sine wave, so subtract Tremolo to Volume level.
1116 		//
1117 		iShadowVolume -= iTremolo;
1118 	}
1119 
1120 	// Clamp volume levels to allowed range.
1121 	//
1122 	if( iShadowVolume > 64 )
1123 	{
1124 		iShadowVolume = 64;
1125 	}
1126 	else if( iShadowVolume < 0 )
1127 	{
1128 		iShadowVolume = 0;
1129 	}
1130 
1131 	this.iTremoloTablePosition = ( this.iTremoloTablePosition + ( iTremoloSpeed << 2 ) ) & 0xff;
1132 
1133 	// Notice the current volume is being set directly,
1134 	// meaning the shadow volume does not change.
1135 	//
1136 	this.iCurrentVolume = iShadowVolume;
1137 };
1138 
1139 // ---------------------------------------------------------------------------
1140 /**
1141  * Apply Protracker Note re-trigger. 
1142  * 
1143  * @param {int} iCurrentTick = The current row tick value.
1144  * @param {int} iRetriggerOn = The number of ticks to re-trigger, this is applied
1145  * as a modulus so a value of 2 causes a trigger every 2 ticks,( Range 0-15 ).
1146  * @param {float} fWaitForDMAToStop = The value to use (in milliseconds) for the pause between samples, typically weasel.FormatNoiseTracker11.WaitForDMAToStop.
1147  *
1148  * @return {bool} True = sample retriggered this Tick, false = Sample not retriggered this Tick.
1149  */
1150 weasel.Channel.prototype.retriggerNote = function( iCurrentTick, iRetriggerOn, fWaitForDMAToStop )
1151 {
1152 	iRetriggerOn &= 0xf;
1153 
1154 	if( 0 != iRetriggerOn )
1155 	{
1156 		if( 0 == iCurrentTick % iRetriggerOn )
1157 		{
1158 			this.delaySampleStart( true, fWaitForDMAToStop );
1159 
1160 			// Protracker Set SampleOffset Command has residual tendencies that
1161 			// affects Retrigger Note and Note Delay in Protracker.
1162 			//
1163 			this.setSamplePosition( this.playFromOffset() );
1164 			return true;
1165 		}
1166 	}
1167 
1168 	return false;
1169 };
1170 
1171 // ---------------------------------------------------------------------------
1172 /**
1173  * Get the circular audio buffer containing the rendered sample.
1174  * 
1175  * @return {Array} = The circular array containing the rendered sample data.
1176  */
1177 weasel.Channel.prototype.getCircularAudioBuffer = function( )
1178 {
1179 	return this.aCircularAudioBuffer;
1180 };
1181 
1182 // ---------------------------------------------------------------------------
1183 /**
1184  * Get the current position in the circular array (rendered sample data is behind this offset).
1185  * 
1186  * @return {int} = The current position in the circular array containing the rendered sample data.
1187  */
1188 weasel.Channel.prototype.getCircularBufferPosition = function( )
1189 {
1190 	return this.iCircularBufferPosition;
1191 };
1192 
1193 // ---------------------------------------------------------------------------
1194 /**
1195  * Set the position in the circular audio buffer.
1196  * 
1197  * @param {int} iPosition = The new position in the the circular audio buffer.
1198  */
1199 weasel.Channel.prototype.setCircularBufferPosition = function( iPosition )
1200 {
1201 	this.iCircularBufferPosition = iPosition;
1202 };
1203 
1204 // ---------------------------------------------------------------------------
1205 /**
1206  * Get the Channel Interpolation type.
1207  * 
1208  * @return {int} = The channel interpolation value, 0 = none, 1 = RLM-D Alias Reduction, 2 = Linear Interpolation.
1209  */
1210 weasel.Channel.prototype.getChannelInterpolation = function()
1211 {
1212 	return this.iChannelInterpolation;
1213 };
1214 
1215 // ---------------------------------------------------------------------------
1216 /**
1217  * Set the Channel Interpolation type.
1218  * 
1219  * @param {int} iInterpolationType =0 = none, 1 = RLM-D Alias Reduction, 2 = Linear Interpolation.
1220  */
1221 weasel.Channel.prototype.setChannelInterpolation = function( iInterpolationType )
1222 {
1223 	var bFound = false;
1224 
1225 	for( var iInterpolation in this.SupportedInterpolationTypes )
1226 	{
1227 		if( this.SupportedInterpolationTypes.hasOwnProperty( iInterpolation ) )	// Ignore inherent Object.prototype properties.
1228 		{
1229 			if( iInterpolationType === this.SupportedInterpolationTypes[ iInterpolation ] )
1230 			{
1231 				bFound = true;
1232 				break;
1233 			}
1234 		}
1235 	}
1236 
1237 	if( bFound )
1238 	{
1239 		this.iChannelInterpolation = iInterpolationType;
1240 	}
1241 };
1242 
1243 // ---------------------------------------------------------------------------
1244 /**
1245  * Set the Sample Accumulator, which is used by makeNoiseAliasReduction() 
1246  * to reduce (but not eliminate) the aliasing, it is set to the first sample of 
1247  * the current instrument, without volume being applied.
1248  */
1249 weasel.Channel.prototype.setSampleAccumulator = function()
1250 {
1251 	this.fSampleAccumulator = 0.0;
1252 this.fSampleAccumulator1 = this.fSampleAccumulator2 = this.fSampleAccumulator3 = this.fSampleAccumulator4 = 0.0;
1253 
1254 	var oSample = this.getSample();
1255 
1256 	if( null == oSample )
1257 	{
1258 		return;
1259 	}
1260 
1261 	if( 0 == oSample.getLength() )
1262 	{
1263 		return;
1264 	}
1265 
1266 	var aSampleData = oSample.getSampleArray();
1267 	var iSamplePosition = this.getSamplePosition() | 0;
1268 	
1269 	if( null == aSampleData )
1270 		return;
1271 	
1272 	if( iSamplePosition >= aSampleData.length )
1273 		return;
1274 	var fSample = aSampleData[ iSamplePosition ];
1275 	this.fSampleAccumulator = this.fSampleAccumulator1 = this.fSampleAccumulator2 = this.fSampleAccumulator3 = this.fSampleAccumulator4 = fSample * ((this.getVolume() / 64.0) * this.getMasterVolume());
1276 };
1277 
1278 // ---------------------------------------------------------------------------
1279 /**
1280  * Make the requested number of silent samples in the circular audio buffer.
1281  * 
1282  * @param {int} iSamplesToFill = The number of samples of silence to make.
1283  */
1284 weasel.Channel.prototype.makeSilence = function( iSamplesToFill )
1285 {
1286 	if( iSamplesToFill <= 0 )
1287 		return;
1288 
1289 	var aBuffer = this.getCircularAudioBuffer();
1290 	var iCircularBuffer = this.getCircularBufferPosition();
1291 	var iBufferLength = aBuffer.length;
1292 
1293 	// No actual Sample to play, so fill with silence (actually the last outputted sample).
1294 	//
1295 	var fLastSample = aBuffer[ ((iCircularBuffer -1) + iBufferLength) % iBufferLength ];
1296 
1297 	while( iSamplesToFill > 0 )
1298 	{
1299 		var iSamplesToEndOfBuffer = iBufferLength - iCircularBuffer;
1300 		var iSamples = iSamplesToEndOfBuffer >= iSamplesToFill ? iSamplesToFill : iSamplesToEndOfBuffer;
1301 		iSamplesToFill-= iSamples;
1302 
1303 		for( var iAlign = iSamples & 0x3; --iAlign >= 0; )
1304 		{
1305 			aBuffer[ iCircularBuffer++ ] = fLastSample;
1306 		}
1307 
1308 		for( iSamples >>>= 2; --iSamples >= 0; )
1309 		{
1310 			aBuffer[ iCircularBuffer++ ] = fLastSample;
1311 			aBuffer[ iCircularBuffer++ ] = fLastSample;
1312 			aBuffer[ iCircularBuffer++ ] = fLastSample;
1313 			aBuffer[ iCircularBuffer++ ] = fLastSample;
1314 		}
1315 
1316 		iCircularBuffer %= iBufferLength;
1317 	}
1318 
1319 	this.setCircularBufferPosition( iCircularBuffer );
1320 
1321 };
1322 
1323 // ---------------------------------------------------------------------------
1324 /**
1325  * Make/render the requested number of instrument samples into the circular audio buffer, at the correct note frequency and volume (but no stereo panning, in mono).
1326  * 
1327  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1328  * @param {int} iSamplesToFill = The Samples to make.
1329  */
1330 weasel.Channel.prototype.makeNoise = function( aSampleData, iSamplesToFill )
1331 {
1332 	var iInterpolationType = this.getChannelInterpolation();
1333 	var fFreqStep = this.getFrequencyStepRate();
1334 
1335 	if( 0 == iInterpolationType )
1336 	{
1337 		this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill );
1338 	}else if(  5 == iInterpolationType )
1339 	{
1340 		if( 1.0 > fFreqStep )
1341 		{
1342 			this.make6PointCubicSplineInterpolation( aSampleData, iSamplesToFill );
1343 		}
1344 		else
1345 		{
1346 			this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill );
1347 		}
1348 	}else if(  4 == iInterpolationType )
1349 	{
1350 		if( 1.0 > fFreqStep )
1351 		{
1352 			this.makeNoiseCatmullRomSplineInterpolation( aSampleData, iSamplesToFill );
1353 		}
1354 		else
1355 		{
1356 			this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill );
1357 		}
1358 	}else if(  3 == iInterpolationType )
1359 	{
1360 		if( 1.0 > fFreqStep )
1361 		{
1362 			this.makeNoiseCubicSplineInterpolation( aSampleData, iSamplesToFill );
1363 		}
1364 		else
1365 		{
1366 			this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill );
1367 		}
1368 	}else if(  2 == iInterpolationType )
1369 	{
1370 		if( 1.0 > fFreqStep )
1371 		{
1372 			this.makeNoiseLinearInterpolation( aSampleData, iSamplesToFill );
1373 		}
1374 		else
1375 		{
1376 			this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill );
1377 		}
1378 	}else if(  1 == iInterpolationType )
1379 	{
1380 		this.makeNoiseRLMDAliasReduction( aSampleData, iSamplesToFill );
1381 	}
1382 };
1383 
1384 // ---------------------------------------------------------------------------
1385 /**
1386  * Make/render the requested number of instrument samples into the circular audio buffer, at the correct note frequency and volume (but no stereo panning, in mono).
1387  * 
1388  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1389  * @param {int} iSamplesToFill = The Samples to make.
1390  */
1391 weasel.Channel.prototype.makeNoiseAmigaStyle = function( aSampleData, iSamplesToFill )
1392 {
1393 	var fCurrentSamplePosition = this.getSamplePosition();
1394 	var aBuffer = this.getCircularAudioBuffer();
1395 	var iCircularBuffer = this.getCircularBufferPosition();
1396 	var iBufferLength = aBuffer.length;
1397 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1398 
1399 	// Play Sample without checking for end or loop.
1400 	//
1401 	var iSampleOffset = fCurrentSamplePosition | 0;
1402 	var fSample = aSampleData[ iSampleOffset ] * fVolume;
1403 
1404 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; )
1405 	{
1406 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1407 		iSamples -= iMaxLengthFill;
1408 
1409 		while( --iMaxLengthFill >= 0 )
1410 		{
1411 			aBuffer[ iCircularBuffer++ ] = fSample;
1412 	
1413 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1414 			{
1415 				var iIntegerStep = fFractionalPart|0;
1416 				iSampleOffset += iIntegerStep;
1417 				fSample = aSampleData[ iSampleOffset ] * fVolume;
1418 				fFractionalPart -= iIntegerStep;
1419 			}
1420 		}
1421 		iCircularBuffer %= iBufferLength;
1422 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1423 	}
1424 
1425 	this.setSamplePosition( fCurrentSamplePosition );
1426 	this.setCircularBufferPosition( iCircularBuffer );
1427 };
1428 
1429 // ---------------------------------------------------------------------------
1430 /**
1431  * Make/render using RLM-D Alias Reduction, the requested number of instrument samples into the circular
1432  * audio buffer, at the correct note frequency and volume (but no stereo panning, in mono).
1433  * This useful technique I worked out on the (ahem) "other" machine (the Atari ST..) in the early 90ies
1434  * it applies a small amount of feedback/echo PER sample byte which quickly fades out
1435  * thus does not destroy the original sample and its not linear interpolation, leaving
1436  * enough of aliasing noise to be heard but not enough to be annoying. This is very useful
1437  * for samples that depend on aliasing noise for timbre, such as percussion or square/sawtooth waveforms
1438  * but smooths out sinewave/low frequency samples. Downfall is its very dependant on the replay frequency (you wont hear any effect at 192khz).
1439  * 
1440  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1441  * @param {int} iSamplesToFill = The Samples to make.
1442  */
1443 weasel.Channel.prototype.makeNoiseRLMDAliasReduction = function( aSampleData, iSamplesToFill )
1444 {
1445 	var fCurrentSamplePosition = this.getSamplePosition();
1446 	var aBuffer = this.getCircularAudioBuffer();
1447 	var iCircularBuffer = this.getCircularBufferPosition();
1448 	var iBufferLength = aBuffer.length;
1449 	var fSampleAccumulator = this.fSampleAccumulator;
1450 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1451 
1452 	// Play Sample without checking for end or loop.
1453 	//
1454 	var iSampleOffset = fCurrentSamplePosition | 0;
1455 	var fSample = aSampleData[ iSampleOffset ];
1456 
1457 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; )
1458 	{
1459 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1460 		iSamples -= iMaxLengthFill;
1461 
1462 		while( --iMaxLengthFill >= 0 )
1463 		{
1464 			aBuffer[ iCircularBuffer++ ] = (fSampleAccumulator = ( fSampleAccumulator + fSample ) * 0.5)  * fVolume;
1465 
1466 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1467 			{
1468 				var iIntegerStep = fFractionalPart|0;
1469 				iSampleOffset += iIntegerStep;
1470 				fSample = aSampleData[ iSampleOffset ];
1471 				fFractionalPart -= iIntegerStep;
1472 			}
1473 		}
1474 		iCircularBuffer %= iBufferLength;
1475 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1476 	}
1477 
1478 	this.fSampleAccumulator = fSampleAccumulator;
1479 	this.setSamplePosition( fCurrentSamplePosition );
1480 	this.setCircularBufferPosition( iCircularBuffer );
1481 };
1482 
1483 // ---------------------------------------------------------------------------
1484 /**
1485  * Make/render using linear interpolation, only call this function if frequency
1486  * step rate is less than or equal to 1 or errors will occur.
1487  * 
1488  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1489  * @param {int} iSamplesToFill = The Samples to make.
1490  */
1491 weasel.Channel.prototype.makeNoiseLinearInterpolation = function( aSampleData, iSamplesToFill )
1492 {
1493 	var fCurrentSamplePosition = this.getSamplePosition();
1494 	var aBuffer = this.getCircularAudioBuffer();
1495 	var iCircularBuffer = this.getCircularBufferPosition();
1496 	var iBufferLength = aBuffer.length;
1497 	var fLastSample = this.fSampleAccumulator;
1498 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1499 
1500 	// Play Sample without checking for end or loop.
1501 	//
1502 	var iSampleOffset = fCurrentSamplePosition | 0;
1503 	var fSample = aSampleData[ iSampleOffset ] * fVolume;
1504 
1505 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset, fInterpolationStep = (fSample - fLastSample) * fFreqStep, fInterpolatedSample = fLastSample + ((fSample - fLastSample) * fFractionalPart) - fInterpolationStep; iSamples > 0; )
1506 	{
1507 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1508 		iSamples -= iMaxLengthFill;
1509 
1510 		while( --iMaxLengthFill >= 0 )
1511 		{
1512 			aBuffer[ iCircularBuffer++ ] = (fInterpolatedSample += fInterpolationStep);
1513 
1514 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1515 			{
1516 				fFractionalPart -= 1.0;
1517 				fLastSample = fSample;
1518 				fSample = aSampleData[ ++iSampleOffset ] * fVolume;
1519 				fInterpolationStep = (fSample - fLastSample) * fFreqStep;
1520 				fInterpolatedSample = fLastSample + ((fSample - fLastSample) * fFractionalPart) - fInterpolationStep;
1521 			}
1522 		}
1523 		iCircularBuffer %= iBufferLength;
1524 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1525 	}
1526 
1527 	this.fSampleAccumulator = fLastSample;
1528 	this.setSamplePosition( fCurrentSamplePosition );
1529 	this.setCircularBufferPosition( iCircularBuffer );
1530 };
1531 
1532 // ---------------------------------------------------------------------------
1533 /**
1534  * Make/render using Catmull-Rom spline interpolation, only call this function if frequency
1535  * step rate is less than or equal to 1 or errors will occur.
1536  * 
1537  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1538  * @param {int} iSamplesToFill = The Samples to make.
1539  * 
1540  * @author Warren Willmey 2011, reference Paul Bourke "Interpolation methods".
1541  */
1542 weasel.Channel.prototype.makeNoiseCatmullRomSplineInterpolation = function( aSampleData, iSamplesToFill )
1543 {
1544 	var fCurrentSamplePosition = this.getSamplePosition();
1545 	var aBuffer = this.getCircularAudioBuffer();
1546 	var iCircularBuffer = this.getCircularBufferPosition();
1547 	var iBufferLength = aBuffer.length;
1548 	var fLastSample0 = this.fSampleAccumulator;
1549 	var fLastSample1 = this.fSampleAccumulator1;
1550 	var fLastSample2 = this.fSampleAccumulator2;
1551 	
1552 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1553 
1554 	// Play Sample without checking for end or loop.
1555 	//
1556 	var iSampleOffset = fCurrentSamplePosition | 0;
1557 	var fSample = aSampleData[ iSampleOffset ] * fVolume;
1558 
1559 	// Catmull-Rom spline, named after Edwin Catmull and Raphael Rom.
1560 	//
1561 	var fWeight0 = -0.5 * fLastSample2 + 1.5 * fLastSample1 - 1.5 * fLastSample0 + 0.5 * fSample;
1562 	var fWeight1 = fLastSample2 - 2.5 * fLastSample1 + 2 * fLastSample0 - 0.5 * fSample;
1563 	var fWeight2 = -0.5 * fLastSample2 + 0.5 * fLastSample0;
1564 
1565 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; )
1566 	{
1567 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1568 		iSamples -= iMaxLengthFill;
1569 
1570 		while( --iMaxLengthFill >= 0 )
1571 		{
1572 			var fInterpolationSquared = fFractionalPart * fFractionalPart;
1573 			
1574 			aBuffer[ iCircularBuffer++ ] =	  fWeight0 * fFractionalPart * fInterpolationSquared 
1575 											+ fWeight1 * fInterpolationSquared 
1576 											+ fWeight2 * fFractionalPart 
1577 											+ fLastSample1;
1578 
1579 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1580 			{
1581 				fFractionalPart -= 1.0;
1582 				fLastSample2 = fLastSample1;
1583 				fLastSample1 = fLastSample0;
1584 				fLastSample0 = fSample;
1585 				fSample = aSampleData[ ++iSampleOffset ] * fVolume;
1586 
1587 				// Catmull-Rom spline, named after Edwin Catmull and Raphael Rom.
1588 				//
1589 				fWeight0 = -0.5 * fLastSample2 + 1.5 * fLastSample1 - 1.5 * fLastSample0 + 0.5 * fSample;
1590 				fWeight1 = fLastSample2 - 2.5 * fLastSample1 + 2 * fLastSample0 - 0.5 * fSample;
1591 				fWeight2 = -0.5 * fLastSample2 + 0.5 * fLastSample0;
1592 			}
1593 		}
1594 		iCircularBuffer %= iBufferLength;
1595 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1596 		
1597 	}
1598 
1599 	this.fSampleAccumulator = fLastSample0;
1600 	this.fSampleAccumulator1 = fLastSample1;
1601 	this.fSampleAccumulator2 = fLastSample2;
1602 	this.setSamplePosition( fCurrentSamplePosition );
1603 	this.setCircularBufferPosition( iCircularBuffer );
1604 };
1605 
1606 // ---------------------------------------------------------------------------
1607 /**
1608  * Make/render using Cubic spline interpolation, only call this function if frequency
1609  * step rate is less than or equal to 1 or errors will occur.
1610  * 
1611  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1612  * @param {int} iSamplesToFill = The Samples to make.
1613  * 
1614  * @author Warren Willmey 2011, reference Paul Bourke "Interpolation methods".
1615  */
1616 weasel.Channel.prototype.makeNoiseCubicSplineInterpolation = function( aSampleData, iSamplesToFill )
1617 {
1618 	var fCurrentSamplePosition = this.getSamplePosition();
1619 	var aBuffer = this.getCircularAudioBuffer();
1620 	var iCircularBuffer = this.getCircularBufferPosition();
1621 	var iBufferLength = aBuffer.length;
1622 	var fLastSample0 = this.fSampleAccumulator;
1623 	var fLastSample1 = this.fSampleAccumulator1;
1624 	var fLastSample2 = this.fSampleAccumulator2;
1625 	
1626 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1627 
1628 	// Play Sample without checking for end or loop.
1629 	//
1630 	var iSampleOffset = fCurrentSamplePosition | 0;
1631 	var fSample = aSampleData[ iSampleOffset ]  * fVolume;
1632 	
1633 	// Cubic Interpolation weights.
1634 	//
1635 	var fWeight0 = fSample - fLastSample0 - fLastSample2 + fLastSample1;
1636 	var fWeight1 = fLastSample2 - fLastSample1 - fWeight0;
1637 	var fWeight2 = fLastSample0 - fLastSample2;
1638 
1639 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; )
1640 	{
1641 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1642 		iSamples -= iMaxLengthFill;
1643 
1644 		while( --iMaxLengthFill >= 0 )
1645 		{
1646 			var fInterpolationSquared = fFractionalPart * fFractionalPart;
1647 			
1648 			aBuffer[ iCircularBuffer++ ] =	  fWeight0 * fFractionalPart * fInterpolationSquared 
1649 											+ fWeight1 * fInterpolationSquared 
1650 											+ fWeight2 * fFractionalPart 
1651 											+ fLastSample1;
1652 
1653 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1654 			{
1655 				fFractionalPart -= 1.0;
1656 				fLastSample2 = fLastSample1;
1657 				fLastSample1 = fLastSample0;
1658 				fLastSample0 = fSample;
1659 				fSample = aSampleData[ ++iSampleOffset ] * fVolume;
1660 
1661 				// Cubic Interpolation weights.
1662 				//
1663 				fWeight0 = fSample - fLastSample0 - fLastSample2 + fLastSample1;
1664 				fWeight1 = fLastSample2 - fLastSample1 - fWeight0;
1665 				fWeight2 = fLastSample0 - fLastSample2;
1666 			}
1667 		}
1668 		iCircularBuffer %= iBufferLength;
1669 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1670 	}
1671 
1672 	this.fSampleAccumulator = fLastSample0;
1673 	this.fSampleAccumulator1 = fLastSample1;
1674 	this.fSampleAccumulator2 = fLastSample2;
1675 	this.setSamplePosition( fCurrentSamplePosition );
1676 	this.setCircularBufferPosition( iCircularBuffer );
1677 };
1678 
1679 
1680 // ---------------------------------------------------------------------------
1681 /**
1682  * Make/render using a 6 point Cubic spline interpolation, only call this function if frequency
1683  * step rate is less than or equal to 1 or errors will occur.
1684  * 
1685  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1686  * @param {int} iSamplesToFill = The Samples to make.
1687  * 
1688  * @author Warren Willmey 2011, references based on algo posted by David Waugh, which in turn comes from Joshua Scholar.
1689  */
1690 weasel.Channel.prototype.make6PointCubicSplineInterpolation = function( aSampleData, iSamplesToFill )
1691 {
1692 	var fCurrentSamplePosition = this.getSamplePosition();
1693 	var aBuffer = this.getCircularAudioBuffer();
1694 	var iCircularBuffer = this.getCircularBufferPosition();
1695 	var iBufferLength = aBuffer.length;
1696 	var fLastSample0 = this.fSampleAccumulator;
1697 	var fLastSample1 = this.fSampleAccumulator1;
1698 	var fLastSample2 = this.fSampleAccumulator2;
1699 	var fLastSample3 = this.fSampleAccumulator3;
1700 	var fLastSample4 = this.fSampleAccumulator4;
1701 	
1702 	var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume();
1703 
1704 	// Play Sample without checking for end or loop.
1705 	//
1706 	var iSampleOffset = fCurrentSamplePosition | 0;
1707 	var fSample = aSampleData[ iSampleOffset ] * fVolume;
1708 	
1709 	// Cubic Interpolation weights.
1710 	//
1711 	var fWeight1 = (fLastSample1-fLastSample3) * 16.0 + ( fLastSample4 - fLastSample0 )* 2.0;
1712 	var fWeight2 = ( fLastSample1 + fLastSample3 ) * 16.0 -fLastSample4 - fLastSample2 * 30.0 - fLastSample0;
1713 	var fWeight3 = fLastSample1 * 66.0 - fLastSample2 * 70.0 - fLastSample0 * 33.0 + fLastSample3 * 39.0 + fSample * 7.0 - fLastSample4 * 9.0;
1714 	var fWeight4 = fLastSample2*126.0-fLastSample1*124.0+fLastSample0*61.0-fLastSample3*64.0- fSample*12.0+fLastSample4*13.0;
1715 	var fWeight5 = ( fLastSample1 - fLastSample2 ) * 50.0 + ( fLastSample3 - fLastSample0 ) * 25.0 + ( fSample - fLastSample4 ) * 5.0;
1716 
1717 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; )
1718 	{
1719 		var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples;
1720 		iSamples -= iMaxLengthFill;
1721 
1722 		while( --iMaxLengthFill >= 0 )
1723 		{
1724 			aBuffer[ iCircularBuffer++ ] =	fLastSample2 + 0.04166666666 * fFractionalPart * (fWeight1
1725 									+ fFractionalPart * ( fWeight2
1726 									+ fFractionalPart * ( fWeight3
1727 									+ fFractionalPart * ( fWeight4
1728 									+ fFractionalPart * ( fWeight5 ) ) )));
1729 
1730 			if( 1.0 <= (fFractionalPart += fFreqStep) )
1731 			{
1732 				fFractionalPart -= 1.0;
1733 				fLastSample4 = fLastSample3;
1734 				fLastSample3 = fLastSample2;
1735 				fLastSample2 = fLastSample1;
1736 				fLastSample1 = fLastSample0;
1737 				fLastSample0 = fSample;
1738 				fSample = aSampleData[ ++iSampleOffset ] * fVolume;
1739 
1740 				// Cubic Interpolation weights.
1741 				//
1742 				fWeight1 = (fLastSample1-fLastSample3) * 16.0 + ( fLastSample4 - fLastSample0 )* 2.0;
1743 				fWeight2 = ( fLastSample1 + fLastSample3 ) * 16.0 -fLastSample4 - fLastSample2 * 30.0 - fLastSample0;
1744 				fWeight3 = fLastSample1 * 66.0 - fLastSample2 * 70.0 - fLastSample0 * 33.0 + fLastSample3 * 39.0 + fSample * 7.0 - fLastSample4 * 9.0;
1745 				fWeight4 = fLastSample2*126.0-fLastSample1*124.0+fLastSample0*61.0-fLastSample3*64.0- fSample*12.0+fLastSample4*13.0;
1746 				fWeight5 = ( fLastSample1 - fLastSample2 ) * 50.0 + ( fLastSample3 - fLastSample0 ) * 25.0 + ( fSample - fLastSample4 ) * 5.0;
1747 			}
1748 		}
1749 		iCircularBuffer %= iBufferLength;
1750 		fCurrentSamplePosition = iSampleOffset + fFractionalPart;
1751 	}
1752 
1753 	this.fSampleAccumulator = fLastSample0;
1754 	this.fSampleAccumulator1 = fLastSample1;
1755 	this.fSampleAccumulator2 = fLastSample2;
1756 	this.fSampleAccumulator3 = fLastSample3;
1757 	this.fSampleAccumulator4 = fLastSample4;
1758 	this.setSamplePosition( fCurrentSamplePosition );
1759 	this.setCircularBufferPosition( iCircularBuffer );
1760 };
1761 
1762 
1763 // ---------------------------------------------------------------------------
1764 /**
1765  * Make the audio data for this sound Channel.
1766  *
1767  * @param {int} iSamplesToFill = The number of samples to make.
1768  */
1769 weasel.Channel.prototype.make = function( iSamplesToFill )
1770 {
1771 	if( iSamplesToFill <= 0 )
1772 	{
1773 		return;
1774 	}
1775 
1776 	var oSample = this.getSample();
1777 	if( null == oSample )
1778 	{
1779 		this.makeSilence( iSamplesToFill );
1780 		return;
1781 	}
1782 
1783 	if( this.bDelaySampleStart )
1784 	{
1785 		// Simulate the pause that occurs between a sample being stopped and a new sample
1786 		// starting. This is ~12 scanlines in Ultimate Soundtracker 1.21, or 0.768ms.
1787 		//
1788 		var iSamplesToDelay = this.iDelayInSamples > iSamplesToFill ? iSamplesToFill : this.iDelayInSamples;
1789 
1790 		this.makeSilence( iSamplesToDelay );
1791 
1792 		iSamplesToFill -= iSamplesToDelay;
1793 		this.iDelayInSamples -= iSamplesToDelay;
1794 
1795 		if( this.iDelayInSamples <= 0 )
1796 		{
1797 			this.bDelaySampleStart = false;
1798 		}
1799 
1800 		if( iSamplesToFill <= 0 )
1801 		{
1802 			return;
1803 		}
1804 	}
1805 	
1806 	var fCurrentSamplePosition = this.getSamplePosition();
1807 	var iSampleEnd = oSample.getLength();
1808 	var fFreqStep = this.getFrequencyStepRate();
1809 
1810 	if( oSample.isLooped() )
1811 	{
1812 		iSampleEnd = oSample.getLoopStart() + oSample.getLoopLength();
1813 
1814 		// If a Noisetracker Loop Quirk sample is playing, then the chained sample
1815 		// occurs when the ENTIRE sample ends (not the end of the sample loop)
1816 		// after the initial starting of the sample for the first time.
1817 		// Once the sample loop is playing then sample chaining occurs as normal.
1818 		//
1819 		if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && true == oSample.getNoisetrackerLoopQuirkMode() && false == oSample.getLoopedYet() )
1820 		{
1821 			iSampleEnd = oSample.getLength();
1822 		}
1823 	}
1824 	else if( 0 == iSampleEnd || fCurrentSamplePosition >= iSampleEnd )
1825 	{
1826 		// Check to see if a Pending Sample object exist and if its Looped.
1827 		//
1828 		if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && this.iPendingInstrument != this.iCurrentInstrument && this.oPendingInstrument != null && this.oPendingInstrument.getSample().isLooped() )
1829 		{
1830 			// Switch to pending sample's loop starting point.
1831 			// This bug/undocumented feature was introduced with Noisetracker
1832 			// and spread into Soundtracker 2.5 and the Protracker series.
1833 			// It allows the ability to start playing another sample as soon
1834 			// as the previous one has finished (chained samples).
1835 			// But only if the pending sample is looped, upon which only the loop
1836 			// is played.
1837 			//
1838 			var oPendingSample = this.oPendingInstrument.getSample();
1839 			oPendingSample.setLoopedYet( true );
1840 			this.setSample( oPendingSample );
1841 			this.setInstrumentNumber( this.iPendingInstrument );
1842 			this.setSamplePosition( oPendingSample.getLoopStart() + (fCurrentSamplePosition - iSampleEnd) );
1843 
1844 			this.make( iSamplesToFill );
1845 			return;
1846 		}
1847 
1848 		// Sample for this channel is not playing, so fill with silence (or the last outputted sample).
1849 		//
1850 		this.makeSilence( iSamplesToFill );
1851 		return;
1852 	}
1853 
1854 
1855 	// Are we near end of sample?
1856 	//
1857 	if( ((fFreqStep * iSamplesToFill) + fCurrentSamplePosition) < (iSampleEnd + fFreqStep) )
1858 	{
1859 		// Play Sample without checking for end or loop.
1860 		//
1861 		this.makeNoise( oSample.getSampleArray(), iSamplesToFill );
1862 		return;
1863 	}
1864 	else
1865 	{
1866 		oSample.setLoopedYet( true );
1867 
1868 		// Play sample, checking for end or loop.
1869 		//
1870 		fCurrentSamplePosition = this.getSamplePosition();
1871 
1872 		if( oSample.isLooped() )
1873 		{
1874 			if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && this.iPendingInstrument != this.iCurrentInstrument && this.oPendingInstrument != null )
1875 			{
1876 				// Noisetracker/Soundtracker2.5/Protracker sample chaining.
1877 				// Play current sample up to end of sample loop.
1878 				//
1879 				var iSamplesToLoopEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep );
1880 				this.makeNoise( oSample.getSampleArray(), iSamplesToLoopEnd );
1881 				iSamplesToFill -= iSamplesToLoopEnd;
1882 
1883 				var oPendingSample = this.oPendingInstrument.getSample();
1884 				oPendingSample.setLoopedYet( true );
1885 
1886 				this.setSample( oPendingSample );
1887 				this.setInstrumentNumber( this.iPendingInstrument );
1888 
1889 				if( oPendingSample.isLooped() )
1890 				{
1891 					// Switch to pending instrument's loop.
1892 					//
1893 					this.setSamplePosition( oPendingSample.getLoopStart() + (this.getSamplePosition() - iSampleEnd) );
1894 				}
1895 				else
1896 				{
1897 					// Pending instrument is not looped, so there
1898 					// is no loop point to use, so switch sample and
1899 					// jump to its end.
1900 					//
1901 					this.setSamplePosition( oPendingSample.getLength() );
1902 				}
1903 
1904 				// Start using Pending Sample instead.
1905 				this.make( iSamplesToFill );
1906 				return;
1907 			}
1908 
1909 			// Play looped sample, but allow playing past sample end and into
1910 			// barrel loop, to reduce the number of iterations required to play sample.
1911 			//
1912 			iSampleEnd += oSample.getBarrelLoopSize();
1913 
1914 			var iLoopStart = oSample.getLoopStart();
1915 			var iLoopLength= oSample.getLoopLength();
1916 			var iRealEndOfLoop = iLoopStart + iLoopLength;
1917 			// Loop sample.
1918 			//
1919 			while( iSamplesToFill > 0 )
1920 			{
1921 				fCurrentSamplePosition = this.getSamplePosition();
1922 				var iIterationsToEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep );
1923 				iIterationsToEnd = iIterationsToEnd >= iSamplesToFill ? iSamplesToFill : iIterationsToEnd;
1924 
1925 				// Play up to, but not beyond end of sample loop point.
1926 				//
1927 				this.makeNoise( oSample.getSampleArray(), iIterationsToEnd );
1928 				iSamplesToFill -= iIterationsToEnd;
1929 
1930 				// Correct sample position to real position within loop points (not within barrel loop).
1931 				//
1932 				fCurrentSamplePosition = this.getSamplePosition();
1933 				if( fCurrentSamplePosition > iRealEndOfLoop )
1934 				{
1935 					this.setSamplePosition( iLoopStart + (( fCurrentSamplePosition - iRealEndOfLoop ) % iLoopLength ) );
1936 				}
1937 			}
1938 		}
1939 		else
1940 		{
1941 			var iIterationsToEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep );
1942 
1943 			// Play up to, but not beyond end of non-looped sample.
1944 			//
1945 			this.makeNoise( oSample.getSampleArray(), iIterationsToEnd );
1946 			iSamplesToFill -= iIterationsToEnd;
1947 
1948 			// Non-looped sample may have ended, but still has samples to fill
1949 			// which get converted into makeSilence().
1950 			//
1951 			this.make( iSamplesToFill );
1952 		}
1953 
1954 	}
1955 };
1956 
1957 // ---------------------------------------------------------------------------
1958 /**
1959  * Make/render using linear interpolation, only call this function if frequency
1960  * step rate is less than or equal to 1 or errors will occur, this routine is currently
1961  * here but not used to highlight the bug that exists when switching between interpolation methods
1962  * within a sample (the interpolation methods are all mis aligned). 
1963  * 
1964  * @param {Array} aSampleData  = The current Instrument Sample Data to render.
1965  * @param {int} iSamplesToFill = The Samples to make.
1966  * 
1967  * @private
1968  */
1969 weasel.Channel.prototype.makeNoiseLinearInterpolationNew = function( aSampleData, iSamplesToFill )
1970 {
1971 	var fCurrentSamplePosition = this.getSamplePosition();
1972 	var aBuffer = this.getCircularAudioBuffer();
1973 	var iCircularBuffer = this.getCircularBufferPosition();
1974 	var iBufferLength = aBuffer.length;
1975 	var fVolume = this.getVolume() / 64.0;
1976 
1977 	// Play Sample without checking for end or loop.
1978 	//
1979 	var iSampleOffset = fCurrentSamplePosition | 0;
1980 	var fSample = aSampleData[ iSampleOffset ];
1981 	var iMod = aSampleData.length;
1982 	fCurrentSamplePosition = (fCurrentSamplePosition + 1) % iMod;
1983 	iSampleOffset = fCurrentSamplePosition | 0;
1984 	var fNextSample = aSampleData[ iSampleOffset ];
1985 
1986 	for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(); --iSamples >= 0; )
1987 	{
1988 		var fInterpolation = fCurrentSamplePosition - iSampleOffset;
1989 		var fSampleInterpolation = ( fSample * ( 1.0 - fInterpolation ) + ( fNextSample * fInterpolation ) );
1990 		aBuffer[ iCircularBuffer++ ] = fSampleInterpolation * fVolume;
1991 		iCircularBuffer %= iBufferLength;
1992 		fCurrentSamplePosition += fFreqStep;
1993 		
1994 		if( ((fCurrentSamplePosition | 0) - iSampleOffset) >= 1  )
1995 		{
1996 			fSample = fNextSample;
1997 			iSampleOffset = fCurrentSamplePosition | 0;
1998 			fNextSample = aSampleData[ iSampleOffset ];
1999 		}
2000 	}
2001 
2002 	this.setSamplePosition( (fCurrentSamplePosition + iMod -1) % iMod );
2003 	this.setCircularBufferPosition( iCircularBuffer );
2004 };
2005 
2006 
2007 // ---------------------------------------------------------------------------
2008 /**
2009  * Is TJC Soundtracker 2 Effect Command AutoSlide enabled?
2010  * 
2011  * @return {bool} = True : AutoSlide enabled, False : AutoSlide disabled.
2012  */
2013 weasel.Channel.prototype.getAutoSlide = function()
2014 {
2015 	return this.bAutoSlide;
2016 };
2017 
2018 // ---------------------------------------------------------------------------
2019 /**
2020  * Set TJC Soundtracker 2 Effect Command AutoSlide enabled.
2021  * 
2022  * @param {bool} bAutoSlide = True : AutoSlide enabled, False : AutoSlide disabled.
2023  */
2024 weasel.Channel.prototype.setAutoSlide = function( bAutoSlide )
2025 {
2026 	this.bAutoSlide = bAutoSlide;
2027 };
2028 
2029 // ---------------------------------------------------------------------------
2030 /**
2031  * Get TJC Soundtracker 2 Effect Command AutoSlide value.
2032  * 
2033  * @return {int} = The amount to slide the volume by this tick [-15 to 15].
2034  */
2035 weasel.Channel.prototype.getAutoSlideValue = function()
2036 {
2037 	return this.iAutoSlideValue;
2038 };
2039 
2040 // ---------------------------------------------------------------------------
2041 /**
2042  * Set TJC Soundtracker 2 Effect Command AutoSlide enabled.
2043  * 
2044  * @param {int} iAutoSlideValue = The amount to slide the volume by this tick [-15 to 15].
2045  */
2046 weasel.Channel.prototype.setAutoSlideValue = function( iAutoSlideValue )
2047 {
2048 	this.iAutoSlideValue = iAutoSlideValue;
2049 };
2050 
2051 // ---------------------------------------------------------------------------
2052 /**
2053  * Set Noisetracker Effect Command Note Portomento target note period (the note to slide too).
2054  * 
2055  * @param {int} iTargetPeriod = The note period to slide too.
2056  */
2057 weasel.Channel.prototype.setNotePortamentoTarget = function( iTargetPeriod )
2058 {
2059 	this.iNotePortamentoTargetPeriod = iTargetPeriod;
2060 };
2061 
2062 // ---------------------------------------------------------------------------
2063 /**
2064  * Set Noisetracker Effect Command Note Portomento slide speed.
2065  * 
2066  * @param {int} iPortamentoSpeed = The speed at which to slide the note, if not zero.
2067  */
2068 weasel.Channel.prototype.setNotePortamentoSpeed = function( iPortamentoSpeed )
2069 {
2070 	if( iPortamentoSpeed > 0 )
2071 	{
2072 		this.iNotePoramentoSpeed = iPortamentoSpeed;
2073 	}
2074 };
2075 
2076 // ---------------------------------------------------------------------------
2077 /**
2078  * Set Noisetracker Vibrato Table Position.
2079  * 
2080  * @param {int} iTableOffset = The current position in the sinewave [0-255]
2081  * of the Vibrato, set to zero when a new sample is started in Noisetracker.
2082  */
2083 weasel.Channel.prototype.setVibratoTablePosition = function( iTableOffset )
2084 {
2085 	this.iVibratoTablePosition = iTableOffset & 0xff;
2086 };
2087 
2088 // ---------------------------------------------------------------------------
2089 /**
2090  * Set Protracker Tremolo Table Position.
2091  * 
2092  * @param {int} iTableOffset = The current position in the sinewave [0-255]
2093  * of the Tremolo, set to zero when a new sample is started in Protracker.
2094  */
2095 weasel.Channel.prototype.setTremoloTablePosition = function( iTableOffset )
2096 {
2097 	this.iTremoloTablePosition = iTableOffset & 0xff;
2098 };
2099 
2100 // ---------------------------------------------------------------------------
2101 /**
2102  * Set/Save Protracker Sample Offset for latter use when 00 is used as a Parameter.
2103  * 
2104  * @param {int} iSampleOffsetParameter = The parameter part of the Set Sample Offset command [0-255].
2105  */
2106 weasel.Channel.prototype.setLastSampleOffsetParameter = function( iSampleOffsetParameter )
2107 {
2108 	this.iLastSampleOffsetParameter = iSampleOffsetParameter;
2109 };
2110 // ---------------------------------------------------------------------------
2111 /**
2112  * Get the saved Protracker Sample Offset for latter use when 00 is used as a Parameter.
2113  * 
2114  * @return {int} = The parameter part of the previously saved Set Sample Offset command.
2115  */
2116 weasel.Channel.prototype.getLastSampleOffsetParameter = function( )
2117 {
2118 	return this.iLastSampleOffsetParameter;
2119 };
2120 
2121 // ---------------------------------------------------------------------------
2122 /**
2123  * Protracker Set Sample Offset command, start playing a sample from a given offset instead of zero.
2124  * 
2125  * @param {int} iSampleOffset = The sample offset.
2126  */
2127 weasel.Channel.prototype.setPlayFromOffset = function( iSampleOffset )
2128 {
2129 	this.iSampleStartOffset = iSampleOffset;
2130 };
2131 
2132 // ---------------------------------------------------------------------------
2133 /**
2134  * Protracker get Sample Offset, the starting offset in samples to play the current sample.
2135  * 
2136  * @return {int} = The starting offset in samples to play the current sample.
2137  */
2138 weasel.Channel.prototype.playFromOffset = function( )
2139 {
2140 	return this.iSampleStartOffset;
2141 };
2142 
2143 // ---------------------------------------------------------------------------
2144 /**
2145  * Set Protracker Sample Fine Tune.
2146  * 
2147  * @param {int} iFineTune = The Fine Tune value as stored in the pattern (0-15).
2148  */
2149 weasel.Channel.prototype.setFineTune = function( iFineTune )
2150 {
2151 	this.iFineTune = iFineTune;
2152 };
2153 
2154 // ---------------------------------------------------------------------------
2155 /**
2156  * Get Protracker Sample Fine Tune.
2157  * 
2158  * @return {int} = The Fine Tune value as stored in the pattern (0-15).
2159  */
2160 weasel.Channel.prototype.getFineTune = function( )
2161 {
2162 	return this.iFineTune;
2163 };
2164 
2165 // ---------------------------------------------------------------------------
2166 /**
2167  * Get Protracker Pattern Row Loop point.
2168  * 
2169  * @return {int} = The row number in the current pattern to loop too.
2170  */
2171 weasel.Channel.prototype.getPatternRowLoopStart = function( )
2172 {
2173 	return this.iPatternRowLoopStart;
2174 };
2175 
2176 // ---------------------------------------------------------------------------
2177 /**
2178  * Set Protracker Pattern Row Loop Effect Command.
2179  * 
2180  * @param {int} iRow = The row number in the current pattern to loop to.
2181  */
2182 weasel.Channel.prototype.setPatternRowLoopStart = function( iRow )
2183 {
2184 	this.iPatternRowLoopStart = iRow;
2185 };
2186 
2187 // ---------------------------------------------------------------------------
2188 /**
2189  * Get Protracker Pattern Row Loop counter.
2190  * 
2191  * @return {int} = The current Pattern Row Loop counter value.
2192  */
2193 weasel.Channel.prototype.getPatternRowLoopCounter = function( )
2194 {
2195 	return this.iPatternRowLoopCounter;
2196 };
2197 
2198 // ---------------------------------------------------------------------------
2199 /**
2200  * Set Protracker Pattern Row Loop Effect Command's loop counter.
2201  * 
2202  * @param {int} iLoopCounter = The number of times to loop.
2203  */
2204 weasel.Channel.prototype.setPatternRowLoopCounter = function( iLoopCounter )
2205 {
2206 	this.iPatternRowLoopCounter = iLoopCounter;
2207 };
2208 
2209 // ---------------------------------------------------------------------------
2210 /**
2211  * Decrement Protracker Pattern Row Loop Effect Command's loop counter.
2212  */
2213 weasel.Channel.prototype.decPatternRowLoopCounter = function( )
2214 {
2215 	if( --this.iPatternRowLoopCounter < 0 )
2216 		this.iPatternRowLoopCounter = 0;
2217 };
2218 
2219 
2220 // ---------------------------------------------------------------------------
2221 /**
2222  * Get Protracker Vibrato Continue Waveform setting.
2223  * 
2224  * @return {bool} = Whether the vibrato waveform will be restart when a new note is encountered.
2225  */
2226 weasel.Channel.prototype.getProtrackerContinueVibratoWaveform = function( )
2227 {
2228 	return this.bVibratoContinueWaveform;
2229 };
2230 
2231 // ---------------------------------------------------------------------------
2232 /**
2233  * Set Protracker Vibrato Continue Waveform when a note is encountered or not.
2234  * 
2235  * @param {bool} bContinue = true : do not restart the waveform when a new note is encountered. false : Restart vibrato waveform when a new note is encountered.
2236  */
2237 weasel.Channel.prototype.setProtrackerContinueVibratoWaveform = function( bContinue )
2238 {
2239 	this.bVibratoContinueWaveform = bContinue;
2240 };
2241 
2242 
2243 // ---------------------------------------------------------------------------
2244 /**
2245  * Get Protracker Vibrato Waveform Type setting.
2246  * 
2247  * @return {weasel.Channel.prototype.ProtrackerVibratoWaveform} = The Protracker Vibrato Waveform type.
2248  */
2249 weasel.Channel.prototype.getProtrackerVibratoWaveformType = function( )
2250 {
2251 	return this.iVibratoWaveformType;
2252 };
2253 
2254 // ---------------------------------------------------------------------------
2255 /**
2256  * Set Protracker Vibrato Waveform Type used for the Protracker Vibrato command.
2257  * 
2258  * @param {weasel.Channel.prototype.ProtrackerVibratoWaveform} iWaveformType = The Vibrato Waveform type to use.
2259  */
2260 weasel.Channel.prototype.setProtrackerVibratoWaveformType = function( iWaveformType )
2261 {
2262 	this.iVibratoWaveformType = iWaveformType;
2263 };
2264 
2265 // ---------------------------------------------------------------------------
2266 /**
2267  * Set Protracker Tremolo Waveform Type used for the Protracker Tremolo command.
2268  * 
2269  * @param {weasel.Channel.prototype.ProtrackerTremoloWaveform} iWaveformType = The Tremolo Waveform type to use.
2270  */
2271 weasel.Channel.prototype.setProtrackerTremoloWaveformType = function( iWaveformType )
2272 {
2273 	this.iTremoloWaveformType = iWaveformType;
2274 };
2275 
2276 // ---------------------------------------------------------------------------
2277 /**
2278  * Get Protracker Tremolo Waveform Type setting.
2279  * 
2280  * @return {weasel.Channel.prototype.ProtrackerTremoloWaveform} = The Protracker Tremolo Waveform type.
2281  */
2282 weasel.Channel.prototype.getProtrackerTremoloWaveformType = function( )
2283 {
2284 	return this.iTremoloWaveformType;
2285 };
2286 
2287 // ---------------------------------------------------------------------------
2288 /**
2289  * Get Protracker Tremolo Continue Waveform setting.
2290  * 
2291  * @return {bool} = Whether the Tremolo waveform will be restart when a new note is encountered.
2292  */
2293 weasel.Channel.prototype.getProtrackerContinueTremoloWaveform = function( )
2294 {
2295 	return this.bTremoloContinueWaveform;
2296 };
2297 
2298 // ---------------------------------------------------------------------------
2299 /**
2300  * Set Protracker Tremolo Continue Waveform when a note is encountered or not.
2301  * 
2302  * @param {bool} bContinue = true : do not restart the waveform when a new note is encountered. false : Restart Tremolo waveform when a new note is encountered.
2303  */
2304 weasel.Channel.prototype.setProtrackerContinueTremoloWaveform = function( bContinue )
2305 {
2306 	this.bTremoloContinueWaveform = bContinue;
2307 };
2308 
2309 // ---------------------------------------------------------------------------
2310 /**
2311  * Get current pattern cell.
2312  * 
2313  * @return {weasel.PatternCell|weasel.MKPatternCell} = The current pattern cell.
2314  */
2315 weasel.Channel.prototype.getCurrentPatternCell = function( )
2316 {
2317 	return this.oPatternCell;
2318 };
2319 
2320 // ---------------------------------------------------------------------------
2321 /**
2322  * Set current pattern cell (this is just used for easy reference and does not
2323  * set the effects/note period/instrument of the current channel).
2324  * 
2325  * @param {weasel.PatternCell|weasel.MKPatternCell} oPatternCell = The current pattern cell, can be null.
2326  */
2327 weasel.Channel.prototype.setCurrentPatternCell = function( oPatternCell )
2328 {
2329 	this.oPatternCell = oPatternCell;
2330 };
2331 
2332 // ---------------------------------------------------------------------------
2333 /**
2334  * Set Protracker Invert Loop Speed.
2335  * 
2336  * @param {int} iInvertLoopSpeed = The Invert Loop Speed.
2337  */
2338 weasel.Channel.prototype.setProtrackerInvertLoopSpeed = function( iInvertLoopSpeed )
2339 {
2340 	if( iInvertLoopSpeed < 0 )
2341 	{
2342 		iInvertLoopSpeed = 0;
2343 	}
2344 	else if( iInvertLoopSpeed > 15 )
2345 	{
2346 		iInvertLoopSpeed = 15;
2347 	}
2348 	this.iInvertLoopSpeed = iInvertLoopSpeed;
2349 };
2350 
2351 // ---------------------------------------------------------------------------
2352 /**
2353  * Set Protracker Glissando mode, which is used during Note Portamento (Glissando
2354  * limits the pitch bend to actual physical keys on a keyboard, for example
2355  * a piano cannot smooth pitch bend between two adjacent keys to goes from one to
2356  * the other. Unlike a trombone).
2357  * 
2358  * @param {bool} bGlissandoMode = Enable/ disable Glissando when using Toneportamento.
2359  */
2360 weasel.Channel.prototype.setGlissando = function( bGlissandoMode )
2361 {
2362 	this.bGlissandoMode = bGlissandoMode;
2363 };
2364 
2365 // ---------------------------------------------------------------------------
2366 /**
2367  * Get Protracker Invert Loop Speed.
2368  * 
2369  * @return {int} = The Protracker Invert Loop Speed.
2370  */
2371 weasel.Channel.prototype.getProtrackerInvertLoopSpeed = function( )
2372 {
2373 	return this.iInvertLoopSpeed;
2374 };
2375 
2376 
2377 // ---------------------------------------------------------------------------
2378 /**
2379  * Set Protracker Invert Loop Offset.
2380  * 
2381  * @param {weasel.Instrument} oInstrument = The current instrument.
2382  */
2383 weasel.Channel.prototype.setProtrackerInvertLoopOffset = function( oInstrument )
2384 {
2385 	// Have to use the instruments attributes as the sample's attributes may have
2386 	// been modified due to Noisetracker/Protracker Loop Quirks mode.
2387 	//
2388 	var iLoopOffsetInBytes = oInstrument.getLoopOffsetInBytes();
2389 	var iOriginalSampleSize = oInstrument.getLengthInWords() * 2;
2390 
2391 	if(iLoopOffsetInBytes > iOriginalSampleSize )
2392 	{
2393 		iLoopOffsetInBytes = iOriginalSampleSize;
2394 	}
2395 
2396 	this.iInvertLoopSampleOffset = this.iInvertLoopSampleLoopStart = iLoopOffsetInBytes;
2397 };
2398 
2399 // ---------------------------------------------------------------------------
2400 /**
2401  * Handle Protracker Invert Loop updates to the current sample.
2402  * 
2403  */
2404 weasel.Channel.prototype.updateProtrackerInvertLoop = function( )
2405 {
2406 	var iSpeed = this.iInvertLoopSpeed;
2407 
2408 	if( 0 == iSpeed )
2409 	{
2410 		return;
2411 	}
2412 
2413 	var iUpdateTime = this.iInvertLoopTimeDelay;
2414 	iUpdateTime += weasel.FormatProTrackerMK.InvertSampleLoop[ iSpeed ];
2415 
2416 	if( iUpdateTime < 128 )
2417 	{
2418 		this.iInvertLoopTimeDelay = iUpdateTime;
2419 		return;
2420 	}
2421 
2422 	this.iInvertLoopTimeDelay = 0;
2423 	var oSample = this.getSample();
2424 
2425 	if( !oSample )
2426 	{
2427 		return;
2428 	}
2429 
2430 	var iLoopEnd = this.iInvertLoopSampleLoopStart + oSample.getLoopLength();
2431 	var iInvertSampleOffset = ++this.iInvertLoopSampleOffset;
2432 	
2433 	if( iInvertSampleOffset >= iLoopEnd )
2434 	{
2435 		this.iInvertLoopSampleOffset = iInvertSampleOffset = this.iInvertLoopSampleLoopStart;
2436 	}
2437 
2438 	oSample.invertSample( iInvertSampleOffset );
2439 };
2440 
2441 // ---------------------------------------------------------------------------
2442 /**
2443  * Get whether there is No Note Delay Quirk to be used when the current sample is played (state changes on a per row basis).
2444  * 
2445  * @return {bool} = true : No Note Delay Quirk (so normal DMA Wait start), false : Note Delay Quirk effecting this channel.
2446  */
2447 weasel.Channel.prototype.getNoNoteDelayQuirk = function( )
2448 {
2449 	return this.bNoNoteDelayQuirk;
2450 };
2451 
2452 // ---------------------------------------------------------------------------
2453 /**
2454  * Set whether there is No Note Delay Quirk to be used when the current sample is played.
2455  * 
2456  * @param {bool} bNoNoteDelayQuirk = True : No Note Delay Quirk, False : Note Delay Quirk effecting this channel.
2457  */
2458 weasel.Channel.prototype.setNoNoteDelayQuirk = function( bNoNoteDelayQuirk )
2459 {
2460 	this.bNoNoteDelayQuirk = bNoNoteDelayQuirk;
2461 };
2462 
2463 // ---------------------------------------------------------------------------
2464 /**
2465  * Get Taketracker/Fasttracker panning position.
2466  * 
2467  * @return {int} = The Taketracker/Fasttracker panning position [0-255] where 0 = full left, 255 full right and 128 is middle.
2468  */
2469 weasel.Channel.prototype.getPanningPosition = function( )
2470 {
2471 	return this.iPanningPosition;
2472 };
2473 
2474 // ---------------------------------------------------------------------------
2475 /**
2476  * Set Taketracker/Fasttracker panning position.
2477  * 
2478  * @param {int} iPanningPosition = The Taketracker/Fasttracker panning position [0-255] where 0 = full left, 255 full right and 128 is middle.
2479  */
2480 weasel.Channel.prototype.setPanningPosition = function( iPanningPosition )
2481 {
2482 	if( iPanningPosition < 0 )
2483 	{
2484 		iPanningPosition = 0;
2485 	}
2486 	else if( iPanningPosition > 255 )
2487 	{
2488 		iPanningPosition = 255;
2489 	}
2490 
2491 	this.iPanningPosition = iPanningPosition;
2492 };
2493 
2494 // ---------------------------------------------------------------------------
2495 /**
2496  * Clear appropriate attributes for a (manual) song restart (not when a song loops round to the beginning).
2497  */
2498 weasel.Channel.prototype.clearChannel = function( )
2499 {
2500 	this.setSample( null );
2501 	this.setNotePeriod( 0 );
2502 	this.setLastSavedNotePeriod( 0 );
2503 	this.setShadowNotePeriod( 0 );
2504 	this.setInstrumentNumber( 0 );
2505 	this.setPendingInstrumentNumber( 0, null );
2506 	this.setVolume( 0 );
2507 	this.setEffectNumber( 0 );
2508 	this.setEffectParameter( 0 );
2509 	this.setAutoSlide( false );
2510 	this.setAutoSlideValue( 0 );
2511 	this.setNotePortamentoTarget( 0 );
2512 	this.iNotePoramentoSpeed = 0;
2513 	this.iVibratoTablePosition = 0;
2514 	this.iLastVibratoParameter = 0;
2515 	this.iLastSampleOffsetParameter = 0;
2516 	this.iSampleStartOffset = 0;
2517 	this.iFineTune = 0;
2518 	this.iPatternRowLoopStart = 0;
2519 	this.iPatternRowLoopCounter = 0;
2520 	this.bVibratoContinueWaveform = false;
2521 	this.iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave;
2522 	this.oPatternCell = null;
2523 	this.iLastTremoloParameter = 0;
2524 	this.iTremoloTablePosition = 0;
2525 	this.bTremoloContinueWaveform = false;
2526 	this.iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave;
2527 	this.iInvertLoopSpeed = 0;
2528 	this.iInvertLoopTimeDelay = 0;
2529 	this.iInvertLoopSampleOffset = 0;
2530 	this.iInvertLoopSampleLoopStart = 0;
2531 	this.bGlissandoMode = false;
2532 	this.bNoNoteDelayQuirk = false;
2533 	this.iPanningPosition = 128;
2534 };
2535 
2536