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 /** Object to contain all the details needed for creating audio data associated 9 * with a soundtracker channel. 10 * 11 * @constructor 12 * @param {weasel.UltimateSoundTracker121|weasel.UltimateSoundTracker18|weasel.DOCSoundTracker9|weasel.DOCSoundTracker22|weasel.TJCSoundTracker2|weasel.DefJamSoundTracker3|weasel.SpreadpointSoundTracker23|weasel.SpreadpointSoundTracker25|weasel.NoiseTracker11|weasel.NoiseTracker20|weasel.ProTrackerMK|weasel.FSTModule} oModule = The current module. 13 * @param {int} iPlaybackFrequency = The playback frequency in hertz to use. 14 * @param {float} fMasterVolume = The master volume for this channel (0.0 to 1.0 range).. 15 * 16 * @author Warren Willmey 2011 17 */ 18 weasel.Channel = function( oModule, iPlaybackFrequency, fMasterVolume ) 19 { 20 this.iCurrentNotePeriod = 0; 21 this.iCurrentInstrument = 0; 22 this.iCurrentEffect = 0; 23 this.iCurrentEffectParameter = 0; 24 this.iCurrentVolume = 0; 25 this.iShadowVolume = 0; 26 this.fMasterVolume = 1.0; 27 this.setMasterVolume( fMasterVolume ); 28 this.iLastSavedNotePeriod = 0; 29 this.iShadowNotePeriod = 0; // Soundtrackers prior to Noisetracker 1.0/1.1 had a separate Note Period used for pitchbends. 30 this.fCurrentSamplePosition = 0.0; 31 this.iPendingInstrument = 0; 32 this.oPendingInstrument = null; 33 this.iDelayedNotePeriod = 0; // Used for Protracker Delay Note command. 34 this.iLastSampleOffsetParameter = 0; // Protracker Sample Offset Command. 35 this.iSampleStartOffset = 0; // Protracker Sample Offset Command. 36 this.iFineTune = 0; // Protracker Fine Tuning of samples. 37 this.iPatternRowLoopStart = 0; // Protracker Pattern Loop Command. 38 this.iPatternRowLoopCounter = 0; // Protracker Pattern Loop Command. 39 this.iPlaybackFrequency = iPlaybackFrequency; 40 this.fCurrentFrequencyStep = 0.0; 41 this.bDelaySampleStart = true; 42 this.iDelayInSamples = 0; 43 this.bNoNoteDelayQuirk = false; // Protracker ED0 and E9x commands cause normal channels to delay by the Wait for DMA software pause. 44 this.bUsePALClockConstant = true; 45 this.oModule = oModule; 46 47 this.aCircularAudioBuffer = weasel.Helper.getFloat32Array( iPlaybackFrequency ); 48 for( var iLength = this.aCircularAudioBuffer.length, aCircularAudioBuffer = this.aCircularAudioBuffer; --iLength >= 0; ) 49 aCircularAudioBuffer[ iLength ] = 0.0; 50 51 this.iCircularBufferPosition = 0; 52 this.fSampleAccumulator = 0.0; 53 this.fSampleAccumulator1 = 0.0; 54 this.fSampleAccumulator2 = 0.0; 55 this.fSampleAccumulator3 = 0.0; 56 this.fSampleAccumulator4 = 0.0; 57 this.oSample = null; 58 this.iChannelInterpolation = 0; 59 60 this.bMute = false; 61 62 this.bAutoSlide = false; // Jungle Command Soundtracker 2 command. 63 this.iAutoSlideValue = 0; // Jungle Command Soundtracker 2 command. 64 this.iNotePortamentoTargetPeriod=0; // Noisetracker 1.0/1.1 command. 65 this.iNotePoramentoSpeed = 0; // Noisetracker 1.0/1.1 command. 66 this.iVibratoTablePosition = 0; // Noisetracker 1.0/1.1 command. 67 this.iLastVibratoParameter = 0; // Noisetracker 1.0/1.1 command. 68 this.bVibratoContinueWaveform = false; // Protracker Vibrato wave control parameter. 69 this.iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave; // Protracker Vibrato waveform type. 70 this.iLastTremoloParameter = 0; // Protracker Tremolo command parameter, updates nibbles separately. 71 this.iTremoloTablePosition = 0; // Protracker Tremolo command. 72 this.bTremoloContinueWaveform = false; // Protracker Tremolo wave control parameter. 73 this.iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave; // Protracker Tremolo waveform type. 74 this.iInvertLoopSpeed = 0; // Protracker Invert Loop control parameter. 75 this.iInvertLoopTimeDelay = 0; // Protracker Invert Loop time delay. 76 this.iInvertLoopSampleOffset = 0; // Protracker Invert Loop byte to modify within sample loop. 77 this.iInvertLoopSampleLoopStart = 0; // Protracker Invert Loop keep tracker of the original loop starting point. 78 this.bGlissandoMode = false; // Protracker Glissando Mode for Toneportamento command. 79 this.iPanningPosition = 128; // Taketracker/Fasttracker Panning Position command 8. 80 this.oPatternCell = null; 81 }; 82 83 84 // --------------------------------------------------------------------------- 85 /** Supported interpolation methods (that may change in future), also used for 86 * display names where '$' is converted to '-' and '_' becomes ' '. 87 * @enum 88 * @type {string} 89 * @const 90 */ 91 weasel.Channel.prototype.SupportedInterpolationTypes = { 92 None : 0 93 , RLM$D_Alias_Reduction : 1 94 , Linear_Interpolation : 2 95 , Cubic_Spline_4_Point_Interpolation : 3 96 , Catmull$Rom_Spline_Interpolation : 4 97 , Spline_6_Point_Interpolation : 5 98 }; 99 100 // --------------------------------------------------------------------------- 101 /** Supported arpeggio modes (different trackers behave slightly differently). 102 * @enum 103 * @type {int} 104 * @const 105 */ 106 weasel.Channel.prototype.ArpeggioMode = { 107 UltimateSoundtracker : 0 108 , Noisetracker : 1 109 , Protracker : 2 110 , FSTModule : 3 111 }; 112 113 // --------------------------------------------------------------------------- 114 /** Supported Protracker Vibrato waveforms, note that Random does not actually 115 * exist, its defined in the help docs (see PT 2.3a) but not in the code and just 116 * results in a Square wave being used. 117 * @enum 118 * @type {int} 119 * @const 120 */ 121 weasel.Channel.prototype.ProtrackerVibratoWaveform = { 122 SineWave : 0 123 , RampDownSawTooth : 1 124 , Square : 2 125 , Random : 3 126 }; 127 128 // --------------------------------------------------------------------------- 129 /** Supported Protracker Tremolo waveforms, note that Random does not actually 130 * exist, its defined in the help docs (see PT 2.3a) but not in the code and just 131 * results in a Square wave being used. 132 * @enum 133 * @type {int} 134 * @const 135 */ 136 weasel.Channel.prototype.ProtrackerTremoloWaveform = { 137 SineWave : 0 138 , RampDownSawTooth : 1 139 , Square : 2 140 , Random : 3 141 }; 142 143 // --------------------------------------------------------------------------- 144 /** 145 * Set new output play back frequency, which requires the resizing of various buffers. 146 * 147 * @param {int} iPlaybackFrequency = new replay play back frequency. 148 * 149 */ 150 weasel.Channel.prototype.changePlaybackFrequency = function( iPlaybackFrequency ) 151 { 152 var aNewCircularBuffer = weasel.Helper.getFloat32Array( iPlaybackFrequency ); 153 for( var iLength = aNewCircularBuffer.length; --iLength >= 0; ) 154 aNewCircularBuffer[ iLength ] = 0.0; 155 156 this.iPlaybackFrequency = iPlaybackFrequency; 157 this.setNotePeriod( this.getNotePeriod() ); 158 this.aCircularAudioBuffer = aNewCircularBuffer; 159 this.setCircularBufferPosition( 0 ); 160 }; 161 162 163 // --------------------------------------------------------------------------- 164 /** 165 * Set the Sample Object for this channel. 166 * 167 * @param {weasel.Sample} oSample = The Sample object to use. 168 */ 169 weasel.Channel.prototype.setSample = function( oSample ) 170 { 171 this.oSample = oSample; 172 }; 173 174 // --------------------------------------------------------------------------- 175 /** 176 * Get the Sample Object for this channel. 177 * 178 * @return {weasel.Sample} = The Sample object to use. 179 */ 180 weasel.Channel.prototype.getSample = function() 181 { 182 return this.oSample; 183 }; 184 185 // --------------------------------------------------------------------------- 186 /** 187 * Indicate that the current sample should not start immediately, and should be 188 * delayed by 0.768ms (12 scanlines) as this is what Karstens' Amiga replay routines does, 189 * the delay is actually to allow the Amiga DMA to stop playing its current sample. 190 * 191 * @param {bool} bDelay = true : delay the sample. 192 * @param {float} fDelayInMs = The delay, in milliseconds, to use. 193 */ 194 weasel.Channel.prototype.delaySampleStart = function( bDelay, fDelayInMs ) 195 { 196 this.bDelaySampleStart = bDelay == true? true : false; 197 198 if( fDelayInMs <= 0.0 ) 199 { 200 // Don't use delay. 201 // 202 this.iDelayInSamples = 0; 203 } 204 else 205 { 206 this.iDelayInSamples = (this.iPlaybackFrequency / ( 1000.0 / fDelayInMs))|0; 207 } 208 }; 209 210 // --------------------------------------------------------------------------- 211 /** 212 * Get the current state of the delayed sample start. 213 * 214 * @return {bool} = The delayed start. 215 */ 216 weasel.Channel.prototype.getDelaySampleStart = function() 217 { 218 return this.bDelaySampleStart; 219 }; 220 221 222 // --------------------------------------------------------------------------- 223 /** 224 * Get list of supported interpolation type, used for populating a drop down list for user selection. 225 * 226 * @return {weasel.Channel.prototype.SupportedInterpolationTypes} = The list of supported interpolation types. 227 */ 228 weasel.Channel.prototype.getSupportedInterpolationTypes = function() 229 { 230 return weasel.Channel.prototype.SupportedInterpolationTypes; 231 }; 232 233 // --------------------------------------------------------------------------- 234 /** 235 * get the note period for this Channel object. 236 * 237 * @return {int} = The current note period. 238 */ 239 weasel.Channel.prototype.getNotePeriod = function() 240 { 241 return this.iCurrentNotePeriod; 242 }; 243 244 245 // --------------------------------------------------------------------------- 246 /** 247 * get the NTSC or PAL Amiga Clock Constant. 248 * 249 * @return {int} = The Amiga Clock Constant. 250 */ 251 weasel.Channel.prototype.getClockConstant = function() 252 { 253 return this.bUsePALClockConstant == true ? weasel.FormatUltimateSoundTracker121.ClockConstantPAL : weasel.FormatUltimateSoundTracker121.ClockConstantNTSC; 254 }; 255 256 // --------------------------------------------------------------------------- 257 /** 258 * get the NTSC or PAL Amiga Clock Constant in human readable form. 259 * 260 * @return string = The Amiga Clock Constant in human readable form e.g. 'PAL' or 'NTSC'. 261 */ 262 weasel.Channel.prototype.getClockConstantType = function() 263 { 264 return this.bUsePALClockConstant == true ? 'PAL' : 'NTSC'; 265 }; 266 267 268 // --------------------------------------------------------------------------- 269 /** 270 * Set Clock Constant to either PAL or NTSC. 271 * 272 * @param bool bClockConstant = true : PAL, false : NTSC. 273 */ 274 weasel.Channel.prototype.setClockConstant = function( bClockConstant ) 275 { 276 this.bUsePALClockConstant = bClockConstant == true ? true : false; 277 278 // Reset the current note period now or Clock Constant wont have effect 279 // until new note is played! 280 // 281 this.setNotePeriod( this.getNotePeriod() ); 282 }; 283 284 // --------------------------------------------------------------------------- 285 /** 286 * Set the note period for this Channel object. 287 * 288 * @param {int} iNotePeriod = The note period. 289 */ 290 weasel.Channel.prototype.setNotePeriod = function( iNotePeriod ) 291 { 292 this.iCurrentNotePeriod = iNotePeriod; 293 294 if( iNotePeriod <= 0 || this.iPlaybackFrequency <= 0 ) 295 { 296 this.fCurrentFrequencyStep = 0.0; 297 return; 298 } 299 this.fCurrentFrequencyStep = this.getClockConstant() / iNotePeriod / this.iPlaybackFrequency; 300 }; 301 302 // --------------------------------------------------------------------------- 303 /** 304 * Get the Frequency Step rate for the current note period at the current Playback Frequency Rate. 305 * 306 * @return float = The frequency step rate of the current note period at the current Playback Frequency Rate. 307 */ 308 weasel.Channel.prototype.getFrequencyStepRate = function() 309 { 310 return this.fCurrentFrequencyStep; 311 }; 312 313 // --------------------------------------------------------------------------- 314 /** 315 * Get the last saved note period for this Channel object. 316 * 317 * @return {int} = The last saved note period. 318 */ 319 weasel.Channel.prototype.getLastSavedNotePeriod = function() 320 { 321 return this.iLastSavedNotePeriod; 322 }; 323 324 // --------------------------------------------------------------------------- 325 /** 326 * Set the last saved period for this Channel object. 327 * 328 * @param {int} iNotePeriod = The note period. 329 */ 330 weasel.Channel.prototype.setLastSavedNotePeriod = function( iNotePeriod ) 331 { 332 this.iLastSavedNotePeriod = iNotePeriod; 333 }; 334 335 // --------------------------------------------------------------------------- 336 /** 337 * Get the last saved note period used for Pitch Bend Command for this Channel object. 338 * Soundtrackers prior to Noisetracker 1.0/1.1 used a separate note period for 339 * Pitch Bend Effect Command. Resulting in different note period when switching 340 * between Arpeggio and Pitch Bend. 341 * 342 * @return {int} = The last saved note period. 343 */ 344 weasel.Channel.prototype.getShadowNotePeriod = function() 345 { 346 return this.iShadowNotePeriod; 347 }; 348 349 // --------------------------------------------------------------------------- 350 /** 351 * Set the last saved period used for Pitch Bend Command for this Channel object. 352 * Soundtrackers prior to Noisetracker 1.0/1.1 used a separate note period for 353 * Pitch Bend Effect Command. Resulting in different note period when switching 354 * between Arpeggio and Pitch Bend. 355 * 356 * @param {int} iNotePeriod = The note period. 357 */ 358 weasel.Channel.prototype.setShadowNotePeriod = function( iNotePeriod ) 359 { 360 this.iShadowNotePeriod = iNotePeriod; 361 }; 362 363 364 // --------------------------------------------------------------------------- 365 /** 366 * Get the instrument number for this Channel object. 367 * 368 * @return {int} = The instrument number. 369 */ 370 weasel.Channel.prototype.getInstrumentNumber = function() 371 { 372 return this.iCurrentInstrument; 373 }; 374 375 // --------------------------------------------------------------------------- 376 /** 377 * Set the instrument number for this Channel object. 378 * 379 * @param {int} iInstrumentNumber = The instrument number. 380 */ 381 weasel.Channel.prototype.setInstrumentNumber = function( iInstrumentNumber ) 382 { 383 this.iCurrentInstrument = iInstrumentNumber; 384 }; 385 386 // --------------------------------------------------------------------------- 387 /** 388 * get the pending instrument number for this Channel object, pending in that 389 * once a new note period fires the instrument will have this number. 390 * 391 * @return {int} = The pending instrument number. 392 */ 393 weasel.Channel.prototype.getPendingInstrumentNumber = function() 394 { 395 return this.iPendingInstrument; 396 }; 397 398 // --------------------------------------------------------------------------- 399 /** 400 * Set the pending instrument number for this Channel object, pending in that 401 * once a new note period fires the instrument will have this number, the pending 402 * instrument (oInstrument) is needed for Noisetracker/Soundtracker2.5/Protracker which 403 * inherit the ability to seamlessly chain samples together (when one loop point has 404 * finished it starts playing the sample loop of the pending instrument, without a pause). 405 * 406 * @param {int} iPendingInstrumentNumber = The pending instrument number. 407 * @param {weasel.Instrument} oInstrument = The pending instrument, which is used for Noisetracker Loop Chaining of samples together. 408 */ 409 weasel.Channel.prototype.setPendingInstrumentNumber = function( iPendingInstrumentNumber, oInstrument ) 410 { 411 this.iPendingInstrument = iPendingInstrumentNumber; 412 this.oPendingInstrument = oInstrument; 413 }; 414 415 // --------------------------------------------------------------------------- 416 /** 417 * Get the Delayed Note Period, used by the Protracker Note Delay command.. 418 * 419 * @return {int} = The note period to be used by the Note Delay command. 420 */ 421 weasel.Channel.prototype.getDelayedNotePeriod = function() 422 { 423 return this.iDelayedNotePeriod; 424 }; 425 426 // --------------------------------------------------------------------------- 427 /** 428 * Set the Delayed Note Period, used by the Protracker Note Delay command. 429 * 430 * @param {int} iNotePeriod = The note period to be used by the Note Delay command. 431 */ 432 weasel.Channel.prototype.setDelayedNotePeriod = function( iNotePeriod ) 433 { 434 this.iDelayedNotePeriod = iNotePeriod; 435 }; 436 437 // --------------------------------------------------------------------------- 438 /** 439 * get the effect number for this Channel object. 440 * 441 * @return {int} = The effect number. 442 */ 443 weasel.Channel.prototype.getEffectNumber = function() 444 { 445 return this.iCurrentEffect; 446 }; 447 448 // --------------------------------------------------------------------------- 449 /** 450 * Set the effect number for this Channel object. 451 * 452 * @param {int} iEffectNumber = The effect number. 453 */ 454 weasel.Channel.prototype.setEffectNumber = function( iEffectNumber ) 455 { 456 this.iCurrentEffect = iEffectNumber; 457 }; 458 459 // --------------------------------------------------------------------------- 460 /** 461 * get the effect parameter for this Channel object. 462 * 463 * @return {int} = The effect parameter. 464 */ 465 weasel.Channel.prototype.getEffectParameter = function() 466 { 467 return this.iCurrentEffectParameter; 468 }; 469 470 // --------------------------------------------------------------------------- 471 /** 472 * Set the effect parameter for this Channel object. 473 * 474 * @param {int} iEffectParameter = The effect parameter. 475 */ 476 weasel.Channel.prototype.setEffectParameter = function( iEffectParameter ) 477 { 478 this.iCurrentEffectParameter = iEffectParameter; 479 }; 480 481 482 // --------------------------------------------------------------------------- 483 /** Set the overriding master volume for this Channel. 484 * 485 * @param {float} fVolume = The volume range ( 0.0 - 1.0 ) although a range of 486 * 0.0 to 2.0 is accepted, the volume levels may not be clamped by the mixer causing clicks/pops etc. 487 */ 488 weasel.Channel.prototype.setMasterVolume = function( fVolume ) 489 { 490 if( undefined == fVolume ) 491 { 492 fVolume = 1.0; 493 } 494 495 if( fVolume < 0.0 ) 496 { 497 fVolume = 0.0; 498 } 499 500 if( fVolume > 2.0 ) 501 { 502 fVolume = 2.0; 503 } 504 505 this.fMasterVolume = fVolume; 506 }; 507 508 // --------------------------------------------------------------------------- 509 /** Get the current master volume of this Channel. 510 * 511 * @return {float} The current master volume. 512 */ 513 weasel.Channel.prototype.getMasterVolume = function( ) 514 { 515 return this.fMasterVolume; 516 }; 517 518 // --------------------------------------------------------------------------- 519 /** 520 * get the volume for this Channel object, when the audio is created it uses this 521 * volume level. 522 * 523 * @return {int} = The current volume. 524 */ 525 weasel.Channel.prototype.getVolume = function() 526 { 527 return this.bMute == true ? 0 : this.iCurrentVolume; 528 }; 529 530 // --------------------------------------------------------------------------- 531 /** 532 * get the shadow volume for this Channel object, which represent the volume 533 * set in the pattern (not in the Volume register of the Amiga, the Protracker 534 * Tremolo command needs this separation). 535 * 536 * @return {int} = The shadow volume. 537 */ 538 weasel.Channel.prototype.getShadowVolume = function() 539 { 540 return this.iShadowVolume; 541 }; 542 543 // --------------------------------------------------------------------------- 544 /** 545 * Mute/un mute this channel. 546 * 547 * @param {boolean} bMute = TRUE: silence this channel, FALSE: un-silence this channel. 548 */ 549 weasel.Channel.prototype.setMute = function( bMute ) 550 { 551 this.bMute = bMute == true ? true : false; 552 }; 553 554 // --------------------------------------------------------------------------- 555 /** 556 * Set the volume for this Channel object. 557 * 558 * @param {int} iVolume = The volume (0-64). 559 */ 560 weasel.Channel.prototype.setVolume = function( iVolume ) 561 { 562 this.iShadowVolume = this.iCurrentVolume = iVolume; 563 }; 564 565 // --------------------------------------------------------------------------- 566 /** 567 * get the position within the sample that is being played (the sample offset) for this Channel object. 568 * 569 * @return {float} = The current sample offset (in samples). 570 */ 571 weasel.Channel.prototype.getSamplePosition = function() 572 { 573 return this.fCurrentSamplePosition; 574 }; 575 576 // --------------------------------------------------------------------------- 577 /** 578 * get the position within the sample that is being played (the sample offset) for this Channel object. 579 * 580 * @param {float} fSamplePosition = The sample offset (in samples). 581 */ 582 weasel.Channel.prototype.setSamplePosition = function( fSamplePosition ) 583 { 584 this.fCurrentSamplePosition = fSamplePosition; 585 }; 586 587 588 // --------------------------------------------------------------------------- 589 /** 590 * Apply arpeggio to the current note. 591 * 592 * @param {int} iCurrentTick = The current row tick value (0-5 range). 593 * @param {weasel.Channel.prototype.ArpeggioMode} iArpeggioMode = Some Soundtracker Arpeggio's were bugged 594 * prior to Noisetracker 1.0/1.1 if they have used the SetSpeed Command they where 595 * unable to handle a current row tick greater than 5 (they just ignored Arpeggio). 596 * And they cannot be combined with Pitch Bend command as they use their own 597 * Note Period for the Arpeggio which does not change with the Pitch Bend (this was 598 * corected in Noisetracker), Protracker has Fine Tune applied. 599 */ 600 weasel.Channel.prototype.arpeggio = function( iCurrentTick, iArpeggioMode ) 601 { 602 var iBasePeriod = 0; 603 604 if( iArpeggioMode == this.ArpeggioMode.UltimateSoundtracker ) 605 { 606 iBasePeriod = this.getLastSavedNotePeriod(); 607 } 608 else 609 { 610 iBasePeriod = this.getShadowNotePeriod(); 611 } 612 613 if( 0 == iBasePeriod ) 614 { 615 this.setNotePeriod( 0 ); 616 return; 617 } 618 619 if( iArpeggioMode != this.ArpeggioMode.UltimateSoundtracker ) 620 { 621 // Noisetracker clamps the Arpeggio tick to the 0-2 range. 622 // 623 iCurrentTick %= 3; 624 } 625 626 if( 0 == iCurrentTick || 3 == iCurrentTick ) 627 { 628 this.setNotePeriod( iBasePeriod ); 629 return; 630 } 631 632 var iNoteOffset = this.getEffectParameter() & 0xf; 633 634 if( 1 == iCurrentTick || 4 == iCurrentTick ) 635 iNoteOffset = (this.getEffectParameter() >>> 4 ) & 0xf; 636 637 var aArpeggioTable = weasel.FormatUltimateSoundTracker121.PeriodTable; 638 var iTableLength = aArpeggioTable.length; 639 var iNote = 0; 640 var iFineTuneOffset = 0; 641 642 if( iArpeggioMode != this.ArpeggioMode.UltimateSoundtracker ) 643 { 644 iTableLength = 36; 645 } 646 647 if( iArpeggioMode == this.ArpeggioMode.Protracker ) 648 { 649 aArpeggioTable = weasel.FormatProTrackerMK.FineTunePeriodTables; 650 iFineTuneOffset = this.getFineTune() * 37; 651 } 652 653 654 if( iArpeggioMode == this.ArpeggioMode.FSTModule ) 655 { 656 aArpeggioTable = weasel.FormatFSTModule.FSTPeriodTable; 657 iTableLength = aArpeggioTable.length; 658 iFineTuneOffset = this.getFineTune(); 659 } 660 661 // Linear search for note in period table. 662 // 663 if( iArpeggioMode == this.ArpeggioMode.UltimateSoundtracker ) 664 { 665 for( ; iNote < iTableLength; iNote++ ) 666 { 667 if( iBasePeriod == aArpeggioTable[ iNote ] ) 668 { 669 break; 670 } 671 } 672 } 673 else if( iArpeggioMode == this.ArpeggioMode.FSTModule ) 674 { 675 for( ; iNote < iTableLength; iNote++ ) 676 { 677 if( iBasePeriod >= aArpeggioTable[ iNote ] ) 678 { 679 break; 680 } 681 } 682 } 683 else 684 { 685 for( ; iNote < iTableLength; iNote++ ) 686 { 687 if( iBasePeriod >= aArpeggioTable[ iNote + iFineTuneOffset ] ) 688 { 689 // Noisetracker allows for note periods that may not be in the arpeggio table. 690 // In which case it it uses the next lowest note as the base of the arpeggio. 691 // 692 break; 693 } 694 } 695 } 696 697 iNote += iNoteOffset; 698 699 if( iNote >= iTableLength ) 700 { 701 if( iArpeggioMode == this.ArpeggioMode.Noisetracker ) 702 { 703 // Into the Random Data zone, which for the Noisetracker 2.0 Editor 704 // is just a bank of zeros. 705 // 706 this.setNotePeriod( 0 ); 707 return; 708 } 709 if( iArpeggioMode == this.ArpeggioMode.Protracker ) 710 { 711 // Do nothing for protracker, allow note to fall into next 712 // fine tune table. 713 } 714 else if( iArpeggioMode == this.ArpeggioMode.FSTModule ) 715 { 716 // Clamp note to maximum period in table. 717 // 718 var iNotePeriod = this.oModule.calcFineTune( this, aArpeggioTable[ iTableLength - 1 ] ); 719 this.setNotePeriod( iNotePeriod ); 720 return; 721 } 722 else 723 { 724 // Ultimate Sountracker only stop note lookup from leaving the 725 // Period Table (including the "random data" section). 726 // Max note playable. 727 // 728 iNote = iTableLength -1; 729 } 730 } 731 732 if( iArpeggioMode == this.ArpeggioMode.FSTModule ) 733 { 734 var iNotePeriod = this.oModule.calcFineTune( this, aArpeggioTable[ iNote ] ); 735 this.setNotePeriod( iNotePeriod ); 736 return; 737 } 738 739 this.setNotePeriod( aArpeggioTable[ iNote + iFineTuneOffset ] ); 740 }; 741 742 // --------------------------------------------------------------------------- 743 /** 744 * Apply pitch bend to the current note, Soundtrackers prior to Noisetracker 1.0/1,1 745 * store separate note periods for Arpeggio and Pitch Bend Commands (Arpeggio 746 * after a Pitchbend with be done on the original note period NOT the final Pitch Bend period). 747 * 748 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores pitch bend). 749 * @param {int} iPitchBendDown = The pitch bend down effect parameter. 750 * @param {int} iPitchBendUp = The pitch bend up effect parameter. 751 */ 752 weasel.Channel.prototype.pitchBend = function( iCurrentTick, iPitchBendDown, iPitchBendUp ) 753 { 754 var iBasePeriod = this.getShadowNotePeriod(); 755 756 // Do not apply effect at the beginning of a new row fetch. 757 // 758 if( 0 == iCurrentTick ) 759 return; 760 761 if( 0 != iPitchBendDown ) 762 { 763 // Pitch bend down note scale (the larger the note period the lower the note pitch). 764 // 765 iBasePeriod += iPitchBendDown; 766 767 if( iBasePeriod > 65535 ) 768 iBasePeriod = 65535; 769 } 770 else if( 0 != iPitchBendUp ) 771 { 772 // Pitch bend up note scale (the smaller the note period the high the note pitch). 773 // 774 iBasePeriod -= iPitchBendUp; 775 776 if( iBasePeriod < 0 ) 777 iBasePeriod = 0; 778 } 779 780 this.setShadowNotePeriod( iBasePeriod ); 781 this.setNotePeriod( iBasePeriod ); 782 }; 783 784 // --------------------------------------------------------------------------- 785 /** 786 * Apply volume slide to current channel, as used by TJC Soundktracker 2, Spreadpoint Soundtracker 2.3, Noisetracker 1.1/2.0. 787 * 788 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores volumeSlide). 789 */ 790 weasel.Channel.prototype.volumeSlide = function( iCurrentTick ) 791 { 792 // Do not apply effect at the beginning of a new row fetch. 793 // 794 if( 0 == iCurrentTick ) 795 return; 796 797 var iSlideVolume = this.getEffectParameter(); 798 799 if( 0 == (iSlideVolume >>> 4) & 0xf ) 800 { 801 // Slide volume down. 802 // 803 iSlideVolume = 0 - (iSlideVolume & 0xf); 804 } 805 else 806 { 807 // Slide Volume up. 808 // 809 iSlideVolume = (iSlideVolume >>> 4) & 0xf; 810 } 811 812 var iVolume = this.getShadowVolume() + iSlideVolume; 813 814 if( iVolume > 64 ) 815 { 816 iVolume = 64; 817 } 818 else if( iVolume < 0 ) 819 { 820 iVolume = 0; 821 } 822 823 this.setVolume( iVolume ); 824 }; 825 826 // --------------------------------------------------------------------------- 827 /** 828 * Apply Noisetracker Note Portamento to current note period, Protracker adds 829 * a Glissando Mode on top. 830 * 831 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento). 832 */ 833 weasel.Channel.prototype.notePortamento = function( iCurrentTick ) 834 { 835 // Do not apply effect at the beginning of a new row fetch. 836 // 837 if( 0 == iCurrentTick ) 838 return; 839 840 var iTargetPeriod = this.iNotePortamentoTargetPeriod; 841 842 if( 0 == iTargetPeriod ) 843 { 844 // If no Note Portamento Target is set then don't pitch bend. 845 // 846 return; 847 } 848 849 var iCurrentPeriod = this.getShadowNotePeriod(); 850 851 if( iCurrentPeriod < iTargetPeriod ) 852 { 853 // Pitch bend down note scale (the larger the note period the lower the note pitch). 854 // 855 iCurrentPeriod += this.iNotePoramentoSpeed; 856 857 if( iCurrentPeriod > iTargetPeriod ) 858 { 859 iCurrentPeriod = iTargetPeriod; 860 this.iNotePortamentoTargetPeriod = 0; 861 } 862 } 863 else if( iCurrentPeriod > iTargetPeriod ) 864 { 865 // Pitch bend up note scale (the smaller the note period the high the note pitch). 866 // 867 iCurrentPeriod -= this.iNotePoramentoSpeed; 868 869 if( iCurrentPeriod < iTargetPeriod ) 870 { 871 iCurrentPeriod = iTargetPeriod; 872 this.iNotePortamentoTargetPeriod = 0; 873 } 874 } 875 876 this.setShadowNotePeriod( iCurrentPeriod ); 877 878 if( this.bGlissandoMode ) 879 { 880 // Protracker Glissando basically rounds the Period value 881 // to the nearest note. 882 // 883 var aFineTunePeriodTables = weasel.FormatProTrackerMK.FineTunePeriodTables; 884 var iFineTuneOffset = this.getFineTune() * 37; 885 var iTableLength = weasel.FormatUltimateSoundTracker121.PeriodTable.length; 886 var iNote = 0; 887 888 for( ; iNote < iTableLength; iNote++ ) 889 { 890 if( iCurrentPeriod >= aFineTunePeriodTables[ iNote + iFineTuneOffset ] ) 891 { 892 break; 893 } 894 } 895 896 iCurrentPeriod = aFineTunePeriodTables[ iNote + iFineTuneOffset ]; 897 } 898 899 this.setNotePeriod( iCurrentPeriod ); 900 }; 901 902 // --------------------------------------------------------------------------- 903 /** 904 * Apply Noisetracker Vibrato Command to current note period. 905 * 906 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento). 907 * @param {bool} bRememberLastVibrato = Remember last Vibrato Effect Parameter (used for VibratoAndVolumeSlide Command). 908 * @param {bool} bNoisetracker20Vibrato = True : Noisetracker 2.0 applies a smaller Vibrato that Noisetracker 1.1. 909 * 910 */ 911 weasel.Channel.prototype.vibrato = function( iCurrentTick, bRememberLastVibrato, bNoisetracker20Vibrato ) 912 { 913 // Do not apply effect at the beginning of a new row fetch. 914 // 915 if( 0 == iCurrentTick ) 916 return; 917 918 if( 0 != this.getEffectParameter() && bRememberLastVibrato ) 919 { 920 // Store the Vibrato Effect Parameter so it can be used if its simplified form 921 // 922 this.iLastVibratoParameter = this.getEffectParameter(); 923 } 924 925 var iVibratoPeriodDivider = bNoisetracker20Vibrato ? 7 : 6; 926 927 var iVibratoSpeed = ( this.iLastVibratoParameter & 0xf0 ) >>> 4; 928 var iVibratoSize = this.iLastVibratoParameter & 0xf; 929 var iVibratoTablePosition = ( this.iVibratoTablePosition >>> 2 ) & 0x1f; 930 var iVibratoPeriod = ( weasel.FormatNoiseTracker11.VibratoTable[ iVibratoTablePosition ] * iVibratoSize ) >>> iVibratoPeriodDivider; 931 var iCurrentPeriod = this.getShadowNotePeriod(); 932 933 if( 0 == (this.iVibratoTablePosition & 0x80) ) 934 { 935 // Positive half of sine wave, so add it to the note period. 936 // 937 iCurrentPeriod += iVibratoPeriod; 938 } 939 else 940 { 941 // Negative half of sine wave, so subtract from note period. 942 // 943 iCurrentPeriod -= iVibratoPeriod; 944 } 945 946 this.iVibratoTablePosition = ( this.iVibratoTablePosition + ( iVibratoSpeed << 2 ) ) & 0xff; 947 948 this.setNotePeriod( iCurrentPeriod ); 949 }; 950 951 // --------------------------------------------------------------------------- 952 /** 953 * Apply Protracker Vibrato Command to current note period, different from 954 * Noisetracker Vibrato in that the parameters (Vibrato rate and depth) get stored 955 * independently and there are 3 different Vibrato Waveforms to choose from. 956 * 957 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento). 958 * @param {bool} bRememberLastVibrato = Remember last Vibrato Effect Parameter (used for VibratoAndVolumeSlide Command). 959 * @param {bool} bNoisetracker20Vibrato = True : Noisetracker 2.0 applies a smaller Vibrato that Noisetracker 1.1, this also applies to Protracker 1.0c and below, Protracker 1.1a and above adopted the same Vibrato Depth as Noisetracker 2.0. 960 * 961 */ 962 weasel.Channel.prototype.protrackerVibrato = function( iCurrentTick, bRememberLastVibrato, bNoisetracker20Vibrato ) 963 { 964 // Do not apply effect at the beginning of a new row fetch. 965 // 966 if( 0 == iCurrentTick ) 967 return; 968 969 if( 0 != this.getEffectParameter() && bRememberLastVibrato ) 970 { 971 // Store the Vibrato Effect Parameter so it can be used if its simplified form 972 // 973 // Protracker Vibrato mode. 974 // 975 var iNibble = this.getEffectParameter() & 0xf0; 976 977 if( iNibble != 0 ) 978 { 979 this.iLastVibratoParameter = this.iLastVibratoParameter & 0xf | iNibble; 980 } 981 982 var iNibble = this.getEffectParameter() & 0xf; 983 984 if( iNibble != 0 ) 985 { 986 this.iLastVibratoParameter = this.iLastVibratoParameter & 0xf0 | iNibble; 987 } 988 } 989 990 var iVibratoTablePosition = ( this.iVibratoTablePosition >>> 2 ) & 0x1f; 991 var iVibratoData = 255; // Square waveform. 992 993 if( this.iVibratoWaveformType == weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave ) 994 { 995 iVibratoData = weasel.FormatNoiseTracker11.VibratoTable[ iVibratoTablePosition ]; 996 } 997 else if( this.iVibratoWaveformType == weasel.Channel.prototype.ProtrackerVibratoWaveform.RampDownSawTooth ) 998 { 999 iVibratoData = iVibratoTablePosition << 3; 1000 1001 if( 0 != (this.iVibratoTablePosition & 0x80) ) 1002 { 1003 iVibratoData = 255 - iVibratoData; 1004 } 1005 } 1006 1007 var iVibratoPeriodDivider = bNoisetracker20Vibrato ? 7 : 6; 1008 1009 var iVibratoSpeed = ( this.iLastVibratoParameter & 0xf0 ) >>> 4; 1010 var iVibratoSize = this.iLastVibratoParameter & 0xf; 1011 var iVibratoPeriod = ( iVibratoData * iVibratoSize ) >>> iVibratoPeriodDivider; 1012 var iCurrentPeriod = this.getShadowNotePeriod(); 1013 1014 if( 0 == (this.iVibratoTablePosition & 0x80) ) 1015 { 1016 // Positive half of sine wave, so add it to the note period. 1017 // 1018 iCurrentPeriod += iVibratoPeriod; 1019 } 1020 else 1021 { 1022 // Negative half of sine wave, so subtract from note period. 1023 // 1024 iCurrentPeriod -= iVibratoPeriod; 1025 } 1026 1027 this.iVibratoTablePosition = ( this.iVibratoTablePosition + ( iVibratoSpeed << 2 ) ) & 0xff; 1028 1029 this.setNotePeriod( iCurrentPeriod ); 1030 }; 1031 1032 // --------------------------------------------------------------------------- 1033 /** 1034 * Apply Protracker Tremolo Command. 1035 * 1036 * @param {int} iCurrentTick = The current row tick value (tick of 0 ignores note portamento). 1037 * @param {bool} bProtrackerBugMode = true : The Protracker series has a bug 1038 * when using the Ramp Down Sawtooth waveform 1039 * during the Tremolo command (the other waveform types are fine). The bug 1040 * is caused by using the Vibrato Table Position instead of the Tremolo Table 1041 * Position during construction of the sawtooth resulting in a Ramp Up (not Ramp Down) 1042 * sawooth if the Vibrato command is not used or a triangle-ish waveform 1043 * that almost randomly changes direction if the Vibrato command has been used on 1044 * the same channel. Even more weird is that I've been sitting on this bug for 1045 * ~20 years! :-O Good to finally get it off the chest so to speak! Especially 1046 * as its obviously just a cut and paste error (the code for the Vibrato is 95% the 1047 * same). False : Behave as expected (without the bug). The Protracker series also 1048 * contains another bug in the Editor (but NOT in th replay.s) in that it resets 1049 * the volume to that of the sample on every tick 0, so for Ramp Down it creates 1050 * an annoying clicking noise. 1051 * 1052 */ 1053 weasel.Channel.prototype.tremolo = function( iCurrentTick, bProtrackerBugMode ) 1054 { 1055 // Do not apply effect at the beginning of a new row fetch. 1056 // 1057 if( 0 == iCurrentTick ) 1058 return; 1059 1060 if( 0 != this.getEffectParameter() ) 1061 { 1062 // Store the Tremolo Effect Parameter so it can be used if its simplified form 1063 // 1064 var iNibble = this.getEffectParameter() & 0xf0; 1065 1066 if( iNibble != 0 ) 1067 { 1068 this.iLastTremoloParameter = this.iLastTremoloParameter & 0xf | iNibble; 1069 } 1070 1071 var iNibble = this.getEffectParameter() & 0xf; 1072 1073 if( iNibble != 0 ) 1074 { 1075 this.iLastTremoloParameter = this.iLastTremoloParameter & 0xf0 | iNibble; 1076 } 1077 } 1078 1079 var iTremoloTablePosition = ( this.iTremoloTablePosition >>> 2 ) & 0x1f; 1080 var iTremoloData = 255; // Square waveform. 1081 1082 if( this.iTremoloWaveformType == weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave ) 1083 { 1084 iTremoloData = weasel.FormatNoiseTracker11.VibratoTable[ iTremoloTablePosition ]; 1085 } 1086 else if( this.iTremoloWaveformType == weasel.Channel.prototype.ProtrackerTremoloWaveform.RampDownSawTooth ) 1087 { 1088 iTremoloData = iTremoloTablePosition << 3; 1089 1090 // Protrackers replay routine accidentally uses the Vibrato Table Position 1091 // NOT the Tremolo Table Position. 1092 // Resulting in a more erratic Saw Tooth. 1093 // 1094 var iTablePosition = true == bProtrackerBugMode ? this.iVibratoTablePosition : this.iTremoloTablePosition; 1095 1096 if( 0 != (iTablePosition & 0x80) ) 1097 { 1098 iTremoloData = 255 - iTremoloData; 1099 } 1100 } 1101 1102 var iTremoloSpeed = ( this.iLastTremoloParameter & 0xf0 ) >>> 4; 1103 var iTremoloSize = this.iLastTremoloParameter & 0xf; 1104 var iTremolo = ( iTremoloData * iTremoloSize ) >>> 6; 1105 var iShadowVolume = this.getShadowVolume(); 1106 1107 if( 0 == (this.iTremoloTablePosition & 0x80) ) 1108 { 1109 // Positive half of sine wave, so add Tremolo to Volume level. 1110 // 1111 iShadowVolume += iTremolo; 1112 } 1113 else 1114 { 1115 // Negative half of sine wave, so subtract Tremolo to Volume level. 1116 // 1117 iShadowVolume -= iTremolo; 1118 } 1119 1120 // Clamp volume levels to allowed range. 1121 // 1122 if( iShadowVolume > 64 ) 1123 { 1124 iShadowVolume = 64; 1125 } 1126 else if( iShadowVolume < 0 ) 1127 { 1128 iShadowVolume = 0; 1129 } 1130 1131 this.iTremoloTablePosition = ( this.iTremoloTablePosition + ( iTremoloSpeed << 2 ) ) & 0xff; 1132 1133 // Notice the current volume is being set directly, 1134 // meaning the shadow volume does not change. 1135 // 1136 this.iCurrentVolume = iShadowVolume; 1137 }; 1138 1139 // --------------------------------------------------------------------------- 1140 /** 1141 * Apply Protracker Note re-trigger. 1142 * 1143 * @param {int} iCurrentTick = The current row tick value. 1144 * @param {int} iRetriggerOn = The number of ticks to re-trigger, this is applied 1145 * as a modulus so a value of 2 causes a trigger every 2 ticks,( Range 0-15 ). 1146 * @param {float} fWaitForDMAToStop = The value to use (in milliseconds) for the pause between samples, typically weasel.FormatNoiseTracker11.WaitForDMAToStop. 1147 * 1148 * @return {bool} True = sample retriggered this Tick, false = Sample not retriggered this Tick. 1149 */ 1150 weasel.Channel.prototype.retriggerNote = function( iCurrentTick, iRetriggerOn, fWaitForDMAToStop ) 1151 { 1152 iRetriggerOn &= 0xf; 1153 1154 if( 0 != iRetriggerOn ) 1155 { 1156 if( 0 == iCurrentTick % iRetriggerOn ) 1157 { 1158 this.delaySampleStart( true, fWaitForDMAToStop ); 1159 1160 // Protracker Set SampleOffset Command has residual tendencies that 1161 // affects Retrigger Note and Note Delay in Protracker. 1162 // 1163 this.setSamplePosition( this.playFromOffset() ); 1164 return true; 1165 } 1166 } 1167 1168 return false; 1169 }; 1170 1171 // --------------------------------------------------------------------------- 1172 /** 1173 * Get the circular audio buffer containing the rendered sample. 1174 * 1175 * @return {Array} = The circular array containing the rendered sample data. 1176 */ 1177 weasel.Channel.prototype.getCircularAudioBuffer = function( ) 1178 { 1179 return this.aCircularAudioBuffer; 1180 }; 1181 1182 // --------------------------------------------------------------------------- 1183 /** 1184 * Get the current position in the circular array (rendered sample data is behind this offset). 1185 * 1186 * @return {int} = The current position in the circular array containing the rendered sample data. 1187 */ 1188 weasel.Channel.prototype.getCircularBufferPosition = function( ) 1189 { 1190 return this.iCircularBufferPosition; 1191 }; 1192 1193 // --------------------------------------------------------------------------- 1194 /** 1195 * Set the position in the circular audio buffer. 1196 * 1197 * @param {int} iPosition = The new position in the the circular audio buffer. 1198 */ 1199 weasel.Channel.prototype.setCircularBufferPosition = function( iPosition ) 1200 { 1201 this.iCircularBufferPosition = iPosition; 1202 }; 1203 1204 // --------------------------------------------------------------------------- 1205 /** 1206 * Get the Channel Interpolation type. 1207 * 1208 * @return {int} = The channel interpolation value, 0 = none, 1 = RLM-D Alias Reduction, 2 = Linear Interpolation. 1209 */ 1210 weasel.Channel.prototype.getChannelInterpolation = function() 1211 { 1212 return this.iChannelInterpolation; 1213 }; 1214 1215 // --------------------------------------------------------------------------- 1216 /** 1217 * Set the Channel Interpolation type. 1218 * 1219 * @param {int} iInterpolationType =0 = none, 1 = RLM-D Alias Reduction, 2 = Linear Interpolation. 1220 */ 1221 weasel.Channel.prototype.setChannelInterpolation = function( iInterpolationType ) 1222 { 1223 var bFound = false; 1224 1225 for( var iInterpolation in this.SupportedInterpolationTypes ) 1226 { 1227 if( this.SupportedInterpolationTypes.hasOwnProperty( iInterpolation ) ) // Ignore inherent Object.prototype properties. 1228 { 1229 if( iInterpolationType === this.SupportedInterpolationTypes[ iInterpolation ] ) 1230 { 1231 bFound = true; 1232 break; 1233 } 1234 } 1235 } 1236 1237 if( bFound ) 1238 { 1239 this.iChannelInterpolation = iInterpolationType; 1240 } 1241 }; 1242 1243 // --------------------------------------------------------------------------- 1244 /** 1245 * Set the Sample Accumulator, which is used by makeNoiseAliasReduction() 1246 * to reduce (but not eliminate) the aliasing, it is set to the first sample of 1247 * the current instrument, without volume being applied. 1248 */ 1249 weasel.Channel.prototype.setSampleAccumulator = function() 1250 { 1251 this.fSampleAccumulator = 0.0; 1252 this.fSampleAccumulator1 = this.fSampleAccumulator2 = this.fSampleAccumulator3 = this.fSampleAccumulator4 = 0.0; 1253 1254 var oSample = this.getSample(); 1255 1256 if( null == oSample ) 1257 { 1258 return; 1259 } 1260 1261 if( 0 == oSample.getLength() ) 1262 { 1263 return; 1264 } 1265 1266 var aSampleData = oSample.getSampleArray(); 1267 var iSamplePosition = this.getSamplePosition() | 0; 1268 1269 if( null == aSampleData ) 1270 return; 1271 1272 if( iSamplePosition >= aSampleData.length ) 1273 return; 1274 var fSample = aSampleData[ iSamplePosition ]; 1275 this.fSampleAccumulator = this.fSampleAccumulator1 = this.fSampleAccumulator2 = this.fSampleAccumulator3 = this.fSampleAccumulator4 = fSample * ((this.getVolume() / 64.0) * this.getMasterVolume()); 1276 }; 1277 1278 // --------------------------------------------------------------------------- 1279 /** 1280 * Make the requested number of silent samples in the circular audio buffer. 1281 * 1282 * @param {int} iSamplesToFill = The number of samples of silence to make. 1283 */ 1284 weasel.Channel.prototype.makeSilence = function( iSamplesToFill ) 1285 { 1286 if( iSamplesToFill <= 0 ) 1287 return; 1288 1289 var aBuffer = this.getCircularAudioBuffer(); 1290 var iCircularBuffer = this.getCircularBufferPosition(); 1291 var iBufferLength = aBuffer.length; 1292 1293 // No actual Sample to play, so fill with silence (actually the last outputted sample). 1294 // 1295 var fLastSample = aBuffer[ ((iCircularBuffer -1) + iBufferLength) % iBufferLength ]; 1296 1297 while( iSamplesToFill > 0 ) 1298 { 1299 var iSamplesToEndOfBuffer = iBufferLength - iCircularBuffer; 1300 var iSamples = iSamplesToEndOfBuffer >= iSamplesToFill ? iSamplesToFill : iSamplesToEndOfBuffer; 1301 iSamplesToFill-= iSamples; 1302 1303 for( var iAlign = iSamples & 0x3; --iAlign >= 0; ) 1304 { 1305 aBuffer[ iCircularBuffer++ ] = fLastSample; 1306 } 1307 1308 for( iSamples >>>= 2; --iSamples >= 0; ) 1309 { 1310 aBuffer[ iCircularBuffer++ ] = fLastSample; 1311 aBuffer[ iCircularBuffer++ ] = fLastSample; 1312 aBuffer[ iCircularBuffer++ ] = fLastSample; 1313 aBuffer[ iCircularBuffer++ ] = fLastSample; 1314 } 1315 1316 iCircularBuffer %= iBufferLength; 1317 } 1318 1319 this.setCircularBufferPosition( iCircularBuffer ); 1320 1321 }; 1322 1323 // --------------------------------------------------------------------------- 1324 /** 1325 * Make/render the requested number of instrument samples into the circular audio buffer, at the correct note frequency and volume (but no stereo panning, in mono). 1326 * 1327 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1328 * @param {int} iSamplesToFill = The Samples to make. 1329 */ 1330 weasel.Channel.prototype.makeNoise = function( aSampleData, iSamplesToFill ) 1331 { 1332 var iInterpolationType = this.getChannelInterpolation(); 1333 var fFreqStep = this.getFrequencyStepRate(); 1334 1335 if( 0 == iInterpolationType ) 1336 { 1337 this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill ); 1338 }else if( 5 == iInterpolationType ) 1339 { 1340 if( 1.0 > fFreqStep ) 1341 { 1342 this.make6PointCubicSplineInterpolation( aSampleData, iSamplesToFill ); 1343 } 1344 else 1345 { 1346 this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill ); 1347 } 1348 }else if( 4 == iInterpolationType ) 1349 { 1350 if( 1.0 > fFreqStep ) 1351 { 1352 this.makeNoiseCatmullRomSplineInterpolation( aSampleData, iSamplesToFill ); 1353 } 1354 else 1355 { 1356 this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill ); 1357 } 1358 }else if( 3 == iInterpolationType ) 1359 { 1360 if( 1.0 > fFreqStep ) 1361 { 1362 this.makeNoiseCubicSplineInterpolation( aSampleData, iSamplesToFill ); 1363 } 1364 else 1365 { 1366 this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill ); 1367 } 1368 }else if( 2 == iInterpolationType ) 1369 { 1370 if( 1.0 > fFreqStep ) 1371 { 1372 this.makeNoiseLinearInterpolation( aSampleData, iSamplesToFill ); 1373 } 1374 else 1375 { 1376 this.makeNoiseAmigaStyle( aSampleData, iSamplesToFill ); 1377 } 1378 }else if( 1 == iInterpolationType ) 1379 { 1380 this.makeNoiseRLMDAliasReduction( aSampleData, iSamplesToFill ); 1381 } 1382 }; 1383 1384 // --------------------------------------------------------------------------- 1385 /** 1386 * Make/render the requested number of instrument samples into the circular audio buffer, at the correct note frequency and volume (but no stereo panning, in mono). 1387 * 1388 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1389 * @param {int} iSamplesToFill = The Samples to make. 1390 */ 1391 weasel.Channel.prototype.makeNoiseAmigaStyle = function( aSampleData, iSamplesToFill ) 1392 { 1393 var fCurrentSamplePosition = this.getSamplePosition(); 1394 var aBuffer = this.getCircularAudioBuffer(); 1395 var iCircularBuffer = this.getCircularBufferPosition(); 1396 var iBufferLength = aBuffer.length; 1397 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1398 1399 // Play Sample without checking for end or loop. 1400 // 1401 var iSampleOffset = fCurrentSamplePosition | 0; 1402 var fSample = aSampleData[ iSampleOffset ] * fVolume; 1403 1404 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; ) 1405 { 1406 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1407 iSamples -= iMaxLengthFill; 1408 1409 while( --iMaxLengthFill >= 0 ) 1410 { 1411 aBuffer[ iCircularBuffer++ ] = fSample; 1412 1413 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1414 { 1415 var iIntegerStep = fFractionalPart|0; 1416 iSampleOffset += iIntegerStep; 1417 fSample = aSampleData[ iSampleOffset ] * fVolume; 1418 fFractionalPart -= iIntegerStep; 1419 } 1420 } 1421 iCircularBuffer %= iBufferLength; 1422 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1423 } 1424 1425 this.setSamplePosition( fCurrentSamplePosition ); 1426 this.setCircularBufferPosition( iCircularBuffer ); 1427 }; 1428 1429 // --------------------------------------------------------------------------- 1430 /** 1431 * Make/render using RLM-D Alias Reduction, the requested number of instrument samples into the circular 1432 * audio buffer, at the correct note frequency and volume (but no stereo panning, in mono). 1433 * This useful technique I worked out on the (ahem) "other" machine (the Atari ST..) in the early 90ies 1434 * it applies a small amount of feedback/echo PER sample byte which quickly fades out 1435 * thus does not destroy the original sample and its not linear interpolation, leaving 1436 * enough of aliasing noise to be heard but not enough to be annoying. This is very useful 1437 * for samples that depend on aliasing noise for timbre, such as percussion or square/sawtooth waveforms 1438 * but smooths out sinewave/low frequency samples. Downfall is its very dependant on the replay frequency (you wont hear any effect at 192khz). 1439 * 1440 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1441 * @param {int} iSamplesToFill = The Samples to make. 1442 */ 1443 weasel.Channel.prototype.makeNoiseRLMDAliasReduction = function( aSampleData, iSamplesToFill ) 1444 { 1445 var fCurrentSamplePosition = this.getSamplePosition(); 1446 var aBuffer = this.getCircularAudioBuffer(); 1447 var iCircularBuffer = this.getCircularBufferPosition(); 1448 var iBufferLength = aBuffer.length; 1449 var fSampleAccumulator = this.fSampleAccumulator; 1450 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1451 1452 // Play Sample without checking for end or loop. 1453 // 1454 var iSampleOffset = fCurrentSamplePosition | 0; 1455 var fSample = aSampleData[ iSampleOffset ]; 1456 1457 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; ) 1458 { 1459 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1460 iSamples -= iMaxLengthFill; 1461 1462 while( --iMaxLengthFill >= 0 ) 1463 { 1464 aBuffer[ iCircularBuffer++ ] = (fSampleAccumulator = ( fSampleAccumulator + fSample ) * 0.5) * fVolume; 1465 1466 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1467 { 1468 var iIntegerStep = fFractionalPart|0; 1469 iSampleOffset += iIntegerStep; 1470 fSample = aSampleData[ iSampleOffset ]; 1471 fFractionalPart -= iIntegerStep; 1472 } 1473 } 1474 iCircularBuffer %= iBufferLength; 1475 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1476 } 1477 1478 this.fSampleAccumulator = fSampleAccumulator; 1479 this.setSamplePosition( fCurrentSamplePosition ); 1480 this.setCircularBufferPosition( iCircularBuffer ); 1481 }; 1482 1483 // --------------------------------------------------------------------------- 1484 /** 1485 * Make/render using linear interpolation, only call this function if frequency 1486 * step rate is less than or equal to 1 or errors will occur. 1487 * 1488 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1489 * @param {int} iSamplesToFill = The Samples to make. 1490 */ 1491 weasel.Channel.prototype.makeNoiseLinearInterpolation = function( aSampleData, iSamplesToFill ) 1492 { 1493 var fCurrentSamplePosition = this.getSamplePosition(); 1494 var aBuffer = this.getCircularAudioBuffer(); 1495 var iCircularBuffer = this.getCircularBufferPosition(); 1496 var iBufferLength = aBuffer.length; 1497 var fLastSample = this.fSampleAccumulator; 1498 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1499 1500 // Play Sample without checking for end or loop. 1501 // 1502 var iSampleOffset = fCurrentSamplePosition | 0; 1503 var fSample = aSampleData[ iSampleOffset ] * fVolume; 1504 1505 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset, fInterpolationStep = (fSample - fLastSample) * fFreqStep, fInterpolatedSample = fLastSample + ((fSample - fLastSample) * fFractionalPart) - fInterpolationStep; iSamples > 0; ) 1506 { 1507 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1508 iSamples -= iMaxLengthFill; 1509 1510 while( --iMaxLengthFill >= 0 ) 1511 { 1512 aBuffer[ iCircularBuffer++ ] = (fInterpolatedSample += fInterpolationStep); 1513 1514 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1515 { 1516 fFractionalPart -= 1.0; 1517 fLastSample = fSample; 1518 fSample = aSampleData[ ++iSampleOffset ] * fVolume; 1519 fInterpolationStep = (fSample - fLastSample) * fFreqStep; 1520 fInterpolatedSample = fLastSample + ((fSample - fLastSample) * fFractionalPart) - fInterpolationStep; 1521 } 1522 } 1523 iCircularBuffer %= iBufferLength; 1524 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1525 } 1526 1527 this.fSampleAccumulator = fLastSample; 1528 this.setSamplePosition( fCurrentSamplePosition ); 1529 this.setCircularBufferPosition( iCircularBuffer ); 1530 }; 1531 1532 // --------------------------------------------------------------------------- 1533 /** 1534 * Make/render using Catmull-Rom spline interpolation, only call this function if frequency 1535 * step rate is less than or equal to 1 or errors will occur. 1536 * 1537 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1538 * @param {int} iSamplesToFill = The Samples to make. 1539 * 1540 * @author Warren Willmey 2011, reference Paul Bourke "Interpolation methods". 1541 */ 1542 weasel.Channel.prototype.makeNoiseCatmullRomSplineInterpolation = function( aSampleData, iSamplesToFill ) 1543 { 1544 var fCurrentSamplePosition = this.getSamplePosition(); 1545 var aBuffer = this.getCircularAudioBuffer(); 1546 var iCircularBuffer = this.getCircularBufferPosition(); 1547 var iBufferLength = aBuffer.length; 1548 var fLastSample0 = this.fSampleAccumulator; 1549 var fLastSample1 = this.fSampleAccumulator1; 1550 var fLastSample2 = this.fSampleAccumulator2; 1551 1552 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1553 1554 // Play Sample without checking for end or loop. 1555 // 1556 var iSampleOffset = fCurrentSamplePosition | 0; 1557 var fSample = aSampleData[ iSampleOffset ] * fVolume; 1558 1559 // Catmull-Rom spline, named after Edwin Catmull and Raphael Rom. 1560 // 1561 var fWeight0 = -0.5 * fLastSample2 + 1.5 * fLastSample1 - 1.5 * fLastSample0 + 0.5 * fSample; 1562 var fWeight1 = fLastSample2 - 2.5 * fLastSample1 + 2 * fLastSample0 - 0.5 * fSample; 1563 var fWeight2 = -0.5 * fLastSample2 + 0.5 * fLastSample0; 1564 1565 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; ) 1566 { 1567 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1568 iSamples -= iMaxLengthFill; 1569 1570 while( --iMaxLengthFill >= 0 ) 1571 { 1572 var fInterpolationSquared = fFractionalPart * fFractionalPart; 1573 1574 aBuffer[ iCircularBuffer++ ] = fWeight0 * fFractionalPart * fInterpolationSquared 1575 + fWeight1 * fInterpolationSquared 1576 + fWeight2 * fFractionalPart 1577 + fLastSample1; 1578 1579 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1580 { 1581 fFractionalPart -= 1.0; 1582 fLastSample2 = fLastSample1; 1583 fLastSample1 = fLastSample0; 1584 fLastSample0 = fSample; 1585 fSample = aSampleData[ ++iSampleOffset ] * fVolume; 1586 1587 // Catmull-Rom spline, named after Edwin Catmull and Raphael Rom. 1588 // 1589 fWeight0 = -0.5 * fLastSample2 + 1.5 * fLastSample1 - 1.5 * fLastSample0 + 0.5 * fSample; 1590 fWeight1 = fLastSample2 - 2.5 * fLastSample1 + 2 * fLastSample0 - 0.5 * fSample; 1591 fWeight2 = -0.5 * fLastSample2 + 0.5 * fLastSample0; 1592 } 1593 } 1594 iCircularBuffer %= iBufferLength; 1595 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1596 1597 } 1598 1599 this.fSampleAccumulator = fLastSample0; 1600 this.fSampleAccumulator1 = fLastSample1; 1601 this.fSampleAccumulator2 = fLastSample2; 1602 this.setSamplePosition( fCurrentSamplePosition ); 1603 this.setCircularBufferPosition( iCircularBuffer ); 1604 }; 1605 1606 // --------------------------------------------------------------------------- 1607 /** 1608 * Make/render using Cubic spline interpolation, only call this function if frequency 1609 * step rate is less than or equal to 1 or errors will occur. 1610 * 1611 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1612 * @param {int} iSamplesToFill = The Samples to make. 1613 * 1614 * @author Warren Willmey 2011, reference Paul Bourke "Interpolation methods". 1615 */ 1616 weasel.Channel.prototype.makeNoiseCubicSplineInterpolation = function( aSampleData, iSamplesToFill ) 1617 { 1618 var fCurrentSamplePosition = this.getSamplePosition(); 1619 var aBuffer = this.getCircularAudioBuffer(); 1620 var iCircularBuffer = this.getCircularBufferPosition(); 1621 var iBufferLength = aBuffer.length; 1622 var fLastSample0 = this.fSampleAccumulator; 1623 var fLastSample1 = this.fSampleAccumulator1; 1624 var fLastSample2 = this.fSampleAccumulator2; 1625 1626 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1627 1628 // Play Sample without checking for end or loop. 1629 // 1630 var iSampleOffset = fCurrentSamplePosition | 0; 1631 var fSample = aSampleData[ iSampleOffset ] * fVolume; 1632 1633 // Cubic Interpolation weights. 1634 // 1635 var fWeight0 = fSample - fLastSample0 - fLastSample2 + fLastSample1; 1636 var fWeight1 = fLastSample2 - fLastSample1 - fWeight0; 1637 var fWeight2 = fLastSample0 - fLastSample2; 1638 1639 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; ) 1640 { 1641 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1642 iSamples -= iMaxLengthFill; 1643 1644 while( --iMaxLengthFill >= 0 ) 1645 { 1646 var fInterpolationSquared = fFractionalPart * fFractionalPart; 1647 1648 aBuffer[ iCircularBuffer++ ] = fWeight0 * fFractionalPart * fInterpolationSquared 1649 + fWeight1 * fInterpolationSquared 1650 + fWeight2 * fFractionalPart 1651 + fLastSample1; 1652 1653 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1654 { 1655 fFractionalPart -= 1.0; 1656 fLastSample2 = fLastSample1; 1657 fLastSample1 = fLastSample0; 1658 fLastSample0 = fSample; 1659 fSample = aSampleData[ ++iSampleOffset ] * fVolume; 1660 1661 // Cubic Interpolation weights. 1662 // 1663 fWeight0 = fSample - fLastSample0 - fLastSample2 + fLastSample1; 1664 fWeight1 = fLastSample2 - fLastSample1 - fWeight0; 1665 fWeight2 = fLastSample0 - fLastSample2; 1666 } 1667 } 1668 iCircularBuffer %= iBufferLength; 1669 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1670 } 1671 1672 this.fSampleAccumulator = fLastSample0; 1673 this.fSampleAccumulator1 = fLastSample1; 1674 this.fSampleAccumulator2 = fLastSample2; 1675 this.setSamplePosition( fCurrentSamplePosition ); 1676 this.setCircularBufferPosition( iCircularBuffer ); 1677 }; 1678 1679 1680 // --------------------------------------------------------------------------- 1681 /** 1682 * Make/render using a 6 point Cubic spline interpolation, only call this function if frequency 1683 * step rate is less than or equal to 1 or errors will occur. 1684 * 1685 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1686 * @param {int} iSamplesToFill = The Samples to make. 1687 * 1688 * @author Warren Willmey 2011, references based on algo posted by David Waugh, which in turn comes from Joshua Scholar. 1689 */ 1690 weasel.Channel.prototype.make6PointCubicSplineInterpolation = function( aSampleData, iSamplesToFill ) 1691 { 1692 var fCurrentSamplePosition = this.getSamplePosition(); 1693 var aBuffer = this.getCircularAudioBuffer(); 1694 var iCircularBuffer = this.getCircularBufferPosition(); 1695 var iBufferLength = aBuffer.length; 1696 var fLastSample0 = this.fSampleAccumulator; 1697 var fLastSample1 = this.fSampleAccumulator1; 1698 var fLastSample2 = this.fSampleAccumulator2; 1699 var fLastSample3 = this.fSampleAccumulator3; 1700 var fLastSample4 = this.fSampleAccumulator4; 1701 1702 var fVolume = (this.getVolume() / 64.0) * this.getMasterVolume(); 1703 1704 // Play Sample without checking for end or loop. 1705 // 1706 var iSampleOffset = fCurrentSamplePosition | 0; 1707 var fSample = aSampleData[ iSampleOffset ] * fVolume; 1708 1709 // Cubic Interpolation weights. 1710 // 1711 var fWeight1 = (fLastSample1-fLastSample3) * 16.0 + ( fLastSample4 - fLastSample0 )* 2.0; 1712 var fWeight2 = ( fLastSample1 + fLastSample3 ) * 16.0 -fLastSample4 - fLastSample2 * 30.0 - fLastSample0; 1713 var fWeight3 = fLastSample1 * 66.0 - fLastSample2 * 70.0 - fLastSample0 * 33.0 + fLastSample3 * 39.0 + fSample * 7.0 - fLastSample4 * 9.0; 1714 var fWeight4 = fLastSample2*126.0-fLastSample1*124.0+fLastSample0*61.0-fLastSample3*64.0- fSample*12.0+fLastSample4*13.0; 1715 var fWeight5 = ( fLastSample1 - fLastSample2 ) * 50.0 + ( fLastSample3 - fLastSample0 ) * 25.0 + ( fSample - fLastSample4 ) * 5.0; 1716 1717 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(), fFractionalPart = fCurrentSamplePosition - iSampleOffset; iSamples > 0; ) 1718 { 1719 var iMaxLengthFill = iCircularBuffer + iSamples > iBufferLength ? iBufferLength - iCircularBuffer : iSamples; 1720 iSamples -= iMaxLengthFill; 1721 1722 while( --iMaxLengthFill >= 0 ) 1723 { 1724 aBuffer[ iCircularBuffer++ ] = fLastSample2 + 0.04166666666 * fFractionalPart * (fWeight1 1725 + fFractionalPart * ( fWeight2 1726 + fFractionalPart * ( fWeight3 1727 + fFractionalPart * ( fWeight4 1728 + fFractionalPart * ( fWeight5 ) ) ))); 1729 1730 if( 1.0 <= (fFractionalPart += fFreqStep) ) 1731 { 1732 fFractionalPart -= 1.0; 1733 fLastSample4 = fLastSample3; 1734 fLastSample3 = fLastSample2; 1735 fLastSample2 = fLastSample1; 1736 fLastSample1 = fLastSample0; 1737 fLastSample0 = fSample; 1738 fSample = aSampleData[ ++iSampleOffset ] * fVolume; 1739 1740 // Cubic Interpolation weights. 1741 // 1742 fWeight1 = (fLastSample1-fLastSample3) * 16.0 + ( fLastSample4 - fLastSample0 )* 2.0; 1743 fWeight2 = ( fLastSample1 + fLastSample3 ) * 16.0 -fLastSample4 - fLastSample2 * 30.0 - fLastSample0; 1744 fWeight3 = fLastSample1 * 66.0 - fLastSample2 * 70.0 - fLastSample0 * 33.0 + fLastSample3 * 39.0 + fSample * 7.0 - fLastSample4 * 9.0; 1745 fWeight4 = fLastSample2*126.0-fLastSample1*124.0+fLastSample0*61.0-fLastSample3*64.0- fSample*12.0+fLastSample4*13.0; 1746 fWeight5 = ( fLastSample1 - fLastSample2 ) * 50.0 + ( fLastSample3 - fLastSample0 ) * 25.0 + ( fSample - fLastSample4 ) * 5.0; 1747 } 1748 } 1749 iCircularBuffer %= iBufferLength; 1750 fCurrentSamplePosition = iSampleOffset + fFractionalPart; 1751 } 1752 1753 this.fSampleAccumulator = fLastSample0; 1754 this.fSampleAccumulator1 = fLastSample1; 1755 this.fSampleAccumulator2 = fLastSample2; 1756 this.fSampleAccumulator3 = fLastSample3; 1757 this.fSampleAccumulator4 = fLastSample4; 1758 this.setSamplePosition( fCurrentSamplePosition ); 1759 this.setCircularBufferPosition( iCircularBuffer ); 1760 }; 1761 1762 1763 // --------------------------------------------------------------------------- 1764 /** 1765 * Make the audio data for this sound Channel. 1766 * 1767 * @param {int} iSamplesToFill = The number of samples to make. 1768 */ 1769 weasel.Channel.prototype.make = function( iSamplesToFill ) 1770 { 1771 if( iSamplesToFill <= 0 ) 1772 { 1773 return; 1774 } 1775 1776 var oSample = this.getSample(); 1777 if( null == oSample ) 1778 { 1779 this.makeSilence( iSamplesToFill ); 1780 return; 1781 } 1782 1783 if( this.bDelaySampleStart ) 1784 { 1785 // Simulate the pause that occurs between a sample being stopped and a new sample 1786 // starting. This is ~12 scanlines in Ultimate Soundtracker 1.21, or 0.768ms. 1787 // 1788 var iSamplesToDelay = this.iDelayInSamples > iSamplesToFill ? iSamplesToFill : this.iDelayInSamples; 1789 1790 this.makeSilence( iSamplesToDelay ); 1791 1792 iSamplesToFill -= iSamplesToDelay; 1793 this.iDelayInSamples -= iSamplesToDelay; 1794 1795 if( this.iDelayInSamples <= 0 ) 1796 { 1797 this.bDelaySampleStart = false; 1798 } 1799 1800 if( iSamplesToFill <= 0 ) 1801 { 1802 return; 1803 } 1804 } 1805 1806 var fCurrentSamplePosition = this.getSamplePosition(); 1807 var iSampleEnd = oSample.getLength(); 1808 var fFreqStep = this.getFrequencyStepRate(); 1809 1810 if( oSample.isLooped() ) 1811 { 1812 iSampleEnd = oSample.getLoopStart() + oSample.getLoopLength(); 1813 1814 // If a Noisetracker Loop Quirk sample is playing, then the chained sample 1815 // occurs when the ENTIRE sample ends (not the end of the sample loop) 1816 // after the initial starting of the sample for the first time. 1817 // Once the sample loop is playing then sample chaining occurs as normal. 1818 // 1819 if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && true == oSample.getNoisetrackerLoopQuirkMode() && false == oSample.getLoopedYet() ) 1820 { 1821 iSampleEnd = oSample.getLength(); 1822 } 1823 } 1824 else if( 0 == iSampleEnd || fCurrentSamplePosition >= iSampleEnd ) 1825 { 1826 // Check to see if a Pending Sample object exist and if its Looped. 1827 // 1828 if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && this.iPendingInstrument != this.iCurrentInstrument && this.oPendingInstrument != null && this.oPendingInstrument.getSample().isLooped() ) 1829 { 1830 // Switch to pending sample's loop starting point. 1831 // This bug/undocumented feature was introduced with Noisetracker 1832 // and spread into Soundtracker 2.5 and the Protracker series. 1833 // It allows the ability to start playing another sample as soon 1834 // as the previous one has finished (chained samples). 1835 // But only if the pending sample is looped, upon which only the loop 1836 // is played. 1837 // 1838 var oPendingSample = this.oPendingInstrument.getSample(); 1839 oPendingSample.setLoopedYet( true ); 1840 this.setSample( oPendingSample ); 1841 this.setInstrumentNumber( this.iPendingInstrument ); 1842 this.setSamplePosition( oPendingSample.getLoopStart() + (fCurrentSamplePosition - iSampleEnd) ); 1843 1844 this.make( iSamplesToFill ); 1845 return; 1846 } 1847 1848 // Sample for this channel is not playing, so fill with silence (or the last outputted sample). 1849 // 1850 this.makeSilence( iSamplesToFill ); 1851 return; 1852 } 1853 1854 1855 // Are we near end of sample? 1856 // 1857 if( ((fFreqStep * iSamplesToFill) + fCurrentSamplePosition) < (iSampleEnd + fFreqStep) ) 1858 { 1859 // Play Sample without checking for end or loop. 1860 // 1861 this.makeNoise( oSample.getSampleArray(), iSamplesToFill ); 1862 return; 1863 } 1864 else 1865 { 1866 oSample.setLoopedYet( true ); 1867 1868 // Play sample, checking for end or loop. 1869 // 1870 fCurrentSamplePosition = this.getSamplePosition(); 1871 1872 if( oSample.isLooped() ) 1873 { 1874 if( true == this.oModule.getNoiseTrackerLoopQuirkEnabled() && this.iPendingInstrument != this.iCurrentInstrument && this.oPendingInstrument != null ) 1875 { 1876 // Noisetracker/Soundtracker2.5/Protracker sample chaining. 1877 // Play current sample up to end of sample loop. 1878 // 1879 var iSamplesToLoopEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep ); 1880 this.makeNoise( oSample.getSampleArray(), iSamplesToLoopEnd ); 1881 iSamplesToFill -= iSamplesToLoopEnd; 1882 1883 var oPendingSample = this.oPendingInstrument.getSample(); 1884 oPendingSample.setLoopedYet( true ); 1885 1886 this.setSample( oPendingSample ); 1887 this.setInstrumentNumber( this.iPendingInstrument ); 1888 1889 if( oPendingSample.isLooped() ) 1890 { 1891 // Switch to pending instrument's loop. 1892 // 1893 this.setSamplePosition( oPendingSample.getLoopStart() + (this.getSamplePosition() - iSampleEnd) ); 1894 } 1895 else 1896 { 1897 // Pending instrument is not looped, so there 1898 // is no loop point to use, so switch sample and 1899 // jump to its end. 1900 // 1901 this.setSamplePosition( oPendingSample.getLength() ); 1902 } 1903 1904 // Start using Pending Sample instead. 1905 this.make( iSamplesToFill ); 1906 return; 1907 } 1908 1909 // Play looped sample, but allow playing past sample end and into 1910 // barrel loop, to reduce the number of iterations required to play sample. 1911 // 1912 iSampleEnd += oSample.getBarrelLoopSize(); 1913 1914 var iLoopStart = oSample.getLoopStart(); 1915 var iLoopLength= oSample.getLoopLength(); 1916 var iRealEndOfLoop = iLoopStart + iLoopLength; 1917 // Loop sample. 1918 // 1919 while( iSamplesToFill > 0 ) 1920 { 1921 fCurrentSamplePosition = this.getSamplePosition(); 1922 var iIterationsToEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep ); 1923 iIterationsToEnd = iIterationsToEnd >= iSamplesToFill ? iSamplesToFill : iIterationsToEnd; 1924 1925 // Play up to, but not beyond end of sample loop point. 1926 // 1927 this.makeNoise( oSample.getSampleArray(), iIterationsToEnd ); 1928 iSamplesToFill -= iIterationsToEnd; 1929 1930 // Correct sample position to real position within loop points (not within barrel loop). 1931 // 1932 fCurrentSamplePosition = this.getSamplePosition(); 1933 if( fCurrentSamplePosition > iRealEndOfLoop ) 1934 { 1935 this.setSamplePosition( iLoopStart + (( fCurrentSamplePosition - iRealEndOfLoop ) % iLoopLength ) ); 1936 } 1937 } 1938 } 1939 else 1940 { 1941 var iIterationsToEnd = Math.ceil( (iSampleEnd - fCurrentSamplePosition) / fFreqStep ); 1942 1943 // Play up to, but not beyond end of non-looped sample. 1944 // 1945 this.makeNoise( oSample.getSampleArray(), iIterationsToEnd ); 1946 iSamplesToFill -= iIterationsToEnd; 1947 1948 // Non-looped sample may have ended, but still has samples to fill 1949 // which get converted into makeSilence(). 1950 // 1951 this.make( iSamplesToFill ); 1952 } 1953 1954 } 1955 }; 1956 1957 // --------------------------------------------------------------------------- 1958 /** 1959 * Make/render using linear interpolation, only call this function if frequency 1960 * step rate is less than or equal to 1 or errors will occur, this routine is currently 1961 * here but not used to highlight the bug that exists when switching between interpolation methods 1962 * within a sample (the interpolation methods are all mis aligned). 1963 * 1964 * @param {Array} aSampleData = The current Instrument Sample Data to render. 1965 * @param {int} iSamplesToFill = The Samples to make. 1966 * 1967 * @private 1968 */ 1969 weasel.Channel.prototype.makeNoiseLinearInterpolationNew = function( aSampleData, iSamplesToFill ) 1970 { 1971 var fCurrentSamplePosition = this.getSamplePosition(); 1972 var aBuffer = this.getCircularAudioBuffer(); 1973 var iCircularBuffer = this.getCircularBufferPosition(); 1974 var iBufferLength = aBuffer.length; 1975 var fVolume = this.getVolume() / 64.0; 1976 1977 // Play Sample without checking for end or loop. 1978 // 1979 var iSampleOffset = fCurrentSamplePosition | 0; 1980 var fSample = aSampleData[ iSampleOffset ]; 1981 var iMod = aSampleData.length; 1982 fCurrentSamplePosition = (fCurrentSamplePosition + 1) % iMod; 1983 iSampleOffset = fCurrentSamplePosition | 0; 1984 var fNextSample = aSampleData[ iSampleOffset ]; 1985 1986 for( var iSamples = iSamplesToFill, fFreqStep = this.getFrequencyStepRate(); --iSamples >= 0; ) 1987 { 1988 var fInterpolation = fCurrentSamplePosition - iSampleOffset; 1989 var fSampleInterpolation = ( fSample * ( 1.0 - fInterpolation ) + ( fNextSample * fInterpolation ) ); 1990 aBuffer[ iCircularBuffer++ ] = fSampleInterpolation * fVolume; 1991 iCircularBuffer %= iBufferLength; 1992 fCurrentSamplePosition += fFreqStep; 1993 1994 if( ((fCurrentSamplePosition | 0) - iSampleOffset) >= 1 ) 1995 { 1996 fSample = fNextSample; 1997 iSampleOffset = fCurrentSamplePosition | 0; 1998 fNextSample = aSampleData[ iSampleOffset ]; 1999 } 2000 } 2001 2002 this.setSamplePosition( (fCurrentSamplePosition + iMod -1) % iMod ); 2003 this.setCircularBufferPosition( iCircularBuffer ); 2004 }; 2005 2006 2007 // --------------------------------------------------------------------------- 2008 /** 2009 * Is TJC Soundtracker 2 Effect Command AutoSlide enabled? 2010 * 2011 * @return {bool} = True : AutoSlide enabled, False : AutoSlide disabled. 2012 */ 2013 weasel.Channel.prototype.getAutoSlide = function() 2014 { 2015 return this.bAutoSlide; 2016 }; 2017 2018 // --------------------------------------------------------------------------- 2019 /** 2020 * Set TJC Soundtracker 2 Effect Command AutoSlide enabled. 2021 * 2022 * @param {bool} bAutoSlide = True : AutoSlide enabled, False : AutoSlide disabled. 2023 */ 2024 weasel.Channel.prototype.setAutoSlide = function( bAutoSlide ) 2025 { 2026 this.bAutoSlide = bAutoSlide; 2027 }; 2028 2029 // --------------------------------------------------------------------------- 2030 /** 2031 * Get TJC Soundtracker 2 Effect Command AutoSlide value. 2032 * 2033 * @return {int} = The amount to slide the volume by this tick [-15 to 15]. 2034 */ 2035 weasel.Channel.prototype.getAutoSlideValue = function() 2036 { 2037 return this.iAutoSlideValue; 2038 }; 2039 2040 // --------------------------------------------------------------------------- 2041 /** 2042 * Set TJC Soundtracker 2 Effect Command AutoSlide enabled. 2043 * 2044 * @param {int} iAutoSlideValue = The amount to slide the volume by this tick [-15 to 15]. 2045 */ 2046 weasel.Channel.prototype.setAutoSlideValue = function( iAutoSlideValue ) 2047 { 2048 this.iAutoSlideValue = iAutoSlideValue; 2049 }; 2050 2051 // --------------------------------------------------------------------------- 2052 /** 2053 * Set Noisetracker Effect Command Note Portomento target note period (the note to slide too). 2054 * 2055 * @param {int} iTargetPeriod = The note period to slide too. 2056 */ 2057 weasel.Channel.prototype.setNotePortamentoTarget = function( iTargetPeriod ) 2058 { 2059 this.iNotePortamentoTargetPeriod = iTargetPeriod; 2060 }; 2061 2062 // --------------------------------------------------------------------------- 2063 /** 2064 * Set Noisetracker Effect Command Note Portomento slide speed. 2065 * 2066 * @param {int} iPortamentoSpeed = The speed at which to slide the note, if not zero. 2067 */ 2068 weasel.Channel.prototype.setNotePortamentoSpeed = function( iPortamentoSpeed ) 2069 { 2070 if( iPortamentoSpeed > 0 ) 2071 { 2072 this.iNotePoramentoSpeed = iPortamentoSpeed; 2073 } 2074 }; 2075 2076 // --------------------------------------------------------------------------- 2077 /** 2078 * Set Noisetracker Vibrato Table Position. 2079 * 2080 * @param {int} iTableOffset = The current position in the sinewave [0-255] 2081 * of the Vibrato, set to zero when a new sample is started in Noisetracker. 2082 */ 2083 weasel.Channel.prototype.setVibratoTablePosition = function( iTableOffset ) 2084 { 2085 this.iVibratoTablePosition = iTableOffset & 0xff; 2086 }; 2087 2088 // --------------------------------------------------------------------------- 2089 /** 2090 * Set Protracker Tremolo Table Position. 2091 * 2092 * @param {int} iTableOffset = The current position in the sinewave [0-255] 2093 * of the Tremolo, set to zero when a new sample is started in Protracker. 2094 */ 2095 weasel.Channel.prototype.setTremoloTablePosition = function( iTableOffset ) 2096 { 2097 this.iTremoloTablePosition = iTableOffset & 0xff; 2098 }; 2099 2100 // --------------------------------------------------------------------------- 2101 /** 2102 * Set/Save Protracker Sample Offset for latter use when 00 is used as a Parameter. 2103 * 2104 * @param {int} iSampleOffsetParameter = The parameter part of the Set Sample Offset command [0-255]. 2105 */ 2106 weasel.Channel.prototype.setLastSampleOffsetParameter = function( iSampleOffsetParameter ) 2107 { 2108 this.iLastSampleOffsetParameter = iSampleOffsetParameter; 2109 }; 2110 // --------------------------------------------------------------------------- 2111 /** 2112 * Get the saved Protracker Sample Offset for latter use when 00 is used as a Parameter. 2113 * 2114 * @return {int} = The parameter part of the previously saved Set Sample Offset command. 2115 */ 2116 weasel.Channel.prototype.getLastSampleOffsetParameter = function( ) 2117 { 2118 return this.iLastSampleOffsetParameter; 2119 }; 2120 2121 // --------------------------------------------------------------------------- 2122 /** 2123 * Protracker Set Sample Offset command, start playing a sample from a given offset instead of zero. 2124 * 2125 * @param {int} iSampleOffset = The sample offset. 2126 */ 2127 weasel.Channel.prototype.setPlayFromOffset = function( iSampleOffset ) 2128 { 2129 this.iSampleStartOffset = iSampleOffset; 2130 }; 2131 2132 // --------------------------------------------------------------------------- 2133 /** 2134 * Protracker get Sample Offset, the starting offset in samples to play the current sample. 2135 * 2136 * @return {int} = The starting offset in samples to play the current sample. 2137 */ 2138 weasel.Channel.prototype.playFromOffset = function( ) 2139 { 2140 return this.iSampleStartOffset; 2141 }; 2142 2143 // --------------------------------------------------------------------------- 2144 /** 2145 * Set Protracker Sample Fine Tune. 2146 * 2147 * @param {int} iFineTune = The Fine Tune value as stored in the pattern (0-15). 2148 */ 2149 weasel.Channel.prototype.setFineTune = function( iFineTune ) 2150 { 2151 this.iFineTune = iFineTune; 2152 }; 2153 2154 // --------------------------------------------------------------------------- 2155 /** 2156 * Get Protracker Sample Fine Tune. 2157 * 2158 * @return {int} = The Fine Tune value as stored in the pattern (0-15). 2159 */ 2160 weasel.Channel.prototype.getFineTune = function( ) 2161 { 2162 return this.iFineTune; 2163 }; 2164 2165 // --------------------------------------------------------------------------- 2166 /** 2167 * Get Protracker Pattern Row Loop point. 2168 * 2169 * @return {int} = The row number in the current pattern to loop too. 2170 */ 2171 weasel.Channel.prototype.getPatternRowLoopStart = function( ) 2172 { 2173 return this.iPatternRowLoopStart; 2174 }; 2175 2176 // --------------------------------------------------------------------------- 2177 /** 2178 * Set Protracker Pattern Row Loop Effect Command. 2179 * 2180 * @param {int} iRow = The row number in the current pattern to loop to. 2181 */ 2182 weasel.Channel.prototype.setPatternRowLoopStart = function( iRow ) 2183 { 2184 this.iPatternRowLoopStart = iRow; 2185 }; 2186 2187 // --------------------------------------------------------------------------- 2188 /** 2189 * Get Protracker Pattern Row Loop counter. 2190 * 2191 * @return {int} = The current Pattern Row Loop counter value. 2192 */ 2193 weasel.Channel.prototype.getPatternRowLoopCounter = function( ) 2194 { 2195 return this.iPatternRowLoopCounter; 2196 }; 2197 2198 // --------------------------------------------------------------------------- 2199 /** 2200 * Set Protracker Pattern Row Loop Effect Command's loop counter. 2201 * 2202 * @param {int} iLoopCounter = The number of times to loop. 2203 */ 2204 weasel.Channel.prototype.setPatternRowLoopCounter = function( iLoopCounter ) 2205 { 2206 this.iPatternRowLoopCounter = iLoopCounter; 2207 }; 2208 2209 // --------------------------------------------------------------------------- 2210 /** 2211 * Decrement Protracker Pattern Row Loop Effect Command's loop counter. 2212 */ 2213 weasel.Channel.prototype.decPatternRowLoopCounter = function( ) 2214 { 2215 if( --this.iPatternRowLoopCounter < 0 ) 2216 this.iPatternRowLoopCounter = 0; 2217 }; 2218 2219 2220 // --------------------------------------------------------------------------- 2221 /** 2222 * Get Protracker Vibrato Continue Waveform setting. 2223 * 2224 * @return {bool} = Whether the vibrato waveform will be restart when a new note is encountered. 2225 */ 2226 weasel.Channel.prototype.getProtrackerContinueVibratoWaveform = function( ) 2227 { 2228 return this.bVibratoContinueWaveform; 2229 }; 2230 2231 // --------------------------------------------------------------------------- 2232 /** 2233 * Set Protracker Vibrato Continue Waveform when a note is encountered or not. 2234 * 2235 * @param {bool} bContinue = true : do not restart the waveform when a new note is encountered. false : Restart vibrato waveform when a new note is encountered. 2236 */ 2237 weasel.Channel.prototype.setProtrackerContinueVibratoWaveform = function( bContinue ) 2238 { 2239 this.bVibratoContinueWaveform = bContinue; 2240 }; 2241 2242 2243 // --------------------------------------------------------------------------- 2244 /** 2245 * Get Protracker Vibrato Waveform Type setting. 2246 * 2247 * @return {weasel.Channel.prototype.ProtrackerVibratoWaveform} = The Protracker Vibrato Waveform type. 2248 */ 2249 weasel.Channel.prototype.getProtrackerVibratoWaveformType = function( ) 2250 { 2251 return this.iVibratoWaveformType; 2252 }; 2253 2254 // --------------------------------------------------------------------------- 2255 /** 2256 * Set Protracker Vibrato Waveform Type used for the Protracker Vibrato command. 2257 * 2258 * @param {weasel.Channel.prototype.ProtrackerVibratoWaveform} iWaveformType = The Vibrato Waveform type to use. 2259 */ 2260 weasel.Channel.prototype.setProtrackerVibratoWaveformType = function( iWaveformType ) 2261 { 2262 this.iVibratoWaveformType = iWaveformType; 2263 }; 2264 2265 // --------------------------------------------------------------------------- 2266 /** 2267 * Set Protracker Tremolo Waveform Type used for the Protracker Tremolo command. 2268 * 2269 * @param {weasel.Channel.prototype.ProtrackerTremoloWaveform} iWaveformType = The Tremolo Waveform type to use. 2270 */ 2271 weasel.Channel.prototype.setProtrackerTremoloWaveformType = function( iWaveformType ) 2272 { 2273 this.iTremoloWaveformType = iWaveformType; 2274 }; 2275 2276 // --------------------------------------------------------------------------- 2277 /** 2278 * Get Protracker Tremolo Waveform Type setting. 2279 * 2280 * @return {weasel.Channel.prototype.ProtrackerTremoloWaveform} = The Protracker Tremolo Waveform type. 2281 */ 2282 weasel.Channel.prototype.getProtrackerTremoloWaveformType = function( ) 2283 { 2284 return this.iTremoloWaveformType; 2285 }; 2286 2287 // --------------------------------------------------------------------------- 2288 /** 2289 * Get Protracker Tremolo Continue Waveform setting. 2290 * 2291 * @return {bool} = Whether the Tremolo waveform will be restart when a new note is encountered. 2292 */ 2293 weasel.Channel.prototype.getProtrackerContinueTremoloWaveform = function( ) 2294 { 2295 return this.bTremoloContinueWaveform; 2296 }; 2297 2298 // --------------------------------------------------------------------------- 2299 /** 2300 * Set Protracker Tremolo Continue Waveform when a note is encountered or not. 2301 * 2302 * @param {bool} bContinue = true : do not restart the waveform when a new note is encountered. false : Restart Tremolo waveform when a new note is encountered. 2303 */ 2304 weasel.Channel.prototype.setProtrackerContinueTremoloWaveform = function( bContinue ) 2305 { 2306 this.bTremoloContinueWaveform = bContinue; 2307 }; 2308 2309 // --------------------------------------------------------------------------- 2310 /** 2311 * Get current pattern cell. 2312 * 2313 * @return {weasel.PatternCell|weasel.MKPatternCell} = The current pattern cell. 2314 */ 2315 weasel.Channel.prototype.getCurrentPatternCell = function( ) 2316 { 2317 return this.oPatternCell; 2318 }; 2319 2320 // --------------------------------------------------------------------------- 2321 /** 2322 * Set current pattern cell (this is just used for easy reference and does not 2323 * set the effects/note period/instrument of the current channel). 2324 * 2325 * @param {weasel.PatternCell|weasel.MKPatternCell} oPatternCell = The current pattern cell, can be null. 2326 */ 2327 weasel.Channel.prototype.setCurrentPatternCell = function( oPatternCell ) 2328 { 2329 this.oPatternCell = oPatternCell; 2330 }; 2331 2332 // --------------------------------------------------------------------------- 2333 /** 2334 * Set Protracker Invert Loop Speed. 2335 * 2336 * @param {int} iInvertLoopSpeed = The Invert Loop Speed. 2337 */ 2338 weasel.Channel.prototype.setProtrackerInvertLoopSpeed = function( iInvertLoopSpeed ) 2339 { 2340 if( iInvertLoopSpeed < 0 ) 2341 { 2342 iInvertLoopSpeed = 0; 2343 } 2344 else if( iInvertLoopSpeed > 15 ) 2345 { 2346 iInvertLoopSpeed = 15; 2347 } 2348 this.iInvertLoopSpeed = iInvertLoopSpeed; 2349 }; 2350 2351 // --------------------------------------------------------------------------- 2352 /** 2353 * Set Protracker Glissando mode, which is used during Note Portamento (Glissando 2354 * limits the pitch bend to actual physical keys on a keyboard, for example 2355 * a piano cannot smooth pitch bend between two adjacent keys to goes from one to 2356 * the other. Unlike a trombone). 2357 * 2358 * @param {bool} bGlissandoMode = Enable/ disable Glissando when using Toneportamento. 2359 */ 2360 weasel.Channel.prototype.setGlissando = function( bGlissandoMode ) 2361 { 2362 this.bGlissandoMode = bGlissandoMode; 2363 }; 2364 2365 // --------------------------------------------------------------------------- 2366 /** 2367 * Get Protracker Invert Loop Speed. 2368 * 2369 * @return {int} = The Protracker Invert Loop Speed. 2370 */ 2371 weasel.Channel.prototype.getProtrackerInvertLoopSpeed = function( ) 2372 { 2373 return this.iInvertLoopSpeed; 2374 }; 2375 2376 2377 // --------------------------------------------------------------------------- 2378 /** 2379 * Set Protracker Invert Loop Offset. 2380 * 2381 * @param {weasel.Instrument} oInstrument = The current instrument. 2382 */ 2383 weasel.Channel.prototype.setProtrackerInvertLoopOffset = function( oInstrument ) 2384 { 2385 // Have to use the instruments attributes as the sample's attributes may have 2386 // been modified due to Noisetracker/Protracker Loop Quirks mode. 2387 // 2388 var iLoopOffsetInBytes = oInstrument.getLoopOffsetInBytes(); 2389 var iOriginalSampleSize = oInstrument.getLengthInWords() * 2; 2390 2391 if(iLoopOffsetInBytes > iOriginalSampleSize ) 2392 { 2393 iLoopOffsetInBytes = iOriginalSampleSize; 2394 } 2395 2396 this.iInvertLoopSampleOffset = this.iInvertLoopSampleLoopStart = iLoopOffsetInBytes; 2397 }; 2398 2399 // --------------------------------------------------------------------------- 2400 /** 2401 * Handle Protracker Invert Loop updates to the current sample. 2402 * 2403 */ 2404 weasel.Channel.prototype.updateProtrackerInvertLoop = function( ) 2405 { 2406 var iSpeed = this.iInvertLoopSpeed; 2407 2408 if( 0 == iSpeed ) 2409 { 2410 return; 2411 } 2412 2413 var iUpdateTime = this.iInvertLoopTimeDelay; 2414 iUpdateTime += weasel.FormatProTrackerMK.InvertSampleLoop[ iSpeed ]; 2415 2416 if( iUpdateTime < 128 ) 2417 { 2418 this.iInvertLoopTimeDelay = iUpdateTime; 2419 return; 2420 } 2421 2422 this.iInvertLoopTimeDelay = 0; 2423 var oSample = this.getSample(); 2424 2425 if( !oSample ) 2426 { 2427 return; 2428 } 2429 2430 var iLoopEnd = this.iInvertLoopSampleLoopStart + oSample.getLoopLength(); 2431 var iInvertSampleOffset = ++this.iInvertLoopSampleOffset; 2432 2433 if( iInvertSampleOffset >= iLoopEnd ) 2434 { 2435 this.iInvertLoopSampleOffset = iInvertSampleOffset = this.iInvertLoopSampleLoopStart; 2436 } 2437 2438 oSample.invertSample( iInvertSampleOffset ); 2439 }; 2440 2441 // --------------------------------------------------------------------------- 2442 /** 2443 * Get whether there is No Note Delay Quirk to be used when the current sample is played (state changes on a per row basis). 2444 * 2445 * @return {bool} = true : No Note Delay Quirk (so normal DMA Wait start), false : Note Delay Quirk effecting this channel. 2446 */ 2447 weasel.Channel.prototype.getNoNoteDelayQuirk = function( ) 2448 { 2449 return this.bNoNoteDelayQuirk; 2450 }; 2451 2452 // --------------------------------------------------------------------------- 2453 /** 2454 * Set whether there is No Note Delay Quirk to be used when the current sample is played. 2455 * 2456 * @param {bool} bNoNoteDelayQuirk = True : No Note Delay Quirk, False : Note Delay Quirk effecting this channel. 2457 */ 2458 weasel.Channel.prototype.setNoNoteDelayQuirk = function( bNoNoteDelayQuirk ) 2459 { 2460 this.bNoNoteDelayQuirk = bNoNoteDelayQuirk; 2461 }; 2462 2463 // --------------------------------------------------------------------------- 2464 /** 2465 * Get Taketracker/Fasttracker panning position. 2466 * 2467 * @return {int} = The Taketracker/Fasttracker panning position [0-255] where 0 = full left, 255 full right and 128 is middle. 2468 */ 2469 weasel.Channel.prototype.getPanningPosition = function( ) 2470 { 2471 return this.iPanningPosition; 2472 }; 2473 2474 // --------------------------------------------------------------------------- 2475 /** 2476 * Set Taketracker/Fasttracker panning position. 2477 * 2478 * @param {int} iPanningPosition = The Taketracker/Fasttracker panning position [0-255] where 0 = full left, 255 full right and 128 is middle. 2479 */ 2480 weasel.Channel.prototype.setPanningPosition = function( iPanningPosition ) 2481 { 2482 if( iPanningPosition < 0 ) 2483 { 2484 iPanningPosition = 0; 2485 } 2486 else if( iPanningPosition > 255 ) 2487 { 2488 iPanningPosition = 255; 2489 } 2490 2491 this.iPanningPosition = iPanningPosition; 2492 }; 2493 2494 // --------------------------------------------------------------------------- 2495 /** 2496 * Clear appropriate attributes for a (manual) song restart (not when a song loops round to the beginning). 2497 */ 2498 weasel.Channel.prototype.clearChannel = function( ) 2499 { 2500 this.setSample( null ); 2501 this.setNotePeriod( 0 ); 2502 this.setLastSavedNotePeriod( 0 ); 2503 this.setShadowNotePeriod( 0 ); 2504 this.setInstrumentNumber( 0 ); 2505 this.setPendingInstrumentNumber( 0, null ); 2506 this.setVolume( 0 ); 2507 this.setEffectNumber( 0 ); 2508 this.setEffectParameter( 0 ); 2509 this.setAutoSlide( false ); 2510 this.setAutoSlideValue( 0 ); 2511 this.setNotePortamentoTarget( 0 ); 2512 this.iNotePoramentoSpeed = 0; 2513 this.iVibratoTablePosition = 0; 2514 this.iLastVibratoParameter = 0; 2515 this.iLastSampleOffsetParameter = 0; 2516 this.iSampleStartOffset = 0; 2517 this.iFineTune = 0; 2518 this.iPatternRowLoopStart = 0; 2519 this.iPatternRowLoopCounter = 0; 2520 this.bVibratoContinueWaveform = false; 2521 this.iVibratoWaveformType = weasel.Channel.prototype.ProtrackerVibratoWaveform.SineWave; 2522 this.oPatternCell = null; 2523 this.iLastTremoloParameter = 0; 2524 this.iTremoloTablePosition = 0; 2525 this.bTremoloContinueWaveform = false; 2526 this.iTremoloWaveformType = weasel.Channel.prototype.ProtrackerTremoloWaveform.SineWave; 2527 this.iInvertLoopSpeed = 0; 2528 this.iInvertLoopTimeDelay = 0; 2529 this.iInvertLoopSampleOffset = 0; 2530 this.iInvertLoopSampleLoopStart = 0; 2531 this.bGlissandoMode = false; 2532 this.bNoNoteDelayQuirk = false; 2533 this.iPanningPosition = 128; 2534 }; 2535 2536