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 };