1 /**
  2  * This file is part of the Web Enabled Audio and Sound Enhancement Library (aka the Weasel audio library) Copyright 2011 - 2013 Warren Willmey. It is covered by the GNU General Public License version 3 as published by the Free Software Foundation, you should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
  3  */
  4 
  5 if( undefined == window.weasel ) window.weasel = {};
  6 
  7 // ---------------------------------------------------------------------------
  8 /** Create a DOC Soundtracker 2.2 module out of the provided data (which has already passed the module sniffer test).
  9  * DOC Soundtracker 2.0 & 2.2 are based upon Master Soundtracker V1.0 which in 
 10  * turn is DOC Soundtracker 9 and Jungle Commands Soundtracker 2 & 3.
 11  * It supports all the Effect Commands in DOC Soundtracker 9 and adds two more:
 12  *   B - Sequence Position Jump.
 13  *   D - Pattern Break.
 14  * DOC Soundtracker 2.0 & 2.2 replay source is based on Jungle Commands Soundtracker 2 & 3.
 15  * DOC Soundtracker 2.2 is also the last of the 15 sample modules, Spreadpoint Soundtracker 2.3
 16  * adds the M.K. format.
 17  * 
 18  * 
 19  * @constructor
 20  * @extends weasel.DOCSoundTracker9
 21  * 
 22  * @param {Array|Uint8Array} aModuleData = The DOC Soundtracker 2.2 module as a byte array that MUST have passed the module sniffer test.
 23  * @param {int} iPlaybackFrequency = The playback frequency in hertz to use (e.g. 44100 ).
 24  * @param {weasel.Sample.prototype.SampleScannerMode} iSampleScannerMode = Scan for IFF Header corruption residue?.
 25  * 
 26  * @author Warren Willmey 2012
 27  */
 28 weasel.DOCSoundTracker22 = function( aModuleData, iPlaybackFrequency, iSampleScannerMode )
 29 {
 30 	this.parent = weasel.DOCSoundTracker9;
 31 	
 32 	// Needed for prototype Inheritance.
 33 	//
 34 	if( aModuleData === undefined || !(( aModuleData instanceof Array ) || ( window.Uint8Array && aModuleData instanceof Uint8Array )) )
 35 		return;
 36 
 37 	this.parent( aModuleData, iPlaybackFrequency, iSampleScannerMode );
 38 	this.sModuleType = weasel.ModuleSniffer.prototype.SupportedModules.DOCSoundTracker22;
 39 
 40 	// DOC Soundtracker 2.0 & 2.2 do not honour the BPM Speed setting and
 41 	// only plays back at 50Hz PAL.
 42 	//
 43 	this.timingOverride( this.TimingOverrides.PAL );
 44 	
 45 	this.aVisitedSequence = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength );
 46 
 47 	this.clearVisitedSequenceTable();
 48 };
 49 
 50 weasel.DOCSoundTracker22.prototype = new weasel.DOCSoundTracker9;
 51 
 52 
 53 // ---------------------------------------------------------------------------
 54 /** Scan all used patterns in sequence order to work out the length of the song
 55  * in ticks, recording each of the sequences starting tick. Due to Sequence Position Jump
 56  * command it may result in certain sequence positions never actually playing.
 57  * Worse, any Position Jump commands that are negative may result in a song that may never
 58  * end.
 59  * 
 60  * @protected
 61  * @override
 62  */
 63 weasel.DOCSoundTracker22.prototype._computeSequenceTableTicks = function( )
 64 {
 65 	var aAlreadyVisitedSequence = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength );
 66 	this.aSequenceTableAccumulatedTicks = new Array( weasel.FormatUltimateSoundTracker121.SequenceTableLength );
 67 
 68 	for( var iLength = aAlreadyVisitedSequence.length; --iLength >= 0; )
 69 	{
 70 		aAlreadyVisitedSequence[ iLength ] = false;
 71 	}
 72 	
 73 	for( var iLength = this.aSequenceTableAccumulatedTicks.length; --iLength >= 0; )
 74 	{
 75 		this.aSequenceTableAccumulatedTicks[ iLength ] = 0;
 76 	}
 77 
 78 	var iTickSpeed = weasel.FormatUltimateSoundTracker121.TicksPerRow;
 79 	var iTickTotal = 0;
 80 	var iSequencePositionJump = -1;
 81 
 82 
 83 	for( var iSongSequenceLength = this.getSongLengthInPatterns(), iSongPosition = 0;
 84 			   iSongPosition < weasel.FormatUltimateSoundTracker121.SequenceTableLength
 85 			&& iSongPosition < iSongSequenceLength
 86 			&& !aAlreadyVisitedSequence[ iSongPosition ]; )
 87 	{
 88 		// Record the Total Ticks needed to get to the beginning of each pattern sequence.
 89 		//
 90 		this.aSequenceTableAccumulatedTicks[ iSongPosition ] = iTickTotal;
 91 		aAlreadyVisitedSequence[ iSongPosition ] = true;
 92 		iSequencePositionJump = -1;
 93 
 94 		var iPatternNumber = this.getPatternNumber( iSongPosition );
 95 		var oPattern = this.getPattern( iPatternNumber );
 96 
 97 		for( var iRowsPerPattern = weasel.FormatUltimateSoundTracker121.NumberOfRowsPerPattern, iRow = 0, bPatternBreak = false; (iRow < iRowsPerPattern) && !bPatternBreak; iRow++ )
 98 		{
 99 			// Order of channels important, there may be more than one Tick Speed effect in a single row.
100 			//
101 			for( var iChannels = this.aSoundChannels.length, iChannel = -1; ++iChannel < iChannels; )
102 			{
103 				var oColumn = oPattern.getColumn( iChannel );
104 				var oPatternCell = oColumn.getCell( iRow );
105 				var iEffectData = oPatternCell.getEffectParameter();
106 
107 				switch( oPatternCell.getEffectNumber() )
108 				{
109 					case weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump :
110 
111 							// If Pattern Break command occurs in any other channel, this command
112 							// turns it into a Sequence Position Jump command instead.
113 							//
114 							bPatternBreak = true;
115 							iSequencePositionJump = iEffectData;
116 						break;
117 
118 					case weasel.FormatDOCSoundTracker22.Effects.PatternBreak :
119 
120 							// Pattern break.
121 							//
122 							bPatternBreak = true;
123 						break;
124 
125 					case weasel.FormatDOCSoundTracker9.Effects.TickSpeed :
126 
127 							// Tick Speed has changed.
128 							//
129 							var iWantedTickSpeed = this._sequenceTableTickSpeedReader( iEffectData );
130 
131 							if( 0 != iWantedTickSpeed )
132 							{
133 								iTickSpeed = iWantedTickSpeed;
134 							}
135 
136 						break;
137 
138 						default:
139 							break;
140 				}
141 			}
142 
143 			iTickTotal += iTickSpeed;
144 		}
145 
146 		// Move onto next Song Position in Sequence List.
147 		//
148 		if( -1 == iSequencePositionJump )
149 		{
150 			iSongPosition++;
151 		}
152 		else
153 		{
154 			iSongPosition = iSequencePositionJump;
155 		}
156 	}
157 
158 	this.iSongLengthInTicks = iTickTotal;
159 };
160 
161 // ---------------------------------------------------------------------------
162 /** Sequence table tick speed reader, reads tick speed from pattern during .
163  * 
164  * @param {int} iEffectData = The effect data of the pattern cell.
165  * 
166  * @return {int} The Tick Speed.
167  * 
168  * @protected
169  */
170 weasel.DOCSoundTracker22.prototype._sequenceTableTickSpeedReader = function( iEffectData )
171 {
172 	return iEffectData & 0xf;
173 };
174 
175 
176 
177 // ---------------------------------------------------------------------------
178 /** Set the row tick speed.
179  * 
180  * @param {int} iTickSpeed = The new row tick speed to use (0-15), a value of 0 is ignored.
181  * 
182  * @override
183  */
184 weasel.DOCSoundTracker22.prototype.setTickSpeed = function( iTickSpeed )
185 {
186 	if( iTickSpeed <= 0 )
187 		return;
188 
189 	this.iTickSpeed = iTickSpeed > 15 ? 15 : iTickSpeed;
190 };
191 
192 // ---------------------------------------------------------------------------
193 /** Process a channel's effects that occur on Tick Zero.
194  * 
195  * @param {weasel.Channel} oChannel = The Channel to process for effects.
196  * 
197  * @protected
198  * @override
199  */
200 weasel.DOCSoundTracker22.prototype._processChannelTick0Effect = function( oChannel )
201 {
202 	switch( oChannel.getEffectNumber() )
203 	{
204 		case weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump :
205 
206 				// If Pattern Break command occurs in any other channel, this command
207 				// turns it into a Sequence Position Jump command instead.
208 				//
209 				var iSequenceJump = oChannel.getEffectParameter();
210 
211 				this.bPatternBreak = true;
212 				this.iPatternBreakToRow = 0;
213 				this.iSequencePositionJump = iSequenceJump;
214 			break;
215 
216 		case weasel.FormatDOCSoundTracker9.Effects.Volume :
217 
218 				var iVolume = oChannel.getEffectParameter();
219 
220 				if( iVolume > 64 )
221 				{
222 					iVolume = 64;
223 				}
224 
225 				oChannel.setVolume( iVolume );
226 
227 			break;
228 
229 		case weasel.FormatDOCSoundTracker22.Effects.PatternBreak :
230 
231 				// Pattern break.
232 				//
233 				this.bPatternBreak = true;
234 				this.iPatternBreakToRow = 0;
235 			break;
236 
237 		case weasel.FormatDOCSoundTracker9.Effects.Filter :
238 
239 				var iFilter = oChannel.getEffectParameter() & 1;
240 
241 				this.bFilterOn = iFilter == 0;	// Filter On = 0 (Amiga Power LED On), Filter Off = 1 (Amiga Power LED Off).
242 			break;
243 
244 		case weasel.FormatDOCSoundTracker9.Effects.TickSpeed :
245 
246 				var iWantedTickSpeed = oChannel.getEffectParameter() & 0xf;
247 				this.setTickSpeed( iWantedTickSpeed );
248 			break;
249 
250 		default :
251 			break;
252 	}
253 };
254 
255 // ---------------------------------------------------------------------------
256 /** 
257  * Song Sequence Position changed, if already visited position then mark song as
258  * ended (as it has looped).
259  *
260  * @protected
261  * @override
262  */
263 weasel.DOCSoundTracker22.prototype._songSequenceChange = function( )
264 {
265 	var iCurrentSequencePosition = this.getCurrentSequencePosition();
266 
267 	if( true == this.aVisitedSequence[ iCurrentSequencePosition ] )
268 	{
269 		this.bSongEnded = true;
270 	}
271 
272 	this.aVisitedSequence[ this.getCurrentSequencePosition() ] = true;
273 	this.iTotalPatternTicks = 0;
274 };
275 
276 // ---------------------------------------------------------------------------
277 /** Clear the Sequence Position Tracking table as Soundtracker 2.2 modules need 
278  * their Sequence Positions tracked to find out if a song has looped via the 
279  * Sequence Position Jump Command.
280  * 
281  */
282 weasel.DOCSoundTracker22.prototype.clearVisitedSequenceTable = function( )
283 {
284 	for( var aVisited = this.aVisitedSequence, iLength = aVisited.length; --iLength >= 0; )
285 	{
286 		aVisited[ iLength ] = false;
287 	}
288 };
289 
290 
291 // ---------------------------------------------------------------------------
292 /** 
293  * Restart song from beginning of module, Soundtracker 2.2 modules
294  * need their Sequence Positions tracked to find out if a song has looped via the Sequence Position Jump Command.
295  * 
296  * @override
297  */
298 weasel.DOCSoundTracker22.prototype.restartSong = function( )
299 {
300 	this.clearVisitedSequenceTable();
301 
302 	// Call restartSong() on parent to reset rest of attributes.
303 	//
304 	this.parent.prototype.restartSong.call( this );
305 };
306