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