<commit_before>/**
 *  Startup or transition into Handshake game
 *  o Start ambient sound
 *   Handshake interaction:
 *  o play handshake play
 *  o stop ambient sound
 *   Mole Game:
 *  We have 3 sounds
 *  1) Light activated: Panel comes on with 'neutral' color using ACTIVE_PANEL_INTENSITY
 *  2) Success interaction: panel turn active and state == TrackedPanels.STATE_IGNORED
 *  3) Fail interaction: panel turn active and state == TrackedPanels.STATE_OFF
 *  Q: Sound on light deactivated? (similar to 1) but opposite)
 *   Success animation
 *  o Play Success sound (missing)
 *   Transition animation to Disk Game
 *  o For each light on event, play lighteffect sound (4 events)
 *  o FIXME: Some sort of demo/helper to clarify interaction
 *   Disk Game
 *  o Start ambient sound (FIXME: Fading vs. loop, new sound from Alain?)
 *  o Start loop sound (loop)
 *  o Start distance sound (loop)
 *  o Calculate distance from success (positive and negative distance)
 *    -> use distance to modulate pitch of distance sound
 *  Level success animation
 *  o On success of level 1 and 2, play success sound
 *  o On perimeter light on event, play lighteffect sound
 *   Success animation
 *  o On success of level 3, play show sound
 *  o For each light off event, play lighteffect sound (3 events)
 *   Transition animation to Simon Game
 *  o No sounds
 *   Simon Game
 *  o On animation playback, play panel sounds
 *  o On correct local interaction, play panel sounds
 *  o On free play, play panel sounds
 *  o On fail local interaction, play failure sound
 *  o On level success, play success sound
 *   Light show and transition back to Start State
 *  o play show sound
 */

const assert = require('assert');
const _ = require('lodash');

const SculptureStore = require('@anyware/game-logic/lib/sculpture-store');
const GAMES = require('@anyware/game-logic/lib/constants/games');
const {TrackedPanels} = require('@anyware/game-logic/lib/utils/tracked-panels');
const Disk = require('@anyware/game-logic/lib/utils/disk');
import * as AudioAPI from './audio-api';
const {Sound, VCFSound} = AudioAPI;

export default class AudioView {
  constructor(store, config) {
    this.store = store;
    this.config = config;
    AudioAPI.init();
  }

  reset() {
  }

  /**
   * Loads all sounds.
   * @param {function(err)} callback Called when done
   */
  load(callback) {
    let lfoFreq = 1/3;
    if (this.config.hasOwnProperty('HANDSHAKE_HARDWARE') &&
        this.config.HANDSHAKE_HARDWARE.hasOwnProperty('PULSE_DELAY')) {
      lfoFreq = 1000/this.config.HANDSHAKE_HARDWARE.PULSE_DELAY;
    }

    // Maps logical sound identifiers to filenames. We'll load these sounds next.
    this.sounds = {
      alone: {
        ambient: new VCFSound({url: 'sounds/Alone_Mode/Pulse_Amb_Loop.wav', lfoFreq, fadeIn: 3, gain: 0.4}),
        handshake: new Sound({url: 'sounds/Alone_Mode/Hand_Shake_01.wav'})
      },
      mole: {
        success: new Sound({url: 'sounds/Game_01/G01_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_01/G01_Negative_01.wav', gain: 0.5}),
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_01/G01_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.33})))
      },
      disk: {
        ambient: new Sound({url: 'sounds/Game_02/G02_Amb_Breath_Loop_01.wav', loop: true}),
        disk2: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_C2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk1: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_Eb2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk0: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_G2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        lighteffect: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav'}),
        success: new Sound({url: 'sounds/Game_02/G02_Success_01.wav'}),
        show: new Sound({url: 'sounds/Game_02/G02_Success_final_01.wav', gain: 0.5})
      },
      simon: {
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_03/G03_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.5}))),
        success: new Sound({url: 'sounds/Game_03/G03_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_03/G03_Negative_01.wav', gain: 0.5}),
        show: new Sound({url: 'sounds/Game_03/G03_Light_Show_01.wav', gain: 1})
      }
    };

    // Traverses this.sounds and replace the filenames with valid sound objects.
    // Populates this._promises
    this._promises = [];
    this._traverse(this.sounds, this._promises);

    // _traverse() will create promises. We call the callback once all promises resolve
    console.log(`${this._promises.length} promises created`);
    Promise.all(this._promises)
      // Don't listen to events until we've loaded all sounds
      .then(() => this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this)))
      .then(() => callback(null))
      .catch(callback.bind(null));
  }

  /*
   * Traverses sound config objects and replaces nodes with valid, loaded, sounds
   * populates the given promises array with promises of loaded sounds
   */
  _traverse(soundconf, promises) {
    for (let key in soundconf) {
      const value = soundconf[key];
      let sound;
      if (typeof value === 'string') sound = soundconf[key] = new Sound({url: value});
      else if (value instanceof Sound) sound = value;

      if (sound) promises.push(sound.load());
      else this._traverse(value, promises);
    }
  }

  _handleChanges(changes) {
    if (this.store.isPlayingHandshakeGame) this._handleHandshakeGame(changes);
    if (this.store.isPlayingMoleGame) this._handleMoleGame(changes);
    if (this.store.isPlayingDiskGame) this._handleDiskGame(changes);
    if (this.store.isPlayingSimonGame) this._handleSimonGame(changes);
  }

  _handleHandshakeGame(changes) {
    // On startup, or when Start State becomes active, play ambient sound
    if (changes.currentGame === GAMES.HANDSHAKE) this.sounds.alone.ambient.play();

    if (changes.handshakes) {
      // Did someone shake my hand?
      if (changes.handshakes[this.config.username]) {
        this.sounds.alone.ambient.stop();
        this.sounds.alone.handshake.play();
      }
      // FIXME: Did someone else shake hands? -> dimmed sound?
    }
  }

  _handleMoleGame(changes) {
    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    /*
      Mole game sounds:
      o If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
      changes.mole.panels.<id> == STATE_IGNORED: Just turned -> success?
      state.mole.panels.<id> == STATE_OFF: failure
    */
    // If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        if (panelChange.active === true) {
          const panelkey = `${stripId},${panelId}`;
          if (changes.mole && changes.mole.panels) {
            const state = changes.mole.panels[panelkey];
            if (state === TrackedPanels.STATE_IGNORED) { // Panel was turned -> success
              this.sounds.mole.success.play();
            }
          }
          else {
            if (this.config.MOLE_GAME.ENABLE_FAILURE_SOUND) {
              const state = this.store.data.get('mole').get('panels').get(panelkey);
              if (!state || state === TrackedPanels.STATE_OFF) {
                this.sounds.mole.failure.play();
              }
            }
          }
        }
        else if (panelChange.intensity > 90) {
          this.sounds.mole.panels[stripId][panelId].play();
        }
      }
    }
  }

  _handleDiskGame(changes) {
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      // End of game
      if (this.store.data.get('disk').get('level') >= this.config.DISK_GAME.LEVELS.length) {
        this.sounds.disk.disk0.stop();
        this.sounds.disk.disk1.stop();
        this.sounds.disk.disk2.stop();
        this.sounds.disk.show.play();
      }
      // End of level
      else {
        this.sounds.disk.success.play();
      }
    }

    // On start of disk game
    // FIXME: Is transition part of the disk game?
    if (changes.currentGame === GAMES.DISK) {
      // Start all sounds in silent mode
      this.sounds.disk.disk0.play({gain: 0});
      this.sounds.disk.disk1.play({gain: 0});
      this.sounds.disk.disk2.play({gain: 0});
    }

    // On start of level
//    if (changes.hasOwnProperty('disk') &&
//        changes.disk.hasOwnProperty('level') &&
//        changes.disk.level < this.config.DISK_GAME.LEVELS.length) {
//    }

    if (changes.disks) {
      const disks = this.store.data.get('disks');

      for (let disk of ['disk0', 'disk1', 'disk2']) {
        if (changes.disks.hasOwnProperty(disk) &&
            changes.disks[disk].hasOwnProperty('direction') &&
            disks.get(disk).get('state') === Disk.STATE_READY) {
          if (changes.disks[disk].direction === Disk.STOPPED) this.sounds.disk[disk].fadeOut();
          else this.sounds.disk[disk].fadeIn();
        }
      }

      const diskgame = this.store.currentGameLogic;
      for (let disk of ['disk0', 'disk1', 'disk2']) {
        const score = diskgame.getDiskScore(disk);
        const pulseFreq = this._calcSingleFreq(score);
        console.log(`score: ${score} pulsefreq: ${pulseFreq}`);
        if (this.sounds.disk[disk].source) this.sounds.disk[disk].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      }
      /* code for global score sound
      const score = diskgame.getScore(disks);
      //console.log(`score: ${score}`);
      // score = 0 -> 540
      const pulseFreq = this._calcFreq(score);
      // FIXME: Set our loopFreq property instead and deal with audio node stuff internally
      //console.log(`pulseFreq: ${pulseFreq}`);
      if (this.sounds.disk.disk0.source) this.sounds.disk.disk0.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk1.source) this.sounds.disk.disk1.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk2.source) this.sounds.disk.disk2.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      */
    }
    // FIXME level success and final success
  }

  _calcSingleFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 45) {
      return map(score, 180, 45, 0.5, 3);
    }
    else {
      return map(score, 45, 0, 3, 10);
    }
  }

  _calcFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 200) {
      return map(score, 540, 200, 0.5, 1);
    }
    else if (score > 100) {
      return map(score, 200, 100, 1, 4);
    }
    else if (score > 50) {
      return map(score, 100, 50, 4, 10);
    }
    else {
      return map(score, 50, 0, 10, 60);
    }
  }

  _handleSimonGame(changes) {
    const simongame = this.store.currentGameLogic;
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      if (simongame.complete) this.sounds.simon.show.play();
      else this.sounds.simon.success.play();
    }
    if (changes.status === SculptureStore.STATUS_FAILURE) this.sounds.simon.failure.play();

    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    // FIXME: This is a hack to support lower volume on non-primary panel
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        if (panelChange.active || panelChange.intensity > 90) {
          this.sounds.simon.panels[stripId][panelId].play({gain: (stripId === simongame.currentStrip) ? 1 : 0.1});
        }
      }
    }
  }
}
<commit_msg>Use local game-logic<commit_after>/**
 *  Startup or transition into Handshake game
 *  o Start ambient sound
 *   Handshake interaction:
 *  o play handshake play
 *  o stop ambient sound
 *   Mole Game:
 *  We have 3 sounds
 *  1) Light activated: Panel comes on with 'neutral' color using ACTIVE_PANEL_INTENSITY
 *  2) Success interaction: panel turn active and state == TrackedPanels.STATE_IGNORED
 *  3) Fail interaction: panel turn active and state == TrackedPanels.STATE_OFF
 *  Q: Sound on light deactivated? (similar to 1) but opposite)
 *   Success animation
 *  o Play Success sound (missing)
 *   Transition animation to Disk Game
 *  o For each light on event, play lighteffect sound (4 events)
 *  o FIXME: Some sort of demo/helper to clarify interaction
 *   Disk Game
 *  o Start ambient sound (FIXME: Fading vs. loop, new sound from Alain?)
 *  o Start loop sound (loop)
 *  o Start distance sound (loop)
 *  o Calculate distance from success (positive and negative distance)
 *    -> use distance to modulate pitch of distance sound
 *  Level success animation
 *  o On success of level 1 and 2, play success sound
 *  o On perimeter light on event, play lighteffect sound
 *   Success animation
 *  o On success of level 3, play show sound
 *  o For each light off event, play lighteffect sound (3 events)
 *   Transition animation to Simon Game
 *  o No sounds
 *   Simon Game
 *  o On animation playback, play panel sounds
 *  o On correct local interaction, play panel sounds
 *  o On free play, play panel sounds
 *  o On fail local interaction, play failure sound
 *  o On level success, play success sound
 *   Light show and transition back to Start State
 *  o play show sound
 */

const assert = require('assert');
const _ = require('lodash');

const SculptureStore = require('../game-logic/sculpture-store');
const GAMES = require('../game-logic/constants/games');
const {TrackedPanels} = require('../game-logic/utils/tracked-panels');
const Disk = require('../game-logic/utils/disk');
import * as AudioAPI from './audio-api';
const {Sound, VCFSound} = AudioAPI;

export default class AudioView {
  constructor(store, config) {
    this.store = store;
    this.config = config;
    AudioAPI.init();
  }

  reset() {
  }

  /**
   * Loads all sounds.
   * @param {function(err)} callback Called when done
   */
  load(callback) {
    let lfoFreq = 1/3;
    if (this.config.hasOwnProperty('HANDSHAKE_HARDWARE') &&
        this.config.HANDSHAKE_HARDWARE.hasOwnProperty('PULSE_DELAY')) {
      lfoFreq = 1000/this.config.HANDSHAKE_HARDWARE.PULSE_DELAY;
    }

    // Maps logical sound identifiers to filenames. We'll load these sounds next.
    this.sounds = {
      alone: {
        ambient: new VCFSound({url: 'sounds/Alone_Mode/Pulse_Amb_Loop.wav', lfoFreq, fadeIn: 3, gain: 0.4}),
        handshake: new Sound({url: 'sounds/Alone_Mode/Hand_Shake_01.wav'})
      },
      mole: {
        success: new Sound({url: 'sounds/Game_01/G01_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_01/G01_Negative_01.wav', gain: 0.5}),
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_01/G01_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.33})))
      },
      disk: {
        ambient: new Sound({url: 'sounds/Game_02/G02_Amb_Breath_Loop_01.wav', loop: true}),
        disk2: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_C2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk1: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_Eb2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk0: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_G2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        lighteffect: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav'}),
        success: new Sound({url: 'sounds/Game_02/G02_Success_01.wav'}),
        show: new Sound({url: 'sounds/Game_02/G02_Success_final_01.wav', gain: 0.5})
      },
      simon: {
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_03/G03_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.5}))),
        success: new Sound({url: 'sounds/Game_03/G03_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_03/G03_Negative_01.wav', gain: 0.5}),
        show: new Sound({url: 'sounds/Game_03/G03_Light_Show_01.wav', gain: 1})
      }
    };

    // Traverses this.sounds and replace the filenames with valid sound objects.
    // Populates this._promises
    this._promises = [];
    this._traverse(this.sounds, this._promises);

    // _traverse() will create promises. We call the callback once all promises resolve
    console.log(`${this._promises.length} promises created`);
    Promise.all(this._promises)
      // Don't listen to events until we've loaded all sounds
      .then(() => this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this)))
      .then(() => callback(null))
      .catch(callback.bind(null));
  }

  /*
   * Traverses sound config objects and replaces nodes with valid, loaded, sounds
   * populates the given promises array with promises of loaded sounds
   */
  _traverse(soundconf, promises) {
    for (let key in soundconf) {
      const value = soundconf[key];
      let sound;
      if (typeof value === 'string') sound = soundconf[key] = new Sound({url: value});
      else if (value instanceof Sound) sound = value;

      if (sound) promises.push(sound.load());
      else this._traverse(value, promises);
    }
  }

  _handleChanges(changes) {
    if (this.store.isPlayingHandshakeGame) this._handleHandshakeGame(changes);
    if (this.store.isPlayingMoleGame) this._handleMoleGame(changes);
    if (this.store.isPlayingDiskGame) this._handleDiskGame(changes);
    if (this.store.isPlayingSimonGame) this._handleSimonGame(changes);
  }

  _handleHandshakeGame(changes) {
    // On startup, or when Start State becomes active, play ambient sound
    if (changes.currentGame === GAMES.HANDSHAKE) this.sounds.alone.ambient.play();

    if (changes.handshakes) {
      // Did someone shake my hand?
      if (changes.handshakes[this.config.username]) {
        this.sounds.alone.ambient.stop();
        this.sounds.alone.handshake.play();
      }
      // FIXME: Did someone else shake hands? -> dimmed sound?
    }
  }

  _handleMoleGame(changes) {
    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    /*
      Mole game sounds:
      o If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
      changes.mole.panels.<id> == STATE_IGNORED: Just turned -> success?
      state.mole.panels.<id> == STATE_OFF: failure
    */
    // If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        if (panelChange.active === true) {
          const panelkey = `${stripId},${panelId}`;
          if (changes.mole && changes.mole.panels) {
            const state = changes.mole.panels[panelkey];
            if (state === TrackedPanels.STATE_IGNORED) { // Panel was turned -> success
              this.sounds.mole.success.play();
            }
          }
          else {
            if (this.config.MOLE_GAME.ENABLE_FAILURE_SOUND) {
              const state = this.store.data.get('mole').get('panels').get(panelkey);
              if (!state || state === TrackedPanels.STATE_OFF) {
                this.sounds.mole.failure.play();
              }
            }
          }
        }
        else if (panelChange.intensity > 90) {
          this.sounds.mole.panels[stripId][panelId].play();
        }
      }
    }
  }

  _handleDiskGame(changes) {
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      // End of game
      if (this.store.data.get('disk').get('level') >= this.config.DISK_GAME.LEVELS.length) {
        this.sounds.disk.disk0.stop();
        this.sounds.disk.disk1.stop();
        this.sounds.disk.disk2.stop();
        this.sounds.disk.show.play();
      }
      // End of level
      else {
        this.sounds.disk.success.play();
      }
    }

    // On start of disk game
    // FIXME: Is transition part of the disk game?
    if (changes.currentGame === GAMES.DISK) {
      // Start all sounds in silent mode
      this.sounds.disk.disk0.play({gain: 0});
      this.sounds.disk.disk1.play({gain: 0});
      this.sounds.disk.disk2.play({gain: 0});
    }

    // On start of level
//    if (changes.hasOwnProperty('disk') &&
//        changes.disk.hasOwnProperty('level') &&
//        changes.disk.level < this.config.DISK_GAME.LEVELS.length) {
//    }

    if (changes.disks) {
      const disks = this.store.data.get('disks');

      for (let disk of ['disk0', 'disk1', 'disk2']) {
        if (changes.disks.hasOwnProperty(disk) &&
            changes.disks[disk].hasOwnProperty('direction') &&
            disks.get(disk).get('state') === Disk.STATE_READY) {
          if (changes.disks[disk].direction === Disk.STOPPED) this.sounds.disk[disk].fadeOut();
          else this.sounds.disk[disk].fadeIn();
        }
      }

      const diskgame = this.store.currentGameLogic;
      for (let disk of ['disk0', 'disk1', 'disk2']) {
        const score = diskgame.getDiskScore(disk);
        const pulseFreq = this._calcSingleFreq(score);
        console.log(`score: ${score} pulsefreq: ${pulseFreq}`);
        if (this.sounds.disk[disk].source) this.sounds.disk[disk].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      }
      /* code for global score sound
      const score = diskgame.getScore(disks);
      //console.log(`score: ${score}`);
      // score = 0 -> 540
      const pulseFreq = this._calcFreq(score);
      // FIXME: Set our loopFreq property instead and deal with audio node stuff internally
      //console.log(`pulseFreq: ${pulseFreq}`);
      if (this.sounds.disk.disk0.source) this.sounds.disk.disk0.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk1.source) this.sounds.disk.disk1.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk2.source) this.sounds.disk.disk2.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      */
    }
    // FIXME level success and final success
  }

  _calcSingleFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 45) {
      return map(score, 180, 45, 0.5, 3);
    }
    else {
      return map(score, 45, 0, 3, 10);
    }
  }

  _calcFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 200) {
      return map(score, 540, 200, 0.5, 1);
    }
    else if (score > 100) {
      return map(score, 200, 100, 1, 4);
    }
    else if (score > 50) {
      return map(score, 100, 50, 4, 10);
    }
    else {
      return map(score, 50, 0, 10, 60);
    }
  }

  _handleSimonGame(changes) {
    const simongame = this.store.currentGameLogic;
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      if (simongame.complete) this.sounds.simon.show.play();
      else this.sounds.simon.success.play();
    }
    if (changes.status === SculptureStore.STATUS_FAILURE) this.sounds.simon.failure.play();

    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    // FIXME: This is a hack to support lower volume on non-primary panel
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        if (panelChange.active || panelChange.intensity > 90) {
          this.sounds.simon.panels[stripId][panelId].play({gain: (stripId === simongame.currentStrip) ? 1 : 0.1});
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>"use strict";

const TsDate = require("./date");
const TsEvent = require("./event");
const TsUtil = require("./util");
const TsBase = require("./base");

class TsEventManager extends TsBase {
    constructor() {
        super();

        this._versions = {};

        this._events = {};
        this._time = null;
    }

    _process(tsSection, callback) {
        const tableId = tsSection.getTableId();

        if (tableId >= 0x4E && tableId <= 0x6F) {
            // Event information
            this.onEit(tsSection);
        } else if (tableId === 0x70) {
            // Time and date
            this.onTdt(tsSection);
        } else if (tableId === 0x73) {
            // Time offset
            this.onTot(tsSection);
        }

        callback();
    }

    onEit(tsSection) {
        // Select H-EIT
        if (tsSection.getPid() !== 0x12) return;

        // Select current sub table
        if (tsSection.getCurrentNextIndicator() === 0) return;

        const tableId = tsSection.getTableId();
        const onid = tsSection.getOriginalNetworkId();
        const tsid = tsSection.getTransportStreamId();
        const sid = tsSection.getServiceId();
        const subTable = TsUtil.getNestedObject(this._versions, [tableId, onid, tsid, sid]);
        const isUpdated = TsUtil.updateSubTable(subTable, tsSection);

        if (!TsUtil.updateSection(subTable, tsSection)) return;

        const objSection = tsSection.decode();

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        if (Object.keys(service).length === 0) {
            service.pf = {
                present: null,
                following: null
            };

            service.schedule = {};

            service.basic = {
                flag: Buffer.alloc(0x100, 0x00),
                ignore: Buffer.alloc(0x100, 0xFF)
            };

            service.extended = {
                flag: Buffer.alloc(0x100, 0x00),
                ignore: Buffer.alloc(0x100, 0xFF)
            };
        }

        for (let i = 0, l = objSection.events.length; i < l; i++) {
            const _event = objSection.events[i];

            let tsEvent;

            // Check schedule
            if (service.schedule.hasOwnProperty(_event.event_id)) {
                tsEvent = service.schedule[_event.event_id];
            } else {
                tsEvent = new TsEvent();

                service.schedule[_event.event_id] = tsEvent;
            }

            // Set section
            tsEvent.setSection(objSection);

            // Set event
            tsEvent.setEvent(_event);

            // Check p/f
            if (objSection.table_id === 0x4E || objSection.table_id === 0x4F) {
                if (objSection.section_number === 0) {
                    // Set present
                    service.pf.present = tsEvent;

                    // Emit "present"
                    this.emit("present", tsEvent);
                } else if (objSection.section_number === 1) {
                    // Set following
                    service.pf.following = tsEvent;

                    // Emit "following"
                    this.emit("following", tsEvent);
                }
            } else {
                // Emit "schedule"
                this.emit("schedule", tsEvent);
            }
        }

        if (objSection.table_id !== 0x4E && objSection.table_id !== 0x4F) {
            const target = (objSection.table_id & 0x0F) < 0x08 ? service.basic : service.extended;
            const tableNumber = objSection.table_id & 0x07;
            const segmentNumber = objSection.section_number >> 3;
            const sectionNumber = objSection.section_number & 0x07;
            const tableOffset = tableNumber << 5;
            const segmentOffset = tableOffset + segmentNumber;

            if (isUpdated) {
                // Update table
                for (let _segmentOffset = tableNumber << 5, l = (tableNumber + 1) << 5; _segmentOffset < l ; _segmentOffset++) {
                    target.flag[_segmentOffset] = 0x00;
                    target.ignore[_segmentOffset] = 0x00;
                }

                // Update excluded tables
                const lastTableNumber = objSection.last_table_id & 0x07;

                for (let _segmentOffset = (lastTableNumber + 1) << 5; _segmentOffset < 0x100 ; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }

                // Update excluded segments
                const lastSegmentNumber = objSection.last_section_number >> 3;

                for (let _segmentOffset = tableOffset + lastSegmentNumber + 1, l = tableOffset + 0x20; _segmentOffset < l ; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }
            }

            if (this._time !== null) {
                // Update past segment
                const currentSegmentOffset = (this._time.getTime() - this._time.getTimezoneOffset() * 60 * 1000) / (3 * 60 * 60 * 1000) & 0x07;

                for (let _segmentOffset = 0; _segmentOffset < currentSegmentOffset; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }
            }

            if (target.flag[segmentOffset] === 0x00) {
                // Update excluded section
                const lastSectionNumber = objSection.segment_last_section_number & 0x07;

                for (let _sectionNumber = lastSectionNumber + 1; _sectionNumber < 0x08; _sectionNumber++) {
                    target.ignore[segmentOffset] |= 1 << _sectionNumber;
                }
            }

            // Update flag field
            target.flag[segmentOffset] |= 1 << sectionNumber;
        }

        // Emit "update"
        this.emit("update", tableId, onid, tsid, sid);
    }

    onTdt(tsSection) {
        const objSection = tsSection.decode();

        this._time = new TsDate(objSection.JST_time).decode();
    }

    onTot(tsSection) {
        const objSection = tsSection.decode();

        this._time = new TsDate(objSection.JST_time).decode();
    }

    hasPresent(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        return service.pf.present !== null;
    }

    hasFollowing(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        return service.pf.following !== null;
    }

    hasSchedule(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        for (let i = 0; i < 2; i++) {
            const target = i === 0 ? service.basic : service.extended;

            for (let j = 0; j < 0x100; j++) {
                if ((target.flag[j] | target.ignore[j]) !== 0xFF) return false;
            }
        }

        return true;
    }

    hasSchedules() {
        for (let keys = Object.keys(this._events), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._events[key];

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    for (let m = 0; m < 2; m++) {
                        const target = m === 0 ? service.basic : service.extended;

                        for (let n = 0; n < 0x100; n++) {
                            if ((target.flag[n] | target.ignore[n]) !== 0xFF) return false;
                        }
                    }
                }
            }
        }

        return true;
    }

    getPresent(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        return service.pf.present;
    }

    getFollowing(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        return service.pf.following;
    }

    getSchedule(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        return service.schedule;
    }

    getSchedules() {
        const schedules = {};

        for (let keys = Object.keys(this._events), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._events[key];

            schedules[key] = {};

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                schedules[key][key2] = {};

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    schedules[key][key2][key3] = service.schedule;
                }
            }
        }

        return schedules;
    }

    getScheduleAmount(onid, tsid, sid) {
        let overall = 0;
        let stored = 0;

        if (!TsUtil.checkNestedObject(this._events, [onid, tsid, sid])) return [stored, overall];

        const service = TsUtil.getNestedObject(this._events, [onid, tsid, sid]);

        for (let i = 0; i < 2; i++) {
            const target = i === 0 ? service.basic : service.extended;

            for (let j = 0; j < 0x100; j++) {
                for (let k = 0; k < 0x08; k++) {
                    if ((target.flag[j] >> k & 0x01) === 1) {
                        stored++;
                        overall++;
                    } else if (((target.ignore[j] ^ 0xFF) >> k & 0x01) === 1) {
                        overall++;
                    }
                }
            }
        }

        return [stored, overall];
    }

    getSchedulesAmount() {
        let overall = 0;
        let stored = 0;

        for (let keys = Object.keys(this._events), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._events[key];

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    for (let m = 0; m < 2; m++) {
                        const target = m === 0 ? service.basic : service.extended;

                        for (let n = 0; n < 0x100; n++) {
                            for (let o = 0; o < 0x08; o++) {
                                if ((target.flag[n] >> o & 0x01) === 1) {
                                    stored++;
                                    overall++;
                                } else if (((target.ignore[n] ^ 0xFF) >> o & 0x01) === 1) {
                                    overall++;
                                }
                            }
                        }
                    }
                }
            }
        }

        return [stored, overall];
    }
}

module.exports = TsEventManager;
<commit_msg>Fix event_manager.js<commit_after>"use strict";

const TsDate = require("./date");
const TsEvent = require("./event");
const TsUtil = require("./util");
const TsBase = require("./base");

class TsEventManager extends TsBase {
    constructor() {
        super();

        this._versions = {};

        this._services = {};
        this._time = null;
    }

    _process(tsSection, callback) {
        const tableId = tsSection.getTableId();

        if (tableId >= 0x4E && tableId <= 0x6F) {
            // Event information
            this.onEit(tsSection);
        } else if (tableId === 0x70) {
            // Time and date
            this.onTdt(tsSection);
        } else if (tableId === 0x73) {
            // Time offset
            this.onTot(tsSection);
        }

        callback();
    }

    onEit(tsSection) {
        // Select H-EIT
        if (tsSection.getPid() !== 0x12) return;

        // Select current sub table
        if (tsSection.getCurrentNextIndicator() === 0) return;

        const tableId = tsSection.getTableId();
        const onid = tsSection.getOriginalNetworkId();
        const tsid = tsSection.getTransportStreamId();
        const sid = tsSection.getServiceId();
        const subTable = TsUtil.getNestedObject(this._versions, [tableId, onid, tsid, sid]);
        const isUpdated = TsUtil.updateSubTable(subTable, tsSection);

        if (!TsUtil.updateSection(subTable, tsSection)) return;

        const objSection = tsSection.decode();

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        if (Object.keys(service).length === 0) {
            service.pf = {
                present: null,
                following: null
            };

            service.schedule = {};

            service.basic = {
                flag: Buffer.alloc(0x100, 0x00),
                ignore: Buffer.alloc(0x100, 0xFF)
            };

            service.extended = {
                flag: Buffer.alloc(0x100, 0x00),
                ignore: Buffer.alloc(0x100, 0xFF)
            };
        }

        for (let i = 0, l = objSection.events.length; i < l; i++) {
            const _event = objSection.events[i];

            let tsEvent;

            // Check schedule
            if (service.schedule.hasOwnProperty(_event.event_id)) {
                tsEvent = service.schedule[_event.event_id];
            } else {
                tsEvent = new TsEvent();

                service.schedule[_event.event_id] = tsEvent;
            }

            // Set section
            tsEvent.setSection(objSection);

            // Set event
            tsEvent.setEvent(_event);

            // Check p/f
            if (objSection.table_id === 0x4E || objSection.table_id === 0x4F) {
                if (objSection.section_number === 0) {
                    // Set present
                    service.pf.present = tsEvent;

                    // Emit "present"
                    this.emit("present", tsEvent);
                } else if (objSection.section_number === 1) {
                    // Set following
                    service.pf.following = tsEvent;

                    // Emit "following"
                    this.emit("following", tsEvent);
                }
            } else {
                // Emit "schedule"
                this.emit("schedule", tsEvent);
            }
        }

        if (objSection.table_id !== 0x4E && objSection.table_id !== 0x4F) {
            const target = (objSection.table_id & 0x0F) < 0x08 ? service.basic : service.extended;
            const tableNumber = objSection.table_id & 0x07;
            const segmentNumber = objSection.section_number >> 3;
            const sectionNumber = objSection.section_number & 0x07;
            const tableOffset = tableNumber << 5;
            const segmentOffset = tableOffset + segmentNumber;

            if (isUpdated) {
                // Update table
                for (let _segmentOffset = tableNumber << 5, l = (tableNumber + 1) << 5; _segmentOffset < l ; _segmentOffset++) {
                    target.flag[_segmentOffset] = 0x00;
                    target.ignore[_segmentOffset] = 0x00;
                }

                // Update excluded tables
                const lastTableNumber = objSection.last_table_id & 0x07;

                for (let _segmentOffset = (lastTableNumber + 1) << 5; _segmentOffset < 0x100 ; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }

                // Update excluded segments
                const lastSegmentNumber = objSection.last_section_number >> 3;

                for (let _segmentOffset = tableOffset + lastSegmentNumber + 1, l = tableOffset + 0x20; _segmentOffset < l ; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }
            }

            if (this._time !== null) {
                // Update past segment
                const currentSegmentOffset = (this._time.getTime() - this._time.getTimezoneOffset() * 60 * 1000) / (3 * 60 * 60 * 1000) & 0x07;

                for (let _segmentOffset = 0; _segmentOffset < currentSegmentOffset; _segmentOffset++) {
                    target.ignore[_segmentOffset] = 0xFF;
                }
            }

            if (target.flag[segmentOffset] === 0x00) {
                // Update excluded section
                const lastSectionNumber = objSection.segment_last_section_number & 0x07;

                for (let _sectionNumber = lastSectionNumber + 1; _sectionNumber < 0x08; _sectionNumber++) {
                    target.ignore[segmentOffset] |= 1 << _sectionNumber;
                }
            }

            // Update flag field
            target.flag[segmentOffset] |= 1 << sectionNumber;
        }

        // Emit "update"
        this.emit("update", tableId, onid, tsid, sid);
    }

    onTdt(tsSection) {
        const objSection = tsSection.decode();

        this._time = new TsDate(objSection.JST_time).decode();
    }

    onTot(tsSection) {
        const objSection = tsSection.decode();

        this._time = new TsDate(objSection.JST_time).decode();
    }

    hasPresent(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        return service.pf.present !== null;
    }

    hasFollowing(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        return service.pf.following !== null;
    }

    hasSchedule(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return false;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        for (let i = 0; i < 2; i++) {
            const target = i === 0 ? service.basic : service.extended;

            for (let j = 0; j < 0x100; j++) {
                if ((target.flag[j] | target.ignore[j]) !== 0xFF) return false;
            }
        }

        return true;
    }

    hasSchedules() {
        for (let keys = Object.keys(this._services), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._services[key];

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    for (let m = 0; m < 2; m++) {
                        const target = m === 0 ? service.basic : service.extended;

                        for (let n = 0; n < 0x100; n++) {
                            if ((target.flag[n] | target.ignore[n]) !== 0xFF) return false;
                        }
                    }
                }
            }
        }

        return true;
    }

    getPresent(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        return service.pf.present;
    }

    getFollowing(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        return service.pf.following;
    }

    getSchedule(onid, tsid, sid) {
        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return null;

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        return service.schedule;
    }

    getSchedules() {
        const schedules = {};

        for (let keys = Object.keys(this._services), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._services[key];

            schedules[key] = {};

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                schedules[key][key2] = {};

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    schedules[key][key2][key3] = service.schedule;
                }
            }
        }

        return schedules;
    }

    getScheduleAmount(onid, tsid, sid) {
        let overall = 0;
        let stored = 0;

        if (!TsUtil.checkNestedObject(this._services, [onid, tsid, sid])) return [stored, overall];

        const service = TsUtil.getNestedObject(this._services, [onid, tsid, sid]);

        for (let i = 0; i < 2; i++) {
            const target = i === 0 ? service.basic : service.extended;

            for (let j = 0; j < 0x100; j++) {
                for (let k = 0; k < 0x08; k++) {
                    if ((target.flag[j] >> k & 0x01) === 1) {
                        stored++;
                        overall++;
                    } else if (((target.ignore[j] ^ 0xFF) >> k & 0x01) === 1) {
                        overall++;
                    }
                }
            }
        }

        return [stored, overall];
    }

    getSchedulesAmount() {
        let overall = 0;
        let stored = 0;

        for (let keys = Object.keys(this._services), i = 0, l = keys.length; i < l; i++) {
            const key = keys[i];
            const originalNetwork = this._services[key];

            for (let keys2 = Object.keys(originalNetwork), j = 0, l2 = keys2.length; j < l2; j++) {
                const key2 = keys2[j];
                const transportStream = originalNetwork[key2];

                for (let keys3 = Object.keys(transportStream), k = 0, l3 = keys3.length; k < l3; k++) {
                    const key3 = keys3[k];
                    const service = transportStream[key3];

                    for (let m = 0; m < 2; m++) {
                        const target = m === 0 ? service.basic : service.extended;

                        for (let n = 0; n < 0x100; n++) {
                            for (let o = 0; o < 0x08; o++) {
                                if ((target.flag[n] >> o & 0x01) === 1) {
                                    stored++;
                                    overall++;
                                } else if (((target.ignore[n] ^ 0xFF) >> o & 0x01) === 1) {
                                    overall++;
                                }
                            }
                        }
                    }
                }
            }
        }

        return [stored, overall];
    }
}

module.exports = TsEventManager;
<|endoftext|>


<commit_before>// TODO:
// - Update comments.
// - Update *removeListener* to support namespaced events - low prio
// - Update *listener* to support namespaced events - low prio

;!function(root) {

  var EventEmitter2 = root.EventEmitter2 = function EventEmitter2(conf) {
    // conf = conf || {};
    // if (conf.delimiter === '*') this.setEventDelimiter(conf.delimiter);
    // this._eventCaseSensitive = conf.eventCaseSensitive;
    // this._eventDelimiter = conf.eventDelimiter || '.';
    // this._maxListeners = conf.maxListeners === 0 ? 0 : conf.maxListeners || 10;
    // this._listeners = {};
  };

  EventEmitter2.prototype.addListener = function(event, listener) {
    var me = this;

    // If the listener is not a function throw an error. A listener that is not
    // a function can not be called when an event is emitted and therefor is
    // not allowed to be added.
    if ('function' !== typeof listener) {
      throw new Error('addListener only accepts instances of Function');
    }

    // Set variables, if they are undefined set default values.
    // PERFORMANCE???
    var delimiter = me._eventDelimiter || (me._eventDelimiter = '.'),
        caseSensative = me._eventCaseSensitive || (me._eventCaseSensitive = false),
        listeners = me._listeners || (me._listeners = {}),
        maxListeners = me._maxListeners || (me._maxListeners = 10),
        event = caseSensative ? event.toLowerCase() : event,
        ns, exploreNs = listeners,
        listenToEvent, eventListeners;
    // var delimiter = me._eventDelimiter,
    //     caseSensative = me._eventCaseSensitive,
    //     listeners = me._listeners,
    //     maxListeners = me._maxListeners,
    //     event = caseSensative ? event.toLowerCase() : event,
    //     ns, exploreNs = listeners,
    //     listenToEvent, eventListeners;

    // If an event starts or ends with a delimiter character throw an error.
    // Starting or ending an event with a delimiter character creates empty
    // namespaces which don't work and therefor are not allowed.
    if (event[event.length-1] === delimiter || event[0] === delimiter) {
      me.eventNameError('addListener');
      return false;
    }

    // If the event is namespaced loop through the namespaces, set seperate
    // events for each namespace and set *listenToEvent* to the last
    // namespace to attach the listener function to this event after the loop.
    if (~event.indexOf(delimiter)) {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // PERFORMANCE???
      if (ns.some(function(event) { return event === ''; })) {
        me.eventNameError('addListener');
        return false;
      }

      // Loop through the namespaces.
      for (var i = 0, l = ns.length; i < l; i++) {

        // If the event is undefined in *exploreNs* it means it doesn't exist
        // in *listeners* so a new event should be created.
        if (!exploreNs[ns[i]]) {
          exploreNs[ns[i]] = {
              _name: ns[i],
              _fn: [],
              _ns: {}
            };
        }

        // If the loop is at the end set *listenToEvent* to the current
        // namespace - which is the last one - to attach the listener to this
        // event. If the loop is not at the end rebase *exploreNs* to loop the
        // current event's namespaces.
        if (i === ns.length - 1) {
          listenToEvent = exploreNs[ns[i]];
        } else {
          exploreNs = exploreNs[ns[i]]._ns;
        }
      }
    }

    // If the event is not namespaced set the single event and set
    // *listenToEvent* to this event to attach the listener to.
    else {

      // If the event is undefined in *listeners* it means it doesn't exist
      // so a new event should be created.
      if (!listeners[event]) {
        listeners[event] = {
          _name: event,
          _fn: [],
          _ns: {}
        };
      }

      // Set *listenToEvent* to the current event to attach the listener to.
      listenToEvent = listeners[event];
    }

    eventListeners = listenToEvent._fn;

    // Signal that a new listener is being added.
    me.emit('newListener', event, listener);

    // If the max amount of listeners has been reached signal and return to
    // cancel the addition of this new event.
    if (eventListeners.length >= maxListeners) {
      me.emit('maxListeners', event);
      return me;
    }

    // Add the listener to the event.
    eventListeners.push(listener);
    return me;
  };

  EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;

  EventEmitter2.prototype.once = function(event, listener) {
    this.many(event, 1, listener);
    return this;
  };

  EventEmitter2.prototype.many = function(event, ttl, listener) {
    var me = this;

    me.addListener(event, function() {
      if(ttl-- == 0) {
        me.removeListener(event, listener);
      } else {
        listener.apply(null, arguments);
      }
    });

    return me;
  };

  EventEmitter2.prototype.emit = function(event) {
    var me = this,
        args = arguments,
        i = 0,
        delimiter = me._eventDelimiter,
        caseSensitive = me._eventCaseSensitive,
        listeners = me._listeners,
        ns, exploreNs = [listeners], collectedListeners = [],
        invoked = false;

    // If an event starts or ends with a delimiter character throw an error.
    // Starting or ending an event with a delimiter character creates empty
    // namespaces which don't work and therefor are not allowed.
    if (event[event.length-1] === delimiter || event[0] === delimiter) {
      me.eventNameError('emit');
      return false;
    }

    // If the event is namespaced loop through the namespaces, find all the
    // listeners for the namespaces and wildcards and add them to the
    // *collectedListeners* array for listener invocation after the loop.
    if (~event.indexOf(delimiter) || event === '*') {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // PERFORMANCE???
      if (ns.some(function(event) { return event === ''; })) {
        me.eventNameError('emit');
        return false;
      }

      // Loop through the namespaces.
      for (i = 0; i < ns.length; i++) {

        // While looping through the namespace array loop through *exploreNs*
        // with *i* as well. This is basicaly the same as looping through the
        // the different levels of *events*. *collectedNs* is used to collect
        // all the namespaces that are going to be explored for the next level
        // of *events*.
        var currentNs = ns[i],
            currentExploreNs = exploreNs[i],
            collectedNs = [];

        // Loop through the current level of *events*.
        for (var key1 in currentExploreNs) {

          // Set the current namespace in *events* that is being explored.
          var exploredNs = currentExploreNs[key1],
              name = caseSensitive ? exploredNs._name.toLowerCase() : exploredNs._name;

          // If there is a match for the namespace or a wildcard collect the
          // next level of namespaces or collect the listeners of this
          // particular namespace.
          if (currentNs === '*' || (name === currentNs || name === '*')) {

            // If the loop is at the end collect the listeners for this
            // particular event and add them to the *collectedListeners* array
            // for listener invocation after the loop. If the loop is not at
            // the end collect the next level of namespaces.
            if (i === ns.length - 1) {
              var listeners = exploredNs._fn;
              if (listeners.length > 0) {
                collectedListeners = collectedListeners.concat(listeners);
              }
            } else {
              for (var key2 in exploredNs._ns) {
                collectedNs.push(exploredNs._ns[key2]);
              }
            }
          }
        }

        exploreNs.push(collectedNs);
      }
    }

    // If the event is not namespaced collect all the listeners for this
    // particular event and from the wildcard event and add them to
    // the *collectedListeners* array for listener invocation.
    else if (event !== '*' && event !== 'all' && listeners[event] && listeners[event]._fn.length > 0) {
      if (listeners['*'] && listeners['*']._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners['*']._fn);
      }
      collectedListeners = collectedListeners.concat(listeners[event]._fn);
    }

    // If the event is "all" fire all events?
    if (event === 'all') {}

    // Loop through the collected listeners and invoce them.
    for (i = 0, l = collectedListeners.length; i < l; i++) {
      collectedListeners[i].apply(me, args);
      invoked = true;
    }

    // If a listener "all" is defined in *events* invoce its functions.
    if (listeners['all'] && listeners['all']._fn.length > 0){
      for (i = 0, l = listeners['all']._fn.length; i < l; i++) {
        listeners['all']._fn[i].apply(me, args);
        invoked = true;
      }
    }

    return invoked;
  };

  EventEmitter2.prototype.removeListener = function(event, listener) {
    var me = this;

    if(listener && me._listeners[event] && me._listeners[event]._fn) {
      var listeners = me._listeners[event]._fn;

      for(var i = 0, l = listeners.length; i < l; i++) {
        if(listener === listeners[i]) {
          // Break out and return the removed listener.
          return listeners.splice(i, 1);
        }
      }
    } else {
      me._listeners[event] = {};
    }
    return me;
  };

  EventEmitter2.prototype.un = EventEmitter2.prototype.removeListener;

  EventEmitter2.prototype.removeAllListeners = function() {
    this._listeners = {};
    return this;
  };

  EventEmitter2.prototype.setEventDelimiter = function(delimiter) {
    if (delimiter === '*' && delimiter.length === 1) {
      throw new Error('setEventDelimiter doesn\'t accept a "*" (wild-card) character');
    } else {
      this._eventDelimiter = delimiter;
    }
    return this;
  };

  EventEmitter2.prototype.setEventCaseSensitive = function(caseSensative) {
    this._eventCaseSensitive = caseSensative;
    return this;
  };

  EventEmitter2.prototype.setMaxListeners = function(n) {
    this._maxListeners = n;
    return this;
  };

  EventEmitter2.prototype.listeners = function(event) {
    if(this._listeners[event]) {
      return this._listeners[event]._fn;
    } else {
      return false;
    }
  };

  EventEmitter2.prototype.eventNameError = function(fn) {
    throw new Error(fn + ' doesn\'t accept empty namespaces or events starting or ending with a "' + this._eventDelimiter + '" (delimiter) character ');
  };

}(typeof exports === 'undefined' ? window : exports);<commit_msg>added support for emitting events that dont have listeners<commit_after>// TODO:
// - Update comments.
// - Update *removeListener* to support namespaced events - low prio
// - Update *listener* to support namespaced events - low prio

;!function(root) {

  var EventEmitter2 = root.EventEmitter2 = function EventEmitter2(conf) {
    // conf = conf || {};
    // if (conf.delimiter === '*') this.setEventDelimiter(conf.delimiter);
    // this._eventCaseSensitive = conf.eventCaseSensitive;
    // this._eventDelimiter = conf.eventDelimiter || '.';
    // this._maxListeners = conf.maxListeners === 0 ? 0 : conf.maxListeners || 10;
    // this._listeners = {};
  };

  EventEmitter2.prototype.addListener = function(event, listener) {
    var me = this;

    // If the listener is not a function throw an error. A listener that is not
    // a function can not be called when an event is emitted and therefor is
    // not allowed to be added.
    if ('function' !== typeof listener) {
      throw new Error('addListener only accepts instances of Function');
    }

    // Set variables, if they are undefined set default values.
    // PERFORMANCE???
    var delimiter = me._eventDelimiter || (me._eventDelimiter = '.'),
        caseSensative = me._eventCaseSensitive || (me._eventCaseSensitive = false),
        listeners = me._listeners || (me._listeners = {}),
        maxListeners = me._maxListeners || (me._maxListeners = 10),
        event = caseSensative ? event.toLowerCase() : event,
        ns, exploreNs = listeners,
        listenToEvent, eventListeners;
    // var delimiter = me._eventDelimiter,
    //     caseSensative = me._eventCaseSensitive,
    //     listeners = me._listeners,
    //     maxListeners = me._maxListeners,
    //     event = caseSensative ? event.toLowerCase() : event,
    //     ns, exploreNs = listeners,
    //     listenToEvent, eventListeners;

    // If an event starts or ends with a delimiter character throw an error.
    // Starting or ending an event with a delimiter character creates empty
    // namespaces which don't work and therefor are not allowed.
    if (event[event.length-1] === delimiter || event[0] === delimiter) {
      me.eventNameError('addListener');
      return false;
    }

    // If the event is namespaced loop through the namespaces, set seperate
    // events for each namespace and set *listenToEvent* to the last
    // namespace to attach the listener function to this event after the loop.
    if (~event.indexOf(delimiter)) {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // PERFORMANCE???
      if (ns.some(function(event) { return event === ''; })) {
        me.eventNameError('addListener');
        return false;
      }

      // Loop through the namespaces.
      for (var i = 0, l = ns.length; i < l; i++) {

        // If the event is undefined in *exploreNs* it means it doesn't exist
        // in *listeners* so a new event should be created.
        if (!exploreNs[ns[i]]) {
          exploreNs[ns[i]] = {
              _name: ns[i],
              _fn: [],
              _ns: {}
            };
        }

        // If the loop is at the end set *listenToEvent* to the current
        // namespace - which is the last one - to attach the listener to this
        // event. If the loop is not at the end rebase *exploreNs* to loop the
        // current event's namespaces.
        if (i === ns.length - 1) {
          listenToEvent = exploreNs[ns[i]];
        } else {
          exploreNs = exploreNs[ns[i]]._ns;
        }
      }
    }

    // If the event is not namespaced set the single event and set
    // *listenToEvent* to this event to attach the listener to.
    else {

      // If the event is undefined in *listeners* it means it doesn't exist
      // so a new event should be created.
      if (!listeners[event]) {
        listeners[event] = {
          _name: event,
          _fn: [],
          _ns: {}
        };
      }

      // Set *listenToEvent* to the current event to attach the listener to.
      listenToEvent = listeners[event];
    }

    eventListeners = listenToEvent._fn;

    // Signal that a new listener is being added.
    me.emit('newListener', event, listener);

    // If the max amount of listeners has been reached signal and return to
    // cancel the addition of this new event.
    if (eventListeners.length >= maxListeners) {
      me.emit('maxListeners', event);
      return me;
    }

    // Add the listener to the event.
    eventListeners.push(listener);
    return me;
  };

  EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;

  EventEmitter2.prototype.once = function(event, listener) {
    this.many(event, 1, listener);
    return this;
  };

  EventEmitter2.prototype.many = function(event, ttl, listener) {
    var me = this;

    me.addListener(event, function() {
      if(ttl-- == 0) {
        me.removeListener(event, listener);
      } else {
        listener.apply(null, arguments);
      }
    });

    return me;
  };

  EventEmitter2.prototype.emit = function(event) {
    var me = this,
        args = arguments,
        i = 0,
        delimiter = me._eventDelimiter,
        caseSensitive = me._eventCaseSensitive,
        listeners = me._listeners,
        ns, exploreNs = [listeners], collectedListeners = [],
        invoked = false;

    // If no listeners are defined the emit will not be able to invoke a
    // function. Therefore return false to exit.
    if (!listeners) return false;

    // If an event starts or ends with a delimiter character throw an error.
    // Starting or ending an event with a delimiter character creates empty
    // namespaces which don't work and therefor are not allowed.
    if (event[event.length-1] === delimiter || event[0] === delimiter) {
      me.eventNameError('emit');
      return false;
    }

    // If the event is namespaced loop through the namespaces, find all the
    // listeners for the namespaces and wildcards and add them to the
    // *collectedListeners* array for listener invocation after the loop.
    if (~event.indexOf(delimiter) || event === '*') {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // PERFORMANCE???
      if (ns.some(function(event) { return event === ''; })) {
        me.eventNameError('emit');
        return false;
      }

      // Loop through the namespaces.
      for (i = 0; i < ns.length; i++) {

        // While looping through the namespace array loop through *exploreNs*
        // with *i* as well. This is basicaly the same as looping through the
        // the different levels of *events*. *collectedNs* is used to collect
        // all the namespaces that are going to be explored for the next level
        // of *events*.
        var currentNs = ns[i],
            currentExploreNs = exploreNs[i],
            collectedNs = [];

        // Loop through the current level of *events*.
        for (var key1 in currentExploreNs) {

          // Set the current namespace in *events* that is being explored.
          var exploredNs = currentExploreNs[key1],
              name = caseSensitive ? exploredNs._name.toLowerCase() : exploredNs._name;

          // If there is a match for the namespace or a wildcard collect the
          // next level of namespaces or collect the listeners of this
          // particular namespace.
          if (currentNs === '*' || (name === currentNs || name === '*')) {

            // If the loop is at the end collect the listeners for this
            // particular event and add them to the *collectedListeners* array
            // for listener invocation after the loop. If the loop is not at
            // the end collect the next level of namespaces.
            if (i === ns.length - 1) {
              var listeners = exploredNs._fn;
              if (listeners.length > 0) {
                collectedListeners = collectedListeners.concat(listeners);
              }
            } else {
              for (var key2 in exploredNs._ns) {
                collectedNs.push(exploredNs._ns[key2]);
              }
            }
          }
        }

        exploreNs.push(collectedNs);
      }
    }

    // If the event is not namespaced collect all the listeners for this
    // particular event and from the wildcard event and add them to
    // the *collectedListeners* array for listener invocation.
    else if (event !== '*' && event !== 'all' && listeners[event] && listeners[event]._fn.length > 0) {
      if (listeners['*'] && listeners['*']._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners['*']._fn);
      }
      collectedListeners = collectedListeners.concat(listeners[event]._fn);
    }

    // If the event is "all" fire all events?
    if (event === 'all') {}

    // Loop through the collected listeners and invoce them.
    for (i = 0, l = collectedListeners.length; i < l; i++) {
      collectedListeners[i].apply(me, args);
      invoked = true;
    }

    // If a listener "all" is defined in *events* invoce its functions.
    if (listeners['all'] && listeners['all']._fn.length > 0){
      for (i = 0, l = listeners['all']._fn.length; i < l; i++) {
        listeners['all']._fn[i].apply(me, args);
        invoked = true;
      }
    }

    return invoked;
  };

  EventEmitter2.prototype.removeListener = function(event, listener) {
    var me = this;

    if(listener && me._listeners[event] && me._listeners[event]._fn) {
      var listeners = me._listeners[event]._fn;

      for(var i = 0, l = listeners.length; i < l; i++) {
        if(listener === listeners[i]) {
          // Break out and return the removed listener.
          return listeners.splice(i, 1);
        }
      }
    } else {
      me._listeners[event] = {};
    }
    return me;
  };

  EventEmitter2.prototype.un = EventEmitter2.prototype.removeListener;

  EventEmitter2.prototype.removeAllListeners = function() {
    this._listeners = {};
    return this;
  };

  EventEmitter2.prototype.setEventDelimiter = function(delimiter) {
    if (delimiter === '*' && delimiter.length === 1) {
      throw new Error('setEventDelimiter doesn\'t accept a "*" (wild-card) character');
    } else {
      this._eventDelimiter = delimiter;
    }
    return this;
  };

  EventEmitter2.prototype.setEventCaseSensitive = function(caseSensative) {
    this._eventCaseSensitive = caseSensative;
    return this;
  };

  EventEmitter2.prototype.setMaxListeners = function(n) {
    this._maxListeners = n;
    return this;
  };

  EventEmitter2.prototype.listeners = function(event) {
    if(this._listeners[event]) {
      return this._listeners[event]._fn;
    } else {
      return false;
    }
  };

  EventEmitter2.prototype.eventNameError = function(fn) {
    throw new Error(fn + ' doesn\'t accept empty namespaces or events starting or ending with a "' + this._eventDelimiter + '" (delimiter) character ');
  };

}(typeof exports === 'undefined' ? window : exports);<|endoftext|>


<commit_before>var SubsEmitter = require('vigour-js/lib/observable/subscribe/constructor')
var execInternal = SubsEmitter.prototype.execInternal

exports.properties = {
  rendered: true
}

exports.on = {
  properties: {
    render: new SubsEmitter({
      pattern: {
        upward: {
          rendered: true
        }
      },
      define: {
        execInternal (bind) {
          var parent = bind
          while (parent) {
            if (parent.rendered) {
              break
            }
            parent.setKey('rendered', true)
            parent = parent.parent
          }
          return execInternal.apply(this, arguments)
        }
      }
    })
  }
}
<commit_msg>fix in render<commit_after>var SubsEmitter = require('vigour-js/lib/observable/subscribe/constructor')
var execInternal = SubsEmitter.prototype.execInternal

exports.properties = {
  rendered: true
}

exports.on = {
  properties: {
    render: new SubsEmitter({
      pattern: {
        $upward: {
          rendered: true
        }
      },
      define: {
        execInternal (bind) {
          var parent = bind
          while (parent) {
            if (parent.rendered) {
              break
            }
            parent.setKey('rendered', true)
            parent = parent.parent
          }
          return execInternal.apply(this, arguments)
        }
      }
    })
  }
}
<|endoftext|>


<commit_before>// Payment receipt uploads list

'use strict';

var _                = require('mano').i18n.bind('View: Component: Payments')
  , normalizeOptions = require('es5-ext/object/normalize-options')
  , _d = _;

module.exports = function (businessProcess/*, options*/) {
	var options               = normalizeOptions(arguments[1])
	  , urlPrefix             = options.urlPrefix || '/'
	  , target                = options.uploadsResolver || businessProcess
	  , paymentReceiptUploads = target.paymentReceiptUploads.applicable;

	return _if(paymentReceiptUploads._size, div(
		{ class: 'table-responsive-container' },
		table({ class: 'submitted-user-data-table user-request-table', configuration: {
			collection: paymentReceiptUploads,
			columns: [{
				class: 'submitted-user-data-table-status',
				data: function (upload) {
					return [
						_if(upload._isApproved, span({ class: 'fa fa-check' })),
						_if(upload._isRejected, span({ class: 'fa fa-exclamation' }))
					];
				}
			}, {
				class: 'submitted-user-data-table-label',
				head: _("Payment receipts"),
				data: function (upload) {
					return _d(upload.document.label, upload.document.getTranslations());
				}
			}, {
				class: 'submitted-user-data-table-date',
				head: _("Issue date"),
				data: function (upload) {
					return upload.document._issueDate;
				}
			}, {
				head: _("Issuer"),
				data: function (upload) {
					return "User";
				}
			}, {
				class: 'submitted-user-data-table-link',
				data: function (upload) {
					return a(
						{ href: urlPrefix + upload.document.docUrl },
						span({ class: 'fa fa-search' }, _("Go to"))
					);
				}
			}],
			headRowAttributes: {},
			rowAttributes: function (upload) {
				return { id: 'receipt-item-' + upload.document.docId };
			}
		} })
	));
};
<commit_msg>-- cleanip<commit_after>// Payment receipt uploads list

'use strict';

var normalizeOptions = require('es5-ext/object/normalize-options')
  , _                = require('mano').i18n.bind('View: Component: Payments')

  , _d = _;

module.exports = function (businessProcess/*, options*/) {
	var options               = normalizeOptions(arguments[1])
	  , urlPrefix             = options.urlPrefix || '/'
	  , target                = options.uploadsResolver || businessProcess
	  , paymentReceiptUploads = target.paymentReceiptUploads.applicable;

	return _if(paymentReceiptUploads._size, div(
		{ class: 'table-responsive-container' },
		table({ class: 'submitted-user-data-table user-request-table', configuration: {
			collection: paymentReceiptUploads,
			columns: [{
				class: 'submitted-user-data-table-status',
				data: function (upload) {
					return [
						_if(upload._isApproved, span({ class: 'fa fa-check' })),
						_if(upload._isRejected, span({ class: 'fa fa-exclamation' }))
					];
				}
			}, {
				class: 'submitted-user-data-table-label',
				head: _("Payment receipts"),
				data: function (upload) {
					return _d(upload.document.label, upload.document.getTranslations());
				}
			}, {
				class: 'submitted-user-data-table-date',
				head: _("Issue date"),
				data: function (upload) {
					return upload.document._issueDate;
				}
			}, {
				head: _("Issuer"),
				data: function (upload) {
					return "User";
				}
			}, {
				class: 'submitted-user-data-table-link',
				data: function (upload) {
					return a(
						{ href: urlPrefix + upload.document.docUrl },
						span({ class: 'fa fa-search' }, _("Go to"))
					);
				}
			}],
			headRowAttributes: {},
			rowAttributes: function (upload) {
				return { id: 'receipt-item-' + upload.document.docId };
			}
		} })
	));
};
<|endoftext|>