From 673b0024c0bdc2949a8f0788699afe46e07d6092 Mon Sep 17 00:00:00 2001 From: ChapelR Date: Thu, 4 Jul 2019 00:46:48 -0400 Subject: [PATCH 01/21] macro api --- dist/harlowe-audio.min.js | 2 +- gulpfile.js | 1 + src/js/get.js | 29 +++++++++++++++++ src/js/macros.js | 67 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/js/macros.js diff --git a/dist/harlowe-audio.min.js b/dist/harlowe-audio.min.js index 944f368..035f78c 100644 --- a/dist/harlowe-audio.min.js +++ b/dist/harlowe-audio.min.js @@ -25,6 +25,6 @@ } }; - !function(){"use strict";var t,e=$("tw-storydata");window.Chapel=window.Chapel||{},window.Chapel.Get={isHarlowe3OrLater:3<=(t=e.attr("format-version").split(".")[0],t=Number(t),Number.isNaN(t)&&(t=3),t<1&&(t=3),t),storyTitle:e.attr("name"),IFID:e.attr("ifid")},options.storagekey=options.storagekey+"-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}"}(),function(){"use strict";var e=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),t=function(t,e){if(window.localStorage)try{t=""+t,"string"!=typeof e&&(e=JSON.stringify(e)),window.localStorage.setItem(t,e)}catch(t){console.error(t)}},o=function(t){if(window.localStorage)try{return t=""+t,window.localStorage.getItem(t)}catch(t){console.error(t)}},n=function(t){if(window.localStorage)try{t=""+t,window.localStorage.removeItem(t)}catch(t){console.error(t)}};function i(e,o){if("object"!=typeof e||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(t){if(void 0!==e[t])throw new Error('Invalid extension: cannot clobber existing property "'+t+'"');e[t]=o[t]})}var r={loaded:[],classes:{},master:{volume:options.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(t){r.master.mute=!!t,$(document).trigger({type:":master-mute",mute:!!t})},isMuted:function(){return!!r.master.mute},volume:function(t){t=Number(t),Number.isNaN(t)||(1this.unwrap.duration&&(t=this.unwrap.duration),this.unwrap.currentTime=t,this},fadeIn:function(t){var e=this;t=t||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*r.master.volume},1e3*t,function(){e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeOut:function(t){t=t||1;var e=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*t,function(){e.stop(),e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeTo:function(t,e){var o=this;if(t=t||1,e=Number(e),!Number.isNaN(e))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(t,e){return e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),a.track.includes(t)?void this.$el.one(t,e):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}};var l=a.track.concat(a.master);function d(t,e){if(!(this instanceof d))return new d(t,e);this.id=t,this.tracks=e.map(function(t){return c.get(t)}),this.looping=!1,this.current="",this.playing=!1}r.on=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).on(t,e):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},r.one=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).one(t,e):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},r.on(":master-mute",c.renew),r.on(":master-volume",c.renew),options.persistPrefs&&(r.on(":master-mute",r.savePrefs),r.on(":master-volume",r.savePrefs)),r.on(":play",function(t){t.track.addToGroup("playing")}),r.on(":stop",function(t){t.track.removeFromGroup("playing")}),options.muteOnBlur&&$(window).on("blur",function(){r.isMuted()||(r.mute(!0),$(window).one("focus",function(){r.mute(!1)}))}),r.classes.Track=c,r.newTrack=c.add,r.track=c.get,r.createGroup=function(t,e,o){!function(t,e){e&&Array.isArray(e)||(e=[]),r.groups.custom[t]=e.map(function(t){return c.get(t)})}(t,o?[].slice.call(arguments).slice(1):e)},r.group=function(t){if(!(this instanceof r.group))return new r.group(t);Object.keys(r.groups.custom).includes(t)?this.members=r.groups.custom[t]:this.members=r.groups[t],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+t+'"!'))},r.group.is=function(t){return this instanceof r.group},r.group.runOnAll=function(t,e,o){t.members.forEach(function(t){t[e].apply(t,o&&Array.isArray(o)?o:[])})},r.group.extend=function(t){i(r.group,t)},r.group.extendPrototype=function(t){i(r.group.prototype,t)},r.group.prototype={constructor:r.group,run:function(t,e,o){null!=o&&(e=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(t)&&r.group.runOnAll(this,t,e)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(t){return this.run("mute",[t]),this},volume:function(t){return this.run("volume",[t]),this},loop:function(t){return this.run("loop",[t]),this}},d.list={},d.is=function(t){return t instanceof d},d.add=function(t,e,o){return o&&(e=[].slice.call(arguments).slice(1)),d.list[t]=new d(t,e),d.list[t]},d.extend=function(t){i(d,t)},d.extendPrototype=function(t){i(d.prototype,t)},d.prototype={constructor:d,clone:function(){return new d(this.id,this.tracks.map(function(t){return t.id}))},shuffle:function(){var t,e,o,n=this.tracks;for(o=n.length-1;0=e.tracks.length&&e.looping)t=0;else if(t>=e.tracks.length)return e.current="",void(e.playing=!1);var o=e.tracks[t],n=o.isLooping();return o.loop(!1),o.play(),e.playing=!0,setTimeout(function(){o.isPlaying()||(e.playing=!1)},20),e.current=o.id,o.$el.one("ended.playlist",function(){t++,o.loop(n),e.play(t)}),e},loop:function(t){return this.looping=!!t,this},stop:function(){var t=c.get(this.current);return t.stop(),t.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}},r.classes.Playlist=d,r.createPlaylist=d.add,r.playlist=function(t){return d.list[t]||null},r.extend=function(t){i(r,t)},r.extendTrack=c.extend,r.extendTrackProto=c.extendPrototype,r.extendGroup=r.group.extend,r.extendGroupProto=r.group.extendPrototype,r.extendPlaylist=d.extend,r.extendPlaylistProto=d.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=r,options.persistPrefs&&r.loadPrefs()}(),function(){"use strict";if(options.controls.show){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),e=$(document.createElement("span")).attr("id","vol-title").append("Volume");options.controls.volumeDisplay||e.css("display","none");var o=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),n=Math.trunc(100*window.Chapel.Audio.master.volume);n<0?n=0:100").on("click",function(t){t.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&r.addClass("muted");var a=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(t){t.preventDefault(),s.toggleClass("closed")}),s=$(document.createElement("div")).attr("id","audio-controls").append(t,e,o,r,a).appendTo(document.body);options.controls.startClosed&&s.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:s,$volume:o,$mute:r,$user:t,close:function(){s.addClass("closed")},open:function(){s.removeClass("closed")},toggle:function(){s.toggleClass("closed")},hide:function(){s.css("display","none")},show:function(){s.css("display","block")},updateVolume:i}}}(),function(t){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var e=State,t=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){t.css("display","block").append('
')}function s(){t.fadeOut(function(){t.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:s,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=t,window.Chapel.Audio.preload=function(){if(!(e.pastLength||e.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var i=100+options.loadDelay,t=Chapel.Audio.classes.Track.list,r=Chapel.Audio.loaded;if(t.length){var a=t.map(function(t){return t.id});options.forceDismiss&&setTimeout(function(){s()},options.loadLimit.total),function t(){if(a.length){var e=a.shift();if(r.includes(e))t();else{var o=Chapel.Audio.classes.Track.get(e);if(o.unwrap.readyState<2){var n=!1;o.$el.one("canplay",function(){t(),n=!0}),setTimeout(function(){n||(o.$el.off("canplay"),t())},options.loadLimit.track)}else r.includes(e)||r.push(e),t()}}else setTimeout(s,i)}()}else setTimeout(s,i)}}}(),function(){"use strict";var t,e,o=options.storagekey+"_hal_restart_",n=(window.sessionStorage?(t=function(t,e){window.sessionStorage.setItem(o+t,e)},e=function(t){return window.sessionStorage.getItem(o+t)}):(t=function(){},e=function(){},console.warn("Session storage is unavailable...")),{save:t,load:e});window.Chapel.Audio.state={_store:n,saveTracks:function(){var t;try{t=Chapel.Audio.classes.Track.list.map(function(t){return{id:t.id,sources:t.sources}}),t=JSON.stringify(t),n.save("tracks",t)}catch(t){console.error(t.message)}},loadTracks:function(){var t;try{t=(t=n.load("tracks"))&&JSON.parse(t),Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.sources&&!Chapel.Audio.classes.Track.has(t.id)?Chapel.Audio.newTrack.apply(null,[t.id].concat(t.sources)):console.warn("Track reload failed...")})}catch(t){console.error(t.message)}},savePlaylists:function(){var t;try{var o=Chapel.Audio.classes.Playlist.list;t=Object.keys(o).map(function(t){var e={};return e.tracks=o[t].tracks.map(function(t){return t.id}),e.id=o[t].id,e}),t=JSON.stringify(t),n.save("playlists",t)}catch(t){console.error(t.message)}},loadPlaylists:function(){var t;try{(t=(t=n.load("playlists"))&&JSON.parse(t))&&Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.tracks&&Chapel.Audio.createPlaylist(t.id,t.tracks)})}catch(t){console.error(t.message)}},saveGroups:function(){var e;try{e={},Object.keys(Chapel.Audio.groups.custom).forEach(function(t){e[t]=Chapel.Audio.groups.custom[t].map(function(t){return"string"==typeof t?t:t.id})}),e=JSON.stringify(e),n.save("groups",e)}catch(t){console.error(t.message)}},loadGroups:function(){var e;try{(e=(e=n.load("groups"))&&JSON.parse(e))&&"object"==typeof e&&(Object.keys(e).forEach(function(t){e[t].map(function(t){return Chapel.Audio.classes.Track.get(t)})}),Chapel.Audio.groups.custom=e)}catch(t){console.error(t.message)}}}}(),function(){"use strict";options.globalA&&void 0===window.A&&(window.A=window.Chapel.Audio),$(document).on("unload",function(){window.Chapel.Audio.savePrefs()}),Chapel.Audio.classes.Track.renew(),Chapel.Audio.controls&&Chapel.Audio.controls.updateVolume(),Chapel.Get.isHarlowe3OrLater&&($(window).on("unload",function(){Chapel.Audio.state.saveTracks(),Chapel.Audio.state.savePlaylists(),Chapel.Audio.state.saveGroups()}),Chapel.Audio.state.loadTracks(),Chapel.Audio.state.loadPlaylists(),Chapel.Audio.state.loadGroups())}(),function(){"use strict";if(options.controls.show){var s=Engine,u=Chapel.Audio.controls.$user,t=function(){return"none"!==u.css("display")},c=function(){return t()||u.css("display","block"),u},e=function(){return t()&&u.css("display","none"),u};Chapel.Audio.menu={hide:e,show:c,isShown:t,links:{add:function(t,e,o){var n,i;if(!t||"string"!=typeof t){var r="undefined";return alert(r),void console.error(r)}o||"function"!=typeof e?(e&&"string"==typeof e&&(n=e),o&&"function"==typeof o&&(i=o)):(i=e,n=null);var a=$(document.createElement("tw-link")).append(t).attr({tabindex:"0",name:t.toLowerCase().trim()}).on("click",function(){n&&s.goToPassage(n),i&&i()}).addClass("story-menu").appendTo(u);return c(),a},clear:function(){return u.empty(),e()},hide:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').addClass("hide")},show:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').removeClass("hide")},toggle:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').toggleClass("hide")},remove:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').remove()}}}}}(),function(){"use strict";if(options.includeFixes){var e=window.prompt;window.prompt=function(){var t=e.apply(null,[].slice.call(arguments));return function(t){var e=(new DOMParser).parseFromString(t,"text/html");return Array.from(e.body.childNodes).some(function(t){return 1===t.nodeType})}(t)?t.replace(/[<>]/g,""):t}}}(); + !function(){"use strict";var t,e=$("tw-storydata");window.Chapel=window.Chapel||{},window.Chapel.Get={isHarlowe3OrLater:3<=(t=e.attr("format-version").split(".")[0],t=Number(t),Number.isNaN(t)&&(t=3),t<1&&(t=3),t),storyTitle:e.attr("name"),IFID:e.attr("ifid")},options.storagekey=options.storagekey+"-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}";var n=require("macros");window.Chapel=window.Chapel||{},window.Chapel.Macros={add:function(e){e&&"object"==typeof e&&Object.keys(e).forEach(function(t){!function(t,o){n.add(t,function(){var t=[].slice.call(arguments).slice(1),e=o.apply(null,t);return"string"==typeof e||"boolean"==typeof e||"number"==typeof e?e:""},n.TypeSignature.zeroOrMore(n.TypeSignature.Any))}(t,e[t])})}}}(),function(){"use strict";var e=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),t=function(t,e){if(window.localStorage)try{t=""+t,"string"!=typeof e&&(e=JSON.stringify(e)),window.localStorage.setItem(t,e)}catch(t){console.error(t)}},o=function(t){if(window.localStorage)try{return t=""+t,window.localStorage.getItem(t)}catch(t){console.error(t)}},n=function(t){if(window.localStorage)try{t=""+t,window.localStorage.removeItem(t)}catch(t){console.error(t)}};function i(e,o){if("object"!=typeof e||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(t){if(void 0!==e[t])throw new Error('Invalid extension: cannot clobber existing property "'+t+'"');e[t]=o[t]})}var r={loaded:[],classes:{},master:{volume:options.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(t){r.master.mute=!!t,$(document).trigger({type:":master-mute",mute:!!t})},isMuted:function(){return!!r.master.mute},volume:function(t){t=Number(t),Number.isNaN(t)||(1this.unwrap.duration&&(t=this.unwrap.duration),this.unwrap.currentTime=t,this},fadeIn:function(t){var e=this;t=t||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*r.master.volume},1e3*t,function(){e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeOut:function(t){t=t||1;var e=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*t,function(){e.stop(),e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeTo:function(t,e){var o=this;if(t=t||1,e=Number(e),!Number.isNaN(e))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(t,e){return e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),a.track.includes(t)?void this.$el.one(t,e):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}};var l=a.track.concat(a.master);function p(t,e){if(!(this instanceof p))return new p(t,e);this.id=t,this.tracks=e.map(function(t){return c.get(t)}),this.looping=!1,this.current="",this.playing=!1}r.on=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).on(t,e):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},r.one=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).one(t,e):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},r.on(":master-mute",c.renew),r.on(":master-volume",c.renew),options.persistPrefs&&(r.on(":master-mute",r.savePrefs),r.on(":master-volume",r.savePrefs)),r.on(":play",function(t){t.track.addToGroup("playing")}),r.on(":stop",function(t){t.track.removeFromGroup("playing")}),options.muteOnBlur&&$(window).on("blur",function(){r.isMuted()||(r.mute(!0),$(window).one("focus",function(){r.mute(!1)}))}),r.classes.Track=c,r.newTrack=c.add,r.track=c.get,r.createGroup=function(t,e,o){!function(t,e){e&&Array.isArray(e)||(e=[]),r.groups.custom[t]=e.map(function(t){return c.get(t)})}(t,o?[].slice.call(arguments).slice(1):e)},r.group=function(t){if(!(this instanceof r.group))return new r.group(t);Object.keys(r.groups.custom).includes(t)?this.members=r.groups.custom[t]:this.members=r.groups[t],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+t+'"!'))},r.group.is=function(t){return this instanceof r.group},r.group.runOnAll=function(t,e,o){t.members.forEach(function(t){t[e].apply(t,o&&Array.isArray(o)?o:[])})},r.group.extend=function(t){i(r.group,t)},r.group.extendPrototype=function(t){i(r.group.prototype,t)},r.group.prototype={constructor:r.group,run:function(t,e,o){null!=o&&(e=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(t)&&r.group.runOnAll(this,t,e)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(t){return this.run("mute",[t]),this},volume:function(t){return this.run("volume",[t]),this},loop:function(t){return this.run("loop",[t]),this}},p.list={},p.is=function(t){return t instanceof p},p.add=function(t,e,o){return o&&(e=[].slice.call(arguments).slice(1)),p.list[t]=new p(t,e),p.list[t]},p.extend=function(t){i(p,t)},p.extendPrototype=function(t){i(p.prototype,t)},p.prototype={constructor:p,clone:function(){return new p(this.id,this.tracks.map(function(t){return t.id}))},shuffle:function(){var t,e,o,n=this.tracks;for(o=n.length-1;0=e.tracks.length&&e.looping)t=0;else if(t>=e.tracks.length)return e.current="",void(e.playing=!1);var o=e.tracks[t],n=o.isLooping();return o.loop(!1),o.play(),e.playing=!0,setTimeout(function(){o.isPlaying()||(e.playing=!1)},20),e.current=o.id,o.$el.one("ended.playlist",function(){t++,o.loop(n),e.play(t)}),e},loop:function(t){return this.looping=!!t,this},stop:function(){var t=c.get(this.current);return t.stop(),t.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}},r.classes.Playlist=p,r.createPlaylist=p.add,r.playlist=function(t){return p.list[t]||null},r.extend=function(t){i(r,t)},r.extendTrack=c.extend,r.extendTrackProto=c.extendPrototype,r.extendGroup=r.group.extend,r.extendGroupProto=r.group.extendPrototype,r.extendPlaylist=p.extend,r.extendPlaylistProto=p.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=r,options.persistPrefs&&r.loadPrefs()}(),function(){"use strict";if(options.controls.show){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),e=$(document.createElement("span")).attr("id","vol-title").append("Volume");options.controls.volumeDisplay||e.css("display","none");var o=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),n=Math.trunc(100*window.Chapel.Audio.master.volume);n<0?n=0:100").on("click",function(t){t.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&r.addClass("muted");var a=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(t){t.preventDefault(),s.toggleClass("closed")}),s=$(document.createElement("div")).attr("id","audio-controls").append(t,e,o,r,a).appendTo(document.body);options.controls.startClosed&&s.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:s,$volume:o,$mute:r,$user:t,close:function(){s.addClass("closed")},open:function(){s.removeClass("closed")},toggle:function(){s.toggleClass("closed")},hide:function(){s.css("display","none")},show:function(){s.css("display","block")},updateVolume:i}}}(),function(t){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var e=State,t=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){t.css("display","block").append('
')}function s(){t.fadeOut(function(){t.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:s,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=t,window.Chapel.Audio.preload=function(){if(!(e.pastLength||e.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var i=100+options.loadDelay,t=Chapel.Audio.classes.Track.list,r=Chapel.Audio.loaded;if(t.length){var a=t.map(function(t){return t.id});options.forceDismiss&&setTimeout(function(){s()},options.loadLimit.total),function t(){if(a.length){var e=a.shift();if(r.includes(e))t();else{var o=Chapel.Audio.classes.Track.get(e);if(o.unwrap.readyState<2){var n=!1;o.$el.one("canplay",function(){t(),n=!0}),setTimeout(function(){n||(o.$el.off("canplay"),t())},options.loadLimit.track)}else r.includes(e)||r.push(e),t()}}else setTimeout(s,i)}()}else setTimeout(s,i)}}}(),function(){"use strict";var t,e,o=options.storagekey+"_hal_restart_",n=(window.sessionStorage?(t=function(t,e){window.sessionStorage.setItem(o+t,e)},e=function(t){return window.sessionStorage.getItem(o+t)}):(t=function(){},e=function(){},console.warn("Session storage is unavailable...")),{save:t,load:e});window.Chapel.Audio.state={_store:n,saveTracks:function(){var t;try{t=Chapel.Audio.classes.Track.list.map(function(t){return{id:t.id,sources:t.sources}}),t=JSON.stringify(t),n.save("tracks",t)}catch(t){console.error(t.message)}},loadTracks:function(){var t;try{t=(t=n.load("tracks"))&&JSON.parse(t),Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.sources&&!Chapel.Audio.classes.Track.has(t.id)?Chapel.Audio.newTrack.apply(null,[t.id].concat(t.sources)):console.warn("Track reload failed...")})}catch(t){console.error(t.message)}},savePlaylists:function(){var t;try{var o=Chapel.Audio.classes.Playlist.list;t=Object.keys(o).map(function(t){var e={};return e.tracks=o[t].tracks.map(function(t){return t.id}),e.id=o[t].id,e}),t=JSON.stringify(t),n.save("playlists",t)}catch(t){console.error(t.message)}},loadPlaylists:function(){var t;try{(t=(t=n.load("playlists"))&&JSON.parse(t))&&Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.tracks&&Chapel.Audio.createPlaylist(t.id,t.tracks)})}catch(t){console.error(t.message)}},saveGroups:function(){var e;try{e={},Object.keys(Chapel.Audio.groups.custom).forEach(function(t){e[t]=Chapel.Audio.groups.custom[t].map(function(t){return"string"==typeof t?t:t.id})}),e=JSON.stringify(e),n.save("groups",e)}catch(t){console.error(t.message)}},loadGroups:function(){var e;try{(e=(e=n.load("groups"))&&JSON.parse(e))&&"object"==typeof e&&(Object.keys(e).forEach(function(t){e[t].map(function(t){return Chapel.Audio.classes.Track.get(t)})}),Chapel.Audio.groups.custom=e)}catch(t){console.error(t.message)}}}}(),function(){"use strict";options.globalA&&void 0===window.A&&(window.A=window.Chapel.Audio),$(document).on("unload",function(){window.Chapel.Audio.savePrefs()}),Chapel.Audio.classes.Track.renew(),Chapel.Audio.controls&&Chapel.Audio.controls.updateVolume(),Chapel.Get.isHarlowe3OrLater&&($(window).on("unload",function(){Chapel.Audio.state.saveTracks(),Chapel.Audio.state.savePlaylists(),Chapel.Audio.state.saveGroups()}),Chapel.Audio.state.loadTracks(),Chapel.Audio.state.loadPlaylists(),Chapel.Audio.state.loadGroups())}(),function(){"use strict";if(options.controls.show){var s=Engine,u=Chapel.Audio.controls.$user,t=function(){return"none"!==u.css("display")},c=function(){return t()||u.css("display","block"),u},e=function(){return t()&&u.css("display","none"),u};Chapel.Audio.menu={hide:e,show:c,isShown:t,links:{add:function(t,e,o){var n,i;if(!t||"string"!=typeof t){var r="undefined";return alert(r),void console.error(r)}o||"function"!=typeof e?(e&&"string"==typeof e&&(n=e),o&&"function"==typeof o&&(i=o)):(i=e,n=null);var a=$(document.createElement("tw-link")).append(t).attr({tabindex:"0",name:t.toLowerCase().trim()}).on("click",function(){n&&s.goToPassage(n),i&&i()}).addClass("story-menu").appendTo(u);return c(),a},clear:function(){return u.empty(),e()},hide:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').addClass("hide")},show:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').removeClass("hide")},toggle:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').toggleClass("hide")},remove:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').remove()}}}}}(),function(){"use strict";var n=Chapel.Audio,t={newtrack:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.newTrack(t,e)}catch(t){alert("Error in the (newtrack:) macro: "+t.message)}},newplaylist:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createPlaylist(t,e)}catch(t){alert("Error in the (newplaylist:) macro: "+t.message)}},newgroup:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createGroup(t,e)}catch(t){alert("Error in the (newgroup:) macro: "+t.message)}},track:function(t,e){try{var o=n.track(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (track:) macro: "+t.message)}},playlist:function(t,e){try{var o=n.playlist(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (playlist:) macro: "+t.message)}},group:function(t,e){try{var o=n.group(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (group:) macro: "+t.message)}}};window.Chapel.Macros.add(t)}(),function(){"use strict";if(options.includeFixes){var e=window.prompt;window.prompt=function(){var t=e.apply(null,[].slice.call(arguments));return function(t){var e=(new DOMParser).parseFromString(t,"text/html");return Array.from(e.body.childNodes).some(function(t){return 1===t.nodeType})}(t)?t.replace(/[<>]/g,""):t}}}(); }()); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index a59dea5..0f35970 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -52,6 +52,7 @@ function buildScripts () { 'state.js', 'setup.js', 'userland.js', + 'macros.js', 'fixes.js' ].map( function (file) { return './src/js/' + file; diff --git a/src/js/get.js b/src/js/get.js index a0f2bd1..bc8cb56 100644 --- a/src/js/get.js +++ b/src/js/get.js @@ -40,4 +40,33 @@ // set storage key for this story with IFID + Story Title options.storagekey = options.storagekey + '-' + Chapel.Get.IFID + '-{' + Chapel.Get.storyTitle + '}'; + + // hack the macro API + var _macros = require('macros'); + function simpleMacro (name, cb) { + _macros.add(name, function () { + var arr = [].slice.call(arguments).slice(1); + var result = cb.apply(null, arr); + if (typeof result === 'string' || typeof result === 'boolean' || typeof result === 'number') { + // only return numbers, booleans, and strings + return result; + } + return ''; + }, _macros.TypeSignature.zeroOrMore(_macros.TypeSignature.Any)); + } + + function addMacros (obj) { + if (!obj || typeof obj !== 'object') { + return; + } + Object.keys(obj).forEach( function (macro) { + simpleMacro(macro, obj[macro]); + }); + } + + window.Chapel = window.Chapel || {}; + + window.Chapel.Macros = { + add : addMacros + }; }()); \ No newline at end of file diff --git a/src/js/macros.js b/src/js/macros.js new file mode 100644 index 0000000..28d43e2 --- /dev/null +++ b/src/js/macros.js @@ -0,0 +1,67 @@ +(function () { + 'use strict'; + + var A = Chapel.Audio; + + // commands should be mapped to methods, for now, commands ARE method names + + var macros = { + // (newtrack: name, source [, source...]) + newtrack : function (name, sources) { + sources = [].slice.call(arguments).slice(1); + try { + return A.newTrack(name, sources); + } catch (err) { + // these should be made into Harlowe errors at some point + alert('Error in the (newtrack:) macro: ' + err.message); + } + }, + // (newplaylist: name, track [, track...]) + newplaylist : function (name, tracks) { + tracks = [].slice.call(arguments).slice(1); + try { + return A.createPlaylist(name, tracks); + } catch (err) { + alert('Error in the (newplaylist:) macro: ' + err.message); + } + }, + // (newgroup: name, track [, track...]) + newgroup : function (name, tracks) { + tracks = [].slice.call(arguments).slice(1); + try { + return A.createGroup(name, tracks); + } catch (err) { + alert('Error in the (newgroup:) macro: ' + err.message); + } + }, + // (track: name, command [, args...]) + track : function (track, command) { + try { + var _get = A.track(track); + return _get[command].apply(_get, [].slice.call(arguments).slice(2)); + } catch (err) { + alert('Error in the (track:) macro: ' + err.message); + } + }, + // (playlist: name, command [, args...]) + playlist : function (list, command) { + try { + var _get = A.playlist(list); + return _get[command].apply(_get, [].slice.call(arguments).slice(2)); + } catch (err) { + alert('Error in the (playlist:) macro: ' + err.message); + } + }, + // (group: name, command [, args...]) + group : function (gr, command) { + try { + var _get = A.group(gr); + return _get[command].apply(_get, [].slice.call(arguments).slice(2)); + } catch (err) { + alert('Error in the (group:) macro: ' + err.message); + } + } + }; + + window.Chapel.Macros.add(macros); +}()); \ No newline at end of file From 0bb220bc59305cd4b3e542d4ada5ca16245ebb0c Mon Sep 17 00:00:00 2001 From: ChapelR Date: Thu, 4 Jul 2019 04:03:40 -0400 Subject: [PATCH 02/21] start playing with a v2 Some testing and experimenting with a potential v2; macro-side support, special passages, refactored code, etc. It's all experimentation for now. --- dist/harlowe-audio.min.js | 2 +- src/js/audio.js | 248 ++++++++++++++++++++++---------------- src/js/get.js | 41 ++++++- src/js/macros.js | 84 ++++++++++++- src/js/setup.js | 11 ++ 5 files changed, 275 insertions(+), 111 deletions(-) diff --git a/dist/harlowe-audio.min.js b/dist/harlowe-audio.min.js index 035f78c..e6e7f99 100644 --- a/dist/harlowe-audio.min.js +++ b/dist/harlowe-audio.min.js @@ -25,6 +25,6 @@ } }; - !function(){"use strict";var t,e=$("tw-storydata");window.Chapel=window.Chapel||{},window.Chapel.Get={isHarlowe3OrLater:3<=(t=e.attr("format-version").split(".")[0],t=Number(t),Number.isNaN(t)&&(t=3),t<1&&(t=3),t),storyTitle:e.attr("name"),IFID:e.attr("ifid")},options.storagekey=options.storagekey+"-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}";var n=require("macros");window.Chapel=window.Chapel||{},window.Chapel.Macros={add:function(e){e&&"object"==typeof e&&Object.keys(e).forEach(function(t){!function(t,o){n.add(t,function(){var t=[].slice.call(arguments).slice(1),e=o.apply(null,t);return"string"==typeof e||"boolean"==typeof e||"number"==typeof e?e:""},n.TypeSignature.zeroOrMore(n.TypeSignature.Any))}(t,e[t])})}}}(),function(){"use strict";var e=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),t=function(t,e){if(window.localStorage)try{t=""+t,"string"!=typeof e&&(e=JSON.stringify(e)),window.localStorage.setItem(t,e)}catch(t){console.error(t)}},o=function(t){if(window.localStorage)try{return t=""+t,window.localStorage.getItem(t)}catch(t){console.error(t)}},n=function(t){if(window.localStorage)try{t=""+t,window.localStorage.removeItem(t)}catch(t){console.error(t)}};function i(e,o){if("object"!=typeof e||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(t){if(void 0!==e[t])throw new Error('Invalid extension: cannot clobber existing property "'+t+'"');e[t]=o[t]})}var r={loaded:[],classes:{},master:{volume:options.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(t){r.master.mute=!!t,$(document).trigger({type:":master-mute",mute:!!t})},isMuted:function(){return!!r.master.mute},volume:function(t){t=Number(t),Number.isNaN(t)||(1this.unwrap.duration&&(t=this.unwrap.duration),this.unwrap.currentTime=t,this},fadeIn:function(t){var e=this;t=t||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*r.master.volume},1e3*t,function(){e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeOut:function(t){t=t||1;var e=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*t,function(){e.stop(),e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeTo:function(t,e){var o=this;if(t=t||1,e=Number(e),!Number.isNaN(e))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(t,e){return e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),a.track.includes(t)?void this.$el.one(t,e):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}};var l=a.track.concat(a.master);function p(t,e){if(!(this instanceof p))return new p(t,e);this.id=t,this.tracks=e.map(function(t){return c.get(t)}),this.looping=!1,this.current="",this.playing=!1}r.on=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).on(t,e):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},r.one=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).one(t,e):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},r.on(":master-mute",c.renew),r.on(":master-volume",c.renew),options.persistPrefs&&(r.on(":master-mute",r.savePrefs),r.on(":master-volume",r.savePrefs)),r.on(":play",function(t){t.track.addToGroup("playing")}),r.on(":stop",function(t){t.track.removeFromGroup("playing")}),options.muteOnBlur&&$(window).on("blur",function(){r.isMuted()||(r.mute(!0),$(window).one("focus",function(){r.mute(!1)}))}),r.classes.Track=c,r.newTrack=c.add,r.track=c.get,r.createGroup=function(t,e,o){!function(t,e){e&&Array.isArray(e)||(e=[]),r.groups.custom[t]=e.map(function(t){return c.get(t)})}(t,o?[].slice.call(arguments).slice(1):e)},r.group=function(t){if(!(this instanceof r.group))return new r.group(t);Object.keys(r.groups.custom).includes(t)?this.members=r.groups.custom[t]:this.members=r.groups[t],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+t+'"!'))},r.group.is=function(t){return this instanceof r.group},r.group.runOnAll=function(t,e,o){t.members.forEach(function(t){t[e].apply(t,o&&Array.isArray(o)?o:[])})},r.group.extend=function(t){i(r.group,t)},r.group.extendPrototype=function(t){i(r.group.prototype,t)},r.group.prototype={constructor:r.group,run:function(t,e,o){null!=o&&(e=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(t)&&r.group.runOnAll(this,t,e)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(t){return this.run("mute",[t]),this},volume:function(t){return this.run("volume",[t]),this},loop:function(t){return this.run("loop",[t]),this}},p.list={},p.is=function(t){return t instanceof p},p.add=function(t,e,o){return o&&(e=[].slice.call(arguments).slice(1)),p.list[t]=new p(t,e),p.list[t]},p.extend=function(t){i(p,t)},p.extendPrototype=function(t){i(p.prototype,t)},p.prototype={constructor:p,clone:function(){return new p(this.id,this.tracks.map(function(t){return t.id}))},shuffle:function(){var t,e,o,n=this.tracks;for(o=n.length-1;0=e.tracks.length&&e.looping)t=0;else if(t>=e.tracks.length)return e.current="",void(e.playing=!1);var o=e.tracks[t],n=o.isLooping();return o.loop(!1),o.play(),e.playing=!0,setTimeout(function(){o.isPlaying()||(e.playing=!1)},20),e.current=o.id,o.$el.one("ended.playlist",function(){t++,o.loop(n),e.play(t)}),e},loop:function(t){return this.looping=!!t,this},stop:function(){var t=c.get(this.current);return t.stop(),t.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}},r.classes.Playlist=p,r.createPlaylist=p.add,r.playlist=function(t){return p.list[t]||null},r.extend=function(t){i(r,t)},r.extendTrack=c.extend,r.extendTrackProto=c.extendPrototype,r.extendGroup=r.group.extend,r.extendGroupProto=r.group.extendPrototype,r.extendPlaylist=p.extend,r.extendPlaylistProto=p.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=r,options.persistPrefs&&r.loadPrefs()}(),function(){"use strict";if(options.controls.show){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),e=$(document.createElement("span")).attr("id","vol-title").append("Volume");options.controls.volumeDisplay||e.css("display","none");var o=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),n=Math.trunc(100*window.Chapel.Audio.master.volume);n<0?n=0:100").on("click",function(t){t.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&r.addClass("muted");var a=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(t){t.preventDefault(),s.toggleClass("closed")}),s=$(document.createElement("div")).attr("id","audio-controls").append(t,e,o,r,a).appendTo(document.body);options.controls.startClosed&&s.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:s,$volume:o,$mute:r,$user:t,close:function(){s.addClass("closed")},open:function(){s.removeClass("closed")},toggle:function(){s.toggleClass("closed")},hide:function(){s.css("display","none")},show:function(){s.css("display","block")},updateVolume:i}}}(),function(t){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var e=State,t=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){t.css("display","block").append('
')}function s(){t.fadeOut(function(){t.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:s,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=t,window.Chapel.Audio.preload=function(){if(!(e.pastLength||e.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var i=100+options.loadDelay,t=Chapel.Audio.classes.Track.list,r=Chapel.Audio.loaded;if(t.length){var a=t.map(function(t){return t.id});options.forceDismiss&&setTimeout(function(){s()},options.loadLimit.total),function t(){if(a.length){var e=a.shift();if(r.includes(e))t();else{var o=Chapel.Audio.classes.Track.get(e);if(o.unwrap.readyState<2){var n=!1;o.$el.one("canplay",function(){t(),n=!0}),setTimeout(function(){n||(o.$el.off("canplay"),t())},options.loadLimit.track)}else r.includes(e)||r.push(e),t()}}else setTimeout(s,i)}()}else setTimeout(s,i)}}}(),function(){"use strict";var t,e,o=options.storagekey+"_hal_restart_",n=(window.sessionStorage?(t=function(t,e){window.sessionStorage.setItem(o+t,e)},e=function(t){return window.sessionStorage.getItem(o+t)}):(t=function(){},e=function(){},console.warn("Session storage is unavailable...")),{save:t,load:e});window.Chapel.Audio.state={_store:n,saveTracks:function(){var t;try{t=Chapel.Audio.classes.Track.list.map(function(t){return{id:t.id,sources:t.sources}}),t=JSON.stringify(t),n.save("tracks",t)}catch(t){console.error(t.message)}},loadTracks:function(){var t;try{t=(t=n.load("tracks"))&&JSON.parse(t),Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.sources&&!Chapel.Audio.classes.Track.has(t.id)?Chapel.Audio.newTrack.apply(null,[t.id].concat(t.sources)):console.warn("Track reload failed...")})}catch(t){console.error(t.message)}},savePlaylists:function(){var t;try{var o=Chapel.Audio.classes.Playlist.list;t=Object.keys(o).map(function(t){var e={};return e.tracks=o[t].tracks.map(function(t){return t.id}),e.id=o[t].id,e}),t=JSON.stringify(t),n.save("playlists",t)}catch(t){console.error(t.message)}},loadPlaylists:function(){var t;try{(t=(t=n.load("playlists"))&&JSON.parse(t))&&Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.tracks&&Chapel.Audio.createPlaylist(t.id,t.tracks)})}catch(t){console.error(t.message)}},saveGroups:function(){var e;try{e={},Object.keys(Chapel.Audio.groups.custom).forEach(function(t){e[t]=Chapel.Audio.groups.custom[t].map(function(t){return"string"==typeof t?t:t.id})}),e=JSON.stringify(e),n.save("groups",e)}catch(t){console.error(t.message)}},loadGroups:function(){var e;try{(e=(e=n.load("groups"))&&JSON.parse(e))&&"object"==typeof e&&(Object.keys(e).forEach(function(t){e[t].map(function(t){return Chapel.Audio.classes.Track.get(t)})}),Chapel.Audio.groups.custom=e)}catch(t){console.error(t.message)}}}}(),function(){"use strict";options.globalA&&void 0===window.A&&(window.A=window.Chapel.Audio),$(document).on("unload",function(){window.Chapel.Audio.savePrefs()}),Chapel.Audio.classes.Track.renew(),Chapel.Audio.controls&&Chapel.Audio.controls.updateVolume(),Chapel.Get.isHarlowe3OrLater&&($(window).on("unload",function(){Chapel.Audio.state.saveTracks(),Chapel.Audio.state.savePlaylists(),Chapel.Audio.state.saveGroups()}),Chapel.Audio.state.loadTracks(),Chapel.Audio.state.loadPlaylists(),Chapel.Audio.state.loadGroups())}(),function(){"use strict";if(options.controls.show){var s=Engine,u=Chapel.Audio.controls.$user,t=function(){return"none"!==u.css("display")},c=function(){return t()||u.css("display","block"),u},e=function(){return t()&&u.css("display","none"),u};Chapel.Audio.menu={hide:e,show:c,isShown:t,links:{add:function(t,e,o){var n,i;if(!t||"string"!=typeof t){var r="undefined";return alert(r),void console.error(r)}o||"function"!=typeof e?(e&&"string"==typeof e&&(n=e),o&&"function"==typeof o&&(i=o)):(i=e,n=null);var a=$(document.createElement("tw-link")).append(t).attr({tabindex:"0",name:t.toLowerCase().trim()}).on("click",function(){n&&s.goToPassage(n),i&&i()}).addClass("story-menu").appendTo(u);return c(),a},clear:function(){return u.empty(),e()},hide:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').addClass("hide")},show:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').removeClass("hide")},toggle:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').toggleClass("hide")},remove:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').remove()}}}}}(),function(){"use strict";var n=Chapel.Audio,t={newtrack:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.newTrack(t,e)}catch(t){alert("Error in the (newtrack:) macro: "+t.message)}},newplaylist:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createPlaylist(t,e)}catch(t){alert("Error in the (newplaylist:) macro: "+t.message)}},newgroup:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createGroup(t,e)}catch(t){alert("Error in the (newgroup:) macro: "+t.message)}},track:function(t,e){try{var o=n.track(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (track:) macro: "+t.message)}},playlist:function(t,e){try{var o=n.playlist(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (playlist:) macro: "+t.message)}},group:function(t,e){try{var o=n.group(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (group:) macro: "+t.message)}}};window.Chapel.Macros.add(t)}(),function(){"use strict";if(options.includeFixes){var e=window.prompt;window.prompt=function(){var t=e.apply(null,[].slice.call(arguments));return function(t){var e=(new DOMParser).parseFromString(t,"text/html");return Array.from(e.body.childNodes).some(function(t){return 1===t.nodeType})}(t)?t.replace(/[<>]/g,""):t}}}(); + !function(){"use strict";var t,e=$("tw-storydata"),o=e.find('tw-passagedata[name="hal.tracks"]'),n=null;if(o.length){var i=o.text().split(/[\n\r]/),r=new Map(i.map(function(t){var e=t.split(":"),o=e[0].trim();return[o=(o=(o=o.replace(/^["']/,"")).replace(/["']$/,"")).trim(),e[1].split(",").map(function(t){return t=(t=(t=(t=t.trim()).replace(/^["']/,"")).replace(/["']$/,"")).trim()})]}));console.log(r),n=r}window.Chapel=window.Chapel||{},window.Chapel.Get={isHarlowe3OrLater:3<=(t=e.attr("format-version").split(".")[0],t=Number(t),Number.isNaN(t)&&(t=3),t<1&&(t=3),t),storyTitle:e.attr("name"),IFID:e.attr("ifid"),fromPassage:n},options.storagekey=options.storagekey+"-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}";var a=require("macros");window.Chapel=window.Chapel||{},window.Chapel.Macros={add:function(e){e&&"object"==typeof e&&Object.keys(e).forEach(function(t){!function(t,o){a.add(t,function(){var t=[].slice.call(arguments).slice(1),e=o.apply(null,t);return"string"==typeof e||"boolean"==typeof e||"number"==typeof e?e:""},a.TypeSignature.zeroOrMore(a.TypeSignature.Any))}(t,e[t])})}}}(),function(){"use strict";var e=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),t=function(t,e){if(window.localStorage)try{t=""+t,"string"!=typeof e&&(e=JSON.stringify(e)),window.localStorage.setItem(t,e)}catch(t){console.error(t)}},o=function(t){if(window.localStorage)try{return t=""+t,window.localStorage.getItem(t)}catch(t){console.error(t)}},n=function(t){if(window.localStorage)try{t=""+t,window.localStorage.removeItem(t)}catch(t){console.error(t)}};function i(e,o){if("object"!=typeof e||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(t){if(void 0!==e[t])throw new Error('Invalid extension: cannot clobber existing property "'+t+'"');e[t]=o[t]})}var r={loaded:[],classes:{},master:{volume:options.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(t){r.master.mute=!!t,$(document).trigger({type:":master-mute",mute:!!t})},isMuted:function(){return!!r.master.mute},volume:function(t){t=Number(t),Number.isNaN(t)||(1this.unwrap.duration&&(t=this.unwrap.duration),this.unwrap.currentTime=t,this},fadeIn:function(t){var e=this;t=t||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*r.master.volume},1e3*t,function(){e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeOut:function(t){t=t||1;var e=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*t,function(){e.stop(),e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeTo:function(t,e){var o=this;if(t=t||1,e=Number(e),!Number.isNaN(e))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(t,e){return e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),a.track.includes(t)?void this.$el.one(t,e):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}};var l=a.track.concat(a.master);function p(t,e){if(!(this instanceof p))return new p(t,e);this.id=t,this.tracks=e.map(function(t){return c.get(t)}),this.looping=!1,this.current="",this.playing=!1}r.on=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).on(t,e):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},r.one=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).one(t,e):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},r.on(":master-mute",c.renew),r.on(":master-volume",c.renew),options.persistPrefs&&(r.on(":master-mute",r.savePrefs),r.on(":master-volume",r.savePrefs)),r.on(":play",function(t){t.track.addToGroup("playing")}),r.on(":stop",function(t){t.track.removeFromGroup("playing")}),options.muteOnBlur&&$(window).on("blur",function(){r.isMuted()||(r.mute(!0),$(window).one("focus",function(){r.mute(!1)}))}),r.classes.Track=c,r.newTrack=c.add,r.track=c.get,r.createGroup=function(t,e,o){!function(t,e){e&&Array.isArray(e)||(e=[]),r.groups.custom[t]=e.map(function(t){return c.get(t)})}(t,o?[].slice.call(arguments).slice(1):e)},r.group=function(t){if(!(this instanceof r.group))return new r.group(t);Object.keys(r.groups.custom).includes(t)?this.members=r.groups.custom[t]:this.members=r.groups[t],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+t+'"!'))},r.group.is=function(t){return this instanceof r.group},r.group.runOnAll=function(t,e,o){t.members.forEach(function(t){t[e].apply(t,o&&Array.isArray(o)?o:[])})},r.group.extend=function(t){i(r.group,t)},r.group.extendPrototype=function(t){i(r.group.prototype,t)},r.group.prototype={constructor:r.group,run:function(t,e,o){null!=o&&(e=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(t)&&r.group.runOnAll(this,t,e)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(t){return this.run("mute",[t]),this},volume:function(t){return this.run("volume",[t]),this},loop:function(t){return this.run("loop",[t]),this}},p.list={},p.is=function(t){return t instanceof p},p.add=function(t,e,o){return o&&(e=[].slice.call(arguments).slice(1)),p.list[t]=new p(t,e),p.list[t]},p.extend=function(t){i(p,t)},p.extendPrototype=function(t){i(p.prototype,t)},p.prototype={constructor:p,clone:function(){return new p(this.id,this.tracks.map(function(t){return t.id}))},shuffle:function(){var t,e,o,n=this.tracks;for(o=n.length-1;0=e.tracks.length&&e.looping)t=0;else if(t>=e.tracks.length)return e.current="",void(e.playing=!1);var o=e.tracks[t],n=o.isLooping();return o.loop(!1),o.play(),e.playing=!0,setTimeout(function(){o.isPlaying()||(e.playing=!1)},20),e.current=o.id,o.$el.one("ended.playlist",function(){t++,o.loop(n),e.play(t)}),e},loop:function(t){return this.looping=!!t,this},stop:function(){var t=c.get(this.current);return t.stop(),t.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}},r.classes.Playlist=p,r.createPlaylist=p.add,r.playlist=function(t){return p.list[t]||null},r.extend=function(t){i(r,t)},r.extendTrack=c.extend,r.extendTrackProto=c.extendPrototype,r.extendGroup=r.group.extend,r.extendGroupProto=r.group.extendPrototype,r.extendPlaylist=p.extend,r.extendPlaylistProto=p.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=r,options.persistPrefs&&r.loadPrefs()}(),function(){"use strict";if(options.controls.show){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),e=$(document.createElement("span")).attr("id","vol-title").append("Volume");options.controls.volumeDisplay||e.css("display","none");var o=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),n=Math.trunc(100*window.Chapel.Audio.master.volume);n<0?n=0:100").on("click",function(t){t.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&r.addClass("muted");var a=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(t){t.preventDefault(),s.toggleClass("closed")}),s=$(document.createElement("div")).attr("id","audio-controls").append(t,e,o,r,a).appendTo(document.body);options.controls.startClosed&&s.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:s,$volume:o,$mute:r,$user:t,close:function(){s.addClass("closed")},open:function(){s.removeClass("closed")},toggle:function(){s.toggleClass("closed")},hide:function(){s.css("display","none")},show:function(){s.css("display","block")},updateVolume:i}}}(),function(t){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var e=State,t=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){t.css("display","block").append('
')}function s(){t.fadeOut(function(){t.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:s,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=t,window.Chapel.Audio.preload=function(){if(!(e.pastLength||e.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var i=100+options.loadDelay,t=Chapel.Audio.classes.Track.list,r=Chapel.Audio.loaded;if(t.length){var a=t.map(function(t){return t.id});options.forceDismiss&&setTimeout(function(){s()},options.loadLimit.total),function t(){if(a.length){var e=a.shift();if(r.includes(e))t();else{var o=Chapel.Audio.classes.Track.get(e);if(o.unwrap.readyState<2){var n=!1;o.$el.one("canplay",function(){t(),n=!0}),setTimeout(function(){n||(o.$el.off("canplay"),t())},options.loadLimit.track)}else r.includes(e)||r.push(e),t()}}else setTimeout(s,i)}()}else setTimeout(s,i)}}}(),function(){"use strict";var t,e,o=options.storagekey+"_hal_restart_",n=(window.sessionStorage?(t=function(t,e){window.sessionStorage.setItem(o+t,e)},e=function(t){return window.sessionStorage.getItem(o+t)}):(t=function(){},e=function(){},console.warn("Session storage is unavailable...")),{save:t,load:e});window.Chapel.Audio.state={_store:n,saveTracks:function(){var t;try{t=Chapel.Audio.classes.Track.list.map(function(t){return{id:t.id,sources:t.sources}}),t=JSON.stringify(t),n.save("tracks",t)}catch(t){console.error(t.message)}},loadTracks:function(){var t;try{t=(t=n.load("tracks"))&&JSON.parse(t),Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.sources&&!Chapel.Audio.classes.Track.has(t.id)?Chapel.Audio.newTrack.apply(null,[t.id].concat(t.sources)):console.warn("Track reload failed...")})}catch(t){console.error(t.message)}},savePlaylists:function(){var t;try{var o=Chapel.Audio.classes.Playlist.list;t=Object.keys(o).map(function(t){var e={};return e.tracks=o[t].tracks.map(function(t){return t.id}),e.id=o[t].id,e}),t=JSON.stringify(t),n.save("playlists",t)}catch(t){console.error(t.message)}},loadPlaylists:function(){var t;try{(t=(t=n.load("playlists"))&&JSON.parse(t))&&Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.tracks&&Chapel.Audio.createPlaylist(t.id,t.tracks)})}catch(t){console.error(t.message)}},saveGroups:function(){var e;try{e={},Object.keys(Chapel.Audio.groups.custom).forEach(function(t){e[t]=Chapel.Audio.groups.custom[t].map(function(t){return"string"==typeof t?t:t.id})}),e=JSON.stringify(e),n.save("groups",e)}catch(t){console.error(t.message)}},loadGroups:function(){var e;try{(e=(e=n.load("groups"))&&JSON.parse(e))&&"object"==typeof e&&(Object.keys(e).forEach(function(t){e[t].map(function(t){return Chapel.Audio.classes.Track.get(t)})}),Chapel.Audio.groups.custom=e)}catch(t){console.error(t.message)}}}}(),function(){"use strict";options.globalA&&void 0===window.A&&(window.A=window.Chapel.Audio),Chapel.Get.fromPassage&&Chapel.Get.fromPassage.forEach(function(t,e){Chapel.Audio.newTrack.apply(null,[e].concat(t))}),$(document).on("unload",function(){window.Chapel.Audio.savePrefs()}),Chapel.Audio.classes.Track.renew(),Chapel.Audio.controls&&Chapel.Audio.controls.updateVolume(),Chapel.Get.isHarlowe3OrLater&&($(window).on("unload",function(){Chapel.Audio.state.saveTracks(),Chapel.Audio.state.savePlaylists(),Chapel.Audio.state.saveGroups()}),Chapel.Audio.state.loadTracks(),Chapel.Audio.state.loadPlaylists(),Chapel.Audio.state.loadGroups())}(),function(){"use strict";if(options.controls.show){var s=Engine,u=Chapel.Audio.controls.$user,t=function(){return"none"!==u.css("display")},c=function(){return t()||u.css("display","block"),u},e=function(){return t()&&u.css("display","none"),u};Chapel.Audio.menu={hide:e,show:c,isShown:t,links:{add:function(t,e,o){var n,i;if(!t||"string"!=typeof t){var r="undefined";return alert(r),void console.error(r)}o||"function"!=typeof e?(e&&"string"==typeof e&&(n=e),o&&"function"==typeof o&&(i=o)):(i=e,n=null);var a=$(document.createElement("tw-link")).append(t).attr({tabindex:"0",name:t.toLowerCase().trim()}).on("click",function(){n&&s.goToPassage(n),i&&i()}).addClass("story-menu").appendTo(u);return c(),a},clear:function(){return u.empty(),e()},hide:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').addClass("hide")},show:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').removeClass("hide")},toggle:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').toggleClass("hide")},remove:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').remove()}}}}}(),function(){"use strict";var n=Chapel.Audio,t={newtrack:function(){var t=[].slice.call(arguments);try{return n.newTrack.apply(null,t)}catch(t){alert("Error in the (newtrack:) macro: "+t.message)}},newplaylist:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createPlaylist(t,e)}catch(t){alert("Error in the (newplaylist:) macro: "+t.message)}},newgroup:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createGroup(t,e)}catch(t){alert("Error in the (newgroup:) macro: "+t.message)}},track:function(t,e){try{var o=n.track(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (track:) macro: "+t.message)}},playlist:function(t,e){try{var o=n.playlist(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (playlist:) macro: "+t.message)}},group:function(t,e){try{var o=n.group(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (group:) macro: "+t.message)}}};window.Chapel.Macros.add(t)}(),function(){"use strict";if(options.includeFixes){var e=window.prompt;window.prompt=function(){var t=e.apply(null,[].slice.call(arguments));return function(t){var e=(new DOMParser).parseFromString(t,"text/html");return Array.from(e.body.childNodes).some(function(t){return 1===t.nodeType})}(t)?t.replace(/[<>]/g,""):t}}}(); }()); \ No newline at end of file diff --git a/src/js/audio.js b/src/js/audio.js index ec73d18..16ec013 100644 --- a/src/js/audio.js +++ b/src/js/audio.js @@ -241,79 +241,85 @@ this.sources = sources; } - Track.list = []; + Object.assign(Track, { + list : [], - Track.is = function (inst) { - return (inst instanceof Track); - }; + is : function (inst) { + return (inst instanceof Track); + }, - Track.has = function (id) { - return Track.list.some( function (track) { - return track.id === id; - }); - }; + has : function (id) { + return Track.list.some( function (track) { + return track.id === id; + }); + }, - Track.emit = function (type, track) { - $(document).trigger({ - type : type, - track : track - }); - track.emit(type); - }; + emit : function (type, track) { + $(document).trigger({ + type : type, + track : track + }); + track.emit(type); + }, - Track.add = function (id) { - var sources = [].slice.call(arguments).slice(1); - var track = new Track(id, sources); - Track.list.push(track); - track.$el.on('canplay', function () { - Track.emit(':available', track); - }); - track.$el.on('canplaythrough', function () { - Track.emit(':loaded', track); - }); - track.attach(); - return track; - }; + add : function (id, sources, test) { + if (test) { + sources = [].slice.call(arguments).slice(1); + } + var track = new Track(id, sources); + Track.list.push(track); + track.$el.on('canplay', function () { + Track.emit(':available', track); + }); + track.$el.on('canplaythrough', function () { + Track.emit(':loaded', track); + }); + track.attach(); + return track; + }, - Track.renew = function () { - Track.list.forEach( function (track) { - track.mute(track.isMuted()); - track.volume(track.getVolume()); - }); - }; + renew : function () { + Track.list.forEach( function (track) { + track.mute(track.isMuted()); + track.volume(track.getVolume()); + }); + }, - Track.getIdx = function (id) { - return Track.list.findIndex( function (track) { - return track.id === id; - }); - }; + getIdx : function (id) { + return Track.list.findIndex( function (track) { + return track.id === id; + }); + }, - Track.get = function (id) { - return Track.list.find( function (track) { - return track.id === id; - }); - }; + get : function (id) { + return Track.list.find( function (track) { + return track.id === id; + }); + }, - Track.extend = function (data) { - _extend(Track, data); - }; + extend : function (data) { + _extend(Track, data); + }, - Track.extendPrototype = function (data) { - _extend(Track.prototype, data); - }; + extendPrototype : function (data) { + _extend(Track.prototype, data); + }, - Track.removeFromDOM = function (track) { - if (typeof track === 'string') { - track = Track.get(track); - } - if (track && Track.is(track)) { - track.unattach(); - } else { - $container.remove(); + removeFromDOM : function (track) { + if (typeof track === 'string') { + track = Track.get(track); + } + if (track && Track.is(track)) { + track.unattach(); + } else { + $container.remove(); + } } - }; + }); + + - Track.prototype = { + Object.assign(Track.prototype, { constructor : Track, emit : function (type) { this.$el.trigger({ @@ -572,7 +578,7 @@ } this.$el.one(type, cb); } - }; + }); var validMaster = validEvents.track.concat(validEvents.master); @@ -635,8 +641,22 @@ } Audio.classes.Track = Track; - Audio.newTrack = Track.add; - Audio.track = Track.get; + Audio.newTrack = function () { + try { + return Track.add.apply(null, arguments); + } catch (err) { + console.error(err.message); + alert('Error in A.newTrack() -> see the console for more information.'); + } + } + Audio.track = function (id) { + try { + return Track.get(id); + } catch (err) { + console.error(err.message); + alert('Error in A.track() -> see the console for more information.'); + } + } function createAudioGroup (groupName, trackIDs) { if (!trackIDs || !Array.isArray(trackIDs)) { @@ -672,27 +692,29 @@ } }; - Audio.group.is = function (inst) { - return this instanceof Audio.group; - }; + Object.assign(Audio.group, { + is : function (inst) { + return (this instanceof Audio.group); + }, - Audio.group.runOnAll = function (group, method, args) { - group.members.forEach( function (track) { - track[method].apply(track, (args && Array.isArray(args)) ? args : []); - }); - }; + runOnAll : function (group, method, args) { + group.members.forEach( function (track) { + track[method].apply(track, (args && Array.isArray(args)) ? args : []); + }); + }, - Audio.group.extend = function (data) { - _extend(Audio.group, data); - }; + extend : function (data) { + _extend(Audio.group, data); + }, - Audio.group.extendPrototype = function (data) { - _extend(Audio.group.prototype, data); - }; + extendPrototype : function (data) { + _extend(Audio.group.prototype, data); + } + }); // Audio.group('playing').pause(); or Audio.group('playing').mute(true); - Audio.group.prototype = { + Object.assign(Audio.group.prototype, { constructor : Audio.group, run : function (method, args, test) { if (test != null) { @@ -726,7 +748,7 @@ this.run('loop', [bool]); return this; } - }; + }); function Playlist (id, trackIDs) { if (!(this instanceof Playlist)) { @@ -741,29 +763,31 @@ this.playing = false; } - Playlist.list = {}; + Object.assign(Playlist, { + list : {}, - Playlist.is = function (inst) { - return inst instanceof Playlist; - }; + is : function (inst) { + return inst instanceof Playlist; + }, - Playlist.add = function (id, trackList, test) { - if (test) { - trackList = [].slice.call(arguments).slice(1); - } - Playlist.list[id] = new Playlist(id, trackList); - return Playlist.list[id]; - }; + add : function (id, trackList, test) { + if (test) { + trackList = [].slice.call(arguments).slice(1); + } + Playlist.list[id] = new Playlist(id, trackList); + return Playlist.list[id]; + }, - Playlist.extend = function (data) { - _extend(Playlist, data); - }; + extend : function (data) { + _extend(Playlist, data); + }, - Playlist.extendPrototype = function (data) { - _extend(Playlist.prototype, data); - }; + extendPrototype : function (data) { + _extend(Playlist.prototype, data); + } + }); - Playlist.prototype = { + Object.assign(Playlist.prototype, { constructor : Playlist, clone : function () { return new Playlist(this.id, this.tracks.map( function (tr) { return tr.id; })); @@ -831,6 +855,9 @@ this.looping = !!bool; return this; }, + isLooping : function () { + return this.looping; + }, stop : function () { var track = Track.get(this.current); track.stop(); @@ -844,14 +871,29 @@ this.playing = false; return this; } - }; + }); // Audio.playlist('myplaylist').play(); or Audio.playlist('myplaylist').loop(true); Audio.classes.Playlist = Playlist; - Audio.createPlaylist = Playlist.add; + Audio.createPlaylist = function () { + try { + Playlist.add.apply(null, arguments); + } catch (err) { + console.error(err.message); + alert('Error in A.createPlaylist() -> see the console for more information.'); + } + } Audio.playlist = function (id) { - return Playlist.list[id] || null; + try { + var list = Playlist.list[id] || null; + if (!list) { + throw new Error('Playlist "' + id + '" does not exist.'); + } + } catch (err) { + console.error(err.message); + alert('Error in A.createPlaylist() -> see the console for more information.'); + } }; // extensions @@ -869,8 +911,4 @@ window.Chapel.Audio = Audio; - if (options.persistPrefs) { - Audio.loadPrefs(); - } - }()); \ No newline at end of file diff --git a/src/js/get.js b/src/js/get.js index bc8cb56..a060db9 100644 --- a/src/js/get.js +++ b/src/js/get.js @@ -5,6 +5,39 @@ var $dataChunk = $('tw-storydata'); + var $trackPassage = $dataChunk.find('tw-passagedata[name="hal.tracks"]'); + + var tracksFromPassage = null; + + if ($trackPassage.length) { + var lines = $trackPassage.text().split(/[\n\r]/).filter( function (l) { + // ignore blank or malformed lines + return l && l.trim() && l.includes(':'); + }); + var tracks = new Map(lines.map( function (line) { + var parts = line.split(':'); + + var trackName = parts[0].trim(); + trackName = trackName.replace(/^["']/, ''); + trackName = trackName.replace(/["']$/, ''); + trackName = trackName.trim(); + + var sources = parts[1].split(',').map( function (src) { + src = src.trim(); + src = src.replace(/^["']/, ''); + src = src.replace(/["']$/, ''); + src = src.trim(); + return src; + }); + + // TODO: test validity of trackName and sources array + + return [trackName, sources]; + })); + console.log(tracks); + tracksFromPassage = tracks; + } + function getHarloweVersion () { var semVer = $dataChunk.attr('format-version'); var major = semVer.split('.')[0]; @@ -33,11 +66,17 @@ window.Chapel = window.Chapel || {}; window.Chapel.Get = { + version : getHarloweVersion(), isHarlowe3OrLater : version3OrLater(), storyTitle : getStoryTitle(), - IFID : getStoryIFID() + IFID : getStoryIFID(), + fromPassage : tracksFromPassage }; + if (Chapel.Get.version < 2) { + throw new Error('The Harlowe Audio Library is only designed to work with Harlowe 2 and 3; you appear to be using Harlowe 1 or an otherwise invalid story format.'); + } + // set storage key for this story with IFID + Story Title options.storagekey = options.storagekey + '-' + Chapel.Get.IFID + '-{' + Chapel.Get.storyTitle + '}'; diff --git a/src/js/macros.js b/src/js/macros.js index 28d43e2..a96157c 100644 --- a/src/js/macros.js +++ b/src/js/macros.js @@ -3,14 +3,78 @@ var A = Chapel.Audio; - // commands should be mapped to methods, for now, commands ARE method names + function isFn (thing) { + return thing && typeof thing === 'function'; + } + + function getCommand (cmd, what) { + if (!cmd || typeof cmd !== 'string') { + return null; + } + cmd = cmd.toLowerCase().trim(); + + switch (cmd) { + // camelcase method names + case 'isplaying': + cmd = 'isPlaying'; + break; + case 'playwhenpossible': + cmd = 'playWhenPossible'; + break; + case 'ismuted': + cmd = 'isAMuted'; + break; + case 'togglemute': + cmd = 'toggleMute'; + break; + case 'getvolume': + cmd = 'getVolume'; + break; + case 'islooping': + cmd = 'isLooping'; + break; + case 'toggleloop': + cmd = 'toggleLoop'; + break; + case 'fadein': + cmd = 'fadeIn'; + break; + case 'fadeout': + cmd = 'fadeOut'; + break; + case 'fadeto': + cmd = 'fadeTo'; + break; + case 'stopall': + cmd = 'stopAll'; + break; + } + + if (cmd === 'isPlaying' && what === 'master') { + cmd = 'audioPlaying'; + } + + if (what === 'group') { + if (isFn(A.group.prototype[cmd])) { + return cmd; + } + } else if (what === 'master') { + if (isFn(A)) { + return cmd; + } + } else { + if (isFn(A.classes[what].prototype[cmd])) { + return cmd; + } + } + } var macros = { // (newtrack: name, source [, source...]) - newtrack : function (name, sources) { - sources = [].slice.call(arguments).slice(1); + newtrack : function () { + var args = [].slice.call(arguments); try { - return A.newTrack(name, sources); + return A.newTrack.apply(null, args); } catch (err) { // these should be made into Harlowe errors at some point alert('Error in the (newtrack:) macro: ' + err.message); @@ -34,10 +98,20 @@ alert('Error in the (newgroup:) macro: ' + err.message); } }, + // (masteraudio: command [, args...]) + masteraudio : function (command) { + try { + command = getCommand(command); + return A[command].apply(null, [].slice.call(arguments).slice(1)); + } catch (err) { + alert('Error in the (track:) macro: ' + err.message); + } + }, // (track: name, command [, args...]) track : function (track, command) { try { var _get = A.track(track); + command = getCommand(command); return _get[command].apply(_get, [].slice.call(arguments).slice(2)); } catch (err) { alert('Error in the (track:) macro: ' + err.message); @@ -47,6 +121,7 @@ playlist : function (list, command) { try { var _get = A.playlist(list); + command = getCommand(command); return _get[command].apply(_get, [].slice.call(arguments).slice(2)); } catch (err) { alert('Error in the (playlist:) macro: ' + err.message); @@ -56,6 +131,7 @@ group : function (gr, command) { try { var _get = A.group(gr); + command = getCommand(command); return _get[command].apply(_get, [].slice.call(arguments).slice(2)); } catch (err) { alert('Error in the (group:) macro: ' + err.message); diff --git a/src/js/setup.js b/src/js/setup.js index b8a6c6a..fbc528f 100644 --- a/src/js/setup.js +++ b/src/js/setup.js @@ -5,6 +5,13 @@ window.A = window.Chapel.Audio; } + if (Chapel.Get.fromPassage) { + // autoload tracks from the `hal.tracks` special passage + Chapel.Get.fromPassage.forEach( function (sources, name) { + Chapel.Audio.newTrack.apply(null, [name].concat(sources)); + }); + } + $(document).on('unload', function () { window.Chapel.Audio.savePrefs(); }); @@ -28,4 +35,8 @@ Chapel.Audio.state.loadGroups(); } + if (options.persistPrefs) { + Audio.loadPrefs(); + } + }()); \ No newline at end of file From e7a97a4c13f8d5ef095ae9cd5ecc11a60add0ec4 Mon Sep 17 00:00:00 2001 From: ChapelR Date: Thu, 4 Jul 2019 21:52:16 -0400 Subject: [PATCH 03/21] some breaking changes - pull configs from a special passage - pull tracks from a special passage (in addition to macros and JS API) - start splitting up docs - get rid of useless API docs - start restructuring code - flatten user configs - streamline build process - stress testing --- dist/harlowe-audio.min.js | 26 +- docs/README.md | 27 +- docs/changelog.md | 29 +- docs/nav.md | 2 +- docs/v1.md | 1382 +++++++++++++++++++++++++++++++++++++ gulpfile.js | 7 +- package.json | 2 +- src/js/audio.js | 9 +- src/js/controlpanel.js | 8 +- src/js/fixes.js | 24 - src/js/get.js | 67 +- src/js/options.js | 21 + src/js/preload.js | 8 +- src/js/setup.js | 4 +- src/js/state.js | 2 + src/js/userland.js | 4 +- src/wrap/wrapper.js | 24 +- 17 files changed, 1532 insertions(+), 114 deletions(-) create mode 100644 docs/v1.md delete mode 100644 src/js/fixes.js create mode 100644 src/js/options.js diff --git a/dist/harlowe-audio.min.js b/dist/harlowe-audio.min.js index e6e7f99..d8f9443 100644 --- a/dist/harlowe-audio.min.js +++ b/dist/harlowe-audio.min.js @@ -1,30 +1,10 @@ ;(function () { /** - * Harlowe Audio Library, by Chapel, v1.3.0 - * + * Harlowe Audio Library, by Chapel, v2.0.0-pre1 + * for Harlowe 2.1.0 and higher * Released under the Unlicense, and dedicated to the public domain. **/ - var options = { - preload : true, - loadDelay : 0, - muteOnBlur : true, - startingVol : 0.5, - storagekey : '%%tw-audio', - persistPrefs : true, - globalA : true, - includeFixes : false, - controls : { - show : true, - startClosed : true, - volumeDisplay : true - }, - loadLimit : { - track : 500, - total : 8000 - } - }; - - !function(){"use strict";var t,e=$("tw-storydata"),o=e.find('tw-passagedata[name="hal.tracks"]'),n=null;if(o.length){var i=o.text().split(/[\n\r]/),r=new Map(i.map(function(t){var e=t.split(":"),o=e[0].trim();return[o=(o=(o=o.replace(/^["']/,"")).replace(/["']$/,"")).trim(),e[1].split(",").map(function(t){return t=(t=(t=(t=t.trim()).replace(/^["']/,"")).replace(/["']$/,"")).trim()})]}));console.log(r),n=r}window.Chapel=window.Chapel||{},window.Chapel.Get={isHarlowe3OrLater:3<=(t=e.attr("format-version").split(".")[0],t=Number(t),Number.isNaN(t)&&(t=3),t<1&&(t=3),t),storyTitle:e.attr("name"),IFID:e.attr("ifid"),fromPassage:n},options.storagekey=options.storagekey+"-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}";var a=require("macros");window.Chapel=window.Chapel||{},window.Chapel.Macros={add:function(e){e&&"object"==typeof e&&Object.keys(e).forEach(function(t){!function(t,o){a.add(t,function(){var t=[].slice.call(arguments).slice(1),e=o.apply(null,t);return"string"==typeof e||"boolean"==typeof e||"number"==typeof e?e:""},a.TypeSignature.zeroOrMore(a.TypeSignature.Any))}(t,e[t])})}}}(),function(){"use strict";var e=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),t=function(t,e){if(window.localStorage)try{t=""+t,"string"!=typeof e&&(e=JSON.stringify(e)),window.localStorage.setItem(t,e)}catch(t){console.error(t)}},o=function(t){if(window.localStorage)try{return t=""+t,window.localStorage.getItem(t)}catch(t){console.error(t)}},n=function(t){if(window.localStorage)try{t=""+t,window.localStorage.removeItem(t)}catch(t){console.error(t)}};function i(e,o){if("object"!=typeof e||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(t){if(void 0!==e[t])throw new Error('Invalid extension: cannot clobber existing property "'+t+'"');e[t]=o[t]})}var r={loaded:[],classes:{},master:{volume:options.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(t){r.master.mute=!!t,$(document).trigger({type:":master-mute",mute:!!t})},isMuted:function(){return!!r.master.mute},volume:function(t){t=Number(t),Number.isNaN(t)||(1this.unwrap.duration&&(t=this.unwrap.duration),this.unwrap.currentTime=t,this},fadeIn:function(t){var e=this;t=t||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*r.master.volume},1e3*t,function(){e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeOut:function(t){t=t||1;var e=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*t,function(){e.stop(),e.volume(o),c.emit(":volume",e),c.emit(":fade",e)}),this},fadeTo:function(t,e){var o=this;if(t=t||1,e=Number(e),!Number.isNaN(e))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(t,e){return e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),a.track.includes(t)?void this.$el.one(t,e):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}};var l=a.track.concat(a.master);function p(t,e){if(!(this instanceof p))return new p(t,e);this.id=t,this.tracks=e.map(function(t){return c.get(t)}),this.looping=!1,this.current="",this.playing=!1}r.on=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).on(t,e):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},r.one=function(t,e){e&&"function"==typeof e?(":"!==(t=t.trim().toLowerCase())[0]&&(t=":"+t),l.includes(t)?$(document).one(t,e):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},r.on(":master-mute",c.renew),r.on(":master-volume",c.renew),options.persistPrefs&&(r.on(":master-mute",r.savePrefs),r.on(":master-volume",r.savePrefs)),r.on(":play",function(t){t.track.addToGroup("playing")}),r.on(":stop",function(t){t.track.removeFromGroup("playing")}),options.muteOnBlur&&$(window).on("blur",function(){r.isMuted()||(r.mute(!0),$(window).one("focus",function(){r.mute(!1)}))}),r.classes.Track=c,r.newTrack=c.add,r.track=c.get,r.createGroup=function(t,e,o){!function(t,e){e&&Array.isArray(e)||(e=[]),r.groups.custom[t]=e.map(function(t){return c.get(t)})}(t,o?[].slice.call(arguments).slice(1):e)},r.group=function(t){if(!(this instanceof r.group))return new r.group(t);Object.keys(r.groups.custom).includes(t)?this.members=r.groups.custom[t]:this.members=r.groups[t],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+t+'"!'))},r.group.is=function(t){return this instanceof r.group},r.group.runOnAll=function(t,e,o){t.members.forEach(function(t){t[e].apply(t,o&&Array.isArray(o)?o:[])})},r.group.extend=function(t){i(r.group,t)},r.group.extendPrototype=function(t){i(r.group.prototype,t)},r.group.prototype={constructor:r.group,run:function(t,e,o){null!=o&&(e=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(t)&&r.group.runOnAll(this,t,e)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(t){return this.run("mute",[t]),this},volume:function(t){return this.run("volume",[t]),this},loop:function(t){return this.run("loop",[t]),this}},p.list={},p.is=function(t){return t instanceof p},p.add=function(t,e,o){return o&&(e=[].slice.call(arguments).slice(1)),p.list[t]=new p(t,e),p.list[t]},p.extend=function(t){i(p,t)},p.extendPrototype=function(t){i(p.prototype,t)},p.prototype={constructor:p,clone:function(){return new p(this.id,this.tracks.map(function(t){return t.id}))},shuffle:function(){var t,e,o,n=this.tracks;for(o=n.length-1;0=e.tracks.length&&e.looping)t=0;else if(t>=e.tracks.length)return e.current="",void(e.playing=!1);var o=e.tracks[t],n=o.isLooping();return o.loop(!1),o.play(),e.playing=!0,setTimeout(function(){o.isPlaying()||(e.playing=!1)},20),e.current=o.id,o.$el.one("ended.playlist",function(){t++,o.loop(n),e.play(t)}),e},loop:function(t){return this.looping=!!t,this},stop:function(){var t=c.get(this.current);return t.stop(),t.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}},r.classes.Playlist=p,r.createPlaylist=p.add,r.playlist=function(t){return p.list[t]||null},r.extend=function(t){i(r,t)},r.extendTrack=c.extend,r.extendTrackProto=c.extendPrototype,r.extendGroup=r.group.extend,r.extendGroupProto=r.group.extendPrototype,r.extendPlaylist=p.extend,r.extendPlaylistProto=p.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=r,options.persistPrefs&&r.loadPrefs()}(),function(){"use strict";if(options.controls.show){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),e=$(document.createElement("span")).attr("id","vol-title").append("Volume");options.controls.volumeDisplay||e.css("display","none");var o=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),n=Math.trunc(100*window.Chapel.Audio.master.volume);n<0?n=0:100").on("click",function(t){t.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&r.addClass("muted");var a=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(t){t.preventDefault(),s.toggleClass("closed")}),s=$(document.createElement("div")).attr("id","audio-controls").append(t,e,o,r,a).appendTo(document.body);options.controls.startClosed&&s.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:s,$volume:o,$mute:r,$user:t,close:function(){s.addClass("closed")},open:function(){s.removeClass("closed")},toggle:function(){s.toggleClass("closed")},hide:function(){s.css("display","none")},show:function(){s.css("display","block")},updateVolume:i}}}(),function(t){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var e=State,t=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){t.css("display","block").append('
')}function s(){t.fadeOut(function(){t.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:s,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=t,window.Chapel.Audio.preload=function(){if(!(e.pastLength||e.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var i=100+options.loadDelay,t=Chapel.Audio.classes.Track.list,r=Chapel.Audio.loaded;if(t.length){var a=t.map(function(t){return t.id});options.forceDismiss&&setTimeout(function(){s()},options.loadLimit.total),function t(){if(a.length){var e=a.shift();if(r.includes(e))t();else{var o=Chapel.Audio.classes.Track.get(e);if(o.unwrap.readyState<2){var n=!1;o.$el.one("canplay",function(){t(),n=!0}),setTimeout(function(){n||(o.$el.off("canplay"),t())},options.loadLimit.track)}else r.includes(e)||r.push(e),t()}}else setTimeout(s,i)}()}else setTimeout(s,i)}}}(),function(){"use strict";var t,e,o=options.storagekey+"_hal_restart_",n=(window.sessionStorage?(t=function(t,e){window.sessionStorage.setItem(o+t,e)},e=function(t){return window.sessionStorage.getItem(o+t)}):(t=function(){},e=function(){},console.warn("Session storage is unavailable...")),{save:t,load:e});window.Chapel.Audio.state={_store:n,saveTracks:function(){var t;try{t=Chapel.Audio.classes.Track.list.map(function(t){return{id:t.id,sources:t.sources}}),t=JSON.stringify(t),n.save("tracks",t)}catch(t){console.error(t.message)}},loadTracks:function(){var t;try{t=(t=n.load("tracks"))&&JSON.parse(t),Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.sources&&!Chapel.Audio.classes.Track.has(t.id)?Chapel.Audio.newTrack.apply(null,[t.id].concat(t.sources)):console.warn("Track reload failed...")})}catch(t){console.error(t.message)}},savePlaylists:function(){var t;try{var o=Chapel.Audio.classes.Playlist.list;t=Object.keys(o).map(function(t){var e={};return e.tracks=o[t].tracks.map(function(t){return t.id}),e.id=o[t].id,e}),t=JSON.stringify(t),n.save("playlists",t)}catch(t){console.error(t.message)}},loadPlaylists:function(){var t;try{(t=(t=n.load("playlists"))&&JSON.parse(t))&&Array.isArray(t)&&t.length&&t.forEach(function(t){t.id&&t.tracks&&Chapel.Audio.createPlaylist(t.id,t.tracks)})}catch(t){console.error(t.message)}},saveGroups:function(){var e;try{e={},Object.keys(Chapel.Audio.groups.custom).forEach(function(t){e[t]=Chapel.Audio.groups.custom[t].map(function(t){return"string"==typeof t?t:t.id})}),e=JSON.stringify(e),n.save("groups",e)}catch(t){console.error(t.message)}},loadGroups:function(){var e;try{(e=(e=n.load("groups"))&&JSON.parse(e))&&"object"==typeof e&&(Object.keys(e).forEach(function(t){e[t].map(function(t){return Chapel.Audio.classes.Track.get(t)})}),Chapel.Audio.groups.custom=e)}catch(t){console.error(t.message)}}}}(),function(){"use strict";options.globalA&&void 0===window.A&&(window.A=window.Chapel.Audio),Chapel.Get.fromPassage&&Chapel.Get.fromPassage.forEach(function(t,e){Chapel.Audio.newTrack.apply(null,[e].concat(t))}),$(document).on("unload",function(){window.Chapel.Audio.savePrefs()}),Chapel.Audio.classes.Track.renew(),Chapel.Audio.controls&&Chapel.Audio.controls.updateVolume(),Chapel.Get.isHarlowe3OrLater&&($(window).on("unload",function(){Chapel.Audio.state.saveTracks(),Chapel.Audio.state.savePlaylists(),Chapel.Audio.state.saveGroups()}),Chapel.Audio.state.loadTracks(),Chapel.Audio.state.loadPlaylists(),Chapel.Audio.state.loadGroups())}(),function(){"use strict";if(options.controls.show){var s=Engine,u=Chapel.Audio.controls.$user,t=function(){return"none"!==u.css("display")},c=function(){return t()||u.css("display","block"),u},e=function(){return t()&&u.css("display","none"),u};Chapel.Audio.menu={hide:e,show:c,isShown:t,links:{add:function(t,e,o){var n,i;if(!t||"string"!=typeof t){var r="undefined";return alert(r),void console.error(r)}o||"function"!=typeof e?(e&&"string"==typeof e&&(n=e),o&&"function"==typeof o&&(i=o)):(i=e,n=null);var a=$(document.createElement("tw-link")).append(t).attr({tabindex:"0",name:t.toLowerCase().trim()}).on("click",function(){n&&s.goToPassage(n),i&&i()}).addClass("story-menu").appendTo(u);return c(),a},clear:function(){return u.empty(),e()},hide:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').addClass("hide")},show:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').removeClass("hide")},toggle:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').toggleClass("hide")},remove:function(t){t=t.toLowerCase().trim(),$('tw-link.story-menu[name="'+t+'"]').remove()}}}}}(),function(){"use strict";var n=Chapel.Audio,t={newtrack:function(){var t=[].slice.call(arguments);try{return n.newTrack.apply(null,t)}catch(t){alert("Error in the (newtrack:) macro: "+t.message)}},newplaylist:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createPlaylist(t,e)}catch(t){alert("Error in the (newplaylist:) macro: "+t.message)}},newgroup:function(t,e){e=[].slice.call(arguments).slice(1);try{return n.createGroup(t,e)}catch(t){alert("Error in the (newgroup:) macro: "+t.message)}},track:function(t,e){try{var o=n.track(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (track:) macro: "+t.message)}},playlist:function(t,e){try{var o=n.playlist(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (playlist:) macro: "+t.message)}},group:function(t,e){try{var o=n.group(t);return o[e].apply(o,[].slice.call(arguments).slice(2))}catch(t){alert("Error in the (group:) macro: "+t.message)}}};window.Chapel.Macros.add(t)}(),function(){"use strict";if(options.includeFixes){var e=window.prompt;window.prompt=function(){var t=e.apply(null,[].slice.call(arguments));return function(t){var e=(new DOMParser).parseFromString(t,"text/html");return Array.from(e.body.childNodes).some(function(t){return 1===t.nodeType})}(t)?t.replace(/[<>]/g,""):t}}}(); + window.Chapel=window.Chapel||{},window.Chapel.options={preload:!0,loadDelay:0,muteOnBlur:!0,startingVol:.5,persistPrefs:!0,globalA:!0,showControls:!0,sidebarStartClosed:!0,volumeDisplay:!0,trackLoadLimit:500,totalLoadLimit:8e3},function(){"use strict";var t=$("tw-storydata"),e=t.find('tw-passagedata[name="hal.tracks"]'),o=null;if(e.length){var n=e.text().split(/[\n\r]/).filter(function(e){return e&&e.trim()&&e.includes(":")});o=new Map(n.map(function(e){var t=e.split(":"),o=t[0].trim();return[o=(o=(o=o.replace(/^["']/,"")).replace(/["']$/,"")).trim(),t[1].split(",").map(function(e){return e=(e=(e=(e=e.trim()).replace(/^["']/,"")).replace(/["']$/,"")).trim()})]}))}var r=t.find('tw-passagedata[name="hal.config"]'),i=null;if(r.length){var a=r.text().split(/[\n\r]/).filter(function(e){return e&&e.trim()&&e.includes(":")}),s={};a.map(function(e){var t=e.split(":"),o=t[0].trim();o=(o=(o=o.replace(/^["']/,"")).replace(/["']$/,"")).trim();var n=t[1].trim();return[o,n=(n=(n=n.replace(/^["']/,"")).replace(/["']$/,"")).trim().toLowerCase()]}).forEach(function(e){s[e[0]]=e[1]}),i=s}function u(){var e=t.attr("format-version").split(".")[0];return e=Number(e),Number.isNaN(e)&&(e=3),e<1&&(e=3),e}if(i&&Object.keys(i).forEach(function(e){var t=e,o=s[e],n=typeof Chapel.options[e];"boolean"==n?"true"===o?Chapel.options[t]=!0:"false"===o&&(Chapel.options[t]=!1):"number"==n?(o=Number(o),Number.isNaN(o)||(Chapel.options[t]=o)):"string"==n&&o&&(Chapel.options[t]=o)}),window.Chapel=window.Chapel||{},window.Chapel.Get={version:u(),isHarlowe3OrLater:3<=u(),storyTitle:t.attr("name"),IFID:t.attr("ifid"),fromPassage:o},Chapel.Get.version<2)throw new Error("The Harlowe Audio Library is only designed to work with Harlowe 2 and 3; you appear to be using Harlowe 1 or an otherwise invalid story format.");Chapel.options.storagekey="%%hal-"+Chapel.Get.IFID+"-{"+Chapel.Get.storyTitle+"}";var l=require("macros");window.Chapel.Macros={add:function(t){t&&"object"==typeof t&&Object.keys(t).forEach(function(e){!function(e,o){l.add(e,function(){var e=[].slice.call(arguments).slice(1),t=o.apply(null,e);return"string"==typeof t||"boolean"==typeof t||"number"==typeof t?t:""},l.TypeSignature.zeroOrMore(l.TypeSignature.Any))}(e,t[e])})}}}(),function(){"use strict";var r=Chapel.options,t=$(document.createElement("div")).attr("id","audio-container").css("display","none").appendTo(document.body),e=function(e,t){if(window.localStorage)try{e=""+e,"string"!=typeof t&&(t=JSON.stringify(t)),window.localStorage.setItem(e,t)}catch(e){console.error(e)}},o=function(e){if(window.localStorage)try{return e=""+e,window.localStorage.getItem(e)}catch(e){console.error(e)}},n=function(e){if(window.localStorage)try{e=""+e,window.localStorage.removeItem(e)}catch(e){console.error(e)}};function i(t,o){if("object"!=typeof t||"object"!=typeof o)throw new Error("Invalid extension.");Object.keys(o).forEach(function(e){if(void 0!==t[e])throw new Error('Invalid extension: cannot clobber existing property "'+e+'"');t[e]=o[e]})}var a={loaded:[],classes:{},master:{volume:r.startingVol,mute:!1},groups:{playing:[],looping:[],custom:{}},mute:function(e){a.master.mute=!!e,$(document).trigger({type:":master-mute",mute:!!e})},isMuted:function(){return!!a.master.mute},volume:function(e){e=Number(e),Number.isNaN(e)||(1this.unwrap.duration&&(e=this.unwrap.duration),this.unwrap.currentTime=e,this},fadeIn:function(e){var t=this;e=e||1;var o=this.getVolume();return this.volume(0),this.play(),this.$el.animate({volume:o*a.master.volume},1e3*e,function(){t.volume(o),c.emit(":volume",t),c.emit(":fade",t)}),this},fadeOut:function(e){e=e||1;var t=this,o=this.getVolume();return this.$el.animate({volume:0},1e3*e,function(){t.stop(),t.volume(o),c.emit(":volume",t),c.emit(":fade",t)}),this},fadeTo:function(e,t){var o=this;if(e=e||1,t=Number(t),!Number.isNaN(t))return 1.on() -> invalid event type"),this)):(console.error(".on() -> invalid callback"),this)},one:function(e,t){return t&&"function"==typeof t?(":"!==(e=e.trim().toLowerCase())[0]&&(e=":"+e),s.track.includes(e)?void this.$el.one(e,t):(console.error(".one() -> invalid event type"),this)):(console.error(".one() -> invalid callback"),this)}});var p=s.track.concat(s.master);function d(e,t){if(!(this instanceof d))return new d(e,t);this.id=e,this.tracks=t.map(function(e){return c.get(e)}),this.looping=!1,this.current="",this.playing=!1}a.on=function(e,t){t&&"function"==typeof t?(":"!==(e=e.trim().toLowerCase())[0]&&(e=":"+e),p.includes(e)?$(document).on(e,t):console.error("Chapel.Audio.on() -> invalid event type")):console.error("Chapel.Audio.on() -> invalid callback")},a.one=function(e,t){t&&"function"==typeof t?(":"!==(e=e.trim().toLowerCase())[0]&&(e=":"+e),p.includes(e)?$(document).one(e,t):console.error("Chapel.Audio.one() -> invalid event type")):console.error("Chapel.Audio.one() -> invalid callback")},a.on(":master-mute",c.renew),a.on(":master-volume",c.renew),r.persistPrefs&&(a.on(":master-mute",a.savePrefs),a.on(":master-volume",a.savePrefs)),a.on(":play",function(e){e.track.addToGroup("playing")}),a.on(":stop",function(e){e.track.removeFromGroup("playing")}),r.muteOnBlur&&$(window).on("blur",function(){a.isMuted()||(a.mute(!0),$(window).one("focus",function(){a.mute(!1)}))}),a.classes.Track=c,a.newTrack=function(){try{return c.add.apply(null,arguments)}catch(e){console.error(e.message),alert("Error in A.newTrack() -> see the console for more information.")}},a.track=function(e){try{return c.get(e)}catch(e){console.error(e.message),alert("Error in A.track() -> see the console for more information.")}},a.createGroup=function(e,t,o){!function(e,t){t&&Array.isArray(t)||(t=[]),a.groups.custom[e]=t.map(function(e){return c.get(e)})}(e,o?[].slice.call(arguments).slice(1):t)},a.group=function(e){if(!(this instanceof a.group))return new a.group(e);Object.keys(a.groups.custom).includes(e)?this.members=a.groups.custom[e]:this.members=a.groups[e],Array.isArray(this.members)||(this.members=[],console.error('Could not find members for track group "'+e+'"!'))},Object.assign(a.group,{is:function(e){return this instanceof a.group},runOnAll:function(e,t,o){e.members.forEach(function(e){e[t].apply(e,o&&Array.isArray(o)?o:[])})},extend:function(e){i(a.group,e)},extendPrototype:function(e){i(a.group.prototype,e)}}),Object.assign(a.group.prototype,{constructor:a.group,run:function(e,t,o){null!=o&&(t=[].slice.call(arguments).slice(1)),c.prototype.hasOwnProperty(e)&&a.group.runOnAll(this,e,t)},play:function(){return this.run("play"),this},pause:function(){return this.run("pause"),this},stop:function(){return this.run("stop"),this},mute:function(e){return this.run("mute",[e]),this},volume:function(e){return this.run("volume",[e]),this},loop:function(e){return this.run("loop",[e]),this}}),Object.assign(d,{list:{},is:function(e){return e instanceof d},add:function(e,t,o){return o&&(t=[].slice.call(arguments).slice(1)),d.list[e]=new d(e,t),d.list[e]},extend:function(e){i(d,e)},extendPrototype:function(e){i(d.prototype,e)}}),Object.assign(d.prototype,{constructor:d,clone:function(){return new d(this.id,this.tracks.map(function(e){return e.id}))},shuffle:function(){var e,t,o,n=this.tracks;for(o=n.length-1;0=t.tracks.length&&t.looping)e=0;else if(e>=t.tracks.length)return t.current="",void(t.playing=!1);var o=t.tracks[e],n=o.isLooping();return o.loop(!1),o.play(),t.playing=!0,setTimeout(function(){o.isPlaying()||(t.playing=!1)},20),t.current=o.id,o.$el.one("ended.playlist",function(){e++,o.loop(n),t.play(e)}),t},loop:function(e){return this.looping=!!e,this},isLooping:function(){return this.looping},stop:function(){var e=c.get(this.current);return e.stop(),e.$el.off(".playlist"),this.current="",this.playing=!1,this},pause:function(){return c.get(this.current).pause(),this.playing=!1,this}}),a.classes.Playlist=d,a.createPlaylist=function(){try{d.add.apply(null,arguments)}catch(e){console.error(e.message),alert("Error in A.createPlaylist() -> see the console for more information.")}},a.playlist=function(e){try{var t=d.list[e]||null;if(!t)throw new Error('Playlist "'+e+'" does not exist.');return t}catch(e){console.error(e.message),alert("Error in A.createPlaylist() -> see the console for more information.")}},a.extend=function(e){i(a,e)},a.extendTrack=c.extend,a.extendTrackProto=c.extendPrototype,a.extendGroup=a.group.extend,a.extendGroupProto=a.group.extendPrototype,a.extendPlaylist=d.extend,a.extendPlaylistProto=d.extendPrototype,window.Chapel=window.Chapel||{},window.Chapel.Audio=a}(),function(){"use strict";var e=Chapel.options;if(e.showControls){var t=$(document.createElement("div")).attr("id","story-menu").css("display","none"),o=$(document.createElement("span")).attr("id","vol-title").append("Volume");e.volumeDisplay||o.css("display","none");var n=$(document.createElement("input")).attr({id:"audio-volume",type:"range",min:1,max:99,step:1,title:"Volume"}),r=Math.trunc(100*window.Chapel.Audio.master.volume);r<0?r=0:100").on("click",function(e){e.preventDefault(),$(this).toggleClass("muted"),Chapel.Audio.mute(!Chapel.Audio.isMuted())});Chapel.Audio.isMuted()&&a.addClass("muted");var s=$(document.createElement("tw-link")).attr("id","audio-panel-toggle").on("click",function(e){e.preventDefault(),u.toggleClass("closed")}),u=$(document.createElement("div")).attr("id","audio-controls").append(t,o,n,a,s).appendTo(document.body);e.sidebarStartClosed&&u.addClass("closed"),window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.controls={$panel:u,$volume:n,$mute:a,$user:t,close:function(){u.addClass("closed")},open:function(){u.removeClass("closed")},toggle:function(){u.toggleClass("closed")},hide:function(){u.css("display","none")},show:function(){u.css("display","block")},updateVolume:i}}}(),function(e){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(e)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(){"use strict";var s=Chapel.options,t=State,e=$(document.createElement("div")).attr("id","audio-overlay").css("display","none").appendTo(document.body);function o(){e.css("display","block").append('
')}function u(){e.fadeOut(function(){e.empty()})}window.Chapel=window.Chapel||{},window.Chapel.Audio=window.Chapel.Audio||{},window.Chapel.Audio.loadScreen={show:o,dismiss:u,kill:function(){$("#audio-overlay").remove()}},window.Chapel.Audio.$overlay=e,window.Chapel.Audio.preload=function(){if(!(t.pastLength||t.futureLength||$.browser.mobile)){$(document).ready(function(){o()});var r=100+s.loadDelay,e=Chapel.Audio.classes.Track.list,i=Chapel.Audio.loaded;if(e.length){var a=e.map(function(e){return e.id});0 [!WARNING] -> If you're using **Harlowe v3.x (or later)**, it is strongly recommended that you define tracks, groups, playlists, and menu links in your Story JavaScript area (or equivalent for your compiler) rather than in `startup`-tagged passages. - -If you just need some audio and don't need anything too complex, start at [Installation](#installation) and then read some of the [examples](#detailed-examples) to see if what you want to do is covered. If you want anything more complex than that, or if you have grand, complex ideas, you'll need to read the whole thing. +[Try the Demo](./demo/ ':ignore') ## Introduction -This is an audio library designed for the [Twine 2](https://twinery.org/) story format [Harlowe (v2.1.0 or later)](https://twine2.neocities.org/). It is the successor [to howler-for-harlowe](https://github.com/ChapelR/howler-for-harlowe) which will likely not be seeing any further updates. +This is an audio library designed for the [Twine 2](https://twinery.org/) story format [Harlowe (v2.1.0 or later)](https://twine2.neocities.org/). This is version 2 of HAL, documentation and downloads for previous version of HAL remain available for those who need them. -**Why Not Keep Working on Howler-for-Harlowe?** +**Why a Second Major Version** -I wanted to, but there were a few things about HowlerJS that made it clash with Harlowe--this typically reared its head as the unfixable history system bug, where Howler just did not give a crap about Harlowe's history system, and they tripped over each other. On top of that, there were a number of features in Howler that I just don't imagine most Twine authors need, and we can put those KB to better use. Overall, this library 9KB, less functional, but more streamlined. - -Howler-for-Harlowe will continue to exist, but I don't intend to fix it other than major bugs or security issues. +HAL v2 fixes some of the issues of HAL v1, but the biggest change is the addition of [macros](#macros) and [special passages](#special-passages) to create a more user-friendly coding experience. JavaScript APIs are largely still available and similar to their v1 counterparts, though many have changed, so you'll want to review the documentation even if you're familiar with HAL v1 and planning on sticking to JavaScript. **What Else Should I Know?** -This library uses the [HTMLMediaElement API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) and [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises). That means Internet Explorer is out (I'm not sure if Harlowe actually supports IE). Other than that, all major modern browsers should work fine, though the volume control may look odd in certain browsers. +This library uses [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises). That means Internet Explorer versions lower than 11 are out. Other than that, all major modern browsers should work fine, though the volume control may look odd in certain browsers--this can be fixed with user-supplied CSS code if necessary. This library is free and dedicated to the public domain. That means you don't need to provide credit or attribution if you don't want to, and you can do anything you like with the code. [Review the license](https://github.com/ChapelR/harlowe-audio/blob/master/LICENSE) for any questions or concerns about that. @@ -38,13 +29,17 @@ The best place to ask for help if the issue is on your end is [the Twine Q&A](ht ## Installation -All you need to install this library is the code. Download the most recent version of HAL here, on the [releases page](https://github.com/ChapelR/harlowe-audio/releases). If you download the code, be sure to open it and mess with it in a text editor, *not* a word processor. Once you've got the code, you'll need to put in in your project. +All you need to install this library is the code. Download the most recent version of HAL here, on the [releases page](https://github.com/ChapelR/harlowe-audio/releases). If decide to edit the code or need to pen it to copy and paste it into your IDE, be sure to open it and mess with it in a text editor, *not* a word processor. How you add this code to your project will depend on the compiler and IDE you use. **In Twine 2 (online or standalone)**, copy and paste the code in `harlowe-audio.min.js` into your [Story JavaScript area](https://twinery.org/wiki/twine2:adding_custom_javascript_and_css), and the code in `harlowe-audio.min.css` into your Story Stylesheet area. **In Tweego or Entwine/Grunt-Entwine**, include the files in your command-line options or in your source code directory as appropriate. Remember to watch your file order, and refer to your compiler's docs if you need help. -**In Twee2**, create a special passage with the `script` tag and place the JavaScript code in that passage, and do the same thing with the CSS and a `style` tag. Refer to its [docs](https://dan-q.github.io/twee2/documentation.html#twee2-syntax-special-passages) for more. +**In Twee2**, create a special passage with the `script` tag and place the JavaScript code in that passage, and do the same thing with the CSS and a `stylesheet` tag. Refer to its [docs](https://dan-q.github.io/twee2/documentation.html#twee2-syntax-special-passages) for more. + +**In Extwee**, you need to create special `script`- and `stylsheet`-tagged passages in your Twee code in much the same way as in Twee2. + +**In other compilers or IDEs**, you'll need to refer to your program's documentation for how to add JavaScript and CSS code. # Overview diff --git a/docs/changelog.md b/docs/changelog.md index 6095d8d..c4266db 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,4 +1,17 @@ -## v1.3.0 +## v2 Series Releases + +### v2.0.0 + +This version of HAL includes a vast array of usability improvements, including the introduction of special passages, audio macros, and a variety of other features. + +I took the opportunity to rewrite large swathes of HAL for this release, meaning some code written for previous versions of HAL may no longer work correctly, hence the major version bump. In addition, since the recommended way to use HAL has changed so dramatically, this version also required a massive documentation rewrite. + +- Added several macros that allow users to interact with HAL using TwineScript. The macros are: `(newtrack:)`, `(newplaylist:)`, `(newgroup:)`, `(track:)`, `(playlist:)`, and `(group:)`. +- Users may now optionally define their tracks in a special passage called `hal.tracks` using the format `trackName: source, source, etc...`; each track definition must go on its own line. + +## v1 Series Releases + +### v1.3.0 This version of HAL should address most issues related to Harlowe v3's refresh/reload persistence for tracks, playlists, and groups. The Menu API calls still need to be set up in Story JavaScript; there's simply too much that can go wrong there. @@ -7,7 +20,7 @@ This version of HAL should address most issues related to Harlowe v3's refresh/r - Updated documentation. - Other internal improvements and updates. -## v1.2.0 +### v1.2.0 This version adds event handler methods to the `track` prototype and to the root `A` object. @@ -16,17 +29,17 @@ This version adds event handler methods to the `track` prototype and to the root - Updated the docs with the new event methods and an event section. - Internal improvements. -## v1.1.2 +### v1.1.2 This patch addresses an issue where `options.controls.show` was throwing on false. The custom events emitted by HAL are now (somewhat) documented. -## v1.1.1 +### v1.1.1 This patch addresses a bug in the *track#fadeTo()* method. -## v1.1.0 +### v1.1.0 This version marks the first non-beta release version! Don't fear using this in production code anymore (or anymore than normal). @@ -36,7 +49,9 @@ This version marks the first non-beta release version! Don't fear using this in - Fixed mobile UI scaling in the demo, but unfortunately it can't be fixed from within the library. - Internal improvements. -## v1.0.1 +## Beta Releases + +### v1.0.1 - Fixed a bug with preloading in some browsers. - Fixed a bug in the `A.createGroup()` method. @@ -44,7 +59,7 @@ This version marks the first non-beta release version! Don't fear using this in - Minor internal improvements. - Now has a demo. -## v1.0.0 +### v1.0.0 - Added the extensions API. - Added complete JavaScript API reference (still messy). diff --git a/docs/nav.md b/docs/nav.md index 191d29b..e010475 100644 --- a/docs/nav.md +++ b/docs/nav.md @@ -1,4 +1,4 @@ * [Docs](/) -* [API](API.md) +* [v1 Docs](v1.md) * [Updates](changelog.md) * [Demo](./demo/ ':ignore') \ No newline at end of file diff --git a/docs/v1.md b/docs/v1.md new file mode 100644 index 0000000..91673f8 --- /dev/null +++ b/docs/v1.md @@ -0,0 +1,1382 @@ +# Getting Started + +> [!DANGER] +> This documentation is for the v1 series of HAL. It is *highly* recommended that you use v2 of HAL and [refer to the v2 docs](/). HAL v1 will only recieve bug fixes and security updates going forward. + +> [!WARNING] +> If you're using **Harlowe v3.x (or later)**, it is strongly recommended that you define tracks, groups, playlists, and menu links in your Story JavaScript area (or equivalent for your compiler) rather than in `startup`-tagged passages. + +If you just need some audio and don't need anything too complex, start at [Installation](#installation) and then read some of the [examples](#detailed-examples) to see if what you want to do is covered. If you want anything more complex than that, or if you have grand, complex ideas, you'll need to read the whole thing. + +## Introduction + +This is an audio library designed for the [Twine 2](https://twinery.org/) story format [Harlowe (v2.1.0 or later)](https://twine2.neocities.org/). It is the successor [to howler-for-harlowe](https://github.com/ChapelR/howler-for-harlowe) which will likely not be seeing any further updates. + +**Why Not Keep Working on Howler-for-Harlowe?** + +I wanted to, but there were a few things about HowlerJS that made it clash with Harlowe--this typically reared its head as the unfixable history system bug, where Howler just did not give a crap about Harlowe's history system, and they tripped over each other. On top of that, there were a number of features in Howler that I just don't imagine most Twine authors need, and we can put those KB to better use. Overall, this library 9KB, less functional, but more streamlined. + +Howler-for-Harlowe will continue to exist, but I don't intend to fix it other than major bugs or security issues. + +**What Else Should I Know?** + +This library uses the [HTMLMediaElement API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) and [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises). That means Internet Explorer is out (I'm not sure if Harlowe actually supports IE). Other than that, all major modern browsers should work fine, though the volume control may look odd in certain browsers. + +This library is free and dedicated to the public domain. That means you don't need to provide credit or attribution if you don't want to, and you can do anything you like with the code. [Review the license](https://github.com/ChapelR/harlowe-audio/blob/master/LICENSE) for any questions or concerns about that. + +**I'm Having Trouble** + +The best place to ask for help if the issue is on your end is [the Twine Q&A](https://twinery.org/questions/), [the Subreddit](https://www.reddit.com/r/twinegames/), or [the Discord server](https://discordapp.com/invite/n5dJvPp). If you suspect that the problem is in the code or otherwise on my end, [open an issue](https://github.com/ChapelR/harlowe-audio/issues/new). + +**Donate** + +> [!NOTE] +> I suggest donating to [Twine development](https://www.patreon.com/klembot) if you really want to help out, but I'd welcome a few dollars if you feel like it. + +[![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/F1F8IC35) + +## Installation + +All you need to install this library is the code. Download the most recent version of HAL here, on the [releases page](https://github.com/ChapelR/harlowe-audio/releases). If you download the code, be sure to open it and mess with it in a text editor, *not* a word processor. Once you've got the code, you'll need to put in in your project. + +**In Twine 2 (online or standalone)**, copy and paste the code in `harlowe-audio.min.js` into your [Story JavaScript area](https://twinery.org/wiki/twine2:adding_custom_javascript_and_css), and the code in `harlowe-audio.min.css` into your Story Stylesheet area. + +**In Tweego or Entwine/Grunt-Entwine**, include the files in your command-line options or in your source code directory as appropriate. Remember to watch your file order, and refer to your compiler's docs if you need help. + +**In Twee2**, create a special passage with the `script` tag and place the JavaScript code in that passage, and do the same thing with the CSS and a `style` tag. Refer to its [docs](https://dan-q.github.io/twee2/documentation.html#twee2-syntax-special-passages) for more. + +# Overview + +The following sections will describe what exactly you can do with this library and how. + +## Configuration + +If you take a look at the code of `harlowe-audio.min.js`, you'll see the `options` object near the top of the script, which you can use to configure the library to meet your needs. It looks like this: + +```javascript +var options = { + preload : true, + loadDelay : 0, + muteOnBlur : true, + startingVol : 0.5, + storagekey : '%%tw-audio', + persistPrefs : true, + globalA : true, + includeFixes : false, + controls : { + show : true, + startClosed : true, + volumeDisplay : true + }, + loadLimit : { + track : 500, + total : 8000 + } +}; +``` + +--- + +- **the `preload` option** + +Set this option to `true` or `false`. + +Controls whether audio should be preloaded when the game is started, or if audio should only be loaded as needed. In most cases, you'll want to preload it. You can optionally include a loading screen by placing the `A.preload()` method beneath your track definitions. + +--- + +- **the `loadDelay` option** + +Set this option to a number representing an amount of milliseconds. + +Causes the loading screen to hang for a configurable number of milliseconds after loading completes. Use this to fine tune for edge cases, or work around [FOUC issues](https://en.wikipedia.org/wiki/Flash_of_unstyled_content). + +--- + +- **the `muteOnBlur` option** + +Set this option to `true` or `false`. + +Setting this option to `true` causes the sound in your game to mute when the user is in another tab or window. + +--- + +- **the `startingVol` option** + +Set this option to a number between `0` and `1`. + +Sets the default starting master volume. + +--- + +- **the `storageKey` option** + +Set this option to any string. + +This library saves some data to local storage. You can use this option to change the key it uses. + +--- + +- **the `persistPrefs` option** + +Set this option to `true` or `false`. + +The master volume and mute are considered user preferences, in that they are intended for users to adjust. You can use this option to cause changes to these settings to be saved and restored from local storage (using the above key). For example, if the user sets the master volume to `0.3`, then closes the game, the master volume will be `0.3` rather than the `startingVol` next time they play the game. + +--- + +- **the `globalA` option** + +Set this option to `true` or `false`. + +By default, and throughout this guide, the API is sent to the global scope as `A`, matching howler-for-harlowe's way of doing things. If you don't want to have that, or if the `window.A` name is already taken, you can find everything at `Chapel.Audio` instead. + +--- + +- **the `includeFixes` option** + +Set this option to `true` or `false`. + +Experimental feature. I'm going to try to clean up a few issues in Harlowe that I find, mostly boring things you'll never notice. Doesn't really belong in an audio library, so if I build a significant amount I will spin this into it's own thing. For now the code doesn't really add too much weight. + +--- + +- **the `controls.show` option** + +Set this option to `true` or `false`. + +This library includes a control panel for users to control the master volume and mute state. You can determine whether or not you want to use it or roll your own (or not include options, you Philistine). + +--- + +- **the `controls.startClosed` option** + +Set this option to `true` or `false`. + +The control panel slides in and out from the side of the screen. Use this option if you want to start it closed (taking up very little space). + +--- + +- **the `controls.volumeDisplay` option** + +Set this option to `true` or `false`. + +Determines whether a text readout of the current volume is displayed next to the volume control. + +--- + +- **the `loadLimit.track` option** + +Set this option to number of milliseconds. + +Use this option to set a tolerance (in MS) for how long the preloading function will wait attempting to load a single track before moving on. In combination with the below option, this setting should be used to make sure the game doesn't just hang forever on slower connections. + +--- + +- **the `loadLimit.total` option** + +Set this option to number of milliseconds. + +Use this option to set a tolerance (in MS) for how long the preloading function will wait attempting to load all of the tracks before dismissing the load screen. In combination with the above option, this setting should be used to make sure the game doesn't just hang forever on slower connections. + +--- + +## Tracks + +Tracks are the meat and potatoes of this library: everything you do is with tracks. Setting up your tracks should be done early, and you have a few options: ~~use [a `startup`-tagged passage](https://twine2.neocities.org/#passagetag_startup) and ` +``` + +The *sources* of your audio files are urls or paths to the audio resources. These can be relative paths or absolute paths, depending on your needs. + +You should try to provide multiple sources to each sound: they'll be the same sound, just in different file formats, to help improve browser support. For example, most browsers can play `.mp3` files just fine, but Chromium users can't without a special plug-in they may not have. But they can play `.ogg` files, but Safari doesn't support `.ogg`... + +```javascript +A.newTrack('theme', 'audio/theme.mp3', 'audio/theme.ogg'); +``` + +If you pass multiple files, only one will be loaded, but the library will look for the first one on the list the browser can use. + +You can pass these sources as an array instead of discreet arguments if you prefer: + +```javascript +A.newTrack('theme', ['audio/theme.mp3', 'audio/theme.ogg']); +``` + +Either way works fine. + +If you're loading a lot of audio, consider including the `A.preload()` method below it, to show a loading screen while that's happening. + +``` +:: audio-init [startup] + +``` + +Once you have some tracks set up and named, you're ready to do things with them! + +## Track Methods + +You can use `A.track()` to access your track, and then use methods like `fadeIn()`, `volume()`, `play()`, and `stop()` to control the track. For instance: + +``` +(link: 'Start the Game')[ + + (goto: 'the beginning') +] +``` + +> [!TIP] +> Most browsers will not play any sound before the user interacts with the page. Once sound plays once, however, you can play sounds without worrying about it, so a setup like the one above is generally wise when you're playing the first sound in your game. There are a few methods that can sneak around this, though, with varying degrees of success. + +You'll also notice that we called two methods in a row: `loop()` and `play()`. This is called *method chaining*. You can't chain every method together, but the ones you can chain will be clearly marked. + +--- + +- **the `.play()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Attempts to start playback of the named track. + +--- + +- **the `.playWhenPossible()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Attempts to start playback of the named track as soon as a user interaction occurs. This track will piggyback off of any click to start the sound, and will "unlock" audio autoplay for the rest of your game. + +--- + +- **the `.pause()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Pauses playback of the track. + +--- + +- **the `.stop()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Stops the track (ends playback and resets its to the beginning). + +--- + +- **the `.loop(bool)` method** + +- Arguments: + - `bool`: (boolean) if `true` sets the track to loop, if `false` sets it to not loop. + +- Returns: the track (chainable). + +Sets the track to loop or stops it from looping. + +--- + +- **the `.toggleLoop()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Sets the track to loop if it isn't looping, or stops it from looping if it is looping. + +--- + +- **the `.seek(time)` method** + +- Arguments: + - `time`: (number) a time in seconds. + +- Returns: the track (chainable). + +Seeks to the indicated time in the track. + +--- + +- **the `.fadeIn(duration)` method** + +- Arguments: + - `duration`: (number) a time in seconds. + +- Returns: the track (chainable). + +Starts playback, fading the track in from `0` to its current volume over the provided number of seconds. + +--- + +- **the `.fadeOut(duration)` method** + +- Arguments: + - `duration`: (number) a time in seconds. + +- Returns: the track (chainable). + +Fades the track from its current volume to `0` over the provided number of seconds, then stops the track. + +--- + +- **the `.fadeTo(duration, level)` method** + +- Arguments: + - `duration`: (number) a time in seconds. + - `level`: (number) the target volume (between 0 and 1). + +- Returns: the track (chainable). + +Causes the track's volume to change from its current volume to the target volume `level` over the indicated number of seconds. + +--- + +- **the `.volume(level)` method** + +- Arguments: + - `level`: (number) the target volume (between 0 and 1). + +- Returns: the track (chainable). + +Changes the track's volume. + +--- + +- **the `.mute(bool)` method** + +- Arguments: + - `bool`: (boolen) if `true`, mutes the track; if `false`, unmutes it. + +- Returns: the track (chainable). + +Changes the track's mute state. + +--- + +- **the `.toggelMute()` method** + +- Arguments: none. + +- Returns: the track (chainable). + +Mutes the track if it's unmuted, unmutes it if it's muted. + +--- + +- **the `.isPlaying()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns whether or not the track is currently playing. Muted and inaudible tracks count as playing. + +--- + +- **the `.isMuted()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns whether or not the track is currently muted. + +--- + +- **the `.isLooping()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns whether or not the track is currently set to loop. + +--- + +- **the `.getVolume()` method** + +- Arguments: none. + +- Returns: number (between 0 and 1). + +Returns the track's current volume; this is the track's volume state and does not reflect any master volume adjustments. + +--- + +- **the `.on(type, callback)` method** + +- Arguments: + - `type`: a valid event type + - `callback`: a function to run as a handler for the indicated event + +- Returns: none. + +Sets up a recurring event handler. Read more [here](#events). + +--- + +- **the `.one(type, callback)` method** + +- Arguments: + - `type`: a valid event type + - `callback`: a function to run as a handler for the indicated event + +- Returns: none. + +Sets up a single-use event handler. Read more [here](#events). + +--- + +## Master Audio Methods + +The master audio methods are used for controlling *all* sound in the game at once. The master audio does not change you tracks, instead it overrides them. For example, if you have the `'theme'` track muted and the `'beep'` track unmuted, `A.mute(true)` will make `'beep'` silent. `A.mute(false)` will not make `'theme'` audible, however. None of these methods are chainable. + +--- + +- **the `A.mute(bool)` method** + +- Arguments: + - `bool`: (boolen) if `true`, mutes all audio; if `false`, unmutes all audio. + +- Returns: none. + +Controls the master mute and unmute state. + +--- + +- **the `A.volume(level)` method** + +- Arguments: + - `level`: (number) a volume level between `0` and `1`. + +- Returns: none. + +Adjusts the master volume level. + +--- + +- **the `A.stopAll()` method** + +- Arguments: none. + +- Returns: none. + +Stops every track that is currently playing. + +--- + +- **the `A.audioPlaying()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns `true` is any audio at all is playing. + +--- + +- **the `A.isMuted()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns the state of the master mute. + +--- + +- **the `A.getVolume()` method** + +- Arguments: none. + +- Returns: number (between 0 and 1). + +Returns the current level of the master volume. + +--- + +- **the `A.clearPrefs()` method** + +- Arguments: none. + +- Returns: none. + +Clears any user preferences that are saved in local storage. + +--- + +- **the `A.preload()` method** + +- Arguments: none. + +- Returns: none. + +Shows a loading screen while all previously defined tracks are cached by the browser. Can be used to completely or selectively preload audio before the game starts. + +--- + +- **the `A.on(type, callback)` method** + +- Arguments: + - `type`: a valid event type + - `callback`: a function to run as a handler for the indicated event + +- Returns: none. + +Sets up a recurring event handler. Read more [here](#events). + +--- + +- **the `A.one(type, callback)` method** + +- Arguments: + - `type`: a valid event type + - `callback`: a function to run as a handler for the indicated event + +- Returns: none. + +Sets up a single-use event handler. Read more [here](#events). + +--- + +## Groups + +Groups are ways to collect and organize tracks, but should *not* be confused with playlists (read on for those). These are designed to allow you to select and control a large number of tracks and do something to them. The methods used by groups are very similar to some of the track methods, but as said, generally do something to all of them at once. + +There are two built-in groups, `'playing'` and `'looping'`, that can be used to control all currently playing and looping tracks. Additionally, you can define your own groups with `A.createGroup()`: + +```javascript +A.createGroup('ui-sounds', 'beep', 'click', 'no'); +``` + +The above code will create a new group called `'ui-sounds'`, with the tracks `'beep'`, `'click'`, and `'no'` as its members. + +You can then act on those sounds with `A.group()`. For example, you might create a setting in your game that allows the user to mute all UI audio sounds like clicks and beeps by creating the group, then: + +``` +{ +(link: 'Mute UI Sounds')[ + +] +} +``` + +## Group Methods + +The methods you can use on groups are a smaller subset of the same ones you can use on tracks. All of these methods are chainable. + +--- + +- **the `.play()` method** + +- Arguments: none. + +- Returns: the group (chainable). + +Attempts to play every sound in the group. At once. Probably not useful, but included for completeness. + +--- + +- **the `.pause()` method** + +- Arguments: none. + +- Returns: the group (chainable). + +Pauses all the tracks in the group. + +--- + +- **the `.stop()` method** + +- Arguments: none. + +- Returns: the group (chainable). + +Stops all the sounds in the group. + +--- + +- **the `.mute(bool)` method** + +- Arguments: + - `bool`: (boolen) if `true`, mutes the tracks; if `false`, unmutes them. + +- Returns: the group (chainable). + +Mutes or unmutes every track in the group. + +--- + +- **the `.volume(level)` method** + +- Arguments: + - `level`: (number) a volume level between `0` and `1`. + +- Returns: the group (chainable). + +Adjusts all the volumes of all the tracks in the group. + +--- + +- **the `.loop(bool)` method** + +- Arguments: + - `bool`: (boolean) if `true`, sets all the tracks to loop; if `false`, stops them from looping. + +- Returns: the group (chainable). + +Set the tracks to loop or stop them from looping. + +--- + +## Playlists + +Playlists are another way to group tracks, but have some special properties. Namely, each playlist itself can be looped (so that they play their tracks in order, then start over again from the first track afterward), and they can be used to select and play random tracks. + +You create playlists with the `A.createPlaylist()` method. + +```javascript +A.createPlaylist('bgmusic', 'theme', 'cool-song', 'techno'); +``` + +The above script will create a playlist named `'bgmusic'`, with `'theme'`, `'cool-song'`, and `'techno'` as its tracks. + +You can access and work on the playlist with the `A.playlist()` method: + +```javascript +A.playlist('bgmusic').loop(true).shuffle().play(); +``` + +The above code would cause the `'bgmusic'` to shuffle its tracks, then play them through on a loop. + +## Playlist Methods + +Like groups, playlists receive a subset of track methods, along with a few methods of their own. Most of these methods are chainable. + +--- + +- **the `.play([start])` method** + +- Arguments: + - `start`: (optional) (number) the track to start at, list is 0-based + +- Returns: the playlist (chainable). + +Plays the playlist. Every track will be played in order, one after another. You can optionally start at any one of the tracks by providing a number (0 will be the first track). + +--- + +- **the `.pause()` method** + +- Arguments: none. + +- Returns: the playlist (chainable). + +Pauses the playlist's playback. + +--- + +- **the `.stop()` method** + +- Arguments: none. + +- Returns: the playlist (chainable). + +Stops the playlist. + +--- + +- **the `.loop(bool)` method** + +- Arguments: + - `bool`: (boolean) causes the playlist to repeat after it ends if `true`. + +- Returns: the playlist (chainable). + +Set the playlist to loop (not each track inside) or set it to stop looping. + +--- + +- **the `.random()` method** + +- Arguments: none. + +- Returns: a track. + +Returns a random track from the playlist--you can then use [track methods](#track-methods) on it. + +--- + +- **the `.shuffle()` method** + +- Arguments: none. + +- Returns: the playlist (chainable). + +Shuffles (randomizes) the playlist. Be warned: the default order cannot be restored without creating a new playlist. + +--- + +- **the `.isPlaying()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns true if the playlist is currently being played in some fashion. + +--- + +## Control Panel + +> [!TIP] +> These APIs are completely unavailable if `options.controls.show` is `false`. + +The control panel is a user interface for controlling the audio that is set up through this library. You can use it to give your players a mute button and a volume control without having to build these functions yourself. + +You may wish to alter the panels CSS to math your game. [Look here](https://github.com/ChapelR/harlowe-audio/blob/master/src/panel.css) to check out it's default styles. + +Some facets of the control panel can be controlled via JavaScript using the following methods: + +--- + +- **the `A.controls.close()` method** + +- Arguments: none. + +- Returns: none. + +Closes (shrinks) the panel. + +--- + +- **the `A.controls.open()` method** + +- Arguments: none. + +- Returns: none. + +Opens (expands) the panel. + +--- + +- **the `A.controls.hide()` method** + +- Arguments: none. + +- Returns: none. + +Hides the panel completely. + +--- + +- **the `A.controls.show()` method** + +- Arguments: none. + +- Returns: none. + +Makes the panel visible after hiding it. + +--- + +## Load Screen + +This library adds a loading screen to Harlowe that is superficially similar to SugarCube's. You can use this load screen by calling the `A.preload()` method after defining some tracks. You can potentially use it for other things too, if you want. Show it by calling `A.loadScreen.show()`, and get rid of it with `A.loadScreen.dismiss()`. That's really all there is to it. + +## The Menu API + +> [!TIP] +> These APIs are completely unavailable if `options.controls.show` is `false`. + +> [!DANGER] +> You should **always** define your menu links Story JavaScript, or your compiler's equivalent script section, never in passages. + +This API allows you to add links to the sidebar as a 'story menu', similar to what can be done in SugarCube. THese links can be used to navigate to a passage, run a JavaScript function, or both. They can be hidden, shown, toggled, and removed at any time. + +--- + +- **the `A.menu.hide()` method** + +- Arguments: none. + +- Returns: the `#story-menu` element (jQuery). + +Hides the story menu portion of the side bar. + +--- + +- **the `A.menu.show()` method** + +- Arguments: none. + +- Returns: the `#story-menu` element (jQuery). + +Shows the story menu portion of the side bar. + +--- + +- **the `A.menu.isShown()` method** + +- Arguments: none. + +- Returns: boolean. + +Returns whether the story menu portion of the side bar is currently visible. + +--- + +- **the `A.menu.links.add(linkText, [passageName], [callback])` method** + +- Arguments: + - `linkText`: (string) the text of the link. + - `passageName`: (optional) (string) a passage name to navigate to when the link is clicked. + - `callback`: (optional) (string) a function to run when the link is clicked. + +- Returns: the generated link (jQuery). + +This method creates a story menu link. You must pass it text to display or it will raise an error. You can then pass it a passage name to navigate to, a callback function to run on click, both, or neither. If you include both, they must be included in the indicated order. + +--- + +- **the `A.menu.links.clear()` method** + +- Arguments: none. + +- Returns: the `#story-menu` element (jQuery). + +This method removes all of the links from the story menu. + +--- + +- **the `A.menu.links.hide(text)` method** + +- Arguments: + - `text`: the text of the link you want to alter. + +- Returns: nothing. + +This method hides a story menu link. If there are multiple links with the same link text, all of them will be hidden. + +--- + +- **the `A.menu.links.show(text)` method** + +- Arguments: + - `text`: the text of the link you want to alter. + +- Returns: nothing. + +This method shows a hidden story menu link. If there are multiple links with the same link text, all of them will be shown. + +--- + +- **the `A.menu.links.toggle(text)` method** + +- Arguments: + - `text`: the text of the link you want to alter. + +- Returns: nothing. + +This method toggles the visibility a story menu link (hiding it if it's visible, showing it if it is hidden). If there are multiple links with the same link text, all of them will be toggled. + +--- + +- **the `A.menu.links.remove(text)` method** + +- Arguments: + - `text`: the text of the link you want to alter. + +- Returns: nothing. + +This method removes a story menu link. If there are multiple links with the same link text, all of them will be removed. Hidden links can be re-shown later, removed links are gone for good and will need to be recreated via `A.menu.links.add()`. + +--- + +# Events + +There are two kinds of events that are triggered by HAL--events triggered on the document *only* and events triggered on *both* the track element and the document. + +## Track Event Methods + +There are two track event methods you can use; `.on()` and `.one()`. The former triggers a handler each time the indicated event occurs, the latter triggers a handler only once. These methods can only be used with [track events](#list-of-track-events). + +```javascript +A.track('some-song').one(':volume', function () { + // occurs only once when the volume of "some-song" is changed + console.log('track "some-song" volume changed'); +}); +``` + +```javascript +A.track('some-song').on(':volume', function () { + // occurs each time the volume of "some-song" is changed + console.log('track "some-song" volume changed'); +}); +``` + +## Global Event Methods + +As with track event methods, there are two: `A.on()` and `A.one()`. These event methods monitor *all* tracks for events, and also monitor for [master audio events](#list-of-master-audio-events). + +```javascript +A.one(':volume', function (ev) { + // occurs only once when the volume of a track is changed + console.log('track "' + ev.track.id + '" volume changed'); +}); +``` + +```javascript +A.on(':volume', function (ev) { + // occurs each time the volume of any track is changed + console.log('track "' + ev.track.id + '" volume changed'); +}); +``` + +## List of Track Events + +These events are triggered on both the document and the track element. The track's definition is available as `.track`. These events may be used with `.on()` and `.one()` to listen for events on specific tracks, or with `A.on()` and `A.one()` to listen for events on any and all tracks. + +| Event | Description | +| --- | --- | +| `:available` | a track's metadata is loaded | +| `:loaded` | a track can be played from start to finish | +| `:play` | a track starts playing | +| `:pause` | a track is paused | +| `:stop` | a track reaches the end or is stopped | +| `:mute` | a track is muted or unmuted | +| `:volume` | a track's volume is changed | + +## List of Master Audio Events + +These events are only available for use with `A.on()` and `A.one()`, and are only triggered on the document element. + +| Event | Description | +| --- | --- | +| `:master-mute` | the master mute control is muted or unmuted | +| `:master-volume` | the master volume is changed | + +## The Event Object + +The event object in track events contains the `track` property, which contains the definition of the track that triggered the event. + +| Property | Description | +| --- | --- | +| `.track.id` | The track's name/id. | +| `.track.$el` | The track's jQuery-wrapped audio element. | +| `.track.unwrap` | The track's `