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