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 The Jungle Command Soundtracker 2 module out of the provided data (which has already passed the module sniffer test).
  9  * TJC Soundtracker 2 is based upon Ultimate Soundtracker 1.21.
 10  * The following Soundtrackers are based on Jungle Commands Soundtracker 2: 
 11  *    # Def Jam Soundtracker 3
 12  *    # Alpha Flight Soundtracker 4
 13  *    # DOC Soundtracker 3
 14  *    # DOC Soundtracker 4
 15  *    # DOC Soundtracker 6
 16  *    
 17  * The replay routines are identical with the exception that Il Scuro (Def Jam Soundtracker 3) added Set Tick Speed Effect Command.
 18  *
 19  * @constructor
 20  * @extends weasel.UltimateSoundTracker121
 21  * 
 22  * @param {Array|Uint8Array} aModuleData = The TJC Soundtracker 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.TJCSoundTracker2 = function( aModuleData, iPlaybackFrequency, iSampleScannerMode )
 29 {
 30 	this.parent = weasel.UltimateSoundTracker121;
 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.TJCSoundTracker2;
 39 
 40 	// TJC Soundtracker 2 does not honour the BPM Speed setting and
 41 	// only playback at 50Hz PAL.
 42 	//
 43 	this.timingOverride( this.TimingOverrides.PAL );
 44 
 45 };
 46 
 47 weasel.TJCSoundTracker2.prototype = new weasel.UltimateSoundTracker121;
 48 
 49 // ---------------------------------------------------------------------------
 50 /** During pitch bends TJC Soundtracker 2 clamps the values to the 3 octave range.
 51  * 
 52  * @param {int} iNotePeriod = The note period to clamp.
 53  * 
 54  * @return {int} The clamped period value.
 55  * 
 56  * @private
 57  */
 58 weasel.TJCSoundTracker2.prototype._clampNotePeriod = function( iNotePeriod )
 59 {
 60 	if( iNotePeriod > weasel.FormatTJCSoundTracker2.MinNotePeriod )
 61 	{
 62 		return weasel.FormatTJCSoundTracker2.MinNotePeriod;
 63 	}
 64 	else if( iNotePeriod < weasel.FormatTJCSoundTracker2.MaxNotePeriod )
 65 	{
 66 		return weasel.FormatTJCSoundTracker2.MaxNotePeriod;
 67 	}
 68 
 69 	return iNotePeriod;
 70 };
 71 
 72 // ---------------------------------------------------------------------------
 73 /** apply volume to immediately, this is because TJC soundtracker 2 applies 
 74  * the new volume level of the pending instrument whether a note is present or not,
 75  * allowing you to change the volume of the sample without actually changing the
 76  * instrument or note period. The instrument volume is overruled by the Volume
 77  * Effect Command.
 78  * 
 79  * @param {weasel.Channel} oChannel = The channel object to apply volume to immediately.
 80  * 
 81  * @protected
 82  * @override
 83  */
 84 weasel.TJCSoundTracker2.prototype._applyVolumeImmediately = function( oChannel )
 85 {
 86 	// Volume change is applied immediately even if there is no new note.
 87 	//
 88 	var iVolume = this.getInstrument( oChannel.getPendingInstrumentNumber() ).getVolume();
 89 
 90 	// Check volume column effect override.
 91 	//
 92 	if( weasel.FormatTJCSoundTracker2.Effects.Volume == oChannel.getEffectNumber() )
 93 	{
 94 		iVolume = oChannel.getEffectParameter();
 95 	}
 96 
 97 	oChannel.setVolume( iVolume );
 98 
 99 };
100 
101 // ---------------------------------------------------------------------------
102 /** Process a channel's effects.
103  * 
104  * @param {weasel.Channel} oChannel = The Channel to process for effects.
105  * 
106  * @protected
107  * @override
108  */
109 weasel.TJCSoundTracker2.prototype._processChannelEffect = function( oChannel )
110 {
111 	if( oChannel.getAutoSlide() )
112 	{
113 		// If AutoSlide command is enabled it is applied before Effect Commands.
114 		// It is also applied if there is a SlideVolume commands (the volume changes twice).
115 		//
116 		var iVolume = oChannel.getShadowVolume() + oChannel.getAutoSlideValue();
117 		
118 		if( iVolume > 64 )
119 		{
120 			iVolume = 64;
121 		}
122 		else if( iVolume < 0 )
123 		{
124 			iVolume = 0;
125 		}
126 
127 		oChannel.setVolume( iVolume );
128 	}
129 
130 	switch( oChannel.getEffectNumber() )
131 	{
132 		case weasel.FormatTJCSoundTracker2.Effects.Arpeggio :
133 
134 				if( oChannel.getEffectParameter() == 0 )
135 					break;
136 
137 				oChannel.arpeggio( this.iCurrentTick, weasel.Channel.prototype.ArpeggioMode.UltimateSoundtracker );
138 
139 			break;
140 
141 		case weasel.FormatTJCSoundTracker2.Effects.PitchbendUp :
142 
143 				oChannel.pitchBend( this.iCurrentTick, 0, oChannel.getEffectParameter() );
144 				oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) );
145 				oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
146 
147 			break;
148 
149 		case weasel.FormatTJCSoundTracker2.Effects.PitchbendDown :
150 
151 				oChannel.pitchBend( this.iCurrentTick, oChannel.getEffectParameter(), 0 );
152 				oChannel.setShadowNotePeriod( this._clampNotePeriod( oChannel.getShadowNotePeriod() ) );
153 				oChannel.setNotePeriod( oChannel.getShadowNotePeriod() );
154 
155 			break;
156 
157 		case weasel.FormatTJCSoundTracker2.Effects.SlideVolume :
158 
159 				oChannel.volumeSlide( this.iCurrentTick );
160 
161 			break;
162 
163 		default :
164 			break;
165 	}
166 };
167 
168 
169 
170 // ---------------------------------------------------------------------------
171 /** Process a channel's effects that occur on Tick Zero.
172  * 
173  * @param {weasel.Channel} oChannel = The Channel to process for effects.
174  * 
175  * @protected
176  * @override
177  */
178 weasel.TJCSoundTracker2.prototype._processChannelTick0Effect = function( oChannel )
179 {
180 	switch( oChannel.getEffectNumber() )
181 	{
182 		case weasel.FormatTJCSoundTracker2.Effects.Arpeggio :
183 
184 				if( 0 == oChannel.getEffectParameter() )
185 				{
186 					// If Effect Command = 0 and Effect Data = 0 then clear
187 					// the AutoSlide command.
188 					//
189 					oChannel.setAutoSlide( false );
190 				}
191 
192 			break;
193 
194 		case weasel.FormatTJCSoundTracker2.Effects.Volume :
195 
196 				var iVolume = oChannel.getEffectParameter();
197 
198 				if( iVolume > 64 )
199 				{
200 					iVolume = 64;
201 				}
202 
203 				oChannel.setVolume( iVolume );
204 
205 			break;
206 
207 
208 		case weasel.FormatTJCSoundTracker2.Effects.AutoSlide :
209 
210 				var iSlideVolume = oChannel.getEffectParameter();
211 
212 				if( 0 == (iSlideVolume >>> 4) & 0xf )
213 				{
214 					// Slide volume down.
215 					//
216 					iSlideVolume = 0 - (iSlideVolume & 0xf);
217 				}
218 				else
219 				{
220 					// Slide Volume up.
221 					//
222 					iSlideVolume = (iSlideVolume >>> 4) & 0xf;
223 				}
224 
225 					oChannel.setAutoSlide( true );
226 					oChannel.setAutoSlideValue( iSlideVolume );
227 			break;
228 
229 		default :
230 			break;
231 	}
232 };
233 // ---------------------------------------------------------------------------
234 /**
235  * Process the effects column for all channels when current tick is zero (a new row has been fetched).
236  * 
237  * @override
238  */
239 weasel.TJCSoundTracker2.prototype.processTick0Effects = function( )
240 {
241 	for( var iChannels = this.aSoundChannels.length, iChannel = -1; ++iChannel < iChannels; )
242 	{
243 		var oChannel = this.aSoundChannels[ iChannel ];
244 
245 		this._processChannelTick0Effect( oChannel );
246 	}
247 };