addon/classes/beat.js
import Ember from 'ember';
/**
* Provides helper classes that represent musical concepts meant to be used by
* classes from the Audio module.
*
* @public
* @module MusicalConcepts
*/
const {
Object: EmberObject,
run: { later }
} = Ember;
/**
* 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;