/* 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));