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 used to store Audio data before it gets sent to the Browser,
  9  *  responsible for mixing the created Channels together into stereo.
 10  * 
 11  * @constructor
 12  * @param {int} iPlaybackFrequency = The playback frequency in hertz to use.
 13  * @param {int} iSoundChannels = The number of sound channels (2 = stereo, anything else will fail).
 14  * @param {int} iBufferSizeInSamples = The size of the audio buffer to use in samples (so this value is the same no matter the number of sound channels).
 15  * 
 16  * @author Warren Willmey 2011
 17  */
 18 weasel.AudioBuffer = function( iPlaybackFrequency, iSoundChannels, iBufferSizeInSamples )
 19 {
 20 	this.iPlaybackFrequency = iPlaybackFrequency;
 21 	this.iSoundChannels = iSoundChannels;
 22 	this.aBuffer = weasel.Helper.getFloat32Array( iBufferSizeInSamples * iSoundChannels );
 23 	this.aCircularAudioBuffer = weasel.Helper.getFloat32Array( iPlaybackFrequency * iSoundChannels );
 24 	this.iBufferSizeInSamples = (this.aBuffer.length / iSoundChannels) | 0;
 25 	this.lWrittenSamples = 0;
 26 	this.iBufferNotFullOffset = 0;
 27 	this.bIgnoreCircularBuffer = true;
 28 
 29 	this.clearAudioBuffers();
 30 
 31 	this.iCircularBufferPosition = 0;
 32 
 33 	this.iMixerType = 1;
 34 	this.fPanningLawDB = -4.5;	// Typical panning law values are -3db (considered analogue mixing desk standard), -4.5db, -6.0db.
 35 	this.fPeakLeft = 0.0;
 36 	this.fPeakRight = 0.0;
 37 	/*
 38 	 * TODO Auto gain is completely missing, currently just a low value constant to stop the majority of clipping from occurring.
 39 	 */
 40 	this.fAutoGain = 0.3;
 41 
 42 	this.bPreviousFilterState = false;
 43 	this.aFilterBufferLeft = weasel.Helper.getFloat32Array( 128 );
 44 	this.aFilterBufferRight = weasel.Helper.getFloat32Array( 128 );
 45 	this.iFilterBufferPosition = 0;
 46 	for( var iLength = this.aFilterBufferLeft.length, aFilterLeft = this.aFilterBufferLeft, aFilterRight = this.aFilterBufferRight; --iLength >= 0; )
 47 	{
 48 		aFilterLeft[ iLength ] = 0.0;
 49 		aFilterRight[ iLength ]= 0.0;
 50 	}
 51 };
 52 
 53 // ---------------------------------------------------------------------------
 54 /** Supported mixer types (that may change in future), also used for 
 55  * display names where '$' is converted to '-' and '_' becomes ' '..
 56  * @enum
 57  * @type {string}
 58  */
 59 weasel.AudioBuffer.prototype.SupportedMixerTypes = {
 60 	  Linear	: 0
 61 	, RLM$D_Fat	: 1
 62 };
 63 
 64 // ---------------------------------------------------------------------------
 65 /**
 66  * Get list of supported mixer type, used for populating a drop down list for user selection.
 67  * 
 68  * @return {weasel.AudioBuffer.prototype.SupportedMixerTypes} = The list of supported mixer types.
 69  */
 70 weasel.AudioBuffer.prototype.getSupportedMixerTypes = function()
 71 {
 72 	return weasel.AudioBuffer.prototype.SupportedMixerTypes;
 73 };
 74 
 75 // ---------------------------------------------------------------------------
 76 /**
 77  * Get the number of written samples, which is the number of samples that have
 78  * left this buffer and been given to the actual audio sub system (the Audio object in most cases).
 79  * This does not include the samples remaining in this buffer, as they have NOT been
 80  * passed to the audio sub system yet.
 81  * 
 82  * @return {long} = The number of Samples written/passed to the audio sub system.
 83  */
 84 weasel.AudioBuffer.prototype.getWrittenSamples = function()
 85 {
 86 	return this.lWrittenSamples;
 87 };
 88 
 89 // ---------------------------------------------------------------------------
 90 /**
 91  * Get the number of sound channels used in this audio buffer.
 92  *
 93  * @return {int} = The number of sound channels used in this buffer (1 = mono, 2 = stereo).
 94  */
 95 weasel.AudioBuffer.prototype.getSoundChannels = function()
 96 {
 97 	return this.iSoundChannels;
 98 };
 99 
100 // ---------------------------------------------------------------------------
101 /**
102  * Get the mixer type.
103  *
104  * @return {int} = The ID of the mixer, 0 = Linear, 1 = RLM D-Fat.
105  */
106 weasel.AudioBuffer.prototype.getMixerType = function()
107 {
108 	return this.iMixerType;
109 };
110 
111 // ---------------------------------------------------------------------------
112 /**
113  * Set the mixer type.
114  *
115  * @param {int} iMixerType = The ID of the mixer to use when mixing the 4 sound channels down to 2 stereo channels, 0 = Linear (quick), 1 = RLM D-Fat (slower).
116  */
117 weasel.AudioBuffer.prototype.setMixerType = function( iMixerType )
118 {
119 	// Currently only 2 mixer types.
120 	//
121 	if( iMixerType != 1 )
122 	{
123 		iMixerType = 0;
124 	}
125 	
126 	this.iMixerType = iMixerType;
127 };
128 
129 // ---------------------------------------------------------------------------
130 /**
131  * Set Ignore Circular Buffer, which records 1 seconds worth of output, only useful for
132  * oscilloscopes and (surprisingly) can be the difference between working and not on slow CPUs.
133  *
134  * @param {bool} bIgnore = true : Dont fill the circular buffer.
135  */
136 weasel.AudioBuffer.prototype.setIgnoreCircularBuffer = function( bIgnore )
137 {
138 	this.bIgnoreCircularBuffer = bIgnore == true ? true : false;
139 };
140 
141 // ---------------------------------------------------------------------------
142 /**
143  * get Ignore Circular Buffer.
144  *
145  * @return {bool} = true : the circular buffer is not being filled.
146  */
147 weasel.AudioBuffer.prototype.getIgnoreCircularBuffer = function()
148 {
149 	return this.bIgnoreCircularBuffer;
150 };
151 
152 
153 //---------------------------------------------------------------------------
154 /**
155 * Clear the audio buffers.
156 *
157 */
158 weasel.AudioBuffer.prototype.clearAudioBuffers = function()
159 {
160 	for( var iLength = this.aBuffer.length, aBuffer = this.aBuffer; --iLength >= 0; )
161 	{
162 		aBuffer[ iLength ] = 0.0;
163 	}
164 
165 	for( var iLength = this.aCircularAudioBuffer.length, aCircularAudioBuffer = this.aCircularAudioBuffer; --iLength >= 0; )
166 	{
167 		aCircularAudioBuffer[ iLength ] = 0.0;
168 	}
169 };
170 
171 // ---------------------------------------------------------------------------
172 /**
173  * Inform AudioBuffer that samples were added to the audio sub system and should
174  * be removed from this AudioBuffer object.
175  * 
176  * @param {long} lSamplesToRemove = The number of samples passed to the audio sub system
177  * 				(that is the number of samples that can now be removed), this value is in mono (ignore stereo or multiple channels). 
178  */
179 weasel.AudioBuffer.prototype.removeSamples = function( lSamplesToRemove )
180 {
181 	if( 0 == lSamplesToRemove )
182 		return;
183 
184 	var iDestination = 0;
185 	var iBufferLengthInSamples = this.getBufferLengthInSamples();
186 	
187 	if( lSamplesToRemove > iBufferLengthInSamples )
188 	{
189 		lSamplesToRemove = iBufferLengthInSamples;
190 	}
191 
192 	if( !this.bIgnoreCircularBuffer )
193 	{
194 		var iCircularBuffer = this.iCircularBufferPosition;
195 
196 		var aSource = this.aBuffer;
197 		var aDest = this.aCircularAudioBuffer;
198 		
199 		for( var iLength = lSamplesToRemove  * this.iSoundChannels, iSource = 0, iMod = aDest.length; iLength > 0 ; )
200 		{
201 			var iMaxLengthCopy = iCircularBuffer + iLength > iMod ? iMod - iCircularBuffer : iLength;
202 
203 			iLength -= iMaxLengthCopy;
204 
205 			for( var iRemainder = iMaxLengthCopy & 3; --iRemainder >= 0; )
206 			{
207 				aDest[ iCircularBuffer++ ] = aSource[ iSource++ ];
208 			}
209 
210 			for( var iBlockCopy = iMaxLengthCopy >>> 2; --iBlockCopy >= 0; )
211 			{
212 				aDest[ iCircularBuffer++ ] = aSource[ iSource++ ];
213 				aDest[ iCircularBuffer++ ] = aSource[ iSource++ ];
214 				aDest[ iCircularBuffer++ ] = aSource[ iSource++ ];
215 				aDest[ iCircularBuffer++ ] = aSource[ iSource++ ];
216 			}
217 
218 			iCircularBuffer %= iMod;
219 		}
220 		this.iCircularBufferPosition = iCircularBuffer;
221 	}
222 
223 	for( var iLength = (iBufferLengthInSamples - lSamplesToRemove - this.samplesToFill() ) * this.iSoundChannels, iSource = lSamplesToRemove * this.iSoundChannels, aBuffer = this.aBuffer; iLength > 0; )
224 	{
225 //		aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
226 		
227 		for( var iRemainder = iLength & 3; --iRemainder >= 0; )
228 		{
229 			aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
230 		}
231 
232 		for( var iBlockCopy = iLength >>> 2; --iBlockCopy >= 0; )
233 		{
234 			aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
235 			aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
236 			aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
237 			aBuffer[ iDestination++ ] = aBuffer[ iSource++ ];
238 		}
239 		
240 		iLength -= iLength;
241 	}
242 
243 	this.iBufferNotFullOffset = iDestination;
244 	this.lWrittenSamples += lSamplesToRemove;
245 
246 };
247 
248 // ---------------------------------------------------------------------------
249 /**
250  * Get the length of the audio buffer in samples (number of channels is ignored, so same value is returned for stereo and mono).
251  * 
252  * @return {int} = The Audio buffer size in samples.
253  */
254 weasel.AudioBuffer.prototype.getBufferLengthInSamples = function()
255 {
256 	return this.iBufferSizeInSamples;
257 };
258 
259 // ---------------------------------------------------------------------------
260 /**
261  * Get the actual audio buffer directly, is used because feeding samples with appendSampleToBuffer() is far too slow.
262  * 
263  * @return {Array} = The Audio buffer array (Audio Channels are interleaved, e.g. left-0, right-0, left-1, right-2, left-3, right-3 etc.
264  */
265 weasel.AudioBuffer.prototype.getBuffer = function()
266 {
267 	return this.aBuffer;
268 };
269 
270 // ---------------------------------------------------------------------------
271 /**
272  * Get the position in the audio buffer directly, is used because feeding samples with appendSampleToBuffer() is far too slow.
273  * 
274  * @return {int} = The offset into the Audio buffer array (beware of stereo etc).
275  */
276 weasel.AudioBuffer.prototype.getBufferPosition = function()
277 {
278 	return this.iBufferNotFullOffset;
279 };
280 
281 // ---------------------------------------------------------------------------
282 /**
283  * Get the samples to fill, the number of sample slots available for use within this audio buffer (ignores the number of channels).
284  * 
285  * @return {int} = Number of sample slots available.
286  */
287 weasel.AudioBuffer.prototype.samplesToFill = function()
288 {
289 	return this.getBufferLengthInSamples() - ((this.iBufferNotFullOffset / this.iSoundChannels) | 0);
290 };
291 
292 // ---------------------------------------------------------------------------
293 /**
294  * Append a sample to this Audio Sample buffer, incredibly slow, so is not recommended for large call volume.
295  * 
296  * @param {float} fSample = The Sample to add.
297  * 
298  * @exception Is thrown if audio buffer is already full.
299  */
300 weasel.AudioBuffer.prototype.appendSampleToBuffer = function( fSample )
301 {
302 	if( this.iBufferNotFullOffset < this.aBuffer.length )
303 	{
304 		this.aBuffer[ this.iBufferNotFullOffset++ ] = fSample;
305 	}
306 	else
307 	{
308 		throw( 'Audio Buffer is already full, sample is ignored.( Buffer Length: ' + this.aBuffer.length + ', Buffer offset: ' + this.iBufferNotFullOffset + ').' );
309 	}
310 };
311 
312 // ---------------------------------------------------------------------------
313 /**
314  * Get the circular audio buffer containing the rendered sample that is passed to the Audio sub system (its in interleaved stereo format).
315  * 
316  * @return {Array} = The circular array containing the rendered sample data.
317  */
318 weasel.AudioBuffer.prototype.getCircularAudioBuffer = function( )
319 {
320 	return this.aCircularAudioBuffer;
321 };
322 
323 // ---------------------------------------------------------------------------
324 /**
325  * Get the current position in the circular array (rendered sample data is behind this offset).
326  * 
327  * @return {int} = The current position in the circular array containing the rendered sample data.
328  */
329 weasel.AudioBuffer.prototype.getCircularBufferPosition = function( )
330 {
331 	return this.iCircularBufferPosition;
332 };
333 
334 // ---------------------------------------------------------------------------
335 /**
336  * RLM-D FAT Compander, which mixes the individual channels together to make a stereo output.
337  * They are mixed using a inlined case optimised version of RLM-D FAT (no master 
338  * volume control, no overflow clamping, specifically for 2 channels). (a rather 
339  * nice [personal opinion!] routine I came up with on the Atari ST in the 90ies 
340  * which keeps the samples nice and loud with minimal distortion),
341  * Yes the name is wrong (its only half a compander!) I was experimenting with 
342  * sample compression (via bit reduction) at the time and wondered what it sounded
343  * like without the expander!.
344  * 
345  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
346  * @param {Array} aSamples1 = The rendered samples for channel 1.
347  * @param {Array} aSamples2 = The rendered samples for channel 2.
348  * @param {Array} aSamples3 = The rendered samples for channel 3.
349  * @param {Array} aSamples4 = The rendered samples for channel 4.
350  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
351  * 
352  * @private
353  */
354 weasel.AudioBuffer.prototype._companderRLMDFat = function( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset )
355 {
356 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
357 
358 	for( var iLength = iSamplesToFill, iMod = aSamples1.length, aBuffer = this.getBuffer(); iLength > 0; )
359 	{
360 		var iMaxLengthFill = iCircularBufferOffset + iLength > iMod ? iMod - iCircularBufferOffset : iLength;
361 		iLength -= iMaxLengthFill;
362 
363 		while( --iMaxLengthFill >= 0 )
364 		{
365 			var fLeft = (aSamples1[ iCircularBufferOffset ] + aSamples4[ iCircularBufferOffset ]) * 0.5;
366 			var fRight= (aSamples2[ iCircularBufferOffset ] + aSamples3[ iCircularBufferOffset++ ]) * 0.5;
367 
368 			aBuffer[ iBufferNotFullOffset++ ] = fLeft  < 0.0 ? -1.0 + (( 1.0 + fLeft  ) * (1.0 + fLeft  ) ) : 1.0 - ( (1.0 - fLeft  ) * ( 1.0 - fLeft  ) );	// Left.
369 			aBuffer[ iBufferNotFullOffset++ ] = fRight < 0.0 ? -1.0 + (( 1.0 + fRight ) * (1.0 + fRight ) ) : 1.0 - ( (1.0 - fRight ) * ( 1.0 - fRight ) );	// Right.
370 		}
371 		iCircularBufferOffset %= iMod; 
372 	}
373 
374 	this.iBufferNotFullOffset = iBufferNotFullOffset;
375 };
376 
377 // ---------------------------------------------------------------------------
378 /**
379  * Linear mixer for channels down to 2 (stereo) channels, overflow clamping.
380  * 
381  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
382  * @param {Array} aSamples1 = The rendered samples for channel 1.
383  * @param {Array} aSamples2 = The rendered samples for channel 2.
384  * @param {Array} aSamples3 = The rendered samples for channel 3.
385  * @param {Array} aSamples4 = The rendered samples for channel 4.
386  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
387  * 
388  * @private
389  */
390 weasel.AudioBuffer.prototype._mixLinear = function( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset )
391 {
392 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
393 
394 	for( var iLength = iSamplesToFill, iMod = aSamples1.length, aBuffer = this.getBuffer(); iLength > 0; )
395 	{
396 		var iMaxLengthFill = iCircularBufferOffset + iLength > iMod ? iMod - iCircularBufferOffset : iLength;
397 		iLength -= iMaxLengthFill;
398 
399 		while( --iMaxLengthFill >= 0 )
400 		{
401 			var fLeft = (aSamples1[ iCircularBufferOffset ] + aSamples4[ iCircularBufferOffset ]) * 0.5;
402 			var fRight= (aSamples2[ iCircularBufferOffset ] + aSamples3[ iCircularBufferOffset++ ]) * 0.5;
403 			
404 			aBuffer[ iBufferNotFullOffset++ ] = fLeft  < -1.0 ? -1.0 : fLeft  > 1.0 ? 1.0 : fLeft;	// Left.
405 			aBuffer[ iBufferNotFullOffset++ ] = fRight < -1.0 ? -1.0 : fRight > 1.0 ? 1.0 : fRight;	// Right.
406 		}
407 		iCircularBufferOffset %= iMod; 
408 	}
409 
410 	this.iBufferNotFullOffset = iBufferNotFullOffset;
411 	
412 };
413 // ---------------------------------------------------------------------------
414 /**
415  * Linear mixer for channels down to 2 (stereo) channels, overflow clamping, with
416  * simulated Amiga Filter (just an averaging filter for now).
417  * 
418  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
419  * @param {Array} aSamples1 = The rendered samples for channel 1.
420  * @param {Array} aSamples2 = The rendered samples for channel 2.
421  * @param {Array} aSamples3 = The rendered samples for channel 3.
422  * @param {Array} aSamples4 = The rendered samples for channel 4.
423  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
424  * 
425  * @private
426  */
427 weasel.AudioBuffer.prototype._mixLinearFiltered = function( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset )
428 {
429 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
430 	var iFilterBufferPosition = this.iFilterBufferPosition;
431 	var iBarrelLoopMask = 0x7f;
432 	
433 	var iSamplesPerFilter = Math.ceil( (this.iPlaybackFrequency / 7000.0) );
434 	if( iSamplesPerFilter < 2 )
435 	{
436 		// If the sampling rate ever goes close the filter rate always
437 		// filter by an average of 2 samples.
438 		//
439 		iSamplesPerFilter = 2;
440 	}
441 
442 	if( !this.bPreviousFilterState )
443 	{
444 		// Filter has just been switched on, copy some of the previously mixed samples
445 		// into the filter buffer as it is out of sync (no data written to it since
446 		//  last time filter was on).
447 		//
448 		this.bPreviousFilterState = true;
449 		this._updateFilterHistory( iSamplesPerFilter );
450 	}
451 
452 	var fReciprocalAverageDivider = 1.0 / iSamplesPerFilter;
453 	var fLeftTotal = 0.0;
454 	var fRightTotal = 0.0;
455 	for( var iSamplesToTotal = 1; iSamplesToTotal <= iSamplesPerFilter; iSamplesToTotal++ )
456 	{
457 		var iBuffPos = (iFilterBufferPosition - iSamplesToTotal) & iBarrelLoopMask;
458 
459 		fLeftTotal += this.aFilterBufferLeft[ iBuffPos ];
460 		fRightTotal+= this.aFilterBufferRight[ iBuffPos ];
461 	}
462 
463 	for( var iLength = iSamplesToFill, iMod = aSamples1.length, aBuffer = this.getBuffer(), aFilterLeft = this.aFilterBufferLeft, aFilterRight = this.aFilterBufferRight; iLength > 0; )
464 	{
465 		var iMaxLengthFill = iCircularBufferOffset + iLength > iMod ? iMod - iCircularBufferOffset : iLength;
466 		iLength -= iMaxLengthFill;
467 
468 		while( --iMaxLengthFill >= 0 )
469 		{
470 			var fLeft = (aSamples1[ iCircularBufferOffset ] + aSamples4[ iCircularBufferOffset ]) * 0.5;
471 			var fRight= (aSamples2[ iCircularBufferOffset ] + aSamples3[ iCircularBufferOffset++ ]) * 0.5;
472 			
473 			aFilterLeft[ iFilterBufferPosition ] = fLeft;
474 			aFilterRight[ iFilterBufferPosition ] = fRight;
475 
476 			fLeftTotal  += fLeft  - aFilterLeft[  (iFilterBufferPosition - iSamplesPerFilter) & iBarrelLoopMask ];
477 			fRightTotal += fRight - aFilterRight[ (iFilterBufferPosition - iSamplesPerFilter) & iBarrelLoopMask ];
478 			iFilterBufferPosition = ++iFilterBufferPosition & iBarrelLoopMask;
479 
480 			fLeft = fLeftTotal * fReciprocalAverageDivider;
481 			fRight = fRightTotal * fReciprocalAverageDivider;
482 
483 
484 			aBuffer[ iBufferNotFullOffset++ ] = fLeft  < -1.0 ? -1.0 : fLeft  > 1.0 ? 1.0 : fLeft;	// Left.
485 			aBuffer[ iBufferNotFullOffset++ ] = fRight < -1.0 ? -1.0 : fRight > 1.0 ? 1.0 : fRight;	// Right.
486 		}
487 		iCircularBufferOffset %= iMod; 
488 	}
489 
490 	this.iBufferNotFullOffset = iBufferNotFullOffset;
491 	this.iFilterBufferPosition = iFilterBufferPosition;
492 };
493 
494 // ---------------------------------------------------------------------------
495 /**
496  * 
497  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
498  * @param {Array} aSamples1 = The rendered samples for channel 1.
499  * @param {Array} aSamples2 = The rendered samples for channel 2.
500  * @param {Array} aSamples3 = The rendered samples for channel 3.
501  * @param {Array} aSamples4 = The rendered samples for channel 4.
502  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
503  */
504 weasel.AudioBuffer.prototype._companderRLMDFatFiltered = function( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset )
505 {
506 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
507 	var iFilterBufferPosition = this.iFilterBufferPosition;
508 	var iBarrelLoopMask = 0x7f;
509 	
510 	var iSamplesPerFilter = Math.ceil( (this.iPlaybackFrequency / 7000.0) );
511 	if( iSamplesPerFilter < 2 )
512 	{
513 		// If the sampling rate ever goes close the filter rate always
514 		// filter by an average of 2 samples.
515 		//
516 		iSamplesPerFilter = 2;
517 	}
518 	
519 	if( !this.bPreviousFilterState )
520 	{
521 		// Filter has just been switched on, copy some of the previously mixed samples
522 		// into the filter buffer as it is out of sync (no data written to it since
523 		//  last time filter was on).
524 		//
525 		this.bPreviousFilterState = true;
526 		this._updateFilterHistory( iSamplesPerFilter );
527 	}
528 
529 	var fReciprocalAverageDivider = 1.0 / iSamplesPerFilter;
530 	var fLeftTotal = 0.0;
531 	var fRightTotal = 0.0;
532 	for( var iSamplesToTotal = 1; iSamplesToTotal <= iSamplesPerFilter; iSamplesToTotal++ )
533 	{
534 		var iBuffPos = (iFilterBufferPosition - iSamplesToTotal) & iBarrelLoopMask;
535 
536 		fLeftTotal += this.aFilterBufferLeft[ iBuffPos ];
537 		fRightTotal+= this.aFilterBufferRight[ iBuffPos ];
538 	}
539 
540 	for( var iLength = iSamplesToFill, iMod = aSamples1.length, aBuffer = this.getBuffer(), aFilterLeft = this.aFilterBufferLeft, aFilterRight = this.aFilterBufferRight; iLength > 0; )
541 	{
542 		var iMaxLengthFill = iCircularBufferOffset + iLength > iMod ? iMod - iCircularBufferOffset : iLength;
543 		iLength -= iMaxLengthFill;
544 
545 		while( --iMaxLengthFill >= 0 )
546 		{
547 			var fLeft = (aSamples1[ iCircularBufferOffset ] + aSamples4[ iCircularBufferOffset ]) * 0.5;
548 			var fRight= (aSamples2[ iCircularBufferOffset ] + aSamples3[ iCircularBufferOffset++ ]) * 0.5;
549 
550 			aFilterLeft[ iFilterBufferPosition ] = fLeft;
551 			aFilterRight[ iFilterBufferPosition ] = fRight;
552 
553 			fLeftTotal  += fLeft  - aFilterLeft[  (iFilterBufferPosition - iSamplesPerFilter) & iBarrelLoopMask ];
554 			fRightTotal += fRight - aFilterRight[ (iFilterBufferPosition - iSamplesPerFilter) & iBarrelLoopMask ];
555 			iFilterBufferPosition = ++iFilterBufferPosition & iBarrelLoopMask;
556 
557 			fLeft = fLeftTotal * fReciprocalAverageDivider;
558 			fRight = fRightTotal * fReciprocalAverageDivider;
559 
560 			aBuffer[ iBufferNotFullOffset++ ] = fLeft  < 0.0 ? -1.0 + (( 1.0 + fLeft  ) * (1.0 + fLeft  ) ) : 1.0 - ( (1.0 - fLeft  ) * ( 1.0 - fLeft  ) );	// Left.
561 			aBuffer[ iBufferNotFullOffset++ ] = fRight < 0.0 ? -1.0 + (( 1.0 + fRight ) * (1.0 + fRight ) ) : 1.0 - ( (1.0 - fRight ) * ( 1.0 - fRight ) );	// Right.
562 		}
563 		iCircularBufferOffset %= iMod; 
564 	}
565 
566 	this.iBufferNotFullOffset = iBufferNotFullOffset;
567 	this.iFilterBufferPosition = iFilterBufferPosition;
568 };
569 
570 // ---------------------------------------------------------------------------
571 /** When the filter switches from Off to On the filter history does not contain
572  * any sample history so copy them from the already mixed audio buffer (which 
573  * conveniently contains un-filtered samples as the filter has only just been turned
574  * on).
575  * 
576  * @param {int} iSamplesPerFilter =  The number of samples the current filter needs.
577  * 
578  * @private
579  */
580 weasel.AudioBuffer.prototype._updateFilterHistory = function( iSamplesPerFilter )
581 {
582 	var iBarrelLoopMask = 0x7f;
583 	var aMixedSamples = this.getBuffer();
584 	var iCopyMixedFrom = (this.iBufferNotFullOffset - (iSamplesPerFilter * this.iSoundChannels) + aMixedSamples.length) % aMixedSamples.length;
585 	var iBuffPos = (this.iFilterBufferPosition - iSamplesPerFilter) & iBarrelLoopMask;
586 
587 	for( var iSamplesToCopy = 1; iSamplesToCopy <= iSamplesPerFilter; iSamplesToCopy++ )
588 	{
589 		this.aFilterBufferLeft[ iBuffPos ] = aMixedSamples[ iCopyMixedFrom++ ] ;
590 		this.aFilterBufferRight[ iBuffPos++ ] = aMixedSamples[ iCopyMixedFrom++ ] ;
591 		iBuffPos &= iBarrelLoopMask;
592 		iCopyMixedFrom %= aMixedSamples.length;
593 	}
594 };
595 
596 // ---------------------------------------------------------------------------
597 /**
598  * Mix sound channels together.
599  * 
600  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
601  * @param {Array} aSamples1 = The rendered samples for channel 1.
602  * @param {Array} aSamples2 = The rendered samples for channel 2.
603  * @param {Array} aSamples3 = The rendered samples for channel 3.
604  * @param {Array} aSamples4 = The rendered samples for channel 4.
605  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
606  * @param {bool} bFilterOn = Mix using the Amiga filter.
607  * 
608  */
609 weasel.AudioBuffer.prototype.mix = function( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset, bFilterOn )
610 {
611 	if( 0 == this.getMixerType() )
612 	{
613 		if( !bFilterOn )
614 		{
615 			this._mixLinear( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset );
616 			this.bPreviousFilterState = false;
617 		}
618 		else
619 		{
620 			this._mixLinearFiltered( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset );
621 		}
622 	}
623 	else
624 	{
625 		if( !bFilterOn )
626 		{
627 			this._companderRLMDFat( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset );
628 			this.bPreviousFilterState = false;
629 		}
630 		else
631 		{
632 			this._companderRLMDFatFiltered( iSamplesToFill, aSamples1, aSamples2, aSamples3, aSamples4, iCircularBufferOffset );
633 		}
634 	}
635 };
636 
637 //---------------------------------------------------------------------------
638 /**
639  * Convert a decibel value into a linear value.
640  * 
641  * @param {float} fDecibel = The decibel value to convert to a linear modifier, e.g. -3.0, -4.5, -6.0.
642  * 
643  * @return {float} = The decibel value as linear.
644  * 
645  * @private
646  * @deprecated	Remove upon further investigation for surround sound (5.1).
647  */
648 weasel.AudioBuffer.prototype._decibelToLinear = function( fDecibel )
649 {
650 	return Math.pow( 10.0, fDecibel / 20.0 );
651 };
652 
653 //---------------------------------------------------------------------------
654 /**
655  * Panning Law for left channel, reduce the channels sound level the close
656  * it gets to the center (so that the sample volume stays approximately the same
657  * as it pans from left to right).
658  * Typical panning law values are:
659  * 		-3db (considered analogue mixing desk standard)
660  * 		-4.5db
661  * 		-6.0db.
662  * 
663  * @param {float} fPanning = The panning position -1 = full left, 0 = center, +1 = full right.
664  * 
665  * @return {float} = The output volume modifier for the left channel (0-1).
666  * 
667  * @deprecated	Remove upon further investigation for surround sound (5.1).
668  */
669 weasel.AudioBuffer.prototype.panningLawLeft = function( fPanning )
670 {
671 	if( fPanning >= 1.0 )
672 	{
673 		return 0.0;
674 	}
675 	else if( fPanning <= -1.0 )
676 	{
677 		return 1.0;
678 	}
679 
680 	var fLeftLaw = fPanning;
681 	var fLeftPanning = 1.0;
682 	if( fPanning > 0.0 )
683 	{
684 		fLeftLaw = 0.0;
685 		fLeftPanning -= fPanning;
686 	}
687 
688 	return this._decibelToLinear( this.fPanningLawDB *( 1.0 + fLeftLaw ) ) * fLeftPanning;
689 };
690 
691 //---------------------------------------------------------------------------
692 /**
693  * Panning Law for right channel, reduce the channels sound level the close
694  * it gets to the center (so that the sample volume stays approximately the same
695  * as it pans from left to right).
696  * Typical panning law values are:
697  * 		-3db (considered analogue mixing desk standard)
698  * 		-4.5db
699  * 		-6.0db.
700  * 
701  * @param {float} fPanning = The panning position -1 = full left, 0 = center, +1 = full right.
702  * 
703  * @return {float} = The output volume modifier for the right channel (0-1).
704  * 
705  * @deprecated	Remove upon further investigation for surround sound (5.1).
706  */
707 weasel.AudioBuffer.prototype.panningLawRight = function( fPanning )
708 {
709 	if( fPanning <= -1.0 )
710 	{
711 		return 0.0;
712 	}
713 	else if( fPanning >= 1.0 )
714 	{
715 		return 1.0;
716 	}
717 
718 	var fRightLaw = fPanning;
719 	var fRightPanning = 1.0;
720 	if( fPanning < 0.0 )
721 	{
722 		fRightLaw = 0.0;
723 		fRightPanning += fPanning;
724 	}
725 
726 	return this._decibelToLinear( this.fPanningLawDB *( 1.0 - fRightLaw ) ) * fRightPanning;
727 };
728 
729 //---------------------------------------------------------------------------
730 /**
731  * Fasttracker 2 Panning Law, which is (as far as I can tell) a logarithmic curve - or at
732  * least part of the curve with ~2.5db roll off for the center channel.
733  * 
734  * @param {float} fPanning = The panning position -1 = full left, 0 = center, +1 = full right.
735  * 
736  * @return {float} = The output volume modifier for the left channel (0-1).
737  */
738 weasel.AudioBuffer.prototype.panningFT2Left = function( fPanning )
739 {
740 	if( fPanning >= 1.0 )
741 	{
742 		return 0.0;
743 	}
744 	else if( fPanning <= -1.0 )
745 	{
746 		return 1.0;
747 	}
748 
749 	fPanning = ( 1.1 - ( fPanning * 0.9 ) ) * 0.5;
750 
751 	return 1 + ( Math.log( fPanning ) / Math.LN10 );
752 };
753 
754 //---------------------------------------------------------------------------
755 /**
756  * Fasttracker 2 Panning Law, which is (as far as I can tell) a logarithmic curve - or at
757  * least part of the curve with ~2.5db roll off for the center channel.
758  * 
759  * @param {float} fPanning = The panning position -1 = full left, 0 = center, +1 = full right.
760  * 
761  * @return {float} = The output volume modifier for the right channel (0-1).
762  */
763 weasel.AudioBuffer.prototype.panningFT2Right = function( fPanning )
764 {
765 	if( fPanning <= -1.0 )
766 	{
767 		return 0.0;
768 	}
769 	else if( fPanning >= 1.0 )
770 	{
771 		return 1.0;
772 	}
773 
774 	fPanning = (( fPanning * 0.9 ) + 1.1 ) * 0.5;
775 
776 	return 1 + ( Math.log( fPanning ) / Math.LN10 );
777 };
778 
779 //---------------------------------------------------------------------------
780 /**
781  * Mix sound a channel into the AudioBuffer.
782  * 
783  * @param {int} iSamplesToFill = The number of samples to mix together (ignoring stereo).
784  * @param {Array} aSamples = The rendered samples for the channel.
785  * @param {int} iCircularBufferOffset = The position in the above sample circular buffers.
786  * @param {int} iPanningPosition = The panning position of the sample (0=left, 128 = middle, 255 = right).
787  */
788 weasel.AudioBuffer.prototype.mixIn = function( iSamplesToFill, aSamples,  iCircularBufferOffset, iPanningPosition )
789 {
790 	var fPanning = (iPanningPosition - 128) / 128.0; 
791 	var fVolumeLeft  = this.panningFT2Left( fPanning ) * this.fAutoGain;
792 	var fVolumeRight  = this.panningFT2Right( fPanning ) * this.fAutoGain;
793 
794 	for( var iLength = iSamplesToFill, iMod = aSamples.length, aBuffer = this.getBuffer(), iBufferNotFullOffset = this.iBufferNotFullOffset; iLength > 0; )
795 	{
796 		var iMaxLengthFill = iCircularBufferOffset + iLength > iMod ? iMod - iCircularBufferOffset : iLength;
797 		iLength -= iMaxLengthFill;
798 
799 		while( --iMaxLengthFill >= 0 )
800 		{
801 			var fSample = aSamples[ iCircularBufferOffset++ ];
802 
803 			aBuffer[ iBufferNotFullOffset++ ] += fSample * fVolumeLeft;		// Left.
804 			aBuffer[ iBufferNotFullOffset++ ] += fSample * fVolumeRight;	// Right.
805 		}
806 
807 		iCircularBufferOffset %= iMod;
808 	}
809 };
810 
811 //---------------------------------------------------------------------------
812 /**
813  * Clear part of the audio buffer (for use with mod with more than 4 channels
814  * as the mixing process is different).
815  * 
816  * @param {int} iSamplesToFill = The number of samples to clear (ignoring stereo).
817  */
818 weasel.AudioBuffer.prototype.clear = function( iSamplesToFill )
819 {
820 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
821 
822 	for( var iLength = iSamplesToFill, aBuffer = this.getBuffer(); iLength > 0; iLength-- )
823 	{
824 		aBuffer[ iBufferNotFullOffset++ ] = 0.0;	// Left.
825 		aBuffer[ iBufferNotFullOffset++ ] = 0.0;	// Right.
826 	}
827 
828 //	this.iBufferNotFullOffset = iBufferNotFullOffset;
829 };
830 
831 //---------------------------------------------------------------------------
832 /**
833  * Clip part of the audio buffer (for use with mod with more than 4 channels
834  * as the mixing process is different), clips to -1.0 to 1.0 range.
835  * 
836  * @param {int} iSamplesToFill = The number of samples to clear (ignoring stereo).
837  */
838 weasel.AudioBuffer.prototype.clip = function( iSamplesToClip )
839 {
840 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
841 	var fPeakLeft = 0.0;
842 	var fPeakRight = 0.0;
843 
844 	for( var iLength = iSamplesToClip, aBuffer = this.getBuffer(); iLength > 0; iLength-- )
845 	{
846 		var fLeft = aBuffer[ iBufferNotFullOffset ];
847 		var fTemp = Math.abs( fLeft );
848 		if( fTemp > fPeakLeft )
849 		{
850 			fPeakLeft = fTemp;
851 		}
852 
853 		aBuffer[ iBufferNotFullOffset++ ] = fLeft  < -1.0 ? -1.0 : fLeft  > 1.0 ? 1.0 : fLeft;	// Left.
854 
855 		var fRight = aBuffer[ iBufferNotFullOffset ];
856 		var fTemp = Math.abs( fRight );
857 		if( fTemp > fPeakRight )
858 		{
859 			fPeakRight = fTemp;
860 		}
861 
862 		aBuffer[ iBufferNotFullOffset++ ] = fRight < -1.0 ? -1.0 : fRight > 1.0 ? 1.0 : fRight;	// Right.
863 	}
864 
865 	this.iBufferNotFullOffset = iBufferNotFullOffset;
866 	this.fPeakLeft = fPeakLeft;
867 	this.fPeakRight = fPeakRight;
868 };
869 
870 
871 //---------------------------------------------------------------------------
872 /**
873  * Clip part of the audio buffer (for use with mod with more than 4 channels
874  * as the mixing process is different), clips to -1.0 to 1.0 range.
875  * 
876  * @param {int} iSamplesToFill = The number of samples to clear (ignoring stereo).
877  */
878 weasel.AudioBuffer.prototype.clipRLMDFat = function( iSamplesToClip )
879 {
880 	var iBufferNotFullOffset = this.iBufferNotFullOffset;
881 	var fPeakLeft = 0.0;
882 	var fPeakRight = 0.0;
883 
884 	for( var iLength = iSamplesToClip, aBuffer = this.getBuffer(); iLength > 0; iLength-- )
885 	{
886 		var fLeft = aBuffer[ iBufferNotFullOffset ];
887 		var fTemp = Math.abs( fLeft );
888 		if( fTemp > fPeakLeft )
889 		{
890 			fPeakLeft = fTemp;
891 		}
892 
893 		fLeft = fLeft  < -1.0 ? -1.0 : fLeft  > 1.0 ? 1.0 : fLeft;	// Left.
894 		aBuffer[ iBufferNotFullOffset++ ] = fLeft  < 0.0 ? -1.0 + (( 1.0 + fLeft  ) * (1.0 + fLeft  ) ) : 1.0 - ( (1.0 - fLeft  ) * ( 1.0 - fLeft  ) );	// Left.
895 
896 		var fRight = aBuffer[ iBufferNotFullOffset ];
897 		var fTemp = Math.abs( fRight );
898 		if( fTemp > fPeakRight )
899 		{
900 			fPeakRight = fTemp;
901 		}
902 
903 		fRight = fRight < -1.0 ? -1.0 : fRight > 1.0 ? 1.0 : fRight;	// Right.
904 		aBuffer[ iBufferNotFullOffset++ ] = fRight < 0.0 ? -1.0 + (( 1.0 + fRight ) * (1.0 + fRight ) ) : 1.0 - ( (1.0 - fRight ) * ( 1.0 - fRight ) );	// Right.
905 	}
906 
907 	this.iBufferNotFullOffset = iBufferNotFullOffset;
908 	this.fPeakLeft = fPeakLeft;
909 	this.fPeakRight = fPeakRight;
910 };