All files / addon/classes beat.js

48.15% Statements 13/27
0% Branches 0/6
58.33% Functions 7/12
42.86% Lines 9/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194                                                    1x                                                                                                                                                 4x   4x   4x 4x                                                                                                                                             5x 5x                     5x 5x          
import EmberObject from '@ember/object';
import { later } from '@ember/runloop';
 
/**
 * This class represents a single "beat" for a rhythmic instrument. An instance of this
 * class can be set to `active` or not to facilitate the way that most drum
 * machines work (when a beat is not `active`, the time that it occupies still
 * exists, but it does not cause audio to play, effectively resulting in a
 * "rest"). It provides properties that track when it is played, and when a "rest"
 * is played in it's place.
 *
 * This class does not have the ability to create audio on it's own and is
 * expected be a "child" of one of the Sound classes. See it's implementation in
 * {{#crossLink "BeatTrack"}}BeatTrack{{/crossLink}} for an example.
 *
 *     // Cannot play audio on it's own.
 *     // Must pass in parentPlay and/or parentPlayIn from a parent class.
 *     Beat.create({
 *       _parentPlayIn: this.playIn.bind(this),
 *       _parentPlay: this.play.bind(this),
 *     });
 *
 * @public
 * @class Beat
 * @todo add playAt
 */
const Beat = EmberObject.extend({
 
  /**
   * If `active` is `true`, all methods of play will cause this instance to play.
   * If `active` is `false`, the `playIfActive()` and `ifActivePlayIn()`
   * methods will treat this instance as a rest (a timed period of silence).
   *
   * @public
   * @property active
   * @type {boolean}
   */
  active: false,
 
  /**
   * Whether a Beat instance is currently playing, considering both active and
   * inactive beats (rests). When switched to `true`, is automatically returned
   * to false after the time specified by the duration property.
   *
   * @public
   * @property currentTimeIsPlaying
   * @type {boolean}
   * @default false
   */
  currentTimeIsPlaying: false,
 
  /**
   * Whether a Beat instance is currently playing, considering only active beats.
   * When switched to `true`, is automatically returned to false after the time
   * specified by the duration property.
   *
   * @public
   * @property isPlaying
   * @type {boolean}
   * @default false
   */
  isPlaying: false,
 
  /**
   * On Beat instance instantiation, this property should be set to the parent's
   * audioBuffer.duration.
   *
   * @property _audioBufferDuration
   * @type {number|null}
   * @private
   */
  _audioBufferDuration: null,
 
  /**
   * If specified, Determines length of time, in milliseconds, before isPlaying
   * and currentTimeIsPlaying are automatically switched back to false after
   * having been switched to true. 100ms is used by default.
   *
   * @public
   * @property duration
   * @type {number}
   * @default 100
   */
  duration: 100,
 
  /**
   * Calls it's parent's `playIn()` method directly to play the beat in
   * `${offset}` seconds.
   *
   * isPlaying and currentTimeIsPlaying are both marked true after the provided
   * offset has elapsed.
   *
   * @public
   * @method playIn
   *
   * @param {number} offset Number of seconds from "now" that the audio should
   * play.
   */
  playIn(offset=0) {
    const msOffset = offset * 1000;
 
    this.get('_parentPlayIn')(offset);
 
    later(() => this._markPlaying(), msOffset);
    later(() => this._markCurrentTimePlaying(), msOffset);
  },
 
  /**
   * If the beat is marked `active`, calls it's parent's `playIn()` method
   * directly to play the beat in `${offset}` seconds.
   *
   * If active, isPlaying is marked true after the provided offset has elapsed.
   *
   * currentTimeIsPlaying is marked true after the provided offset has elapsed,
   * even if beat is not active.
   *
   * @public
   * @method ifActivePlayIn
   *
   * @param {number} offset Number of seconds from "now" that the audio should
   * play.
   */
  ifActivePlayIn(offset=0) {
    const msOffset = offset * 1000;
 
    if (this.get('active')) {
      this.get('_parentPlayIn')(offset);
      later(() => this._markPlaying(), msOffset);
    }
 
    later(() => this._markCurrentTimePlaying(), msOffset);
  },
 
  /**
   * Calls it's parent's `play()` method directly to play the beat immediately.
   *
   * isPlaying and currentTimeIsPlaying are both immediately marked true.
   *
   * @public
   * @method play
   */
  play() {
    this.get('_parentPlay')();
    this._markPlaying();
    this._markCurrentTimePlaying();
  },
 
  /**
   * If `active`, calls it's parent's `play()` method directly to play the beat
   * immediately.
   *
   * If `active`, isPlaying is immediately marked true.
   *
   * currentTimeIsPlaying is immediately marked true, even if beat is not active.
   *
   * @public
   * @method playIfActive
   */
  playIfActive() {
    if (this.get('active')) {
      this.get('_parentPlay')();
      this._markPlaying();
    }
 
    this._markCurrentTimePlaying();
  },
 
  /**
   * Sets `isPlaying` to `true` and sets up a timer that sets `isPlaying` back
   * to false after `duration` has elapsed.
   *
   * @method _markPlaying
   * @private
   */
  _markPlaying() {
    this.set('isPlaying', true);
    later(() => this.set('isPlaying', false), this.get('duration'));
  },
 
  /**
   * Sets `currentTimeIsPlaying` to `true` and sets up a timer that sets
   * `currentTimeIsPlaying` back to false after `duration` has elapsed.
   *
   * @method _markCurrentTimePlaying
   * @private
   */
  _markCurrentTimePlaying() {
    this.set('currentTimeIsPlaying', true);
    later(() => this.set('currentTimeIsPlaying', false), this.get('duration'));
  }
});
 
export default Beat;