/* global soundManager, console, document, navigator, turntablesById, window */
(function(window) {
/** @license
* SoundManager 2: "Turntable UI": Demo application
* Copyright (c) 2015, Scott Schiller. All rights reserved.
* http://www.schillmania.com/projects/soundmanager2/
* Code provided under BSD license.
* http://schillmania.com/projects/soundmanager2/license.txt
*/
'use strict';
var turntables;
var events;
var utils;
var sounds;
var config;
var state;
var isMonophonic;
var localMethods;
var playSound;
// Default behaviours.
config = {
// if specified, CSS className required for links to be played
requireCSS: null,
// CSS className that will prevent links from being played
excludeCSS: 'turntable-exclude',
// when a sound finishes, find and play the next one?
playNext: true,
// show a record right away? (otherwise, "techniques" slipmat.)
hasRecordAtStart: false,
// play some background noise when end of record is hit.
useEndOfRecordNoise: true,
// samples from a real turntable. add more to taste.
endOfRecordNoise: [
'audio/record-noise-1.mp3',
'audio/record-noise-2.mp3'
],
// for assigning a target (turntable) to play. e.g., some.mp3
htmlAttribute: 'data-turntable',
// turntable innards. modify at own risk.
turntable: {
tonearm: {
angleToRecord: 16, // tonearm angle to outer edge of record
recordAngleSpan: 26 // outer edge -> inner edge of record
}
},
/**
* recommendation: don't edit these, use methods on window.turntables[] instead.
* to control sounds, call methods on turntables[0] etc. which will affect sounds in turn.
*/
soundOptions: {
multiShot: false,
onload: function(ok) {
if (!ok && !this.duration) {
// treat as a failure.
events.sound.error.apply(this, arguments);
}
},
whileplaying: function() {
events.sound.whileplaying.apply(this, arguments);
},
onfinish: function() {
events.sound.finish.apply(this, arguments);
}
}
};
sounds = {
endOfRecordNoise: []
};
state = {
endOfRecordNoise: null,
soundFinished: false,
lastLink: null
};
// devices that likely block auto-play and only allow one sound to be played at a time
isMonophonic = navigator.userAgent.match(/iphone|ipad|android|tablet|mobile/i);
function findTheDamnLink(o) {
// click events may have a nested target element instead of the link. Find the parent .
// link was clicked.
if (o && o.nodeName === 'A') {
return o;
}
// nested case.
if (o && o.parentNode) {
do {
o = o.parentNode;
} while (o && o.nodeName !== 'A' && o.parentNode);
}
return o;
}
function canPlayLink(a) {
// can SM2 play the , and does it specifically include or exclude via CSS?
return (
a && soundManager.canPlayLink(a)
&& (!config.requireCSS || utils.css.has(a, config.requireCSS))
&& (!config.excludeCSS || !utils.css.has(a, config.excludeCSS))
);
}
function findNextLink(o) {
// find current link in page, play next in DOM (or first, if no match.)
var foundCurrent, i, j, links, playableLinks, result, target;
// check for data- turntable target attribute.
target = o.getAttribute(config.htmlAttribute);
links = document.getElementsByTagName('a');
playableLinks = [];
for (i = 0, j = links.length; i < j; i++) {
if (canPlayLink(links[i])) {
// no data- target attribute, OR same target
if (!target || links[i].getAttribute(config.htmlAttribute) === target) {
playableLinks.push(links[i]);
// if last-active link found, take this one and exit.
if (foundCurrent) {
result = links[i];
break;
}
// is this the current link?
if (o === links[i]) {
foundCurrent = true;
}
}
}
}
// no match in DOM, perhaps rewritten via AJAX while playing etc.? Take the first.
if (!foundCurrent) {
result = playableLinks[0];
}
// if result is current, return null to avoid double-playing.
if (result === o) {
result = null;
}
return result;
}
events = {
mouse: {
click: function(e) {
// look for and play links to sounds.
var target,
ttID,
turntable;
target = findTheDamnLink(e.target);
if (canPlayLink(target)) {
// should this play on a particular turntable? (by HTML ID)
ttID = target.getAttribute('data-turntable');
// get the proper turntable.
turntable = turntablesById[ttID] || turntables[0];
// track this link for later.
state.lastLink = target;
playSound(turntable, target.href);
// artwork URL?
turntable.methods.setArtwork(target.getAttribute('data-artwork') || '');
utils.events.preventDefault(e);
return false;
}
return true;
}
},
sound: {
whileplaying: function() {
var progress = (this.position / this.durationEstimate);
if (progress >= 0 && this._turntable) {
// base "tonearm over record" angle, plus additional angle to move across the record.
this._turntable.methods.setTonearmAngle(config.turntable.tonearm.angleToRecord + (config.turntable.tonearm.recordAngleSpan * progress));
}
},
error: function() {
// something failed. 404, decode error, network loss etc.
// handle as though a sound finished.
if (window.console && console.warn) {
console.warn('Turntable failed to load ' + this.url);
}
events.sound.finish.apply(this);
},
finish: function() {
var nextLink;
state.finished = true;
if (config.playNext) {
nextLink = findNextLink(state.lastLink);
// click handler again
if (nextLink) {
events.mouse.click({
target: nextLink
});
}
}
// nothing else to play?
if (!nextLink && this._turntable) {
if (config.useEndOfRecordNoise && sounds.endOfRecordNoise.length) {
// make sure we're at end of record
this._turntable.methods.setTonearmAngle(config.turntable.tonearm.angleToRecord + config.turntable.tonearm.recordAngleSpan);
// end of record?
state.endOfRecordNoise = sounds.endOfRecordNoise[parseInt(Math.random() * sounds.endOfRecordNoise.length, 10)];
state.endOfRecordNoise.play({
loops: 999
});
} else {
// no more to do?
this._turntable.methods.stop();
}
}
}
}
};
playSound = function(turntable, url, load_only) {
var tt,
sound;
// if no turntable specified, take the first one.
tt = (turntable || turntables[0]);
if (tt.id) {
// second param: don't complain to console when sound doesn't exist.
sound = soundManager.getSoundById(tt.id, true);
}
// first play?
if (!sound) {
sound = soundManager.createSound({
id: tt.id,
url: url
});
} else if (sound.url !== url) {
// loading a new URL?
sound.stop();
}
state.finished = false;
// associate sound events with the given turntable.
// TODO: handle one sound object per table.
sound._turntable = turntable;
config.soundOptions.url = url;
// stop any previous record noise
if (state.endOfRecordNoise) {
state.endOfRecordNoise.stop();
state.endOfRecordNoise = null;
}
// if motor is already on, and the sound hasn't started yet (i.e., turntable motor was already on), start it now.
if (tt.data.power.motor && !sound.playState && !load_only) {
sound.play(config.soundOptions);
}
// start the turntable, add a slipmat and record if there isn't one already.
tt.methods.addSlipmat();
tt.methods.addRecord();
if (!load_only) {
tt.methods.powerOn();
tt.methods.start();
events.sound.whileplaying.apply(sound);
}
};
function applyDefaults() {
var i, j;
if (!isMonophonic && config.useEndOfRecordNoise && config.endOfRecordNoise.length) {
for (i = 0, j = config.endOfRecordNoise.length; i < j; i++) {
sounds.endOfRecordNoise.push(soundManager.createSound({
url: config.endOfRecordNoise[i]
}));
}
}
if (config.hasRecordAtStart) {
for (i = 0, j = turntables.length; i < j; i++) {
turntables[i].methods.addRecord();
}
}
}
function assignEvents() {
// default turntable behaviours
// TODO: use utils.mixin?
var i, j;
turntables.on.start = function(tt) {
soundManager.play(tt.id, config.soundOptions);
};
turntables.on.stop = function(tt) {
soundManager.pause(tt.id);
if (state.endOfRecordNoise) {
state.endOfRecordNoise.stop();
}
};
// tack on localMethods to turntable object
// TODO: use proper mixin
// note use of Function.bind (IE 9, Chrome 7, Firefox 4, Opera 11.60, Safari 5.1.4) to correct scope ('this') within handler.
if (localMethods.load.bind) {
for (i = 0, j = turntables.length; i < j; i++) {
turntables[i].methods.load = localMethods.load.bind(turntables[i]);
}
}
// a little hackish: global for now
turntables.config = config;
}
// will be mixed into global turntable API. runs within scope of turntable instance.
localMethods = {
// convenience method for scripting, i.e., if you want to load a sound (and optionally, with associated artwork URL), without playing it.
load: function(soundURL, artworkURL) {
playSound(this, soundURL, true);
if (artworkURL) {
this.methods.setArtwork(artworkURL);
}
}
};
function init() {
applyDefaults();
turntables = window.turntables;
assignEvents();
// local references
utils = turntables.utils;
// watch clicks, load and play MP3s etc. on the turntable UI.
utils.events.add(document, 'click', events.mouse.click);
}
soundManager.onready(init);
}(window));