<commit_before>/* global Uint8Array */

import tags from './type-tags.js';
import Extendable from './extendable.js';
import type { ExtensionMap, ExtensionPoint } from './extendable.js';

type SuperPackedValue = Array<number>;
type Keyset = Array<string>;

/*
todo:
mapl, bmapl
timestamp
extension
factor out various common threads
*/

export default class Decoder extends Extendable {
  strings: Array<string>
  keysets: Array<Keyset>
  buffer : SuperPackedValue
  ptr: number

  constructor() {
    super();
    this.strings = [];
    this.keysets = [];
    this.buffer = [];
    this.ptr = 0;
  }

  static decode(buffer : SuperPackedValue, options? : { omittedKeysets? : Array<Keyset>, extensions? : ExtensionMap } = {}) : any {
    let d = new Decoder;
    if (options.extensions != null) {
      // $FlowFixMe: flow doesn't understand that ext is an ExtensionPoint
      Object.keys(options.extensions).forEach((ext : ExtensionPoint) => {
        // $FlowFixMe: flow doesn't understand that options.extensions is non-null here
        let extension = options.extensions[ext];
        d.extend(ext, extension.detector, extension.serialiser, extension.deserialiser);
      });
    }
    return d.decode(buffer, options);
  }

  decode(buffer : SuperPackedValue, options? : { omittedKeysets? : Array<Keyset> } = {}) : any {
    this.buffer = buffer;
    if (buffer[0] === tags.STRLUT) {
      this.strings = (this.decodeValue(): Array<string>);
      this.keysets = (this.decodeValue(): Array<Keyset>);
    }
    if (options.omittedKeysets != null) {
      let k = [];
      [].push.apply(k, options.omittedKeysets);
      [].push.apply(k, this.keysets);
      this.keysets = k;
    }
    return this.decodeValue();
  }

  readUInt32() {
    return this.buffer[this.ptr++] * 0x1000000 + (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]);
  }

  readFloat(eBits : 8 | 11, mBits : 23 | 52) {
    let bias = (1 << (eBits - 1)) - 1;
    let byteLength = (eBits + mBits + 1) / 8;

    let bytes = this.buffer.slice(this.ptr, this.ptr + byteLength);
    this.ptr += byteLength;

    // read sign, exponent, and beginning of mantissa from first two bytes
    let sign = (bytes[0] >>> 7) > 0 ? -1 : 1;
    let leadingBytes = (bytes[0] << 8) | bytes[1];
    let leadingMBits = 16 - (eBits + 1);
    let exp = (leadingBytes >>> leadingMBits) & ((1 << eBits) - 1);
    let mantissa = leadingBytes & ((1 << leadingMBits) - 1);

    // read remainder of mantissa
    for (let i = 2; i < byteLength; ++i) {
      mantissa = mantissa * 256 + bytes[i];
    }

    if (exp === (1 << eBits) - 1) {
      // NaN and +/- Infinity
      return (mantissa === 0 ? sign : 0) / 0;
    } else if (exp > 0) {
      // normal
      return sign * Math.pow(2, exp - bias) * (1 + mantissa / Math.pow(2, mBits));
    } else if (mantissa !== 0) {
      // subnormal
      return sign * Math.pow(2, -(bias - 1)) * (mantissa / Math.pow(2, mBits));
    }
    return sign * 0;
  }

  readString(length: number) {
    let str = '';
    for (let i = 0; i < length; ++i) {
      str += String.fromCharCode(this.buffer[this.ptr++]);
    }
    return decodeURIComponent(escape(str));
  }

  readArray(length: number): any {
    let out = [];
    for (let i = 0; i < length; ++i) {
      out.push(this.decodeValue(this.buffer));
    }
    return out;
  }

  readBooleanArray(length: number): any {
    let out = [];
    for (let i = 0; i < length; ++i) {
      out[i] = (this.buffer[this.ptr + (i >> 3)] & (0x80 >> (i % 8))) > 0;
    }
    this.ptr += (length >> 3) + (length % 8 !== 0);
    return out;
  }

  // todo: factor out "read type_ length value"
  // readUInt() { ... }

  decodeValue(): any {
    let type = this.buffer[this.ptr++];
    if (type < tags.UINT14_BASE) {
      return type;
    } else if (type < tags.NINT4_BASE) {
      return (type ^ tags.UINT14_BASE) << 8 | this.buffer[this.ptr++];
    } else if (type < tags.BARRAY4_BASE) {
      return -(type ^ tags.NINT4_BASE);
    } else if (type < tags.ARRAY5_BASE) {
      return this.readBooleanArray(type ^ tags.BARRAY4_BASE);
    } else if (type < tags.STR5_BASE) {
      return this.readArray(type ^ tags.ARRAY5_BASE);
    } else if (type < tags.FALSE) {
      return this.readString(type ^ tags.STR5_BASE);
    }
    switch (type) {
      case tags.FALSE: return false;
      case tags.TRUE: return true;
      case tags.NULL: return null;
      case tags.UNDEFINED: return void 0;
      case tags.UINT16:
        return (this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++];
      case tags.UINT24:
        return (this.buffer[this.ptr++]) << 16 | (this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++];
      case tags.UINT32:
        return this.readUInt32();
      case tags.UINT64:
        return this.readUInt32() * 0x100000000 + this.readUInt32();
      case tags.NINT8:
        return -(this.buffer[this.ptr++]);
      case tags.NINT16:
        return -((this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++]);
      case tags.NINT32:
        return -this.readUInt32();
      case tags.NINT64:
        return -(this.readUInt32() * 0x100000000 + this.readUInt32());
      case tags.FLOAT32:
        return this.readFloat(8, 23);
      case tags.DOUBLE64:
        return this.readFloat(11, 52);

      case tags.TIMESTAMP:
        return new Date(
          (this.buffer[this.ptr] & 0x80) > 0
            ? -(
                (~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) * 0x1000000 +
                (~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) + 1
              )
            : (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) * 0x1000000 +
              (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++])
        );

      case tags.BINARY_: {
        let length: number = this.decodeValue();
        // alternative to Uint8Array.from(...) for old browsers
        let out = new Uint8Array;
        for (let i = 0; i < length; ++i) {
          out[i] = this.buffer[this.ptr + i];
        }
        this.ptr += length;
        return out.buffer;
      }

      case tags.CSTRING: {
        let str = '';
        while (this.buffer[this.ptr] !== 0) {
          str += String.fromCharCode(this.buffer[this.ptr++]);
        }
        this.ptr++;
        return decodeURIComponent(escape(str));
      }
      case tags.STR8:
        return this.readString(this.buffer[this.ptr++]);
      case tags.STR_:
        return this.readString(this.decodeValue());
      case tags.STRREF:
        return this.strings[this.buffer[this.ptr++]];

      case tags.ARRAY8:
      case tags.STRLUT:
        return this.readArray(this.buffer[this.ptr++]);
      case tags.ARRAY_:
        return this.readArray(this.decodeValue());

      case tags.BARRAY8:
        return this.readBooleanArray(this.buffer[this.ptr++]);
      case tags.BARRAY_:
        return this.readBooleanArray(this.decodeValue());

      case tags.MAP_: {
        let out = {};
        let keysetIndex = this.decodeValue();
        let keys = this.keysets[keysetIndex];
        keys.forEach((key) => {
          out[key] = this.decodeValue();
        });
        return out;
      }

      case tags.BMAP_: {
        let out = {};
        let keysetIndex = this.decodeValue();
        let keys = this.keysets[keysetIndex];
        let bools = this.readBooleanArray(keys.length);
        for (let i = 0; i < keys.length; ++i) {
          out[keys[i]] = bools[i];
        }
        return out;
      }

      case tags.EXTENSION: {
        let ext : ExtensionPoint = this.decodeValue();
        return this.extensions[ext].deserialiser(this.decodeValue());
      }

      default:
        // This should never happen.
        throw new Error('Unknown case');
    }
  }
}

export const decode = Decoder.decode;
<commit_msg>use a const to make flow happy<commit_after>/* global Uint8Array */

import tags from './type-tags.js';
import Extendable from './extendable.js';
import type { ExtensionMap, ExtensionPoint } from './extendable.js';

type SuperPackedValue = Array<number>;
type Keyset = Array<string>;

/*
todo:
mapl, bmapl
timestamp
extension
factor out various common threads
*/

export default class Decoder extends Extendable {
  strings: Array<string>
  keysets: Array<Keyset>
  buffer : SuperPackedValue
  ptr: number

  constructor() {
    super();
    this.strings = [];
    this.keysets = [];
    this.buffer = [];
    this.ptr = 0;
  }

  static decode(buffer : SuperPackedValue, options? : { omittedKeysets? : Array<Keyset>, extensions? : ExtensionMap } = {}) : any {
    let d = new Decoder;
    const extensions = options.extensions;
    if (extensions != null) {
      // $FlowFixMe: flow doesn't understand that ext is an ExtensionPoint
      Object.keys(extensions).forEach((ext : ExtensionPoint) => {
        let extension = extensions[ext];
        d.extend(ext, extension.detector, extension.serialiser, extension.deserialiser);
      });
    }
    return d.decode(buffer, options);
  }

  decode(buffer : SuperPackedValue, options? : { omittedKeysets? : Array<Keyset> } = {}) : any {
    this.buffer = buffer;
    if (buffer[0] === tags.STRLUT) {
      this.strings = (this.decodeValue(): Array<string>);
      this.keysets = (this.decodeValue(): Array<Keyset>);
    }
    if (options.omittedKeysets != null) {
      let k = [];
      [].push.apply(k, options.omittedKeysets);
      [].push.apply(k, this.keysets);
      this.keysets = k;
    }
    return this.decodeValue();
  }

  readUInt32() {
    return this.buffer[this.ptr++] * 0x1000000 + (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]);
  }

  readFloat(eBits : 8 | 11, mBits : 23 | 52) {
    let bias = (1 << (eBits - 1)) - 1;
    let byteLength = (eBits + mBits + 1) / 8;

    let bytes = this.buffer.slice(this.ptr, this.ptr + byteLength);
    this.ptr += byteLength;

    // read sign, exponent, and beginning of mantissa from first two bytes
    let sign = (bytes[0] >>> 7) > 0 ? -1 : 1;
    let leadingBytes = (bytes[0] << 8) | bytes[1];
    let leadingMBits = 16 - (eBits + 1);
    let exp = (leadingBytes >>> leadingMBits) & ((1 << eBits) - 1);
    let mantissa = leadingBytes & ((1 << leadingMBits) - 1);

    // read remainder of mantissa
    for (let i = 2; i < byteLength; ++i) {
      mantissa = mantissa * 256 + bytes[i];
    }

    if (exp === (1 << eBits) - 1) {
      // NaN and +/- Infinity
      return (mantissa === 0 ? sign : 0) / 0;
    } else if (exp > 0) {
      // normal
      return sign * Math.pow(2, exp - bias) * (1 + mantissa / Math.pow(2, mBits));
    } else if (mantissa !== 0) {
      // subnormal
      return sign * Math.pow(2, -(bias - 1)) * (mantissa / Math.pow(2, mBits));
    }
    return sign * 0;
  }

  readString(length: number) {
    let str = '';
    for (let i = 0; i < length; ++i) {
      str += String.fromCharCode(this.buffer[this.ptr++]);
    }
    return decodeURIComponent(escape(str));
  }

  readArray(length: number): any {
    let out = [];
    for (let i = 0; i < length; ++i) {
      out.push(this.decodeValue(this.buffer));
    }
    return out;
  }

  readBooleanArray(length: number): any {
    let out = [];
    for (let i = 0; i < length; ++i) {
      out[i] = (this.buffer[this.ptr + (i >> 3)] & (0x80 >> (i % 8))) > 0;
    }
    this.ptr += (length >> 3) + (length % 8 !== 0);
    return out;
  }

  // todo: factor out "read type_ length value"
  // readUInt() { ... }

  decodeValue(): any {
    let type = this.buffer[this.ptr++];
    if (type < tags.UINT14_BASE) {
      return type;
    } else if (type < tags.NINT4_BASE) {
      return (type ^ tags.UINT14_BASE) << 8 | this.buffer[this.ptr++];
    } else if (type < tags.BARRAY4_BASE) {
      return -(type ^ tags.NINT4_BASE);
    } else if (type < tags.ARRAY5_BASE) {
      return this.readBooleanArray(type ^ tags.BARRAY4_BASE);
    } else if (type < tags.STR5_BASE) {
      return this.readArray(type ^ tags.ARRAY5_BASE);
    } else if (type < tags.FALSE) {
      return this.readString(type ^ tags.STR5_BASE);
    }
    switch (type) {
      case tags.FALSE: return false;
      case tags.TRUE: return true;
      case tags.NULL: return null;
      case tags.UNDEFINED: return void 0;
      case tags.UINT16:
        return (this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++];
      case tags.UINT24:
        return (this.buffer[this.ptr++]) << 16 | (this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++];
      case tags.UINT32:
        return this.readUInt32();
      case tags.UINT64:
        return this.readUInt32() * 0x100000000 + this.readUInt32();
      case tags.NINT8:
        return -(this.buffer[this.ptr++]);
      case tags.NINT16:
        return -((this.buffer[this.ptr++]) << 8 | this.buffer[this.ptr++]);
      case tags.NINT32:
        return -this.readUInt32();
      case tags.NINT64:
        return -(this.readUInt32() * 0x100000000 + this.readUInt32());
      case tags.FLOAT32:
        return this.readFloat(8, 23);
      case tags.DOUBLE64:
        return this.readFloat(11, 52);

      case tags.TIMESTAMP:
        return new Date(
          (this.buffer[this.ptr] & 0x80) > 0
            ? -(
                (~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) * 0x1000000 +
                (~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) + 1
              )
            : (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) * 0x1000000 +
              (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++])
        );

      case tags.BINARY_: {
        let length: number = this.decodeValue();
        // alternative to Uint8Array.from(...) for old browsers
        let out = new Uint8Array;
        for (let i = 0; i < length; ++i) {
          out[i] = this.buffer[this.ptr + i];
        }
        this.ptr += length;
        return out.buffer;
      }

      case tags.CSTRING: {
        let str = '';
        while (this.buffer[this.ptr] !== 0) {
          str += String.fromCharCode(this.buffer[this.ptr++]);
        }
        this.ptr++;
        return decodeURIComponent(escape(str));
      }
      case tags.STR8:
        return this.readString(this.buffer[this.ptr++]);
      case tags.STR_:
        return this.readString(this.decodeValue());
      case tags.STRREF:
        return this.strings[this.buffer[this.ptr++]];

      case tags.ARRAY8:
      case tags.STRLUT:
        return this.readArray(this.buffer[this.ptr++]);
      case tags.ARRAY_:
        return this.readArray(this.decodeValue());

      case tags.BARRAY8:
        return this.readBooleanArray(this.buffer[this.ptr++]);
      case tags.BARRAY_:
        return this.readBooleanArray(this.decodeValue());

      case tags.MAP_: {
        let out = {};
        let keysetIndex = this.decodeValue();
        let keys = this.keysets[keysetIndex];
        keys.forEach((key) => {
          out[key] = this.decodeValue();
        });
        return out;
      }

      case tags.BMAP_: {
        let out = {};
        let keysetIndex = this.decodeValue();
        let keys = this.keysets[keysetIndex];
        let bools = this.readBooleanArray(keys.length);
        for (let i = 0; i < keys.length; ++i) {
          out[keys[i]] = bools[i];
        }
        return out;
      }

      case tags.EXTENSION: {
        let ext : ExtensionPoint = this.decodeValue();
        return this.extensions[ext].deserialiser(this.decodeValue());
      }

      default:
        // This should never happen.
        throw new Error('Unknown case');
    }
  }
}

export const decode = Decoder.decode;
<|endoftext|>


<commit_before>import animate;
import ui.ViewPool as ViewPool;
import ui.ParticleEngine as ParticleEngine;
import ui.BlendEngine as BlendEngine;

import .defaultImages;
import .effectsLibrary;

import .EffectsParticleEngine;
import .EffectsBlendEngine;

var Effects = Class(function () {

/////////////////////////
// ~ ~ Private API ~ ~ //
/////////////////////////

  /**
    *
Effects Manager Class
    * ~ individual effects are added automatically from {@link effectsLibrary}
    * ~ effects can also be added manually via register functions

    *
Although this is possible to register custom effects, particles, etc., it's recommended to avoid until better tested and documented! Read the code in if you're super curious!

    * @class Effects
    */
  this.init = function () {
    this._animations = [];
    this._particleEngines = new ViewPool({ ctor: EffectsParticleEngine });
    this._blendEngines = new ViewPool({ ctor: EffectsBlendEngine });

    // register the default animation effects
    var anims = effectsLibrary.animationEffects;
    for (var name in anims) {
      this.registerAnimationEffect(name, anims[name]);
    }

    // register the default particle effects
    var particles = effectsLibrary.particleEffects;
    for (var name in particles) {
      this.registerParticleEffect(name, particles[name]);
    }

    // register the default composite effects
    var composites = effectsLibrary.compositeEffects;
    for (var name in composites) {
      this.registerCompositeEffect(name, composites[name]);
    }

    // subscribe to the devkit's tick once it's finished initializing
    setTimeout(bind(this, function() {
      GC.app.engine.subscribe('Tick', bind(this, _tick));
    }), 0);
  };

  var durationDefaults = effectsLibrary.getDefaults('duration');
  var loopDefaults = effectsLibrary.getDefaults('loop');
  var blendDefaults = effectsLibrary.getDefaults('blend');
  var followDefaults = effectsLibrary.getDefaults('follow');
  var behindDefaults = effectsLibrary.getDefaults('behind');

  /**
    * updates particle and blend engines and removes finished engines
    * @func Effects#_tick
    * @arg {number} dt
    */
  var _tick = function (dt) {
    // update particle engines
    this._particleEngines.forEachActiveView(function (engine) {
      if (engine._activeParticles.length) {
        engine.update(dt);
      } else {
        engine.stop();
        this._particleEngines.releaseView(engine);
      }
    }, this);

    // update blend engines
    this._blendEngines.forEachActiveView(function (engine) {
      if (engine._activeParticleObjects.length) {
        engine.update(dt);
      } else {
        engine.stop();
        this._blendEngines.releaseView(engine);
      }
    }, this);
  };

  /**
   * @typedef {Object} EffectsOpts
   * @property {number} delay - in milliseconds
   * @property {number} duration - in milliseconds, change the time an effect takes to complete, defaults to 1000 for most effects
   * @property {number} scale - change the general scale or magnitude of an effect, defaults to 1 for most effects
   * @property {boolean} loop - whether or not to continually repeat an effect, defaults to false for most effects
   * @property {boolean} blend - whether or not to blend an effect using composite operations, defaults to false for most effects, only affects particles
   * @property {boolean} follow - whether particles should follow a view as it moves, defaults to false for most effects, only affects particles
   * @property {boolean} behind - whether particles should be in front or behind the view, defaults to false for most effects, only affects particles
   */
  /**
   * normalizes opts passed in to effects with reasonable defaults. For a description of the defualt opts see {@link EffectsOpts}
   * @func Effects#_applyDefaultOpts
   * @param  name
   * @param  {Object} opts
   * @return {EffectsOpts} opts
   */
  var _applyDefaultOpts = function (name, opts) {
    opts = opts || {};
    opts.delay = opts.delay || 0;
    opts.duration = opts.duration !== void 0 ? opts.duration : durationDefaults[name] || 1000;
    opts.scale = opts.scale || 1;
    opts.loop = opts.loop !== void 0 ? opts.loop : loopDefaults[name];
    opts.blend = opts.blend !== void 0 ? opts.blend : blendDefaults[name];
    opts.follow = opts.follow !== void 0 ? opts.follow : followDefaults[name];
    opts.behind = opts.behind !== void 0 ? opts.behind : behindDefaults[name];
    return opts;
  };

  /**
   * wrapper for handling pause, resume, stop API
   * @func Effects#_applyState
   * @param  {View|Object} view - If an object is given, object.view will be used.
   * @param  {String} name
   * @param  {String} state
   */
  var _applyState = function (view, name, state) {
    view = (view && view.view) || view;

    if (!view) {
      // apply state to all animation effects globally
      this._animations.forEach(function (anim) {
        anim.interrupting = true;
        anim[state]();
        anim.interrupting = false;
      }, this);
      // apply state to all particle effects globally
      this._particleEngines.forEachActiveView(function (engine) {
        engine[state](true);
      }, this);
      // apply state to all composite effects globally
      this._blendEngines.forEachActiveView(function (engine) {
        engine[state](true);
      }, this);
    } else if (!name) {
      // apply state to all animation effects for a specific view
      this._animations.forEach(function (anim) {
        if (anim.subject === view) {
          anim.interrupting = true;
          anim[state]();
          anim.interrupting = false;
        }
      }, this);
      // apply state to all particle effects for a specific view
      this._particleEngines.forEachActiveView(function (engine) {
        if (engine.subject === view) {
          engine[state](false);
        }
      }, this);
      // apply state to all composite effects for a specific view
      this._blendEngines.forEachActiveView(function (engine) {
        if (engine.subject === view) {
          engine[state](false);
        }
      }, this);
    } else {
      // apply state to all animation effects for a specific view and specific effect name
      this._animations.forEach(function (anim) {
        if (anim.subject === view && anim._group === name) {
          anim.interrupting = true;
          anim[state]();
          anim.interrupting = false;
        }
      }, this);
      // apply state to all particle effects for a specific view and specific effect name
      this._particleEngines.forEachActiveView(function (engine) {
        if (engine.subject === view && engine._group === name) {
          engine[state](false);
        }
      }, this);
      // apply state to all composite effects for a specific view and specific effect name
      this._blendEngines.forEachActiveView(function (engine) {
        if (engine.subject === view && engine._group === name) {
          engine[state](false);
        }
      }, this);
    }
  };

////////////////////////
// ~ ~ Public API ~ ~ //
////////////////////////

  /**
   * pauses all effects globally, all effects on a given view, or a specific effect on a specific view
   * @function Effects#pause
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.pause = function (view, name) {
    _applyState.call(this, view, name, 'pause');
  };

  /**
   * resumes all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#resume
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.resume = function (view, name) {
    _applyState.call(this, view, name, 'resume');
  };

  /**
   * clears all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#clear
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  /**
   * @method Effects#stop
   * @alias Effects#clear
   */
  this.clear = this.stop = function (view, name) {
    _applyState.call(this, view, name, 'stop');
  };

  /**
   * instantly and safely finishes all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#commit
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.commit = function (view, name) {
    _applyState.call(this, view, name, 'commit');
  };

  /**
   * @callback AnimationEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {animation}   anim
   */
  /**
   * uses timestep's {@link animate} to create animation effects, like bounces or shakes
   * @method Effects#registerAnimationEffect
   * @param  {String} name
   * @param  {AnimationEffectCallback} fn
   * @return {animate}
   */
  this.registerAnimationEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep animations and ensure safe completion if already active
      var anim = animate(view, name);
      anim.stop = anim.clear;
      anim.interrupting = true;
      anim.commit();
      anim.interrupting = false;
      this._animations.push(anim);

      // call the animation function with normalized opts
      opts = _applyDefaultOpts(name, opts);
      fn.call(this, view, opts, anim);

      // remove the anim ref and loop the animation if specified
      anim.then(bind(this, function () {
        this._animations.splice(this._animations.indexOf(anim), 1);
        if (opts.loop && !anim.interrupting) {
          anim.clear();
          this[name](view, opts);
        }
      }));

      return anim;
    });
  };

  /**
   * @callback ParticleEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {EffectsParticleEngine} engine
   */
  /**
   * uses timestep's {@link ParticleEngine} to create particle effects, like sparkles or explosions
   * @method Effects#registerParticleEffect
   * @param  {String} name
   * @param  {ParticleEffectCallback} fn
   * @return {ParticleEngine|null}
   */
  this.registerParticleEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep engine and add as a sibling view to the subject view
      opts = _applyDefaultOpts(name, opts);
      var engine = this._particleEngines.obtainView({});
      var parent = view.getSuperview() || GC.app;
      var vs = view.style;
      var es = engine.style;
      engine._group = name;
      engine.subject = view;
      engine.paused = false;
      engine.follow = opts.follow;
      parent.addSubview(engine);

      // the engine should be positioned and sized to match the subject view
      opts.images = opts.images || defaultImages.get(name);
      es.x = vs.x || 0;
      es.y = vs.y || 0;
      es.offsetX = vs.offsetX || 0;
      es.offsetY = vs.offsetY || 0;
      es.width = vs.width || 1;
      es.height = vs.height || 1;
      es.anchorX = vs.anchorX;
      es.anchorY = vs.anchorY;
      es.zIndex = opts.behind ? vs.zIndex - 1 : vs.zIndex + 1;
      es.scale = opts.scale * vs.scale;
      es.flipX = vs.flipX;
      es.flipY = vs.flipY;

      // call the particle function with normalized opts
      fn.call(this, view, opts, engine);
      if (engine._activeParticles.length) {
        view[name + 'Engine'] = engine;
        // particle engine loop, repeats the effect
        if (opts.loop) {
          engine.animLoop.wait(opts.duration)
            .then(bind(this, function () {
              if (view[name + 'Engine'] === engine) {
                engine.stop(true);
                this._particleEngines.releaseView(engine);
                this[name](view, opts);
              }
            }));
        }
        return engine;
      } else {
        this._particleEngines.releaseView(engine);
        return null;
      }
    });
  };

  /**
   * @callback CompositeEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {EffectsBlendEngine} engine
   */
  /**
   * uses timestep's {@link BlendEngine} to create composited particle effects, like disco-mode
   * @method Effects#registerCompositeEffect
   * @param  {String}   name
   * @param  {CompositeEffectCallback} fn
   * @return {BlendEngine|null}
   */
  this.registerCompositeEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep blend engine and add as a sibling view to the subject view
      opts = _applyDefaultOpts(name, opts);
      var engine = this._blendEngines.obtainView({});
      var parent = view.getSuperview() || GC.app;
      var vs = view.style;
      var es = engine.style;
      engine._group = name;
      engine.subject = view;
      engine.paused = false;
      engine.follow = opts.follow;
      engine._forceCenterX = vs.width / 2 || 0.5;
      engine._forceCenterY = vs.height / 2 || 0.5;
      parent.addSubview(engine);

      // the engine should be positioned and sized to match the subject view
      opts.images = opts.images || defaultImages.get(name);
      es.x = vs.x || 0;
      es.y = vs.y || 0;
      es.offsetX = vs.offsetX || 0;
      es.offsetY = vs.offsetY || 0;
      es.width = vs.width || 1;
      es.height = vs.height || 1;
      es.anchorX = vs.anchorX;
      es.anchorY = vs.anchorY;
      es.zIndex = opts.behind ? vs.zIndex - 1 : vs.zIndex + 1;
      es.scale = opts.scale * vs.scale;
      es.flipX = vs.flipX;
      es.flipY = vs.flipY;

      // call the particle function with normalized opts
      fn.call(this, view, opts, engine);
      if (engine._activeParticleObjects.length) {
        view[name + 'Engine'] = engine;
        // blend engine loop, repeats the effect
        if (opts.loop) {
          engine.animLoop.wait(opts.duration)
            .then(bind(this, function () {
              if (view[name + 'Engine'] === engine) {
                engine.stop(true);
                this._blendEngines.releaseView(engine);
                this[name](view, opts);
              }
            }));
        }
        return engine;
      } else {
        this._blendEngines.releaseView(engine);
        return null;
      }
    });
  };

});

// this class is a singleton instantiated on import
exports = new Effects();
<commit_msg>force stop on engine clean up<commit_after>import animate;
import ui.ViewPool as ViewPool;
import ui.ParticleEngine as ParticleEngine;
import ui.BlendEngine as BlendEngine;

import .defaultImages;
import .effectsLibrary;

import .EffectsParticleEngine;
import .EffectsBlendEngine;

var Effects = Class(function () {

/////////////////////////
// ~ ~ Private API ~ ~ //
/////////////////////////

  /**
    *
Effects Manager Class
    * ~ individual effects are added automatically from {@link effectsLibrary}
    * ~ effects can also be added manually via register functions

    *
Although this is possible to register custom effects, particles, etc., it's recommended to avoid until better tested and documented! Read the code in if you're super curious!

    * @class Effects
    */
  this.init = function () {
    this._animations = [];
    this._particleEngines = new ViewPool({ ctor: EffectsParticleEngine });
    this._blendEngines = new ViewPool({ ctor: EffectsBlendEngine });

    // register the default animation effects
    var anims = effectsLibrary.animationEffects;
    for (var name in anims) {
      this.registerAnimationEffect(name, anims[name]);
    }

    // register the default particle effects
    var particles = effectsLibrary.particleEffects;
    for (var name in particles) {
      this.registerParticleEffect(name, particles[name]);
    }

    // register the default composite effects
    var composites = effectsLibrary.compositeEffects;
    for (var name in composites) {
      this.registerCompositeEffect(name, composites[name]);
    }

    // subscribe to the devkit's tick once it's finished initializing
    setTimeout(bind(this, function() {
      GC.app.engine.subscribe('Tick', bind(this, _tick));
    }), 0);
  };

  var durationDefaults = effectsLibrary.getDefaults('duration');
  var loopDefaults = effectsLibrary.getDefaults('loop');
  var blendDefaults = effectsLibrary.getDefaults('blend');
  var followDefaults = effectsLibrary.getDefaults('follow');
  var behindDefaults = effectsLibrary.getDefaults('behind');

  /**
    * updates particle and blend engines and removes finished engines
    * @func Effects#_tick
    * @arg {number} dt
    */
  var _tick = function (dt) {
    // update particle engines
    this._particleEngines.forEachActiveView(function (engine) {
      if (engine._activeParticles.length) {
        engine.update(dt);
      } else {
        engine.stop(true);
        this._particleEngines.releaseView(engine);
      }
    }, this);

    // update blend engines
    this._blendEngines.forEachActiveView(function (engine) {
      if (engine._activeParticleObjects.length) {
        engine.update(dt);
      } else {
        engine.stop(true);
        this._blendEngines.releaseView(engine);
      }
    }, this);
  };

  /**
   * @typedef {Object} EffectsOpts
   * @property {number} delay - in milliseconds
   * @property {number} duration - in milliseconds, change the time an effect takes to complete, defaults to 1000 for most effects
   * @property {number} scale - change the general scale or magnitude of an effect, defaults to 1 for most effects
   * @property {boolean} loop - whether or not to continually repeat an effect, defaults to false for most effects
   * @property {boolean} blend - whether or not to blend an effect using composite operations, defaults to false for most effects, only affects particles
   * @property {boolean} follow - whether particles should follow a view as it moves, defaults to false for most effects, only affects particles
   * @property {boolean} behind - whether particles should be in front or behind the view, defaults to false for most effects, only affects particles
   */
  /**
   * normalizes opts passed in to effects with reasonable defaults. For a description of the defualt opts see {@link EffectsOpts}
   * @func Effects#_applyDefaultOpts
   * @param  name
   * @param  {Object} opts
   * @return {EffectsOpts} opts
   */
  var _applyDefaultOpts = function (name, opts) {
    opts = opts || {};
    opts.delay = opts.delay || 0;
    opts.duration = opts.duration !== void 0 ? opts.duration : durationDefaults[name] || 1000;
    opts.scale = opts.scale || 1;
    opts.loop = opts.loop !== void 0 ? opts.loop : loopDefaults[name];
    opts.blend = opts.blend !== void 0 ? opts.blend : blendDefaults[name];
    opts.follow = opts.follow !== void 0 ? opts.follow : followDefaults[name];
    opts.behind = opts.behind !== void 0 ? opts.behind : behindDefaults[name];
    return opts;
  };

  /**
   * wrapper for handling pause, resume, stop API
   * @func Effects#_applyState
   * @param  {View|Object} view - If an object is given, object.view will be used.
   * @param  {String} name
   * @param  {String} state
   */
  var _applyState = function (view, name, state) {
    view = (view && view.view) || view;

    if (!view) {
      // apply state to all animation effects globally
      this._animations.forEach(function (anim) {
        anim.interrupting = true;
        anim[state]();
        anim.interrupting = false;
      }, this);
      // apply state to all particle effects globally
      this._particleEngines.forEachActiveView(function (engine) {
        engine[state](true);
      }, this);
      // apply state to all composite effects globally
      this._blendEngines.forEachActiveView(function (engine) {
        engine[state](true);
      }, this);
    } else if (!name) {
      // apply state to all animation effects for a specific view
      this._animations.forEach(function (anim) {
        if (anim.subject === view) {
          anim.interrupting = true;
          anim[state]();
          anim.interrupting = false;
        }
      }, this);
      // apply state to all particle effects for a specific view
      this._particleEngines.forEachActiveView(function (engine) {
        if (engine.subject === view) {
          engine[state](false);
        }
      }, this);
      // apply state to all composite effects for a specific view
      this._blendEngines.forEachActiveView(function (engine) {
        if (engine.subject === view) {
          engine[state](false);
        }
      }, this);
    } else {
      // apply state to all animation effects for a specific view and specific effect name
      this._animations.forEach(function (anim) {
        if (anim.subject === view && anim._group === name) {
          anim.interrupting = true;
          anim[state]();
          anim.interrupting = false;
        }
      }, this);
      // apply state to all particle effects for a specific view and specific effect name
      this._particleEngines.forEachActiveView(function (engine) {
        if (engine.subject === view && engine._group === name) {
          engine[state](false);
        }
      }, this);
      // apply state to all composite effects for a specific view and specific effect name
      this._blendEngines.forEachActiveView(function (engine) {
        if (engine.subject === view && engine._group === name) {
          engine[state](false);
        }
      }, this);
    }
  };

////////////////////////
// ~ ~ Public API ~ ~ //
////////////////////////

  /**
   * pauses all effects globally, all effects on a given view, or a specific effect on a specific view
   * @function Effects#pause
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.pause = function (view, name) {
    _applyState.call(this, view, name, 'pause');
  };

  /**
   * resumes all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#resume
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.resume = function (view, name) {
    _applyState.call(this, view, name, 'resume');
  };

  /**
   * clears all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#clear
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  /**
   * @method Effects#stop
   * @alias Effects#clear
   */
  this.clear = this.stop = function (view, name) {
    _applyState.call(this, view, name, 'stop');
  };

  /**
   * instantly and safely finishes all effects globally, all effects on a given view, or a specific effect on a specific view
   * @method Effects#commit
   * @param  {View}   [view]
   * @param  {String} [name]
   */
  this.commit = function (view, name) {
    _applyState.call(this, view, name, 'commit');
  };

  /**
   * @callback AnimationEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {animation}   anim
   */
  /**
   * uses timestep's {@link animate} to create animation effects, like bounces or shakes
   * @method Effects#registerAnimationEffect
   * @param  {String} name
   * @param  {AnimationEffectCallback} fn
   * @return {animate}
   */
  this.registerAnimationEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep animations and ensure safe completion if already active
      var anim = animate(view, name);
      anim.stop = anim.clear;
      anim.interrupting = true;
      anim.commit();
      anim.interrupting = false;
      this._animations.push(anim);

      // call the animation function with normalized opts
      opts = _applyDefaultOpts(name, opts);
      fn.call(this, view, opts, anim);

      // remove the anim ref and loop the animation if specified
      anim.then(bind(this, function () {
        this._animations.splice(this._animations.indexOf(anim), 1);
        if (opts.loop && !anim.interrupting) {
          anim.clear();
          this[name](view, opts);
        }
      }));

      return anim;
    });
  };

  /**
   * @callback ParticleEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {EffectsParticleEngine} engine
   */
  /**
   * uses timestep's {@link ParticleEngine} to create particle effects, like sparkles or explosions
   * @method Effects#registerParticleEffect
   * @param  {String} name
   * @param  {ParticleEffectCallback} fn
   * @return {ParticleEngine|null}
   */
  this.registerParticleEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep engine and add as a sibling view to the subject view
      opts = _applyDefaultOpts(name, opts);
      var engine = this._particleEngines.obtainView({});
      var parent = view.getSuperview() || GC.app;
      var vs = view.style;
      var es = engine.style;
      engine._group = name;
      engine.subject = view;
      engine.paused = false;
      engine.follow = opts.follow;
      parent.addSubview(engine);

      // the engine should be positioned and sized to match the subject view
      opts.images = opts.images || defaultImages.get(name);
      es.x = vs.x || 0;
      es.y = vs.y || 0;
      es.offsetX = vs.offsetX || 0;
      es.offsetY = vs.offsetY || 0;
      es.width = vs.width || 1;
      es.height = vs.height || 1;
      es.anchorX = vs.anchorX;
      es.anchorY = vs.anchorY;
      es.zIndex = opts.behind ? vs.zIndex - 1 : vs.zIndex + 1;
      es.scale = opts.scale * vs.scale;
      es.flipX = vs.flipX;
      es.flipY = vs.flipY;

      // call the particle function with normalized opts
      fn.call(this, view, opts, engine);
      if (engine._activeParticles.length) {
        view[name + 'Engine'] = engine;
        // particle engine loop, repeats the effect
        if (opts.loop) {
          engine.animLoop.wait(opts.duration)
            .then(bind(this, function () {
              if (view[name + 'Engine'] === engine) {
                engine.stop(true);
                this._particleEngines.releaseView(engine);
                this[name](view, opts);
              }
            }));
        }
        return engine;
      } else {
        this._particleEngines.releaseView(engine);
        return null;
      }
    });
  };

  /**
   * @callback CompositeEffectCallback
   * @param {View}   view
   * @param {EffectsOpts} opts
   * @param {EffectsBlendEngine} engine
   */
  /**
   * uses timestep's {@link BlendEngine} to create composited particle effects, like disco-mode
   * @method Effects#registerCompositeEffect
   * @param  {String}   name
   * @param  {CompositeEffectCallback} fn
   * @return {BlendEngine|null}
   */
  this.registerCompositeEffect = function (name, fn) {
    this[name] = bind(this, function (view, opts) {
      // allow entities and other objects that have views
      view = view.view || view;

      // prep blend engine and add as a sibling view to the subject view
      opts = _applyDefaultOpts(name, opts);
      var engine = this._blendEngines.obtainView({});
      var parent = view.getSuperview() || GC.app;
      var vs = view.style;
      var es = engine.style;
      engine._group = name;
      engine.subject = view;
      engine.paused = false;
      engine.follow = opts.follow;
      engine._forceCenterX = vs.width / 2 || 0.5;
      engine._forceCenterY = vs.height / 2 || 0.5;
      parent.addSubview(engine);

      // the engine should be positioned and sized to match the subject view
      opts.images = opts.images || defaultImages.get(name);
      es.x = vs.x || 0;
      es.y = vs.y || 0;
      es.offsetX = vs.offsetX || 0;
      es.offsetY = vs.offsetY || 0;
      es.width = vs.width || 1;
      es.height = vs.height || 1;
      es.anchorX = vs.anchorX;
      es.anchorY = vs.anchorY;
      es.zIndex = opts.behind ? vs.zIndex - 1 : vs.zIndex + 1;
      es.scale = opts.scale * vs.scale;
      es.flipX = vs.flipX;
      es.flipY = vs.flipY;

      // call the particle function with normalized opts
      fn.call(this, view, opts, engine);
      if (engine._activeParticleObjects.length) {
        view[name + 'Engine'] = engine;
        // blend engine loop, repeats the effect
        if (opts.loop) {
          engine.animLoop.wait(opts.duration)
            .then(bind(this, function () {
              if (view[name + 'Engine'] === engine) {
                engine.stop(true);
                this._blendEngines.releaseView(engine);
                this[name](view, opts);
              }
            }));
        }
        return engine;
      } else {
        this._blendEngines.releaseView(engine);
        return null;
      }
    });
  };

});

// this class is a singleton instantiated on import
exports = new Effects();
<|endoftext|>


<commit_before>
import {writeValue} from './memory';
import {pointerType} from './type';
import {PointerValue} from './value';
import {findClosestBlockScope, findClosestFunctionScope} from './scope';

const applyLoadEffect = function (state, effect) {
  // ['load', ref]
  const ref = effect[1];
  state.memoryLog = state.memoryLog.push(effect);
};

const applyStoreEffect = function (state, effect) {
  // ['store', ref, value]
  const ref = effect[1];
  const value = effect[2];
  state.memory = writeValue(state.memory, ref, value);
  state.memoryLog = state.memoryLog.push(effect);
};

const applyEnterEffect = function (state, effect) {
  // ['enter', blockNode]
  const parentScope = state.scope;
  const blockNode = effect[1];
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'block',
    blockNode
  }
};

const applyLeaveEffect = function (state, effect) {
  // ['leave', blockNode]
  const scope = findClosestBlockScope(state.scope, effect[1]);
  if (!scope) {
    console.log('stack underflow', state.scope, effect);
    throw new Error('stack underflow');
  }
  state.scope = scope.parent;
};

const applyCallEffect = function (state, effect) {
  // ['call', cont, [func, args...]]
  const parentScope = state.scope;
  const cont = effect[1];
  const values = effect[2];
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'function',
    cont,
    values
  };
};

const applyReturnEffect = function (state, effect) {
  // ['return', result]
  const scope = findClosestFunctionScope(state.scope);
  if (!scope) {
    console.log('stack underflow', state.scope, effect);
    throw new Error('stack underflow');
  }
  // Pop all scopes up to and including the function's scope.
  state.scope = scope.parent;
  // Transfer control to the caller's continuation,
  state.control = scope.cont;
  // passing the return value.
  state.result = effect[1];
  // Set direction to 'out' to indicate that a function was exited.
  state.direction = 'out';
};

const applyVardeclEffect = function (state, effect) {
  const parentScope = state.scope;
  const name = effect[1];
  const type = effect[2];
  const init = effect[3];
  const address = parentScope.limit - type.size;
  const ref = new PointerValue(pointerType(type), address);
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: address,
    kind: 'variable',
    name, type, ref
  };
  if (init) {
    applyStoreEffect(state, ['store', ref, init]);
  }
};

export const defaultEffects = {
  load: applyLoadEffect,
  store: applyStoreEffect,
  enter: applyEnterEffect,
  leave: applyLeaveEffect,
  call: applyCallEffect,
  return: applyReturnEffect,
  vardecl: applyVardeclEffect
};

// applyEffect applies an effect by shallowly mutating the passed state.
export const applyEffect = function (state, effect, options) {
  const {effectHandlers} = options;
  const handler = effectHandlers[effect[0]];
  if (typeof handler === 'function') {
    return handler(state, effect);
  } else {
    console.log('invalid handler', effect[0])
    return state;
  }
};
<commit_msg>handle default return value of main as defined by C99<commit_after>
import {writeValue} from './memory';
import {scalarTypes, pointerType} from './type';
import {IntegralValue, PointerValue} from './value';
import {findClosestBlockScope, findClosestFunctionScope} from './scope';

const applyLoadEffect = function (state, effect) {
  // ['load', ref]
  const ref = effect[1];
  state.memoryLog = state.memoryLog.push(effect);
};

const applyStoreEffect = function (state, effect) {
  // ['store', ref, value]
  const ref = effect[1];
  const value = effect[2];
  state.memory = writeValue(state.memory, ref, value);
  state.memoryLog = state.memoryLog.push(effect);
};

const applyEnterEffect = function (state, effect) {
  // ['enter', blockNode]
  const parentScope = state.scope;
  const blockNode = effect[1];
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'block',
    blockNode
  }
};

const applyLeaveEffect = function (state, effect) {
  // ['leave', blockNode]
  const scope = findClosestBlockScope(state.scope, effect[1]);
  if (!scope) {
    console.log('stack underflow', state.scope, effect);
    throw new Error('stack underflow');
  }
  state.scope = scope.parent;
};

const applyCallEffect = function (state, effect) {
  // ['call', cont, [func, args...]]
  const parentScope = state.scope;
  const cont = effect[1];
  const values = effect[2];
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'function',
    cont,
    values
  };
};

const applyReturnEffect = function (state, effect) {
  // ['return', result]
  const result = effect[1];
  const scope = findClosestFunctionScope(state.scope);
  if (!scope) {
    console.log('stack underflow', state.scope, effect);
    throw new Error('stack underflow');
  }
  // Pop all scopes up to and including the function's scope.
  state.scope = scope.parent;
  // Transfer control to the caller's continuation…
  state.control = scope.cont;
  // passing the return value to the caller (handling the special case for
  // control leaving the 'main' function without a return statement, where
  // C99 defines the result as being 0).
  if (!result && scope.cont.values[0].name === 'main') {
    state.result = new IntegralValue(scalarTypes['int'], 0);
  } else {
    state.result = result;
  }
  // Set direction to 'out' to indicate that a function was exited.
  state.direction = 'out';
};

const applyVardeclEffect = function (state, effect) {
  const parentScope = state.scope;
  const name = effect[1];
  const type = effect[2];
  const init = effect[3];
  const address = parentScope.limit - type.size;
  const ref = new PointerValue(pointerType(type), address);
  state.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: address,
    kind: 'variable',
    name, type, ref
  };
  if (init) {
    applyStoreEffect(state, ['store', ref, init]);
  }
};

export const defaultEffects = {
  load: applyLoadEffect,
  store: applyStoreEffect,
  enter: applyEnterEffect,
  leave: applyLeaveEffect,
  call: applyCallEffect,
  return: applyReturnEffect,
  vardecl: applyVardeclEffect
};

// applyEffect applies an effect by shallowly mutating the passed state.
export const applyEffect = function (state, effect, options) {
  const {effectHandlers} = options;
  const handler = effectHandlers[effect[0]];
  if (typeof handler === 'function') {
    return handler(state, effect);
  } else {
    console.log('invalid handler', effect[0])
    return state;
  }
};
<|endoftext|>


<commit_before>/**
 * favella.js
 *
 * Copyright (c) 2015 Alberto Pagliarini
 * Licensed under the MIT license
 * https://github.com/batopa/favella/blob/master/LICENSE
 */

if ('speechSynthesis' in window) {

    (function(window) {

        'use strict';

        /**
         * Save original console error function
         * @type {Object} console.error function
         */
        var consoleError = window.console.error;

        /**
         * Configuration. Possible options are:
         *
         * - parentalControl: true to avoid using curses
         * - curses: list of curses to append to console.error() message
         * - speakOptions: speak options used in SpeechSynthesisUtterance object
         * - enabled: false to disable Favella.speak()
         * - muteConsole: true to mute only console.error()
         * 				  Favella.speak() will continue to work
         * - recognitionOptions: options used in SpeechRecognition object
         *
         * @type {Object}
         */
        var config = {
            parentalControl: false,
            curses: [],
            speakOptions: {
                voice: null,
                voiceURI: 'native',
                volume: 1,
                rate: 1,
                pitch: 0,
                lang: 'en-US',
                onstart: function(e) {},
                onend: function(e) {},
                onerror: function(e) {},
                onpause: function(e) {},
                onboundary: function(e) {},
                onmark: function(e) {},
            },
            enabled: true,
            muteConsole: false,
            recognitionOptions: {
                lang: 'en-US',
                continuous: false,
                interimResults: false,
                onaudiostart: function(e) {},
                onsoundstart: function(e) {},
                onspeechstart: function(e) {},
                onspeechend: function(e) {},
                onsoundend: function(e) {},
                onaudioend: function(e) {},
                onresult: function(e, result) {},
                onnomatch: function(e) {},
                onerror: function(e) {},
                onstart: function(e) {},
                onend: function(e) {}
            }
        };

        /**
         * List of SpeechSynthesisVoice available
         * @type {Array}
         */
        var voices = [];

        /**
         * If speech recognition is active
         *
         * @type {SpeechRecognition}
         */
        var recognition = null;

        // Normalize SpeechRecognition object. For now only webkit prefix exists
        if (!('SpeechRecognition' in window) && ('webkitSpeechRecognition' in window)) {
            window.SpeechRecognition = window.webkitSpeechRecognition;
        }

        /**
         * Return an object with all keys present in defaultObj
         * If key is present in both defaultObj and obj use obj[key] value else use defaultObj[key] value
         *
         * @param {Object} obj the object on which add the defaults
         * @param {Object} defaultObj the referred default object
         * @return {Object}
         */
        var defaults = function(obj, defaultObj) {
            var result = {};
            Object.keys(defaultObj)
                .forEach(function(key) {
                    if (typeof obj[key] === 'undefined') {
                        result[key] = defaultObj[key];
                    } else if (typeof defaultObj[key] !== 'undefined') {
                        result[key] = obj[key];
                    }
                });
            return result;
        };

        var Favella = {

            /**
             * Return the current configuration or a specific key
             *
             * @param {string} name the name of config key
             * @return {Object}
             */
            getConfig: function(name) {
                var c = (name && typeof config[name] !== 'undefined') ? config[name] : config;
                return c;
            },

            /**
             * Setup speak options
             * @return {void}
             */
            setup: function(options) {
                if (options && typeof options == 'object') {
                    Object.keys(options)
                        .forEach(function(name) {
                            var value = options[name];
                            if ((name == 'speakOptions' || name == 'recognitionOptions') && typeof value == 'object') {
                                config[name] = defaults(options[name], config[name]);
                            } else if (name == 'curses' && Array.isArray(value)) {
                                config.curses = value;
                            } else if (name == 'parentalControl') {
                                config.parentalControl = !!value;
                            } else if (name == 'mute') {
                                if (value === true || value == 'console') {
                                    config.mute = value;
                                }
                            }
                        });
                }
                return this;
            },

            /**
             * Mute all or just console.error()
             *
             * @param {String} what if it is 'console' then just silence the console.error()
             * @return {void}
             */
            mute: function(what) {
                if (what && what == 'console') {
                    config.muteConsole = true;
                    console.log('console.error muted');
                } else {
                    config.enabled = false;
                    console.log('Ho perso la favella (I lost the power of speech)!');
                }
            },

            /**
             * Unmute what is silenced
             *
             * @return {void}
             */
            unmute: function() {
                // only for console.log() message
                if (!config.enabled) {
                    console.log('Ho riacquistato la favella (I recover the power of speech)!');
                } else if (config.muteConsole) {
                    console.log('console.error unmuted');
                }
                config.enabled = true;
                config.muteConsole = false;
            },

            /**
             * Is Favella mute?
             *
             * @param {String} what
             * @return {boolean}
             */
            isMute: function(what) {
                return (what && what == 'console') ? config.muteConsole : !config.enabled;
            },

            /**
             * If Favella is listening (speech recognition on)
             *
             * @return {Boolean}
             */
            isListening: function() {
                return !!recognition;
            },

            /**
             * Return the speechSynthesisVoices corresponding to lang
             * If no lang corresponding exists return 'en-US'
             *
             * @param {String} lang the language as it-IT, en-US,...
             * @return {Object} speechSynthesisVoices
             */
            getVoice: function(lang) {
                var voice = null;
                var voices = this.getVoices();
                if (voices.length) {
                    voices.forEach(function(v) {
                        if (v.lang == lang) {
                            voice = v;
                        }
                    });
                    if (!voice) {
                        console.log('Sorry, ' + lang + ' is not supported. Fallback to en-US.');
                        voice = this.getVoice('en-US');
                    }
                }
                return voice;
            },

            /**
             * Start speech recognition
             *
             * @param {Object} options params to configure SpeechRecognition.
             *                         See config.recognitionOptions for the defaults used
             * @return {Object} Favella
             */
            listen: function(options) {
                if (window.SpeechRecognition && !this.isListening()) {
                    var finalTranscript = '';
                    recognition = new SpeechRecognition();
                    options = options || {};
                    options = defaults(options, config.recognitionOptions);
                    Object.keys(options)
                        .forEach(function(name) {
                            if (name == 'onresult') {
                                recognition.onresult = function(e) {
                                    var tmpTranscript = '',
                                        interimTranscript = '',
                                        partialTranscript = '',
                                        prevFinalTranscript = finalTranscript;
                                    for (var i = e.resultIndex; i < e.results.length; ++i) {
                                        if (e.results[i].isFinal) {
                                            finalTranscript += e.results[i][0].transcript;
                                        } else {
                                            interimTranscript += e.results[i][0].transcript;
                                        }
                                    }
                                    partialTranscript = finalTranscript.replace(prevFinalTranscript, '');
                                    options.onresult(e, {
                                        isFinal: !interimTranscript,
                                        interim: interimTranscript,
                                        final:  finalTranscript,
                                        partial: partialTranscript
                                    });
                                };
                            } else if (name == 'onend') {
                                recognition.onend = function(e) {
                                    options.onend(e);
                                    recognition = null;
                                };
                            } else if (name == 'onerror') {
                                recognition.onerror = function(eventError) {
                                    console.error(eventError.type + ': ' + eventError.error);
                                };
                            } else {
                                recognition[name] = options[name];
                            }
                        });
                    recognition.start();
                }
                return this;
            },

            /**
             * Stop or abort speech recognition
             *
             * @param {Boolean} abort true to abort instead of stop
             * @return {void}
             */
            stopListen: function(abort) {
                if (this.isListening()) {
                    if (abort) {
                        recognition.abort();
                    } else {
                        recognition.stop();
                    }
                }
            },

            /**
             * Speak the message.
             * Eventually append a curse :)
             *
             * @param {String} message the text to speak
             * @param {Object} options params to configure the speaker.
             *                         see speakOptions for all options
             * @return {Object} Favella
             */
            speak: function(message, options) {
                if (!config.enabled) {
                    return false;
                }
                if (!message || typeof message != 'string') {
                    message = 'Something goes wrong';
                }

                options = options || {};
                options = defaults(options, config.speakOptions);
                var msg = new SpeechSynthesisUtterance();
                var voices = this.getVoices();
                msg.voice = this.getVoice(options.lang);
                console.log('Voice selected: ' + msg.voice.name);
                msg.voiceURI = msg.voice.voiceURI;
                msg.volume = options.volume; // 0 to 1
                msg.rate = options.rate; // 0.1 to 10
                msg.pitch = options.pitch; //0 to 2
                msg.lang = msg.voice.lang;
                msg.text = message;

                // add events
                ['onstart', 'onend', 'onerror', 'onboundary', 'onmark']
                    .forEach(function(name) {
                        if (options[name] && typeof options[name] == 'function') {
                            msg[name] = options[name];
                        }
                    });

                window.speechSynthesis.speak(msg);
                return this;
            },

            /**
             * Useless parrote mode on/off
             * Favella listen and repeat
             *
             * @param {String} lang the language to use
             * @return {void}
             */
            parrotMode: function(lang) {
                if (!this.isListening()) {
                    var v = this.getVoice(lang);
                    if (v.lang != lang) {
                        console.error('Language not supported. Parrot mode fail');
                        return;
                    }
                    var that = this;
                    this.speak('Parrot mode on', {
                        onend: function() {
                            var recOpt = {
                                lang: v.lang,
                                interimResults: false,
                                continuous: true,
                                onresult: function(e, result) {
                                    if (result.isFinal && result.partial) {
                                        console.log(result.partial);
                                        that.speak(result.partial, {
                                            lang: v.lang,
                                            volume: 1,
                                            rate: 1,
                                            pitch: 0.1
                                        });
                                    }
                                }
                            };
                            that.listen(recOpt);
                        }
                    });
                } else {
                    this.stopListen();
                    this.speak('Parrode mode off');
                }
            },

            /**
             * Wrap speechSynthesis.getVoices() and save it in private voices var
             * Return a list of SpeechSynthesisVoice available
             *
             * @param {boolean} force if you want to force to get voices from speechsynthesis
             * @return {void}
             */
            getVoices: function(force) {
                if (!voices.length || force) {
                    voices = window.speechSynthesis.getVoices();
                }
                return voices;
            },

            /**
             * Wrap speechSynthesis.pause()
             * Pause any utterances that are being spoken
             *
             * @return {void}
             */
            pause: function() {
                window.speechSynthesis.pause();
            },

            /**
             * Wrap speechSynthesis.resume()
             * Resume an utterances that was previously paused
             *
             * @return {void}
             */
            resume: function() {
                window.speechSynthesis.resume();
            },

            /**
             * Wrap speechSynthesis.cancel()
             * Stop speaking and remove all utterances from the queue
             *
             * @return {void}
             */
            cancel: function() {
                window.speechSynthesis.cancel();
            },

            /**
             * Wrap speechSynthesis.speaking
             * Return true if Favella is speaking
             *
             * @return {boolean}
             */
            isSpeaking: function() {
                return window.speechSynthesis.speaking;
            },

            /**
             * Wrap speechSynthesis.pending
             * Return true if there are utterances in the queue that have not yet started speaking
             *
             * @return {boolean}
             */
            isPending: function() {
                return window.speechSynthesis.pending;
            },

            /**
             * Wrap speechSynthesis.paused
             * Return true if Favella is paused
             *
             * @return {boolean}
             */
            isPaused: function() {
                return window.speechSynthesis.paused;
            },

            /**
             * How do you say "favella"?
             *
             * @param {boolean} fail used to test missing italian voice situation
             * @return {void}
             */
            me: function(fail) {
                var lang = (fail) ? 'en-US' : 'it-IT';
                var voice = this.getVoice(lang);
                if (voice) {
                    var msg = [];
                    if (voice.lang == 'it-IT') {
                        msg.push('favella');
                    } else {
                        msg = [
                            'Sorry, I cannot pronunce properly because missing italian voice. I will try anyway.',
                            'favella.',
                            'Shit!'
                        ];
                    }
                    var that = this;
                    msg.forEach(function(m) {
                        that.speak(m, {
                            volume: 1,
                            rate: 1,
                            pitch: 0,
                            lang: voice.lang
                        });
                    });
                } else {
                    console.log('Missing voice :(');
                }
            }

        };

        // cancel pending speaking
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.cancel();
        }

        // wait on voices to be loaded before fetching list
        window.speechSynthesis.onvoiceschanged = function() {
            Favella.getVoices(true);
        };

        /**
         * Override standard console.error()
         * Before write in console it speaks the error
         * @return {void}
         */
        window.console.error = function() {
            if (!Favella.isMute('console')) {
                var args = Array.prototype.slice.call(arguments);
                var message = args[0];
                var config = Favella.getConfig();
                if (!config.parentalControl && config.curses.length) {
                    message += '. ' +  config.curses[Math.floor(Math.random() * config.curses.length)] + '!';
                }
                Favella.speak(message);
            }
            consoleError.apply(window.console, arguments);
        };

        // export Favella module
        if (typeof module !== 'undefined' && module.exports) {
            module.exports = Favella;
        } else {
            window.Favella = Favella;
        }

    })(window);

} else {
    console.log('Sorry, speechSynthesis is not supported on this browser');
}
<commit_msg>feat: allow to search `speechSynthesis` voices by name<commit_after>/**
 * favella.js
 *
 * Copyright (c) 2015 Alberto Pagliarini
 * Licensed under the MIT license
 * https://github.com/batopa/favella/blob/master/LICENSE
 */

if ('speechSynthesis' in window) {

    (function(window) {

        'use strict';

        /**
         * Save original console error function
         * @type {Object} console.error function
         */
        var consoleError = window.console.error;

        /**
         * Configuration. Possible options are:
         *
         * - parentalControl: true to avoid using curses
         * - curses: list of curses to append to console.error() message
         * - speakOptions: speak options used in SpeechSynthesisUtterance object
         * - enabled: false to disable Favella.speak()
         * - muteConsole: true to mute only console.error()
         * 				  Favella.speak() will continue to work
         * - recognitionOptions: options used in SpeechRecognition object
         *
         * @type {Object}
         */
        var config = {
            parentalControl: false,
            curses: [],
            speakOptions: {
                voice: null,
                voiceName: null,
                voiceURI: null,
                volume: 1,
                rate: 1,
                pitch: 0,
                lang: 'en-US',
                onstart: function(e) {},
                onend: function(e) {},
                onerror: function(e) {},
                onpause: function(e) {},
                onboundary: function(e) {},
                onmark: function(e) {},
            },
            enabled: true,
            muteConsole: false,
            recognitionOptions: {
                lang: 'en-US',
                continuous: false,
                interimResults: false,
                onaudiostart: function(e) {},
                onsoundstart: function(e) {},
                onspeechstart: function(e) {},
                onspeechend: function(e) {},
                onsoundend: function(e) {},
                onaudioend: function(e) {},
                onresult: function(e, result) {},
                onnomatch: function(e) {},
                onerror: function(e) {},
                onstart: function(e) {},
                onend: function(e) {}
            }
        };

        /**
         * List of SpeechSynthesisVoice available
         * @type {Array}
         */
        var voices = [];

        /**
         * If speech recognition is active
         *
         * @type {SpeechRecognition}
         */
        var recognition = null;

        // Normalize SpeechRecognition object. For now only webkit prefix exists
        if (!('SpeechRecognition' in window) && ('webkitSpeechRecognition' in window)) {
            window.SpeechRecognition = window.webkitSpeechRecognition;
        }

        /**
         * Return an object with all keys present in defaultObj
         * If key is present in both defaultObj and obj use obj[key] value else use defaultObj[key] value
         *
         * @param {Object} obj the object on which add the defaults
         * @param {Object} defaultObj the referred default object
         * @return {Object}
         */
        var defaults = function(obj, defaultObj) {
            var result = {};
            Object.keys(defaultObj)
                .forEach(function(key) {
                    if (typeof obj[key] === 'undefined') {
                        result[key] = defaultObj[key];
                    } else if (typeof defaultObj[key] !== 'undefined') {
                        result[key] = obj[key];
                    }
                });
            return result;
        };

        var Favella = {

            /**
             * Return the current configuration or a specific key
             *
             * @param {string} name the name of config key
             * @return {Object}
             */
            getConfig: function(name) {
                var c = (name && typeof config[name] !== 'undefined') ? config[name] : config;
                return c;
            },

            /**
             * Setup speak options
             * @return {void}
             */
            setup: function(options) {
                if (options && typeof options == 'object') {
                    Object.keys(options)
                        .forEach(function(name) {
                            var value = options[name];
                            if ((name == 'speakOptions' || name == 'recognitionOptions') && typeof value == 'object') {
                                config[name] = defaults(options[name], config[name]);
                            } else if (name == 'curses' && Array.isArray(value)) {
                                config.curses = value;
                            } else if (name == 'parentalControl') {
                                config.parentalControl = !!value;
                            } else if (name == 'mute') {
                                if (value === true || value == 'console') {
                                    config.mute = value;
                                }
                            }
                        });
                }
                return this;
            },

            /**
             * Mute all or just console.error()
             *
             * @param {String} what if it is 'console' then just silence the console.error()
             * @return {void}
             */
            mute: function(what) {
                if (what && what == 'console') {
                    config.muteConsole = true;
                    console.log('console.error muted');
                } else {
                    config.enabled = false;
                    console.log('Ho perso la favella (I lost the power of speech)!');
                }
            },

            /**
             * Unmute what is silenced
             *
             * @return {void}
             */
            unmute: function() {
                // only for console.log() message
                if (!config.enabled) {
                    console.log('Ho riacquistato la favella (I recover the power of speech)!');
                } else if (config.muteConsole) {
                    console.log('console.error unmuted');
                }
                config.enabled = true;
                config.muteConsole = false;
            },

            /**
             * Is Favella mute?
             *
             * @param {String} what
             * @return {boolean}
             */
            isMute: function(what) {
                return (what && what == 'console') ? config.muteConsole : !config.enabled;
            },

            /**
             * If Favella is listening (speech recognition on)
             *
             * @return {Boolean}
             */
            isListening: function() {
                return !!recognition;
            },

            /**
             * Return the speechSynthesisVoices corresponding to `voice`.
             * If no corresponding voice was found, return the voice for 'en-US'.
             *
             * @param {String} voice A speechSynthesisVoices name or a lang as 'it-IT', 'en-US', ...
             * @return {Object} speechSynthesisVoices
             */
            getVoice: function (voice) {
                var voices = this.getVoices();
                if (voices.length) {
                    for (var i = 0; i < voices.length; i++) {
                        if ((voices[i].name === voice) || (voices[i].lang === voice)) {
                            return voices[i];
                        }
                    }

                    console.log('Sorry, ' + foundVoice + ' is not supported. Fallback to en-US.');
                    return this.getVoice('en-US');
                }
            },

            /**
             * Start speech recognition
             *
             * @param {Object} options params to configure SpeechRecognition.
             *                         See config.recognitionOptions for the defaults used
             * @return {Object} Favella
             */
            listen: function(options) {
                if (window.SpeechRecognition && !this.isListening()) {
                    var finalTranscript = '';
                    recognition = new SpeechRecognition();
                    options = options || {};
                    options = defaults(options, config.recognitionOptions);
                    Object.keys(options)
                        .forEach(function(name) {
                            if (name == 'onresult') {
                                recognition.onresult = function(e) {
                                    var tmpTranscript = '',
                                        interimTranscript = '',
                                        partialTranscript = '',
                                        prevFinalTranscript = finalTranscript;
                                    for (var i = e.resultIndex; i < e.results.length; ++i) {
                                        if (e.results[i].isFinal) {
                                            finalTranscript += e.results[i][0].transcript;
                                        } else {
                                            interimTranscript += e.results[i][0].transcript;
                                        }
                                    }
                                    partialTranscript = finalTranscript.replace(prevFinalTranscript, '');
                                    options.onresult(e, {
                                        isFinal: !interimTranscript,
                                        interim: interimTranscript,
                                        final:  finalTranscript,
                                        partial: partialTranscript
                                    });
                                };
                            } else if (name == 'onend') {
                                recognition.onend = function(e) {
                                    options.onend(e);
                                    recognition = null;
                                };
                            } else if (name == 'onerror') {
                                recognition.onerror = function(eventError) {
                                    console.error(eventError.type + ': ' + eventError.error);
                                };
                            } else {
                                recognition[name] = options[name];
                            }
                        });
                    recognition.start();
                }
                return this;
            },

            /**
             * Stop or abort speech recognition
             *
             * @param {Boolean} abort true to abort instead of stop
             * @return {void}
             */
            stopListen: function(abort) {
                if (this.isListening()) {
                    if (abort) {
                        recognition.abort();
                    } else {
                        recognition.stop();
                    }
                }
            },

            /**
             * Speak the message.
             * Eventually append a curse :)
             *
             * @param {String} message the text to speak
             * @param {Object} options params to configure the speaker.
             *                         see speakOptions for all options
             * @return {Object} Favella
             */
            speak: function(message, options) {
                if (!config.enabled) {
                    return false;
                }
                if (!message || typeof message != 'string') {
                    message = 'Something goes wrong';
                }

                options = options || {};
                options = defaults(options, config.speakOptions);
                var msg = new SpeechSynthesisUtterance();
                msg.voice = this.getVoice(options.voiceName || options.lang);
                console.log('Voice selected: ' + msg.voice.name);
                msg.voiceURI = msg.voice.voiceURI;
                msg.volume = options.volume; // 0 to 1
                msg.rate = options.rate; // 0.1 to 10
                msg.pitch = options.pitch; //0 to 2
                msg.lang = msg.voice.lang;
                msg.text = message;

                // add events
                ['onstart', 'onend', 'onerror', 'onboundary', 'onmark']
                    .forEach(function(name) {
                        if (options[name] && typeof options[name] == 'function') {
                            msg[name] = options[name];
                        }
                    });

                window.speechSynthesis.speak(msg);
                return this;
            },

            /**
             * Useless parrote mode on/off
             * Favella listen and repeat
             *
             * @param {String} lang the language to use
             * @return {void}
             */
            parrotMode: function(lang) {
                if (!this.isListening()) {
                    var v = this.getVoice(lang);
                    if (v.lang != lang) {
                        console.error('Language not supported. Parrot mode fail');
                        return;
                    }
                    var that = this;
                    this.speak('Parrot mode on', {
                        onend: function() {
                            var recOpt = {
                                lang: v.lang,
                                interimResults: false,
                                continuous: true,
                                onresult: function(e, result) {
                                    if (result.isFinal && result.partial) {
                                        console.log(result.partial);
                                        that.speak(result.partial, {
                                            lang: v.lang,
                                            volume: 1,
                                            rate: 1,
                                            pitch: 0.1
                                        });
                                    }
                                }
                            };
                            that.listen(recOpt);
                        }
                    });
                } else {
                    this.stopListen();
                    this.speak('Parrode mode off');
                }
            },

            /**
             * Wrap speechSynthesis.getVoices() and save it in private voices var
             * Return a list of SpeechSynthesisVoice available
             *
             * @param {boolean} force if you want to force to get voices from speechsynthesis
             * @return {void}
             */
            getVoices: function(force) {
                if (!voices.length || force) {
                    voices = window.speechSynthesis.getVoices();
                }
                return voices;
            },

            /**
             * Wrap speechSynthesis.pause()
             * Pause any utterances that are being spoken
             *
             * @return {void}
             */
            pause: function() {
                window.speechSynthesis.pause();
            },

            /**
             * Wrap speechSynthesis.resume()
             * Resume an utterances that was previously paused
             *
             * @return {void}
             */
            resume: function() {
                window.speechSynthesis.resume();
            },

            /**
             * Wrap speechSynthesis.cancel()
             * Stop speaking and remove all utterances from the queue
             *
             * @return {void}
             */
            cancel: function() {
                window.speechSynthesis.cancel();
            },

            /**
             * Wrap speechSynthesis.speaking
             * Return true if Favella is speaking
             *
             * @return {boolean}
             */
            isSpeaking: function() {
                return window.speechSynthesis.speaking;
            },

            /**
             * Wrap speechSynthesis.pending
             * Return true if there are utterances in the queue that have not yet started speaking
             *
             * @return {boolean}
             */
            isPending: function() {
                return window.speechSynthesis.pending;
            },

            /**
             * Wrap speechSynthesis.paused
             * Return true if Favella is paused
             *
             * @return {boolean}
             */
            isPaused: function() {
                return window.speechSynthesis.paused;
            },

            /**
             * How do you say "favella"?
             *
             * @param {boolean} fail used to test missing italian voice situation
             * @return {void}
             */
            me: function (fail) {
                var lang = 'it-IT';
                var voiceName = this.getConfig('speakOptions').voiceName;
                if (fail) {
                    lang = 'en-US';
                    voiceName = '';
                }
                var voice = this.getVoice(lang);
                if (voice) {
                    var msg = [];
                    if (voice.lang == 'it-IT') {
                        msg.push('favella');
                    } else {
                        msg = [
                            'Sorry, I cannot pronunce properly because missing italian voice. I will try anyway.',
                            'favella.',
                            'Shit!'
                        ];
                    }
                    var that = this;
                    msg.forEach(function(m) {
                        that.speak(m, {
                            volume: 1,
                            rate: 1,
                            pitch: 0,
                            lang: voice.lang
                        });
                    });
                } else {
                    console.log('Missing voice :(');
                }
            }

        };

        // cancel pending speaking
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.cancel();
        }

        // wait on voices to be loaded before fetching list
        window.speechSynthesis.onvoiceschanged = function() {
            Favella.getVoices(true);
        };

        /**
         * Override standard console.error()
         * Before write in console it speaks the error
         * @return {void}
         */
        window.console.error = function() {
            if (!Favella.isMute('console')) {
                var args = Array.prototype.slice.call(arguments);
                var message = args[0];
                var config = Favella.getConfig();
                if (!config.parentalControl && config.curses.length) {
                    message += '. ' +  config.curses[Math.floor(Math.random() * config.curses.length)] + '!';
                }
                Favella.speak(message);
            }
            consoleError.apply(window.console, arguments);
        };

        // export Favella module
        if (typeof module !== 'undefined' && module.exports) {
            module.exports = Favella;
        } else {
            window.Favella = Favella;
        }

    })(window);

} else {
    console.log('Sorry, speechSynthesis is not supported on this browser');
}
<|endoftext|>


<commit_before>/**
 * @ngdoc module
 * @name angular.filters
 * @description
 *  bunch of useful filters for angularJS
 */

angular.module('angular.filter', [

  'a8m.ucfirst',
  'a8m.uri-encode',
  'a8m.remove-spaces',
  'a8m.strip-tags',
  'a8m.stringular',
  'a8m.truncate',

  'a8m.concat',
  'a8m.unique',
  'a8m.is-empty',
  'a8m.after',
  'a8m.after-where',
  'a8m.before',
  'a8m.before-where',
  'a8m.where',
  'a8m.reverse',
  'a8m.remove',
  'a8m.remove-with',

  'a8m.math',
  'a8m.math.max',
  'a8m.math.min',

  'a8m.angular',
  'a8m.is-null'

]);
<commit_msg>fix(filter module): register group-by module<commit_after>/**
 * @ngdoc module
 * @name angular.filters
 * @description
 *  bunch of useful filters for angularJS
 */

angular.module('angular.filter', [

  'a8m.ucfirst',
  'a8m.uri-encode',
  'a8m.remove-spaces',
  'a8m.strip-tags',
  'a8m.stringular',
  'a8m.truncate',

  'a8m.concat',
  'a8m.unique',
  'a8m.is-empty',
  'a8m.after',
  'a8m.after-where',
  'a8m.before',
  'a8m.before-where',
  'a8m.where',
  'a8m.reverse',
  'a8m.remove',
  'a8m.remove-with',
  'a8m.group-by',

  'a8m.math',
  'a8m.math.max',
  'a8m.math.min',

  'a8m.angular',
  'a8m.is-null'

]);
<|endoftext|>