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 Mahoney & Kaktus's Noisetracker 1.1 module out of the provided data (which has already passed the module sniffer test).
  9  * 
 10  * @constructor
 11  * @extends weasel.SpreadpointSoundTracker23
 12  * 
 13  * @param {Array|Uint8Array} aModuleData = The Mahoney & Kaktus Noisetracker 1.1 module as a byte array that MUST have passed the module sniffer test.
 14  * @param {int} iPlaybackFrequency = The playback frequency in hertz to use (e.g. 44100 ).
 15  * @param {weasel.Sample.prototype.SampleScannerMode} iSampleScannerMode = Scan for IFF Header corruption residue?.
 16  * 
 17  * @author Warren Willmey 2013
 18  */
 19 weasel.NoiseTracker11 = function( aModuleData, iPlaybackFrequency, iSampleScannerMode )
 20 {
 21 	this.parent = weasel.SpreadpointSoundTracker23;
 22 
 23 	// Needed for prototype Inheritance.
 24 	//
 25 	if( aModuleData === undefined || !(( aModuleData instanceof Array ) || ( window.Uint8Array && aModuleData instanceof Uint8Array )) )
 26 		return;
 27 
 28 	this.bSampleLoopOffsetInWords = true;
 29 	this.bNoiseTrackerLoopQuirk = undefined == this.bNoiseTrackerLoopQuirk ? true : this.bNoiseTrackerLoopQuirk;
 30 
 31 	this.parent( aModuleData, iPlaybackFrequency, iSampleScannerMode );
 32 
 33 	this._extractSongRestartSequencePosition();
 34 
 35 	// Song (BPM) Speed is ignored by Noisetracker 1.1, however for the sake
 36 	// of consistency they would be 125bpm due to PAL VBL timing.
 37 	// But due to Ultimate Soundtracker et al slightly different derived value
 38 	// use a value of 120 to indicate PAL VBL.
 39 	//
 40 	this.setSongSpeed( 120 );
 41 
 42 	this.sModuleType = weasel.ModuleSniffer.prototype.SupportedModules.NoiseTracker11;
 43 
 44 };
 45 
 46 weasel.NoiseTracker11.prototype = new weasel.SpreadpointSoundTracker23;
 47 
 48 // ---------------------------------------------------------------------------
 49 /** Sequence table tick speed reader, reads tick speed from pattern during .
 50  * 
 51  * @param {int} iEffectData = The effect data of the pattern cell.
 52  * 
 53  * @return {int} The Tick Speed.
 54  * 
 55  * @protected
 56  * @override
 57  */
 58 weasel.NoiseTracker11.prototype._sequenceTableTickSpeedReader = function( iEffectData )
 59 {
 60 	return iEffectData > 31 ? 31 : iEffectData;
 61 };
 62 
 63 // ---------------------------------------------------------------------------
 64 /** Set the row tick speed.
 65  * 
 66  * @param {int} iTickSpeed = The new row tick speed to use (0-31), a value of 0 is ignored.
 67  * 
 68  * @override
 69  */
 70 weasel.NoiseTracker11.prototype.setTickSpeed = function( iTickSpeed )
 71 {
 72 	if( iTickSpeed <= 0 )
 73 		return;
 74 
 75 	this.iTickSpeed = iTickSpeed > 31 ? 31 : iTickSpeed;
 76 };
 77 
 78 // ---------------------------------------------------------------------------
 79 /** Some Soundtrackers have effects that need processing out of the normal order,
 80  * such as the Note Portamento command in Noisetracker.
 81  * 
 82  * @param {weasel.Channel} oChannel = The Channel to process for effects.
 83  * @param {int} iNotePeriod = The note period yet to be set for this oChannel object.
 84  *
 85  * @return {bool} =	false : the fetch row process should continue for this oChannel object, 
 86  * 					true  : stop the fetch row process for this oChannel object but continue for the other channels..
 87  * 
 88  * @protected
 89  * @override
 90  */
 91 weasel.NoiseTracker11.prototype._exceptionPreprocessEffects = function( oChannel, iNotePeriod )
 92 {
 93 	if( weasel.FormatNoiseTracker11.Effects.TonePortamento == oChannel.getEffectNumber() )
 94 	{
 95 		oChannel.setNotePortamentoTarget( iNotePeriod );
 96 		return true;
 97 	}
 98 
 99 	return false;
100 };
101 
102 
103 // ---------------------------------------------------------------------------
104 /** Process a channel's effects.
105  * 
106  * @param {weasel.Channel} oChannel = The Channel to process for effects.
107  * 
108  * @protected
109  * @override
110  * 
111  * TODO Examine this function for removal, as ALL Noisetracker 1.1 modules will play the same when using the Noisetracker 2.0 _processChannelEffect() function. The exception being if "bNotSoStrict" flag has been used during Sniffing.
112  */
113 weasel.NoiseTracker11.prototype._processChannelEffect = function( oChannel )
114 {
115 	switch( oChannel.getEffectNumber() )
116 	{
117 		case weasel.FormatDOCSoundTracker9.Effects.Arpeggio :
118 
119 				if( oChannel.getEffectParameter() == 0 )
120 				{
121 					// Restore the (shadow) note period when no effect occurs.
122 					//
123 					oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
124 					break;
125 				}
126 
127 
128 				oChannel.arpeggio( this.iCurrentTick, weasel.Channel.prototype.ArpeggioMode.Noisetracker );
129 
130 			break;
131 
132 		case weasel.FormatDOCSoundTracker9.Effects.PitchbendUp :
133 
134 				oChannel.pitchBend( this.iCurrentTick, 0, oChannel.getEffectParameter() );
135 				oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) );
136 				oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
137 
138 			break;
139 
140 		case weasel.FormatDOCSoundTracker9.Effects.PitchbendDown :
141 
142 				oChannel.pitchBend( this.iCurrentTick, oChannel.getEffectParameter(), 0 );
143 				oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) );
144 				oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
145 
146 			break;
147 
148 		case weasel.FormatNoiseTracker11.Effects.TonePortamento :
149 			
150 				oChannel.setNotePortamentoSpeed( oChannel.getEffectParameter() );
151 				oChannel.notePortamento( this.iCurrentTick );
152 				oChannel.setNotePeriod( this._clampNotePeriod( oChannel.getNotePeriod() ) );
153 			break;
154 
155 		case weasel.FormatNoiseTracker11.Effects.Vibrato :
156 
157 				oChannel.vibrato( this.iCurrentTick, true, this.getVibratoMode() );
158 			break;
159 
160 		case weasel.FormatSpreadpointSoundTracker23.Effects.VolumeSlide :
161 
162 				// Restore (shadow) note period when a Effect Command 10 occurs.
163 				//
164 				oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
165 
166 				oChannel.volumeSlide( this.iCurrentTick );
167 
168 			break;
169 
170 		default :
171 					// Restore the note period when a Effect Command
172 					// that is not processed on a non tick 0 occurs. Such as
173 					// Set Volume Command.
174 					// Commands 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 restore the
175 					// period value. This value is taken from the Shadow Note Period
176 					// as the pitch bend/Portamento period value is maintained.
177 					//
178 					oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
179 			break;
180 	}
181 };
182 
183 // ---------------------------------------------------------------------------
184 /** Process a channel's effects that occur on Tick Zero.
185  * 
186  * @param {weasel.Channel} oChannel = The Channel to process for effects.
187  * 
188  * @protected
189  * @override
190  */
191 weasel.NoiseTracker11.prototype._processChannelTick0Effect = function( oChannel )
192 {
193 	switch( oChannel.getEffectNumber() )
194 	{
195 		case weasel.FormatDOCSoundTracker22.Effects.SequencePositionJump :
196 
197 				// If Pattern Break command occurs in any other channel, this command
198 				// turns it into a Sequence Position Jump command instead.
199 				//
200 				var iSequenceJump = oChannel.getEffectParameter();
201 
202 				this.bPatternBreak = true;
203 				this.iSequencePositionJump = iSequenceJump;
204 				this.iPatternBreakToRow = 0;
205 			break;
206 
207 		case weasel.FormatDOCSoundTracker9.Effects.Volume :
208 
209 				var iVolume = oChannel.getEffectParameter();
210 
211 				if( iVolume > 64 )
212 				{
213 					iVolume = 64;
214 				}
215 
216 				oChannel.setVolume( iVolume );
217 
218 			break;
219 
220 		case weasel.FormatDOCSoundTracker22.Effects.PatternBreak :
221 
222 				// Pattern break.
223 				//
224 				this.bPatternBreak = true;
225 				this.iPatternBreakToRow = 0;
226 			break;
227 
228 		case weasel.FormatDOCSoundTracker9.Effects.Filter :
229 
230 				var iFilter = oChannel.getEffectParameter() & 1;
231 
232 				this.bFilterOn = iFilter == 0;	// Filter On = 0 (Amiga Power LED On), Filter Off = 1 (Amiga Power LED Off).
233 			break;
234 
235 		case weasel.FormatDOCSoundTracker9.Effects.TickSpeed :
236 
237 				this.setTickSpeed( oChannel.getEffectParameter() );
238 			break;
239 
240 		default :
241 			break;
242 	}
243 };
244 
245 
246 // ---------------------------------------------------------------------------
247 /** Apply the Channel's Noisetracker pattern cell to the channel, only needed due to
248  * sample loop chaining being applied.
249  * 
250  * @param {weasel.Channel} oChannel = The channel object to apply volume to immediately.
251  * @return {weasel.MKPatternCell} = The current PatternCell object for the provided channel number.
252  * 
253  * @protected
254  * @override
255  */
256 weasel.NoiseTracker11.prototype._applyPatternCell = function( oChannel, oPatternCell )
257 {
258 	// Always set effect command and effect parameter.
259 	//
260 	oChannel.setEffectNumber( oPatternCell.getEffectNumber() );
261 	oChannel.setEffectParameter( oPatternCell.getEffectParameter() );
262 	oChannel.setCurrentPatternCell( oPatternCell );
263 
264 	var iInstrumentNumber = oPatternCell.getInstrumentNumber();
265 
266 	// Only change instrument number if set in pattern.
267 	//
268 	if( iInstrumentNumber > 0 )
269 	{
270 		// Allow for Noisetracker sample loop chaining.
271 		//
272 		oChannel.setPendingInstrumentNumber( iInstrumentNumber, this.getInstrument( iInstrumentNumber ) );
273 
274 		// Volume change is applied immediately even if there is no new note.
275 		//
276 		this._applyVolumeImmediately( oChannel );
277 	}
278 
279 	var iNotePeriod = oPatternCell.getNotePeriod();
280 
281 	// Only change note if set in pattern.
282 	//
283 	if( 0 != iNotePeriod )
284 	{
285 		if( this._exceptionPreprocessEffects( oChannel, iNotePeriod ) )
286 		{
287 			return;
288 		}
289 
290 		// Noisetracker 1.1 Vibrato command.
291 		//
292 		oChannel.setVibratoTablePosition( 0 );
293 
294 		// If period value is already too low (like 2604hz) then do not change the instrument.
295 		// Due to the Amiga DMA not being given enough time to stop, so it does not play the new sample (continues with old sample).
296 		//
297 		if( oChannel.getNotePeriod() >= 1374 )
298 		{
299 			oChannel.setNotePeriod( iNotePeriod );
300 			oChannel.setLastSavedNotePeriod( iNotePeriod );
301 			oChannel.setShadowNotePeriod( iNotePeriod );
302 
303 			return;
304 		}
305 
306 		this.startPendingSample( oChannel, iNotePeriod, this.WaitForDMAToStop() );
307 	}
308 };
309 
310 // ---------------------------------------------------------------------------
311 /**
312  * Get the constant used for waiting for the DMA to stop in milliseconds. 
313  * 
314  * @return {float} = The millisecond pause used by this module type.
315  * 
316  * @override
317  */
318 weasel.NoiseTracker11.prototype.WaitForDMAToStop = function()
319 {
320 	return weasel.FormatNoiseTracker11.WaitForDMAToStop;
321 };
322 
323 // ---------------------------------------------------------------------------
324 /**
325  * Extract the Noisetracker Song Restart Sequence Position, which reuses the Song Speed byte of Spreadpoint Soundtracker 2.3.
326  * 
327  * @private
328  */
329 weasel.NoiseTracker11.prototype._extractSongRestartSequencePosition = function( )
330 {
331 	try
332 	{
333 		var iSongRestartSequence = weasel.Helper.getByte( this.aModuleData, weasel.FormatNoiseTracker11.SongRestartPosition );
334 		this.iSongRestartSequence = iSongRestartSequence;
335 	}catch ( oException )
336 	{
337 	}
338 };
339