Source: modules/Mixer.js

/**
 * @summary
 *    A mixer mixes two sources together.
 *    Mixer example on codepen:
 *    <a href="https://codepen.io/xangadix/pen/zewydR" target="_blank">codepen</a>
 *
 * @description
 *   ### Mixmode
 Mixers support a [`Mixmode`](Mixer.html#mixMode).
 The Mixmode defines the curvature of the crossfade.

 In a regular crossfade, source 1 would fade out while source 2 fades in. At the center both sources are then both at 50% opacity; however, 2 sources with 50% opacity only add up to ~75% opacity, not to 100%. This means that the output is **darker** in the middle of  the crossfade then it is at both ends. This is the default _Mixmode_, the other modes play with these settings

 ```
   1: NORMAL (default),   regular, linear crossfade
   2: HARD,               switches with a hard cut at 50%
   3: NAM,                fades with an upward curvature forcing 100% opacity throughout the crossfade (lighter!)
   4: FAM,                fades with a downward curve, forcing a 'overlay' period
   5: NON DARK,           Never goes dark, 0-2 linear curve, capped at 1 and .36
   6: LEFT,               forces the pod on 0 (locks pod)
   7: RIGHT,              forces the pod on 1 (locks pod)
   8: CENTER,             forces both sources at ~66% (locks pod)
   9: BOOM                forces both sources at 100%, allows for overflow (lighter!) (locks pod)
 ```

 ### Blendmode
 Mixers also support a [`Blendmode`](Mixer.html#blendMode).
 Think of them as the a Photoshop Blendmodes. They tell the mixer how to blend Source 1 and Source 2 together.

 ```
   1 ADD (default),
   2 SUBSTRACT,
   3 MULTIPLY,
   4 DARKEN,
   5 COLOUR BURN,
   6 LINEAR_BURN,
   7 LIGHTEN,
   8 SCREEN,
   9 COLOUR_DODGE,
   10 LINEAR_DODGE,
   11 OVERLAY,
   12 SOFT_LIGHT,
   13 HARD_LIGHT,
   14 VIVID_LIGHT,
   15 LINEAR_LIGHT,
   16 PIN_LIGHT,
   17 DIFFERENCE,
   18 EXCLUSION
 ```

 Switch both mixer and blendmode in realtime:

 ```
 mixer1.mixMode()       // shows mixmode (default 1, NORMAL)
 mixer1.mixMode(8)      // set MixMode to BOOM
 mixer1.blendMode(1)    // set blendmode to ADD (default)
 mixer1.blendMode(14)   // set blendmode to VIVID_LIGHT
 ```

 Move the pod up and down over time, or fade from source1 to source2 and back
 again.
 ```
 ar c = 0;
 setInterval( function() {
   c += 0.01
   mixer1.pod ( ( Math.sin(c) * 0.5 ) + 0.5 );
 })
 ```

 *
 * @example let myMixer = new Mixer( renderer, { source1: myVideoSource, source2: myOtherMixer });
 * @constructor Module#Mixer
 * @implements Module
 * @param renderer:GlRenderer
 * @param options:Object
 * @author Sense Studios
 */

 // of 18: 1 ADD (default), 2 SUBSTRACT, 3 MULTIPLY, 4 DARKEN, 5 COLOUR BURN,
 // 6 LINEAR_BURN, 7 LIGHTEN,  8 SCREEN, 9 COLOUR_DODGE, 10 LINEAR_DODGE,
 // 11 OVERLAY, 12 SOFT_LIGHT, 13 HARD_LIGHT, 14 VIVID_LIGHT, 15 LINEAR_LIGHT,
 // 16 PIN_LIGHT, 17 DIFFERENCE, 18 EXCLUSION

 // of 8 1: NORMAL, 2: HARD, 3: NAM, 4: FAM, 5: LEFT, 6: RIGHT, 7: CENTER, 8: BOOM

/*
  class Polygon extend Shape {
    constructor(height, width) {
      this.x = super.x
      this.y = super.y
      this.height = height;
      this.width = width;
    }

    get area() {
      return this.calcArea()
    }

    // klass.area

    set area(a) {
   }

    // klass.area = 2

    calcArea() {
      return this.height * this.width;
    }

    // klass.calcArea( ... )

    static info() {
      return "lalalala info"
    }

    // Class.info()
  }
*/

var Mixer = class {

  static function_list() {
    return [["BLEND", "method","blendMode"], ["MIX", "method","mixMode"], ["POD", "set", "pod"] ]
  }

  constructor( renderer, options ) {

    // create and instance
    var _self = this;
    if (renderer == undefined) return

    // set or get uid
    if ( options.uuid == undefined ) {
      _self.uuid = "Mixer_" + (((1+Math.random())*0x100000000)|0).toString(16).substring(1);
    } else {
      _self.uuid = options.uuid
    }

    // add to renderer
    renderer.add(_self)

    // set options
    var _options;
    if ( options != undefined ) _options = options

    // set type
    _self.type = "Module";

    // add local variables
    var alpha1 = 1;
    var alpha2 = 0;
    var pod = 0;

    // hoist an own bpm here
    var currentBPM = 128
    var currentMOD = 1
    var currentBpmFunc = function() { return currentBPM; }
    _self.autoFade = false
    _self.fading = false

    var mixmode = 1;
    _self.mixmodes = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

    var blendmode = 1;
    _self.blendmodes = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ];

    var source1, source2;
    source1 = options.source1 //|| options.src1;   // Mandatory
    source2 = options.source2 //|| options.src2;   // Mandatory

    _self.init = function() {

      // add uniforms to renderer
      renderer.customUniforms[_self.uuid+'_mixmode'] = { type: "i", value: 1 }
      renderer.customUniforms[_self.uuid+'_blendmode'] = { type: "i", value: 1 }
      //renderer.customUniforms[_self.uuid+'_pod'] = { type: "f", value: 0.5 }
      renderer.customUniforms[_self.uuid+'_alpha1'] = { type: "f", value: 0.5 }
      renderer.customUniforms[_self.uuid+'_alpha2'] = { type: "f", value: 0.5 }
      renderer.customUniforms[_self.uuid+'_sampler'] = { type: "t", value: null }

      // add uniforms to fragmentshader
      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform int '+_self.uuid+'_mixmode;\n/* custom_uniforms */')
      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform int '+_self.uuid+'_blendmode;\n/* custom_uniforms */')
      //renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform float '+_self.uuid+'_pod;\n/* custom_uniforms */')
      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform float '+_self.uuid+'_alpha1;\n/* custom_uniforms */')
      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform float '+_self.uuid+'_alpha2;\n/* custom_uniforms */')
      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_uniforms */', 'uniform vec4 '+_self.uuid+'_output;\n/* custom_uniforms */')

      // add blendmodes helper, we only need it once
      if ( renderer.fragmentShader.indexOf('vec4 blend ( vec4 src, vec4 dst, int blendmode )') == -1 ) {
        renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_helpers */',
  `
  vec4 blend ( vec4 src, vec4 dst, int blendmode ) {
    if ( blendmode ==  1 ) return src + dst;
    if ( blendmode ==  2 ) return src - dst;
    if ( blendmode ==  3 ) return src * dst;
    if ( blendmode ==  4 ) return min(src, dst);
    if ( blendmode ==  5)  return vec4((src.x == 0.0) ? 0.0 : (1.0 - ((1.0 - dst.x) / src.x)), (src.y == 0.0) ? 0.0 : (1.0 - ((1.0 - dst.y) / src.y)), (src.z == 0.0) ? 0.0 : (1.0 - ((1.0 - dst.z) / src.z)),1.0);
    if ( blendmode ==  6 ) return (src + dst) - 1.0;
    if ( blendmode ==  7 ) return max(src, dst);
    if ( blendmode ==  8 ) return (src + dst) - (src * dst);
    if ( blendmode ==  9 ) return vec4((src.x == 1.0) ? 1.0 : min(1.0, dst.x / (1.0 - src.x)), (src.y == 1.0) ? 1.0 : min(1.0, dst.y / (1.0 - src.y)), (src.z == 1.0) ? 1.0 : min(1.0, dst.z / (1.0 - src.z)), 1.0);
    if ( blendmode == 10 ) return src + dst;
    if ( blendmode == 11 ) return vec4((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)), (dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)), (dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z)), 1.0);
    if ( blendmode == 12 ) return vec4((src.x <= 0.5) ? (dst.x - (1.0 - 2.0 * src.x) * dst.x * (1.0 - dst.x)) : (((src.x > 0.5) && (dst.x <= 0.25)) ? (dst.x + (2.0 * src.x - 1.0) * (4.0 * dst.x * (4.0 * dst.x + 1.0) * (dst.x - 1.0) + 7.0 * dst.x)) : (dst.x + (2.0 * src.x - 1.0) * (sqrt(dst.x) - dst.x))), (src.y <= 0.5) ? (dst.y - (1.0 - 2.0 * src.y) * dst.y * (1.0 - dst.y)) : (((src.y > 0.5) && (dst.y <= 0.25)) ? (dst.y + (2.0 * src.y - 1.0) * (4.0 * dst.y * (4.0 * dst.y + 1.0) * (dst.y - 1.0) + 7.0 * dst.y)) : (dst.y + (2.0 * src.y - 1.0) * (sqrt(dst.y) - dst.y))), (src.z <= 0.5) ? (dst.z - (1.0 - 2.0 * src.z) * dst.z * (1.0 - dst.z)) : (((src.z > 0.5) && (dst.z <= 0.25)) ? (dst.z + (2.0 * src.z - 1.0) * (4.0 * dst.z * (4.0 * dst.z + 1.0) * (dst.z - 1.0) + 7.0 * dst.z)) : (dst.z + (2.0 * src.z - 1.0) * (sqrt(dst.z) - dst.z))), 1.0);
    if ( blendmode == 13 ) return vec4((src.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - src.x) * (1.0 - dst.x)), (src.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - src.y) * (1.0 - dst.y)), (src.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - src.z) * (1.0 - dst.z)), 1.0);
    if ( blendmode == 14 ) return vec4((src.x <= 0.5) ? (1.0 - (1.0 - dst.x) / (2.0 * src.x)) : (dst.x / (2.0 * (1.0 - src.x))), (src.y <= 0.5) ? (1.0 - (1.0 - dst.y) / (2.0 * src.y)) : (dst.y / (2.0 * (1.0 - src.y))), (src.z <= 0.5) ? (1.0 - (1.0 - dst.z) / (2.0 * src.z)) : (dst.z / (2.0 * (1.0 - src.z))),1.0);
    if ( blendmode == 15 ) return 2.0 * src + dst - 1.0;
    if ( blendmode == 16 ) return vec4((src.x > 0.5) ? max(dst.x, 2.0 * (src.x - 0.5)) : min(dst.x, 2.0 * src.x), (src.x > 0.5) ? max(dst.y, 2.0 * (src.y - 0.5)) : min(dst.y, 2.0 * src.y), (src.z > 0.5) ? max(dst.z, 2.0 * (src.z - 0.5)) : min(dst.z, 2.0 * src.z),1.0);
    if ( blendmode == 17 ) return abs(dst - src);
    if ( blendmode == 18 ) return src + dst - 2.0 * src * dst;
    return src + dst;
  }
  /* custom_helpers */
  `
        );
      }

      var shadercode = ""
      shadercode += "vec4 "+_self.uuid+"_output = vec4( blend( "
      shadercode += source1.uuid+"_output * "+_self.uuid+"_alpha1, "
      shadercode += source2.uuid+"_output * "+_self.uuid+"_alpha2, "
      shadercode += _self.uuid+"_blendmode ) "
      shadercode += ")"
      shadercode += " + vec4(  "+source1.uuid+"_output.a < 1.0 ? "+source2.uuid+"_output.rgba * ( "+_self.uuid+"_alpha1 - "+source1.uuid+"_output.a ) : vec4( 0.,0.,0.,0. )  ) "
      shadercode += " + vec4(  "+source2.uuid+"_output.a < 1.0 ? "+source1.uuid+"_output.rgba * ( "+_self.uuid+"_alpha2 - - "+source2.uuid+"_output.a ) : vec4( 0.,0.,0.,0. )  ) "
      shadercode += ";\n"
      shadercode += "  /* custom_main */  "

      renderer.fragmentShader = renderer.fragmentShader.replace('/* custom_main */', shadercode )
    }

    // autofade bpm
    var starttime = (new Date()).getTime()
    var c = 0
    var cnt = 0

    // fade time
    var fadeAtTime = 0
    var fadeTime = 0
    var fadeTo = "b"
    var fadeDuration = 0

    /** @function Addon#Mixer~update */

    /**
     * @description
     *  binds _currentBpmFunc_ to a function
     *  whatever BPM _currentBpmFunc_ returns will be bpm used.
     *  it's called on update
     * @example
     *   var mixer1 = new Mixer( renderer, { source1: file, source2: file})
     *   var audioanalysis = new AudioAnalysis( renderer, { audio: file })
     *   audioanalysis.bindBPM( audioanalysis.getBPM() * 0.5 )
     * @function Module#Mixer#bindBpm
     * @param {function} binding allows for overriding internal bpm
     */

    _self.update = function() {

      if ( _self.autoFade ) { // maybe call this bpmFollow?
        // pod = currentBPM
        currentBPM = currentBpmFunc()
        c = ((new Date()).getTime() - starttime) / 1000;
        _self.pod( ( Math.sin( c * Math.PI * currentBPM * currentMOD / 60 ) / 2 + 0.5 ) )
      }

      if ( _self.fading ) { // then call this autoFade

        var now = (new Date()).getTime()
        fadeAtTime = (fadeTime - now);
        var _num = fadeAtTime/fadeDuration
        if (fadeTo =="b") _num = Math.abs(_num - 1)
        //console.log("fader...", _num, Math.abs(_num - 1), fadeAtTime, fadeTime, now, fadeDuration, fadeTo)
        if (_num < 0 ) _num = 0
        if (_num > 1 ) _num = 1

        _self.pod( _num )

        if ( fadeAtTime < 0 ) {
          _self.fading = false

          // allstop
          _num = Math.round(_num)
          _self.pod(_num)
        }
      }
    }

    /** @function Addon#Mixer~render */
    _self.render = function() {
      return pod
    }

    // ---------------------------------------------------------------------------
    // HELPERS
    // ---------------------------------------------------------------------------

    // you shouldnt be able to set these directly
    _self.alpha1 = function() { return alpha1 }
    _self.alpha2 = function() { return alpha2 }

    /**
     * @function Module#Mixer#mixMode
     * @param {integer} mixmode index of the Mixmode
     *
     * @description
     *  gets or sets the _mixMode_, there are 8 MixModes available, numbered 1-9;
     *  ```
     *  1: NORMAL (default),   regular, linear crossfade
     *  2: HARD,               switches with a hard cut at 50%
     *  3: NAM,                fades with an upward curvature forcing 100% opacity throughout the crossfade (lighter!)
     *  4: FAM,                fades with a downward curve, forcing a 'overlay' period
     *  5: NON DARK,           Never goes dark, 0-2 linear curve, capped at 1 and .36
     *  6: LEFT,               forces the pod on 0 (locks pod)
     *  7: RIGHT,              forces the pod on 1 (locks pod)
     *  8: CENTER,             forces both sources at ~66% (locks pod)
     *  9: BOOM                forces both sources at 100%, allows for overflow (lighter!) (locks pod)
     *  ```
     *
    */
    _self.mixMode = function( _num ) {
      if ( _num != undefined ) { mixmode = _num }
      return mixmode
    }

    /**
     * @description
     *  gets or sets the _blendMode_, there are 18 Blendmodes available, numbered 1-18;
     *  ```
     *  1 ADD (default),
     *  2 SUBSTRACT,
     *  3 MULTIPLY,
     *  4 DARKEN,
     *  5 COLOUR BURN,
     *  6 LINEAR_BURN,
     *  7 LIGHTEN,
     *  8 SCREEN,
     *  9 COLOUR_DODGE,
     *  10 LINEAR_DODGE,
     *  11 OVERLAY,
     *  12 SOFT_LIGHT,
     *  13 HARD_LIGHT,
     *  14 VIVID_LIGHT,
     *  15 LINEAR_LIGHT,
     *  16 PIN_LIGHT,
     *  17 DIFFERENCE,
     *  18 EXCLUSION
     *  ```
     * @function Module#Mixer#blendMode
     * @param {integer} blendmode index of the Blendmode
    */
    _self.blendMode = function( _num ) {
      if ( _num != undefined ) {
        blendmode = _num
        renderer.customUniforms[_self.uuid+'_blendmode'].value = blendmode
      }
      _self.pod( _self.pod() ) // update pod, precaution
      return blendmode
    }

    /**
     * @description the position of the handle, fader or pod. 0 is left, 1 is right
     * @function Module#Mixer#pod
     * @param {float} position position of the handle
     */
    _self.pod = function( _num ) {
      //console.log("---> POD:", _num)
      if ( _num != undefined ) {

        // set pod position
        pod = _num

        // evaluate current mix style
        // MIXMODE 1 normal mix
        if (mixmode == 1) {
          alpha1 = pod
          alpha2 = 1 - pod
        }

        // MIXMODE 2 hard mix
        if (mixmode == 2) {
          alpha1 = Math.round( pod )
          alpha2 = Math.round( 1-pod )
        }

        // MIXMODE 3 NAM mix
        if (mixmode == 3) {
          alpha1 = ( pod * 2 );
          alpha2 = 2 - ( pod * 2 );
          if ( alpha1 > 1 ) alpha1 = 1;
          if ( alpha2 > 1 ) alpha2 = 1;
        }

        // MIXMODE 4 FAM mix
        if (mixmode == 4) {
          alpha1 = ( pod * 2 );
          alpha2 = 2 - ( pod * 2 );
        }

        // MIXMODE 5 Non Dark mix
        if (mixmode == 5) {
          alpha1 = ( pod * 2 );
          alpha2 = 2 - ( pod * 2 );
          if ( alpha1 > 1 ) alpha1 = 1;
          if ( alpha2 > 1 ) alpha2 = 1;
          alpha1 += 0.36;
          alpha2 += 0.36;
        }

        // MIXMODE 6 left
        if (mixmode == 6) {
          alpha1 = 1;
          alpha2 = 0;
        }

        // MIXMODE 7 right
        if (mixmode == 7) {
          alpha1 = 0;
          alpha2 = 1;
        }

        // MIXMODE 8 center
        if (mixmode == 8) {
          alpha1 = 0.5;
          alpha2 = 0.5;
        }

        // MIXMODE 9 BOOM
        if (mixmode == 9) {
          alpha1 = 1;
          alpha2 = 1;
        }

        // DEPRICATED BECAUSE OF actual ALPHA
        // MIXMODE X ADDITIVE MIX LEFT (use with lumkey en chromkey)
        if (mixmode == 10 ) {
          alpha1 = pod
          alpha2 = 1;
        }

        // MIXMODE X ADDITIVE MIX RIGHT (use with lumkey en chromkey)
        if (mixmode == 11 ) {
          alpha1 = 1;
          alpha2 = pod
        }

        // send alphas to the shader
        renderer.customUniforms[_self.uuid+'_alpha1'].value = alpha1;
        renderer.customUniforms[_self.uuid+'_alpha2'].value = alpha2;
      }
      return pod;
    }

    /**
     * @description
     *  gets or sets the _bpm_ or beats per minutes, locally in this mixer
     *  defaults to 128
     * @function Module#Mixer#bpm
     * @param {number} bpm beats per minute
    */
    _self.bpm = function(_num) {
        if ( _num  != undefined ) currentBPM = _num
        return currentBPM
    }

    /**
     * @description
     *  gets or sets the _currentMOD_ or modifyer for the bpm
     *  this way you can modify the actual tempo, make the beats
     *  follow on half speed, or dubbel speed or *4, *2, /2, /4 etc.
     * @function Module#Mixer#bpmMod
     * @param {number} currentMod beat multiplyer for tempo
    */
    _self.bpmMod = function( _num ) {
      if ( _num  != undefined ) currentMOD = _num
      return currentMOD
    }

    /**
     * @description
     *  binds _currentBpmFunc_ to a function
     *  whatever BPM _currentBpmFunc_ returns will be bpm used.
     *  it's called on update
     * @example
     *   var mixer1 = new Mixer( renderer, { source1: file, source2: file})
     *   var audioanalysis = new AudioAnalysis( renderer, { audio: file })
     *   audioanalysis.bindBPM( audioanalysis.getBPM() * 0.5 )
     * @function Module#Mixer#bindBpm
     * @param {function} binding allows for overriding internal bpm
     */
    _self.bindBpm = function( _func ) {
        currentBpmFunc = _func
    }

    /**
     * @description
     *  sets setAutoFade true/false
     * @function Module#Mixer#setAutoFade
     * @param {boolean} autoFade to do, or do not
    */
    _self.setAutoFade = function( _bool ) {
      if ( _bool.toLowerCase() == "true" ) _self.autoFade = true
      if ( _bool.toLowerCase() == "false" ) _self.autoFade = false
    }

    /**
     * @description
     *  fades from one channel to the other in _duration_ milliseconds
     * @function Module#Mixer#fade
     * @param {float} fadeDuration the duration of the fade
    */
    _self.fade = function( _duration ) {
      var current = _self.pod()

      // starts the loop
      _self.fading = true

      var now = (new Date()).getTime()
      fadeTime = ( now + _duration );
      _self.pod() > 0.5 ? fadeTo = "a" : fadeTo = "b"
      console.log("fadeTo", fadeTo, fadeTime, now, _duration)
      fadeDuration = _duration
    }
  }
}