mirror of https://github.com/kurisufriend/0x40-web
- Removed binaries from repo - Full respack support - All UIs implemented except Xmas - Javascript is now modular and clean - Almost 10x lower memory usage - Runs well on Chrome/FF/Android/iOS - Keyboard shortcuts for song/image changing - Auto/manual image modes - Implemented all beat types - including colour fade - Normalise buildup speed and length as per original flash - Animated imagesserial
parent
db16a57656
commit
8cccc393bf
@ -0,0 +1,2 @@ |
|||||||
|
original/ |
||||||
|
respacks/ |
@ -0,0 +1,313 @@ |
|||||||
|
/* Heavily based on Kepstin's wonderful CSS work |
||||||
|
https://github.com/kepstin/0x40hues-html5/blob/master/hues-m.css */ |
||||||
|
|
||||||
|
.ModernUI { |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-beatbar { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
max-width: 992px; |
||||||
|
height: 30px; |
||||||
|
margin: 0 auto; |
||||||
|
overflow: hidden; |
||||||
|
left: 8px; |
||||||
|
right: 8px; |
||||||
|
color: white; |
||||||
|
background: rgba(127,127,127,0.5); |
||||||
|
border-color: rgba(0,0,0,0.5); |
||||||
|
border-width: 0 4px 4px; |
||||||
|
border-style: solid; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-beatleft, .hues-m-beatright, .hues-m-songtitle, .hues-m-imagename, .hues-m-huename { |
||||||
|
color: white; |
||||||
|
background: rgba(0,0,0,0.7); |
||||||
|
height: 20px; |
||||||
|
line-height: 20px; |
||||||
|
font-size: 12px; |
||||||
|
overflow: hidden; |
||||||
|
white-space: nowrap; |
||||||
|
border-radius: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-huename { |
||||||
|
font-size: 8px; |
||||||
|
height: 12px; |
||||||
|
line-height: 12px; |
||||||
|
border-radius: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-beatleft, .hues-m-beatright { |
||||||
|
position: absolute; |
||||||
|
padding: 0 0 0 20px; |
||||||
|
top: 5px; |
||||||
|
overflow: hidden; |
||||||
|
border-radius: 0 10px 10px 0; |
||||||
|
} |
||||||
|
.hues-m-beatleft { |
||||||
|
transform: scaleX(-1); |
||||||
|
-webkit-transform: scaleX(-1); |
||||||
|
left: 8px; |
||||||
|
right: 50%; |
||||||
|
} |
||||||
|
.hues-m-beatright { |
||||||
|
left: 50%; |
||||||
|
right: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-beatcenter { |
||||||
|
position: absolute; |
||||||
|
top: -6px; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
margin: 0 auto; |
||||||
|
height: 40px; |
||||||
|
width: 40px; |
||||||
|
color: white; |
||||||
|
background: rgb(80,80,80); |
||||||
|
font-size: 20px; |
||||||
|
line-height: 40px; |
||||||
|
border-radius: 20px; |
||||||
|
text-align: center; |
||||||
|
box-shadow: inset 0 0 12px rgba(0,0,0,0.5); |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-beatcenter > span { |
||||||
|
-moz-animation-duration: 150ms; |
||||||
|
-webkit-animation-duration: 150ms; |
||||||
|
animation-duration: 150ms; |
||||||
|
-moz-animation-name: hues-m-beatcenter; |
||||||
|
-webkit-animation-name: hues-m-beatcenter; |
||||||
|
animation-name: hues-m-beatcenter; |
||||||
|
-moz-animation-fill-mode: forwards; |
||||||
|
-webkit-animation-fill-mode: forwards; |
||||||
|
animation-fill-mode: forwards; |
||||||
|
} |
||||||
|
@-moz-keyframes hues-m-beatcenter { |
||||||
|
from { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
50% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
to { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
@-webkit-keyframes hues-m-beatcenter { |
||||||
|
from { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
50% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
to { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
@keyframes hues-m-beatcenter { |
||||||
|
from { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
50% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
to { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-controls { |
||||||
|
position: absolute; |
||||||
|
bottom: 0; |
||||||
|
max-width: 992px; |
||||||
|
height: 104px; |
||||||
|
margin: 0 auto; |
||||||
|
overflow: hidden; |
||||||
|
left: 8px; |
||||||
|
right: 8px; |
||||||
|
color: rgba(255,255,255,0.7); |
||||||
|
background: rgba(127,127,127,0.5); |
||||||
|
border-color: rgba(0,0,0,0.5); |
||||||
|
border-width: 4px 4px 0; |
||||||
|
border-style: solid; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-songtitle, .hues-m-imagename, .hues-m-huename { |
||||||
|
position: absolute; |
||||||
|
left: 8px; |
||||||
|
right: 8px; |
||||||
|
text-align: center; |
||||||
|
padding: 0 4px; |
||||||
|
} |
||||||
|
.hues-m-songtitle { |
||||||
|
bottom: 5px; |
||||||
|
} |
||||||
|
.hues-m-imagename { |
||||||
|
bottom: 29px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-songtitle > a:link, .hues-m-songtitle > a:visited, .hues-m-imagename > a:link, .hues-m-imagename > a:visited { |
||||||
|
display: block; |
||||||
|
color: inherit; |
||||||
|
text-decoration: none; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
.hues-m-songtitle > a.small, .hues-m-imagename > a.small { |
||||||
|
font-size: 10px; |
||||||
|
} |
||||||
|
.hues-m-songtitle > a.x-small, .hues-m-imagename > a.x-small { |
||||||
|
font-size: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-leftbox { |
||||||
|
position: absolute; |
||||||
|
bottom: 50px; |
||||||
|
left: 0; |
||||||
|
right: 50%; |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-rightbox { |
||||||
|
position: absolute; |
||||||
|
bottom: 50px; |
||||||
|
left: 50%; |
||||||
|
right: 0; |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-huename { |
||||||
|
bottom: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-vol-bar { |
||||||
|
position: absolute; |
||||||
|
height: 20px; |
||||||
|
bottom: 21px; |
||||||
|
left: 32px; |
||||||
|
right: 32px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-m-vol-bar > button { |
||||||
|
display: block; |
||||||
|
position: absolute; |
||||||
|
left: 0; |
||||||
|
bottom: 14px; |
||||||
|
right: 0; |
||||||
|
height: 12px; |
||||||
|
color: inherit; |
||||||
|
font: inherit; |
||||||
|
font-size: 12px; |
||||||
|
line-height: 12px; |
||||||
|
text-align: center; |
||||||
|
padding: 0; |
||||||
|
width: 100%; |
||||||
|
background: transparent; |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.hues-m-vol-bar > input { |
||||||
|
display: block; |
||||||
|
position: absolute; |
||||||
|
left: 0; |
||||||
|
bottom: 0; |
||||||
|
right: 0; |
||||||
|
height: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
/* Fun slider stuff! */ |
||||||
|
|
||||||
|
input[type=range] { |
||||||
|
width: 100%; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
height: 12px; |
||||||
|
background: transparent; |
||||||
|
-webkit-appearance: none; |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-webkit-slider-runnable-track { |
||||||
|
width: 100%; |
||||||
|
height: 4px; |
||||||
|
background: rgba(255,255,255,0.7); |
||||||
|
border: none; |
||||||
|
border-radius: 0; |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-webkit-slider-thumb { |
||||||
|
-webkit-appearance: none; |
||||||
|
box-shadow: none; |
||||||
|
border: none; |
||||||
|
height: 12px; |
||||||
|
width: 4px; |
||||||
|
border-radius: 0; |
||||||
|
background: rgb(255,255,255); |
||||||
|
margin-top: -4px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-moz-range-track { |
||||||
|
width: 100%; |
||||||
|
height: 4px; |
||||||
|
background: rgba(255,255,255,0.7); |
||||||
|
border: none; |
||||||
|
border-radius: 0; |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-moz-range-thumb { |
||||||
|
box-shadow: none; |
||||||
|
border: none; |
||||||
|
height: 12px; |
||||||
|
width: 4px; |
||||||
|
border-radius: 0; |
||||||
|
background: rgb(255,255,255); |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-ms-track { |
||||||
|
width: 100%; |
||||||
|
background: transparent; /* Hides the slider so custom styles can be added */ |
||||||
|
border-color: transparent; |
||||||
|
color: transparent; |
||||||
|
height: 4px; |
||||||
|
border-width: 4px 0; |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-ms-fill-lower { |
||||||
|
background: rgba(255,255,255,0.7); |
||||||
|
} |
||||||
|
input[type=range]::-ms-fill-upper { |
||||||
|
background: rgba(0,0,0,0.7); |
||||||
|
} |
||||||
|
|
||||||
|
input[type=range]::-ms-thumb { |
||||||
|
box-shadow: none; |
||||||
|
border: none; |
||||||
|
height: 12px; |
||||||
|
width: 4px; |
||||||
|
border-radius: 0; |
||||||
|
background: rgb(255,255,255); |
||||||
|
} |
||||||
|
|
||||||
|
@media (min-width: 768px) { |
||||||
|
.hues-m-controls { |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
.hues-m-songtitle, .hues-m-imagename { |
||||||
|
left: 192px; |
||||||
|
right: 192px; |
||||||
|
} |
||||||
|
.hues-m-leftbox { |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
right: auto; |
||||||
|
width: 192px; |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
.hues-m-rightbox { |
||||||
|
bottom: 0; |
||||||
|
left: auto; |
||||||
|
right: 0; |
||||||
|
width: 192px; |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
/* TODO: Fix scrollbar */ |
||||||
|
|
||||||
|
.RetroUI { |
||||||
|
} |
||||||
|
|
||||||
|
.hues-r-container { |
||||||
|
position: absolute; |
||||||
|
bottom: 0px; |
||||||
|
|
||||||
|
white-space: nowrap; |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
font-size: 5pt; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-r-container a:link, .hues-r-container a:visited { |
||||||
|
display: block; |
||||||
|
color: inherit; |
||||||
|
text-decoration: none; |
||||||
|
overflow: hidden; |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
/* TODO: Fix scrollbar */ |
||||||
|
|
||||||
|
.WeedUI { |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-w-beatleft, .hues-w-beatright { |
||||||
|
font-size: 13px; |
||||||
|
position: absolute; |
||||||
|
padding: 0 0 0 5px; |
||||||
|
top: 5px; |
||||||
|
overflow: hidden; |
||||||
|
border-radius: 0 10px 10px 0; |
||||||
|
} |
||||||
|
.hues-w-beatleft { |
||||||
|
transform: scaleX(-1); |
||||||
|
-webkit-transform: scaleX(-1); |
||||||
|
left: 8px; |
||||||
|
right: 50%; |
||||||
|
} |
||||||
|
.hues-w-beatright { |
||||||
|
left: 50%; |
||||||
|
right: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-w-beataccent { |
||||||
|
position: absolute; |
||||||
|
left: 0; right: 0; |
||||||
|
margin-left:auto; |
||||||
|
margin-right:auto; |
||||||
|
margin-top: 15px; |
||||||
|
text-align:center; |
||||||
|
font-size: 35px; |
||||||
|
opacity: 0; |
||||||
|
text-shadow: -2px 2px 0px #666; |
||||||
|
|
||||||
|
animation-name: fallspin; |
||||||
|
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); |
||||||
|
animation-duration: 0.5s; |
||||||
|
|
||||||
|
-webkit-animation-name: fallspin; |
||||||
|
-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); |
||||||
|
-webkit-animation-duration: 0.5s; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes fallspin { |
||||||
|
from {transform: rotate(0deg) translate(0px, 0px); |
||||||
|
opacity: 1;} |
||||||
|
} |
||||||
|
@-webkit-keyframes fallspin { |
||||||
|
from {-webkit-transform: rotate(0deg) translate(0px, 0px); |
||||||
|
opacity: 1;} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
/* Heavily based on Kepstin's wonderful CSS work |
||||||
|
https://github.com/kepstin/0x40hues-html5/blob/master/hues-m.css */ |
||||||
|
|
||||||
|
.XmasUI { |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
} |
||||||
|
|
||||||
|
.hues-x-controls { |
||||||
|
position: absolute; |
||||||
|
bottom: 0; |
||||||
|
max-width: 992px; |
||||||
|
height: 50px; |
||||||
|
margin: 0 auto; |
||||||
|
overflow: hidden; |
||||||
|
left: 8px; |
||||||
|
right: 8px; |
||||||
|
color: rgba(255,255,255,0.7); |
||||||
|
} |
||||||
|
|
||||||
|
.hues-x-beatbar { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
max-width: 992px; |
||||||
|
height: 30px; |
||||||
|
margin: 0 auto; |
||||||
|
overflow: hidden; |
||||||
|
left: 8px; |
||||||
|
right: 8px; |
||||||
|
color: white; |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
@font-face { |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
font-style: normal; |
||||||
|
font-weight: normal; |
||||||
|
-webkit-font-smoothing: none; |
||||||
|
font-smooth: never; |
||||||
|
src: local("PetMe64Web"); |
||||||
|
src: local('Pet Me 64'), local('Pet Me 64'), url("PetMe64.woff") format('woff'); |
||||||
|
} |
||||||
|
|
||||||
|
html, body { |
||||||
|
height: 100%; |
||||||
|
margin: 0; padding: 0; |
||||||
|
overflow: hidden; |
||||||
|
background-color:#ffffff; |
||||||
|
} |
||||||
|
|
||||||
|
#waifu { |
||||||
|
display: block; |
||||||
|
height: 100%; |
||||||
|
padding: 0; |
||||||
|
|
||||||
|
z-index: -10; |
||||||
|
} |
||||||
|
|
||||||
|
#preloadHelper { |
||||||
|
background-color: #FFF; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
display:flex; |
||||||
|
justify-content:center; |
||||||
|
align-items:center; |
||||||
|
flex-direction: column; |
||||||
|
font-family: 'PetMe64Web'; |
||||||
|
font-size: 25pt; |
||||||
|
|
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
z-index: 10; |
||||||
|
} |
||||||
|
|
||||||
|
#preloadHelper.loaded { |
||||||
|
opacity: 0; |
||||||
|
animation-name: fadeout; |
||||||
|
animation-duration: 1s; |
||||||
|
-webkit-animation-name: fadeout; |
||||||
|
-webkit-animation-duration: 3s; |
||||||
|
} |
||||||
|
|
||||||
|
#preloader { |
||||||
|
display: block; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
#preSub { |
||||||
|
font-size: 12pt; |
||||||
|
} |
||||||
|
|
||||||
|
#huesSettings { |
||||||
|
background: rgba(127,127,127,0.5); |
||||||
|
border-color: rgba(0,0,0,0.5); |
||||||
|
border-width: 4px; |
||||||
|
border-style: solid; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes fadeout { |
||||||
|
from {opacity: 1;} |
||||||
|
to {opacity: 0;} |
||||||
|
} |
||||||
|
@-webkit-keyframes fadeout { |
||||||
|
from {opacity: 1;} |
||||||
|
to {opacity: 0;} |
||||||
|
} |
@ -0,0 +1,303 @@ |
|||||||
|
|
||||||
|
/* Takes an element name to attach to, and an audio context element for |
||||||
|
getting the current time with reasonable accuracy */ |
||||||
|
function HuesCanvas(element, aContext, core) { |
||||||
|
'use strict'; |
||||||
|
this.aContext = aContext; |
||||||
|
this.core = core; |
||||||
|
|
||||||
|
this.needsRedraw = false; |
||||||
|
this.colour = 0xFFFFFF; |
||||||
|
this.image = null; |
||||||
|
|
||||||
|
this.animTimeout; |
||||||
|
this.animFrame; |
||||||
|
|
||||||
|
// set later
|
||||||
|
this.blurDecay; |
||||||
|
this.blurAmount; |
||||||
|
this.blurIterations; |
||||||
|
this.blurMin; |
||||||
|
this.blurMax; |
||||||
|
this.blurDelta; |
||||||
|
this.blurAlpha; |
||||||
|
// dynamic
|
||||||
|
this.blurStart = 0; |
||||||
|
this.blurDistance = 0; |
||||||
|
this.xBlur = false; |
||||||
|
this.yBlur = false; |
||||||
|
|
||||||
|
this.blackout = false; |
||||||
|
this.blackoutColour = "#000"; // for the whiteout case we must store this
|
||||||
|
this.blackoutTimeout; |
||||||
|
|
||||||
|
this.colourFade = false; |
||||||
|
this.colourFadeStart=0; |
||||||
|
this.colourFadeLength=0; |
||||||
|
this.oldColour=0xFFFFFF; |
||||||
|
this.newColour=0xFFFFFF; |
||||||
|
|
||||||
|
this.blendMode = "hard-light"; |
||||||
|
// Chosen because they look decent
|
||||||
|
this.setBlurAmount(15); |
||||||
|
this.setBlurIterations(5); |
||||||
|
this.setBlurDecay(25); |
||||||
|
this.canvas = document.getElementById(element).getContext("2d"); |
||||||
|
window.addEventListener('resize', this.resizeHandler(this)); |
||||||
|
this.resize(); |
||||||
|
|
||||||
|
this.animating = true; |
||||||
|
requestAnimationFrame(this.getAnimLoop()); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.resizeHandler = function(that) { |
||||||
|
return function() {that.resize();}; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.resize = function() { |
||||||
|
// height is constant 720px, we expand width to suit
|
||||||
|
var ratio = window.innerWidth / window.innerHeight; |
||||||
|
this.canvas.canvas.width = 720 * ratio + 1; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.redraw = function() { |
||||||
|
var offset; // for centering/right/left align
|
||||||
|
var bOpacity; |
||||||
|
var width = this.canvas.canvas.width; |
||||||
|
|
||||||
|
var cTime = this.aContext.currentTime; |
||||||
|
// white BG for the hard light filter
|
||||||
|
this.canvas.globalAlpha = 1; |
||||||
|
this.canvas.globalCompositeOperation = "source-over"; |
||||||
|
if(this.blackout) { |
||||||
|
// original is 3 frames at 30fps, this is close
|
||||||
|
bOpacity = (cTime - this.blackoutStart)*10; |
||||||
|
if(bOpacity > 1) { // optimise the draw
|
||||||
|
this.canvas.fillStyle = this.blackoutColour; |
||||||
|
this.canvas.fillRect(0,0,width,720); |
||||||
|
this.needsRedraw = false; |
||||||
|
return; |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.canvas.fillStyle = "#FFF"; |
||||||
|
this.canvas.fillRect(0,0,width,720); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.image) { |
||||||
|
var bitmap = this.image.animated ? |
||||||
|
this.image.bitmaps[this.animFrame] : this.image.bitmap; |
||||||
|
switch(this.image.align) { |
||||||
|
case "left": |
||||||
|
offset = 0; |
||||||
|
break; |
||||||
|
case "right": |
||||||
|
offset = width - bitmap.width; |
||||||
|
break; |
||||||
|
default: |
||||||
|
offset = width/2 - bitmap.width/2; |
||||||
|
break; |
||||||
|
} |
||||||
|
if(this.xBlur || this.yBlur) { |
||||||
|
this.canvas.globalAlpha = this.blurAlpha; |
||||||
|
var delta = cTime - this.blurStart; |
||||||
|
this.blurDistance = this.blurAmount * Math.exp(-this.blurDecay * delta); |
||||||
|
} |
||||||
|
if(this.xBlur) { |
||||||
|
for(var i=this.blurMin; i<=this.blurMax; i+= this.blurDelta) { |
||||||
|
this.canvas.drawImage(bitmap, Math.floor(this.blurDistance * i) + offset, 0); |
||||||
|
} |
||||||
|
} else if(this.yBlur) { |
||||||
|
for(var i=this.blurMin; i<=this.blurMax; i+= this.blurDelta) { |
||||||
|
this.canvas.drawImage(bitmap, offset, Math.floor(this.blurDistance * i)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.canvas.globalAlpha = 1; |
||||||
|
this.canvas.drawImage(bitmap, offset, 0); |
||||||
|
} |
||||||
|
} |
||||||
|
this.canvas.globalAlpha = 0.7; |
||||||
|
this.canvas.fillStyle = this.intToHex(this.colour); |
||||||
|
this.canvas.globalCompositeOperation = this.blendMode; |
||||||
|
this.canvas.fillRect(0,0,width,720); |
||||||
|
if(this.blackout) { |
||||||
|
this.canvas.globalAlpha = bOpacity; |
||||||
|
this.canvas.fillStyle = this.blackoutColour; |
||||||
|
this.canvas.fillRect(0,0,width,720); |
||||||
|
this.needsRedraw = true; |
||||||
|
} else { |
||||||
|
this.needsRedraw = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Second fastest method from |
||||||
|
http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
|
||||||
|
It stil does millions of ops per second, and isn't ugly like the integer if/else */ |
||||||
|
HuesCanvas.prototype.intToHex = function(num) { |
||||||
|
return '#' + ("00000"+num.toString(16)).slice(-6); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.getAnimLoop = function() { |
||||||
|
var that = this; |
||||||
|
return function() {that.animationLoop()}; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.animationLoop = function() { |
||||||
|
if (this.colourFade) { |
||||||
|
var delta = this.aContext.currentTime - this.colourFadeStart; |
||||||
|
var fadeVal = delta / this.colourFadeLength; |
||||||
|
if (fadeVal >= 1) { |
||||||
|
this.stopFade(); |
||||||
|
this.colour = this.newColour; |
||||||
|
} else { |
||||||
|
this.mixColours(fadeVal); |
||||||
|
} |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
if(this.blackoutTimeout && this.aContext.currentTime > this.blackoutTimeout) { |
||||||
|
this.clearBlackout(); |
||||||
|
} |
||||||
|
if(this.image && this.image.animated |
||||||
|
&& this.animTimeout < this.aContext.currentTime) { |
||||||
|
this.animFrame++; |
||||||
|
this.animFrame %= this.image.frameDurations.length; |
||||||
|
this.animTimeout = this.aContext.currentTime + |
||||||
|
this.image.frameDurations[this.animFrame]/1000; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
if(this.blurStart) { |
||||||
|
var dist = this.blurDistance / this.blurAmount; |
||||||
|
if(this.xBlur) |
||||||
|
this.core.blurUpdated(dist, 0); |
||||||
|
else |
||||||
|
this.core.blurUpdated(0, dist); |
||||||
|
} |
||||||
|
if(this.blurStart && this.blurDistance < 0.3) { |
||||||
|
this.core.blurUpdated(0, 0); |
||||||
|
this.blurDistance = 0; |
||||||
|
this.blurStart = 0; |
||||||
|
this.xBlur = this.yBlur = false; |
||||||
|
this.redraw(); |
||||||
|
} else if(this.blurStart) { |
||||||
|
this.redraw(); |
||||||
|
} else if(this.needsRedraw){ |
||||||
|
this.redraw(); |
||||||
|
} |
||||||
|
if(this.animating) { |
||||||
|
requestAnimationFrame(this.getAnimLoop()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setImage = function(image) { |
||||||
|
this.needsRedraw = true; |
||||||
|
this.image = image; |
||||||
|
// Null images don't need anything interesting done to them
|
||||||
|
if(!image || (!image.bitmap && !image.bitmaps)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if(image.animated) { |
||||||
|
this.animFrame = 0; |
||||||
|
this.animTimeout = this.aContext.currentTime + image.frameDurations[0]/1000; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setColour = function(colour) { |
||||||
|
this.stopFade(); |
||||||
|
this.colour = colour; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.doBlackout = function(whiteout) { |
||||||
|
if (typeof(whiteout)==='undefined') whiteout = false; |
||||||
|
if(whiteout) { |
||||||
|
this.blackoutColour = "#FFF"; |
||||||
|
} else { |
||||||
|
this.blackoutColour = "#000"; |
||||||
|
} |
||||||
|
this.blackoutTimeout = 0; // indefinite
|
||||||
|
this.blackoutStart = this.aContext.currentTime; |
||||||
|
this.blackout = true; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
// for song changes
|
||||||
|
HuesCanvas.prototype.clearBlackout = function() { |
||||||
|
this.blackout = false; |
||||||
|
this.blackoutTimeout = 0; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.doShortBlackout = function(beatTime) { |
||||||
|
this.doBlackout(); |
||||||
|
// GetRandomWaifu(); TODO IMPLEMENT IN CORE INSTEAD
|
||||||
|
this.blackoutTimeout = this.aContext.currentTime + beatTime / 1.7; |
||||||
|
// looks better if we go right to black
|
||||||
|
this.blackoutStart = 0; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.doColourFade = function(length, newColour) { |
||||||
|
this.colourFade = true; |
||||||
|
this.colourFadeLength = length; |
||||||
|
this.colourFadeStart = this.aContext.currentTime; |
||||||
|
this.oldColour = this.colour; |
||||||
|
this.newColour = newColour; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.stopFade = function() { |
||||||
|
this.colourFade = false; |
||||||
|
this.colourFadeStart = 0; |
||||||
|
this.colourFadeLength = 0; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.mixColours = function(percent) { |
||||||
|
percent = Math.min(1, percent); |
||||||
|
var oldR = this.oldColour >> 16 & 0xFF; |
||||||
|
var oldG = this.oldColour >> 8 & 0xFF; |
||||||
|
var oldB = this.oldColour & 0xFF; |
||||||
|
var newR = this.newColour >> 16 & 0xFF; |
||||||
|
var newG = this.newColour >> 8 & 0xFF; |
||||||
|
var newB = this.newColour & 0xFF; |
||||||
|
var mixR = oldR * (1 - percent) + newR * percent; |
||||||
|
var mixG = oldG * (1 - percent) + newG * percent; |
||||||
|
var mixB = oldB * (1 - percent) + newB * percent; |
||||||
|
this.colour = mixR << 16 | mixG << 8 | mixB; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.doXBlur = function() { |
||||||
|
this.blurStart = this.aContext.currentTime; |
||||||
|
this.blurDistance = this.blurAmount; |
||||||
|
this.xBlur = true; |
||||||
|
this.yBlur = false; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.doYBlur = function() { |
||||||
|
this.blurStart = this.aContext.currentTime; |
||||||
|
this.blurDistance = this.blurAmount; |
||||||
|
this.xBlur = false; |
||||||
|
this.yBlur = true; |
||||||
|
this.needsRedraw = true; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setBlurDecay = function(decay) { |
||||||
|
this.blurDecay = decay; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setBlurIterations = function(iterations) { |
||||||
|
this.blurIterations = iterations; |
||||||
|
this.blurDelta = this.blurAmount / this.blurIterations; |
||||||
|
this.blurAlpha = 1/(this.blurIterations/2); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setBlurAmount = function(amount) { |
||||||
|
this.blurAmount = amount; |
||||||
|
this.blurMin = -this.blurAmount/2; |
||||||
|
this.blurMax = this.blurAmount/2; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCanvas.prototype.setAnimating = function(anim) { |
||||||
|
if(!this.animating && anim) { |
||||||
|
requestAnimationFrame(this.animationLoop); |
||||||
|
} |
||||||
|
this.animating = anim; |
||||||
|
} |
@ -0,0 +1,843 @@ |
|||||||
|
var defaultSettings = { |
||||||
|
load : true, // Debugging var, for loading zips or not
|
||||||
|
autoplay : true, // Debug, play first song automatically?
|
||||||
|
blurQuality: 2, // low/med/high/extreme 0-3
|
||||||
|
|
||||||
|
// UI accessible config
|
||||||
|
// Autosong stuff is a todo, becuase why even implement that
|
||||||
|
smartAlign: true, |
||||||
|
blurAmount: 1, // 0,1,2,3 = off,low,med,high
|
||||||
|
blurDecay: 2, // 0,1,2,3 = slow,med,fast,faster!
|
||||||
|
colourSet: "normal", // normal, pastel, 420
|
||||||
|
blackoutUI: false, |
||||||
|
playBuildups: "on", // off, once, on
|
||||||
|
volume : 0.7 |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore = function(defaults) { |
||||||
|
// Bunch-o-initialisers
|
||||||
|
this.version = "0x01"; |
||||||
|
this.beatIndex = 0; |
||||||
|
this.beatLength=-1; |
||||||
|
this.currentSong; |
||||||
|
this.currentImage; |
||||||
|
this.songIndex=-1; |
||||||
|
this.colourIndex=0; |
||||||
|
this.imageIndex=-1; |
||||||
|
this.isFullAuto = true; |
||||||
|
this.currentVolume=70; |
||||||
|
this.volumeMuted=false; |
||||||
|
this.loopCount=0; |
||||||
|
this.doRandom = false; |
||||||
|
this.lastSC=0; |
||||||
|
this.buildupDiff=0; |
||||||
|
this.animTimeoutID; |
||||||
|
this.fadeOut=false; |
||||||
|
this.fadeDirection=false; |
||||||
|
this.loadedFiles=0; |
||||||
|
this.doBuildup=true; |
||||||
|
this.userInterface = null; |
||||||
|
|
||||||
|
for(var attr in defaultSettings) { |
||||||
|
if(defaults[attr] == undefined) |
||||||
|
defaults[attr] = defaultSettings[attr]; |
||||||
|
} |
||||||
|
|
||||||
|
console.log("0x40 Hues - start your engines!"); |
||||||
|
this.colours = this.oldColours; |
||||||
|
this.uiArray = []; |
||||||
|
this.lastSongArray = []; |
||||||
|
this.lastImageArray = []; |
||||||
|
this.settings = new HuesSettings(defaults); |
||||||
|
this.autoSong = this.settings.autosong; |
||||||
|
this.resourceManager = new Resources(); |
||||||
|
this.soundManager = new SoundManager(); |
||||||
|
if(!this.soundManager.canUse) { |
||||||
|
this.error("Web Audio API not supported in this browser."); |
||||||
|
return; |
||||||
|
} |
||||||
|
this.renderer = new HuesCanvas("waifu", this.soundManager.context, this); |
||||||
|
|
||||||
|
this.uiArray.push(new RetroUI(), new WeedUI(), new ModernUI(), new XmasUI()); |
||||||
|
this.changeUI(1); |
||||||
|
this.settings.connectCore(this); |
||||||
|
|
||||||
|
// todo: only after respacks loaded?
|
||||||
|
var that = this; |
||||||
|
if(defaults.load) { |
||||||
|
this.resourceManager.addAll(defaults.respacks, function() { |
||||||
|
document.getElementById("preloadHelper").className = "loaded"; |
||||||
|
window.setTimeout(function() { |
||||||
|
document.getElementById("preloadHelper").style.display = "none"; |
||||||
|
}, 1500); |
||||||
|
that.setImage(0); |
||||||
|
if(defaults.autoplay) { |
||||||
|
that.setSong(0); |
||||||
|
// TODO delete me
|
||||||
|
that.previousSong(); |
||||||
|
that.previousSong(); |
||||||
|
} |
||||||
|
}, function(progress) { |
||||||
|
var prog = document.getElementById("preMain"); |
||||||
|
var scale = Math.floor(progress * 0x40); |
||||||
|
prog.textContent = '0x' + ("00"+scale.toString(16)).slice(-2); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
document.getElementById("preloadHelper").style.display = "none"; |
||||||
|
} |
||||||
|
|
||||||
|
document.onkeydown = function(e){ |
||||||
|
e = e || window.event; |
||||||
|
var key = e.keyCode || e.which; |
||||||
|
return that.keyHandler(key); |
||||||
|
}; |
||||||
|
|
||||||
|
window.onerror = function(msg, url, line, col, error) { |
||||||
|
that.error(msg); |
||||||
|
|
||||||
|
// Get more info in console
|
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
this.animationLoop(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.animationLoop = function() { |
||||||
|
var that = this; |
||||||
|
if(!this.soundManager.playing) { |
||||||
|
requestAnimationFrame(function() {that.animationLoop()}); |
||||||
|
return; |
||||||
|
} |
||||||
|
var now = this.soundManager.currentTime(); |
||||||
|
if(now < 0) { |
||||||
|
this.userInterface.updateTime(0); |
||||||
|
} else { |
||||||
|
this.userInterface.updateTime(this.soundManager.displayableTime()); |
||||||
|
} |
||||||
|
for(var beatTime = this.beatIndex * this.beatLength; beatTime < now; |
||||||
|
beatTime = ++this.beatIndex * this.beatLength) { |
||||||
|
var beat = this.getBeat(this.beatIndex); |
||||||
|
this.beater(beat); |
||||||
|
} |
||||||
|
requestAnimationFrame(function() {that.animationLoop()}); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.getCurrentMode = function() { |
||||||
|
return this.isFullAuto ? "FULL AUTO" : "NORMAL"; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.getSafeBeatIndex = function() { |
||||||
|
if(!this.soundManager.playing) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
if(this.beatIndex < 0) { |
||||||
|
return 0; |
||||||
|
} else { |
||||||
|
return this.beatIndex % this.currentSong.rhythm.length; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.blurUpdated = function(x, y) { |
||||||
|
this.userInterface.blurUpdated(x, y); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.nextSong = function() { |
||||||
|
this.lastSongArray = []; |
||||||
|
var index = (this.songIndex + 1) % this.resourceManager.enabledSongs.length; |
||||||
|
this.setSong(index); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.previousSong = function() { |
||||||
|
this.lastSongArray = []; |
||||||
|
var index = ((this.songIndex - 1) + this.resourceManager.enabledSongs.length) % this.resourceManager.enabledSongs.length; |
||||||
|
this.setSong(index); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.setSong = function(index) { |
||||||
|
this.soundManager.stop(); |
||||||
|
this.songIndex = index; |
||||||
|
this.currentSong = this.resourceManager.enabledSongs[this.songIndex]; |
||||||
|
if (this.currentSong == undefined) { |
||||||
|
this.currentSong = {"name":"None", "title":"None", "rhythm":".", "source":null, "crc":"none", "sound":null, "enabled":true, "filename":"none"}; |
||||||
|
} |
||||||
|
console.log("Next song:", this.songIndex, this.currentSong); |
||||||
|
this.userInterface.setSongText(); |
||||||
|
this.loopCount = 0; |
||||||
|
if (this.currentSong.buildup) { |
||||||
|
if (this.currentSong.force) { |
||||||
|
if (this.currentSong.force != "song") { |
||||||
|
this.currentSong.buildupPlayed = false; |
||||||
|
this.doBuildup = true; |
||||||
|
} else { |
||||||
|
this.currentSong.buildupPlayed = true; |
||||||
|
this.doBuildup = false; |
||||||
|
} |
||||||
|
this.currentSong.force = null; |
||||||
|
} else { |
||||||
|
switch (this.settings.buildups) { |
||||||
|
case "off": |
||||||
|
this.currentSong.buildupPlayed = true; |
||||||
|
this.doBuildup = false; |
||||||
|
break; |
||||||
|
case "on": |
||||||
|
this.currentSong.buildupPlayed = false; |
||||||
|
this.doBuildup = true; |
||||||
|
break; |
||||||
|
case "once": |
||||||
|
this.doBuildup = !this.currentSong.buildupPlayed; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
this.resetAudio(); |
||||||
|
var that = this; |
||||||
|
this.soundManager.playSong(this.currentSong, function() { |
||||||
|
that.fillBuildup(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.fillBuildup = function() { |
||||||
|
this.beatLength = this.soundManager.loopLength / this.currentSong.rhythm.length; |
||||||
|
var buildBeats = Math.floor(this.soundManager.loopStart / this.beatLength) + 1; |
||||||
|
if (this.currentSong.buildupRhythm == null) { |
||||||
|
this.currentSong.buildupRhythm = ""; |
||||||
|
} |
||||||
|
if (this.currentSong.buildupRhythm.length < buildBeats) { |
||||||
|
console.log("Filling buildup beatmap"); |
||||||
|
while (this.currentSong.buildupRhythm.length < buildBeats) { |
||||||
|
this.currentSong.buildupRhythm = this.currentSong.buildupRhythm + "."; |
||||||
|
} |
||||||
|
} |
||||||
|
console.log("Buildup length:", buildBeats); |
||||||
|
this.beatIndex = this.doBuildup ? -this.currentSong.buildupRhythm.length : 0; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.randomSong = function() { |
||||||
|
var index=Math.floor((Math.random() * this.resourceManager.enabledSongs.length)); |
||||||
|
if (index == this.songIndex && this.resourceManager.enabledSongs.length > 1 || !(this.lastSongArray.indexOf(index) == -1)) { |
||||||
|
this.randomSong(); |
||||||
|
} else { |
||||||
|
console.log("Randoming a song!"); |
||||||
|
this.setSong(index); |
||||||
|
this.lastSongArray.push(index); |
||||||
|
noRepeat = Math.min(5, Math.floor((this.resourceManager.enabledSongs.length / 2))); |
||||||
|
while (this.lastSongArray.length > noRepeat && noRepeat >= 0) { |
||||||
|
this.lastSongArray.shift(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
/* |
||||||
|
HuesCore.prototype.onLoop = function() { |
||||||
|
this.loopCount = this.loopCount + 1; |
||||||
|
switch (this.settings.autosong) { |
||||||
|
case "loop": |
||||||
|
console.log("Checking loops"); |
||||||
|
if (this.loopCount >= this.settings.autosongDelay) { |
||||||
|
this.startSongChangeFade(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case "time": |
||||||
|
console.log("Checking times"); |
||||||
|
if (this.currentSong.sound && this.calculateSongLength(this.currentSong.sound) / 1000 * this.loopCount >= this.settings.autosongDelay * 60) { |
||||||
|
this.startSongChangeFade(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.startSongChangeFade = function() { |
||||||
|
this.fadeDirection = true; |
||||||
|
this.fadeOut = true; |
||||||
|
} |
||||||
|
*/ |
||||||
|
HuesCore.prototype.songDataUpdated = function() { |
||||||
|
if (this.currentSong) { |
||||||
|
this.beatLength = 0; |
||||||
|
this.userInterface.updateLists(); |
||||||
|
this.userInterface.updateTexts(); |
||||||
|
this.userInterface.setSongText(); |
||||||
|
this.userInterface.setImageText(); |
||||||
|
} else { |
||||||
|
this.beatLength = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.resetAudio = function() { |
||||||
|
this.samplePosition = 0; |
||||||
|
this.beatIndex = 0; |
||||||
|
this.position = 0; |
||||||
|
this.lastSC = 0; |
||||||
|
this.buildupDiff = 0; |
||||||
|
this.songDataUpdated(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.randomImage = function() { |
||||||
|
var len = this.resourceManager.enabledImages.length; |
||||||
|
var index=Math.floor(Math.random() * len); |
||||||
|
if ((index == this.imageIndex || this.lastImageArray.indexOf(index) != -1) && len > 1) { |
||||||
|
this.randomImage(); |
||||||
|
} else { |
||||||
|
this.setImage(index); |
||||||
|
this.lastImageArray.push(index); |
||||||
|
var cull = Math.min(20, Math.floor((len / 2))); |
||||||
|
while (this.lastImageArray.length > cull && cull >= 0) { |
||||||
|
this.lastImageArray.shift(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.getImageIndex = function() { |
||||||
|
return this.imageIndex; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.setImage = function(index) { |
||||||
|
this.imageIndex = index; |
||||||
|
var img=this.resourceManager.enabledImages[this.imageIndex]; |
||||||
|
if (img == this.currentImage && !(img == null)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (img) { |
||||||
|
this.currentImage = img; |
||||||
|
} else if (!this.currentImage) { |
||||||
|
this.currentImage = {"name":"None", "fullname":"None", "align":"center", "bitmap":null, "source":null, "enabled":true}; |
||||||
|
this.imageIndex = -1; |
||||||
|
this.lastImageArray = []; |
||||||
|
} |
||||||
|
this.renderer.setImage(this.currentImage); |
||||||
|
this.userInterface.setImageText(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.nextImage = function() { |
||||||
|
this.setIsFullAuto(false); |
||||||
|
var img=(this.imageIndex + 1) % this.resourceManager.enabledImages.length; |
||||||
|
this.setImage(img); |
||||||
|
this.lastImageArray = []; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.previousImage = function() { |
||||||
|
this.setIsFullAuto(false); |
||||||
|
var img=((this.imageIndex - 1) + this.resourceManager.enabledImages.length) % this.resourceManager.enabledImages.length; |
||||||
|
this.setImage(img); |
||||||
|
this.lastImageArray = []; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.randomColourIndex = function() { |
||||||
|
var index=Math.floor((Math.random() * 64)); |
||||||
|
if (index == this.colourIndex) { |
||||||
|
return this.randomColourIndex(); |
||||||
|
} |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.randomColour = function() { |
||||||
|
var index=this.randomColourIndex(); |
||||||
|
this.setColour(index); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.setColour = function(index) { |
||||||
|
this.colourIndex = index; |
||||||
|
this.renderer.setColour(this.colours[this.colourIndex].c); |
||||||
|
this.userInterface.setColourText(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.getBeat = function(index) { |
||||||
|
if(index < 0) { |
||||||
|
return this.currentSong.buildupRhythm[this.currentSong.buildupRhythm.length+index]; |
||||||
|
} else { |
||||||
|
return this.currentSong.rhythm[index % this.currentSong.rhythm.length]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.beater = function(beat) { |
||||||
|
this.userInterface.beat(); |
||||||
|
switch(beat) { |
||||||
|
case 'X': |
||||||
|
case 'x': |
||||||
|
this.renderer.doYBlur(); |
||||||
|
break; |
||||||
|
case 'O': |
||||||
|
case 'o': |
||||||
|
this.renderer.doXBlur(); |
||||||
|
break; |
||||||
|
case '+': |
||||||
|
this.renderer.doXBlur(); |
||||||
|
this.renderer.doBlackout(); |
||||||
|
break; |
||||||
|
case '¤': |
||||||
|
this.renderer.doXBlur(); |
||||||
|
this.renderer.doBlackout(true); |
||||||
|
break; |
||||||
|
case '|': |
||||||
|
this.renderer.doShortBlackout(this.beatLength); |
||||||
|
this.randomColour(); |
||||||
|
break; |
||||||
|
case ':': |
||||||
|
this.randomColour(); |
||||||
|
break; |
||||||
|
case '*': |
||||||
|
if(this.isFullAuto) { |
||||||
|
this.randomImage(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case '=': |
||||||
|
if(this.isFullAuto) { |
||||||
|
this.randomImage(); |
||||||
|
} |
||||||
|
case '~': |
||||||
|
// case: fade in build, not in rhythm. Must max out fade timer.
|
||||||
|
var maxSearch = this.currentSong.rhythm.length; |
||||||
|
if(this.beatIndex < 0) { |
||||||
|
maxSearch -= this.beatIndex; |
||||||
|
} |
||||||
|
var fadeLen; |
||||||
|
for (fadeLen = 1; fadeLen <= maxSearch; fadeLen++) { |
||||||
|
if (this.getBeat(fadeLen + this.beatIndex) != ".") { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
this.stopFade(); |
||||||
|
this.startFade(fadeLen * this.beatLength); |
||||||
|
break; |
||||||
|
} |
||||||
|
if ([".", "+", "|", "¤"].indexOf(beat) == -1) { |
||||||
|
this.renderer.clearBlackout(); |
||||||
|
} |
||||||
|
if([".", "+", ":", "*", "X", "O", "~", "="].indexOf(beat) == -1) { |
||||||
|
this.randomColour(); |
||||||
|
if (this.isFullAuto) { |
||||||
|
this.randomImage(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.getBeatString = function(length) { |
||||||
|
length = length ? length : 256; |
||||||
|
|
||||||
|
var beatString = ""; |
||||||
|
var song = this.currentSong; |
||||||
|
if (song) { |
||||||
|
if(this.beatIndex < 0) { |
||||||
|
beatString = song.buildupRhythm.slice( |
||||||
|
song.buildupRhythm.length + this.beatIndex); |
||||||
|
} else { |
||||||
|
beatString = song.rhythm.slice(this.beatIndex % song.rhythm.length); |
||||||
|
} |
||||||
|
while (beatString.length < length) { |
||||||
|
beatString += song.rhythm; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return beatString; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.setIsFullAuto = function(auto) { |
||||||
|
this.isFullAuto = auto; |
||||||
|
if (this.userInterface) { |
||||||
|
this.userInterface.modeUpdated(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*HuesCore.prototype.enterFrame = function() { |
||||||
|
this.setTexts(); |
||||||
|
if (this.fadeOut) { |
||||||
|
// 30fps frame locked, TODO
|
||||||
|
delta = this.fadeDirection ? -2 : 2; |
||||||
|
if (this.soundChannel) { |
||||||
|
fadeTo = Math.max(0, Math.min(this.currentVolume, soundManager.volume + delta)); |
||||||
|
this.soundManager.setVolume(fadeTo); |
||||||
|
if (fadeTo == 0) { |
||||||
|
this.fadeOut = false; |
||||||
|
this.fadeDirection = false; |
||||||
|
if (this.settings.autosongShuffle) { |
||||||
|
this.randomSong(); |
||||||
|
} else { |
||||||
|
this.nextSong(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}*/ |
||||||
|
|
||||||
|
HuesCore.prototype.respackLoaded = function() { |
||||||
|
this.init(); |
||||||
|
this.userInterface.updateLists(); |
||||||
|
} |
||||||
|
|
||||||
|
/*HuesCore.prototype.rightClickListener = function(event) { |
||||||
|
switch (event) { |
||||||
|
case flash.events.MouseEvent.RIGHT_CLICK: |
||||||
|
this.toggleSettingsWindow(); |
||||||
|
break; |
||||||
|
case flash.events.MouseEvent.MOUSE_WHEEL: |
||||||
|
if (event.delta > 0) { |
||||||
|
this.soundManager.increaseVolume(); |
||||||
|
} else { |
||||||
|
this.soundManager.decreaseVolume(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
}*/ |
||||||
|
|
||||||
|
HuesCore.prototype.changeUI = function(index) { |
||||||
|
if (index >= 0 && this.uiArray.length > index && !(this.userInterface == this.uiArray[index])) { |
||||||
|
if(this.userInterface) |
||||||
|
this.userInterface.disconnect(); |
||||||
|
this.userInterface = this.uiArray[index]; |
||||||
|
this.userInterface.connectCore(this); |
||||||
|
this.userInterface.updateLists(); |
||||||
|
this.userInterface.updateTexts(); |
||||||
|
this.userInterface.setSongText(); |
||||||
|
this.userInterface.setImageText(); |
||||||
|
this.userInterface.setColourText(this.colourIndex); |
||||||
|
this.userInterface.beat(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.settingsUpdated = function() { |
||||||
|
console.log("Updating according to this.settings"); |
||||||
|
this.blurMultiplier = {"low":0.5, "medium":1, "high":4}[this.settings.blurAmount]; |
||||||
|
this.blurDecayMultiplier = {"low":0.3, "medium":0.6, "high":1, "vhigh":1.6}[this.settings.blurDecay]; |
||||||
|
//this.settings.blackoutUI;
|
||||||
|
// todo: blackoutUI
|
||||||
|
switch (this.settings.colours) { |
||||||
|
case "normal": |
||||||
|
this.colours = this.oldColours; |
||||||
|
break; |
||||||
|
case "pastel": |
||||||
|
this.colours = this.pastelColours; |
||||||
|
break; |
||||||
|
case "gp": |
||||||
|
this.colours = this.weedColours; |
||||||
|
break; |
||||||
|
} |
||||||
|
switch (this.settings.ui) { |
||||||
|
case "retro": |
||||||
|
this.changeUI(0); |
||||||
|
break; |
||||||
|
case "weed": |
||||||
|
this.changeUI(1); |
||||||
|
break; |
||||||
|
case "modern": |
||||||
|
this.changeUI(2); |
||||||
|
break; |
||||||
|
case "xmas": |
||||||
|
this.changeUI(3); |
||||||
|
break; |
||||||
|
} |
||||||
|
/*if (this.autoSong == "off" && !(this.settings.autosong == "off")) { |
||||||
|
console.log("Resetting loopCount since AutoSong was enabled"); |
||||||
|
this.loopCount = 0; |
||||||
|
this.autoSong = this.settings.autosong; |
||||||
|
}*/ |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.enabledChanged = function() { |
||||||
|
this.resourceManager.rebuildEnabled(); |
||||||
|
this.userInterface.updateLists(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.hideLists = function() { |
||||||
|
this.userInterface.songList.hide(); |
||||||
|
this.userInterface.imageList.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.toggleSongList = function() { |
||||||
|
this.userInterface.songList.toggleHide(); |
||||||
|
this.userInterface.imageList.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.toggleImageList = function() { |
||||||
|
this.userInterface.imageList.toggleHide(); |
||||||
|
this.userInterface.songList.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.openSongSource = function() { |
||||||
|
if (this.currentSong && this.currentSong.source) { |
||||||
|
window.open(this.currentSong.source,'_blank'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.openImageSource = function() { |
||||||
|
if (this.currentImage && this.currentImage.source) { |
||||||
|
window.open(this.currentImage.source,'_blank'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.keyHandler = function(key) { |
||||||
|
switch (key) { |
||||||
|
case 37: // LEFT
|
||||||
|
this.previousImage(); |
||||||
|
break; |
||||||
|
case 39: // RIGHT
|
||||||
|
this.nextImage(); |
||||||
|
break; |
||||||
|
case 38: // UP
|
||||||
|
this.nextSong(); |
||||||
|
break; |
||||||
|
case 40: // DOWN
|
||||||
|
this.previousSong(); |
||||||
|
break; |
||||||
|
case 70: // F
|
||||||
|
this.setIsFullAuto(!this.isFullAuto); |
||||||
|
break; |
||||||
|
case 109: // NUMPAD_SUBTRACT
|
||||||
|
case 189: // MINUS
|
||||||
|
case 173: // MINUS, legacy
|
||||||
|
this.soundManager.decreaseVolume(); |
||||||
|
break; |
||||||
|
case 107: // NUMPAD_ADD
|
||||||
|
case 187: // EQUAL
|
||||||
|
case 61: // EQUAL, legacy
|
||||||
|
this.soundManager.increaseVolume(); |
||||||
|
break; |
||||||
|
case 77: // M
|
||||||
|
this.soundManager.toggleMute(); |
||||||
|
break; |
||||||
|
case 72: // H
|
||||||
|
this.userInterface.toggleHide(); |
||||||
|
break; |
||||||
|
case 82: // R
|
||||||
|
this.window.showRespacks(); |
||||||
|
break; |
||||||
|
case 69: // E
|
||||||
|
this.window.showEditor(); |
||||||
|
break; |
||||||
|
case 79: // O
|
||||||
|
this.window.showOptions(); |
||||||
|
break; |
||||||
|
case 73: // I
|
||||||
|
this.window.showInfo(); |
||||||
|
break; |
||||||
|
case 49: // NUMBER_1
|
||||||
|
this.changeUI(0); |
||||||
|
break; |
||||||
|
case 50: // NUMBER_2
|
||||||
|
this.changeUI(1); |
||||||
|
break; |
||||||
|
case 51: // NUMBER_3
|
||||||
|
this.changeUI(2); |
||||||
|
break; |
||||||
|
case 52: // NUMBER_4
|
||||||
|
this.changeUI(3); |
||||||
|
break; |
||||||
|
case 76: // L
|
||||||
|
this.loadLocal(); |
||||||
|
break; |
||||||
|
case 67: // C
|
||||||
|
this.toggleImageList(); |
||||||
|
break; |
||||||
|
case 83: // S
|
||||||
|
this.toggleSongList(); |
||||||
|
break; |
||||||
|
case 87: // W
|
||||||
|
this.toggleSettingsWindow(); |
||||||
|
break; |
||||||
|
case 16: // SHIFT
|
||||||
|
this.randomSong(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.error = function(message) { |
||||||
|
document.getElementById("preSub").textContent = "Error: " + message; |
||||||
|
document.getElementById("preMain").style.color = "#F00"; |
||||||
|
} |
||||||
|
|
||||||
|
HuesCore.prototype.oldColours =
|
||||||
|
[{'c': 0x000000, 'n': 'black'}, |
||||||
|
{'c': 0x550000, 'n': 'brick'}, |
||||||
|
{'c': 0xAA0000, 'n': 'crimson'}, |
||||||
|
{'c': 0xFF0000, 'n': 'red'}, |
||||||
|
{'c': 0x005500, 'n': 'turtle'}, |
||||||
|
{'c': 0x555500, 'n': 'sludge'}, |
||||||
|
{'c': 0xAA5500, 'n': 'brown'}, |
||||||
|
{'c': 0xFF5500, 'n': 'orange'}, |
||||||
|
{'c': 0x00AA00, 'n': 'green'}, |
||||||
|
{'c': 0x55AA00, 'n': 'grass'}, |
||||||
|
{'c': 0xAAAA00, 'n': 'maize'}, |
||||||
|
{'c': 0xFFAA00, 'n': 'citrus'}, |
||||||
|
{'c': 0x00FF00, 'n': 'lime'}, |
||||||
|
{'c': 0x55FF00, 'n': 'leaf'}, |
||||||
|
{'c': 0xAAFF00, 'n': 'chartreuse'}, |
||||||
|
{'c': 0xFFFF00, 'n': 'yellow'}, |
||||||
|
{'c': 0x000055, 'n': 'midnight'}, |
||||||
|
{'c': 0x550055, 'n': 'plum'}, |
||||||
|
{'c': 0xAA0055, 'n': 'pomegranate'}, |
||||||
|
{'c': 0xFF0055, 'n': 'rose'}, |
||||||
|
{'c': 0x005555, 'n': 'swamp'}, |
||||||
|
{'c': 0x555555, 'n': 'dust'}, |
||||||
|
{'c': 0xAA5555, 'n': 'dirt'}, |
||||||
|
{'c': 0xFF5555, 'n': 'blossom'}, |
||||||
|
{'c': 0x00AA55, 'n': 'sea'}, |
||||||
|
{'c': 0x55AA55, 'n': 'ill'}, |
||||||
|
{'c': 0xAAAA55, 'n': 'haze'}, |
||||||
|
{'c': 0xFFAA55, 'n': 'peach'}, |
||||||
|
{'c': 0x00FF55, 'n': 'spring'}, |
||||||
|
{'c': 0x55FF55, 'n': 'mantis'}, |
||||||
|
{'c': 0xAAFF55, 'n': 'brilliant'}, |
||||||
|
{'c': 0xFFFF55, 'n': 'canary'}, |
||||||
|
{'c': 0x0000AA, 'n': 'navy'}, |
||||||
|
{'c': 0x5500AA, 'n': 'grape'}, |
||||||
|
{'c': 0xAA00AA, 'n': 'mauve'}, |
||||||
|
{'c': 0xFF00AA, 'n': 'purple'}, |
||||||
|
{'c': 0x0055AA, 'n': 'cornflower'}, |
||||||
|
{'c': 0x5555AA, 'n': 'deep'}, |
||||||
|
{'c': 0xAA55AA, 'n': 'lilac'}, |
||||||
|
{'c': 0xFF55AA, 'n': 'lavender'}, |
||||||
|
{'c': 0x00AAAA, 'n': 'aqua'}, |
||||||
|
{'c': 0x55AAAA, 'n': 'steel'}, |
||||||
|
{'c': 0xAAAAAA, 'n': 'grey'}, |
||||||
|
{'c': 0xFFAAAA, 'n': 'pink'}, |
||||||
|
{'c': 0x00FFAA, 'n': 'bay'}, |
||||||
|
{'c': 0x55FFAA, 'n': 'marina'}, |
||||||
|
{'c': 0xAAFFAA, 'n': 'tornado'}, |
||||||
|
{'c': 0xFFFFAA, 'n': 'saltine'}, |
||||||
|
{'c': 0x0000FF, 'n': 'blue'}, |
||||||
|
{'c': 0x5500FF, 'n': 'twilight'}, |
||||||
|
{'c': 0xAA00FF, 'n': 'orchid'}, |
||||||
|
{'c': 0xFF00FF, 'n': 'magenta'}, |
||||||
|
{'c': 0x0055FF, 'n': 'azure'}, |
||||||
|
{'c': 0x5555FF, 'n': 'liberty'}, |
||||||
|
{'c': 0xAA55FF, 'n': 'royalty'}, |
||||||
|
{'c': 0xFF55FF, 'n': 'thistle'}, |
||||||
|
{'c': 0x00AAFF, 'n': 'ocean'}, |
||||||
|
{'c': 0x55AAFF, 'n': 'sky'}, |
||||||
|
{'c': 0xAAAAFF, 'n': 'periwinkle'}, |
||||||
|
{'c': 0xFFAAFF, 'n': 'carnation'}, |
||||||
|
{'c': 0x00FFFF, 'n': 'cyan'}, |
||||||
|
{'c': 0x55FFFF, 'n': 'turquoise'}, |
||||||
|
{'c': 0xAAFFFF, 'n': 'powder'}, |
||||||
|
{'c': 0xFFFFFF, 'n': 'white'}]; |
||||||
|
HuesCore.prototype.pastelColours =
|
||||||
|
[{'c': 0xCD4A4A, 'n': 'Mahogany'}, |
||||||
|
{'c': 0xFAE7B5, 'n': 'Banana Mania'}, |
||||||
|
{'c': 0x9F8170, 'n': 'Beaver'}, |
||||||
|
{'c': 0x232323, 'n': 'Black'}, |
||||||
|
{'c': 0xBC5D58, 'n': 'Chestnut'}, |
||||||
|
{'c': 0xDD9475, 'n': 'Copper'}, |
||||||
|
{'c': 0x9ACEEB, 'n': 'Cornflower'}, |
||||||
|
{'c': 0x2B6CC4, 'n': 'Denim'}, |
||||||
|
{'c': 0xEFCDB8, 'n': 'Desert Sand'}, |
||||||
|
{'c': 0x6E5160, 'n': 'Eggplant'}, |
||||||
|
{'c': 0x1DF914, 'n': 'Electric Lime'}, |
||||||
|
{'c': 0x71BC78, 'n': 'Fern'}, |
||||||
|
{'c': 0xFCD975, 'n': 'Goldenrod'}, |
||||||
|
{'c': 0xA8E4A0, 'n': 'Granny Smith Apple'}, |
||||||
|
{'c': 0x95918C, 'n': 'Gray'}, |
||||||
|
{'c': 0x1CAC78, 'n': 'Green'}, |
||||||
|
{'c': 0xFF1DCE, 'n': 'Hot Magenta'}, |
||||||
|
{'c': 0xB2EC5D, 'n': 'Inch Worm'}, |
||||||
|
{'c': 0x5D76CB, 'n': 'Indigo'}, |
||||||
|
{'c': 0xFDFC74, 'n': 'Laser Lemon'}, |
||||||
|
{'c': 0xFCB4D5, 'n': 'Lavender'}, |
||||||
|
{'c': 0xFFBD88, 'n': 'Macaroni and Cheese'}, |
||||||
|
{'c': 0x979AAA, 'n': 'Manatee'}, |
||||||
|
{'c': 0xFF8243, 'n': 'Mango Tango'}, |
||||||
|
{'c': 0xFDBCB4, 'n': 'Melon'}, |
||||||
|
{'c': 0x1A4876, 'n': 'Midnight Blue'}, |
||||||
|
{'c': 0xFFA343, 'n': 'Neon Carrot'}, |
||||||
|
{'c': 0xBAB86C, 'n': 'Olive Green'}, |
||||||
|
{'c': 0xFF7538, 'n': 'Orange'}, |
||||||
|
{'c': 0xE6A8D7, 'n': 'Orchid'}, |
||||||
|
{'c': 0x414A4C, 'n': 'Outer Space'}, |
||||||
|
{'c': 0xFF6E4A, 'n': 'Outrageous Orange'}, |
||||||
|
{'c': 0x1CA9C9, 'n': 'Pacific Blue'}, |
||||||
|
{'c': 0xC5D0E6, 'n': 'Periwinkle'}, |
||||||
|
{'c': 0x8E4585, 'n': 'Plum'}, |
||||||
|
{'c': 0x7442C8, 'n': 'Purple Heart'}, |
||||||
|
{'c': 0xD68A59, 'n': 'Raw Sienna'}, |
||||||
|
{'c': 0xE3256B, 'n': 'Razzmatazz'}, |
||||||
|
{'c': 0xEE204D, 'n': 'Red'}, |
||||||
|
{'c': 0x1FCECB, 'n': 'Robin Egg Blue'}, |
||||||
|
{'c': 0x7851A9, 'n': 'Royal Purple'}, |
||||||
|
{'c': 0xFF9BAA, 'n': 'Salmon'}, |
||||||
|
{'c': 0xFC2847, 'n': 'Scarlet'}, |
||||||
|
{'c': 0x9FE2BF, 'n': 'Sea Green'}, |
||||||
|
{'c': 0xA5694F, 'n': 'Sepia'}, |
||||||
|
{'c': 0x8A795D, 'n': 'Shadow'}, |
||||||
|
{'c': 0x45CEA2, 'n': 'Shamrock'}, |
||||||
|
{'c': 0xFB7EFD, 'n': 'Shocking Pink'}, |
||||||
|
{'c': 0xECEABE, 'n': 'Spring Green'}, |
||||||
|
{'c': 0xFD5E53, 'n': 'Sunset Orange'}, |
||||||
|
{'c': 0xFAA76C, 'n': 'Tan'}, |
||||||
|
{'c': 0xFC89AC, 'n': 'Tickle Me Pink'}, |
||||||
|
{'c': 0xDBD7D2, 'n': 'Timberwolf'}, |
||||||
|
{'c': 0x17806D, 'n': 'Tropical Rain Forest'}, |
||||||
|
{'c': 0x77DDE7, 'n': 'Turquoise Blue'}, |
||||||
|
{'c': 0xFFA089, 'n': 'Vivid Tangerine'}, |
||||||
|
{'c': 0x8F509D, 'n': 'Vivid Violet'}, |
||||||
|
{'c': 0xEDEDED, 'n': 'White'}, |
||||||
|
{'c': 0xFF43A4, 'n': 'Wild Strawberry'}, |
||||||
|
{'c': 0xFC6C85, 'n': 'Wild Watermelon'}, |
||||||
|
{'c': 0xCDA4DE, 'n': 'Wisteria'}, |
||||||
|
{'c': 0xFCE883, 'n': 'Yellow'}, |
||||||
|
{'c': 0xC5E384, 'n': 'Yellow Green'}, |
||||||
|
{'c': 0xFFB653, 'n': 'Yellow Orange'}]; |
||||||
|
HuesCore.prototype.weedColours =
|
||||||
|
[{'c': 0x00FF00, 'n': 'Green'}, |
||||||
|
{'c': 0x5A6351, 'n': 'Lizard'}, |
||||||
|
{'c': 0x636F57, 'n': 'Cactus'}, |
||||||
|
{'c': 0x4A7023, 'n': 'Kakapo'}, |
||||||
|
{'c': 0x3D5229, 'n': 'Wet Moss'}, |
||||||
|
{'c': 0x659D32, 'n': 'Tree Moss'}, |
||||||
|
{'c': 0x324F17, 'n': 'Lime Rind'}, |
||||||
|
{'c': 0x7F8778, 'n': 'Flight Jacket'}, |
||||||
|
{'c': 0xBCED91, 'n': 'Green Mist'}, |
||||||
|
{'c': 0x488214, 'n': 'Holly'}, |
||||||
|
{'c': 0x577A3A, 'n': 'Mtn Dew Bottle'}, |
||||||
|
{'c': 0x748269, 'n': 'Seaweed Roll'}, |
||||||
|
{'c': 0x83F52C, 'n': 'Neon Green'}, |
||||||
|
{'c': 0xC0D9AF, 'n': 'Lichen'}, |
||||||
|
{'c': 0xA6D785, 'n': 'Guacamole'}, |
||||||
|
{'c': 0x687E5A, 'n': 'Pond Scum'}, |
||||||
|
{'c': 0x3F602B, 'n': 'Douglas Fir'}, |
||||||
|
{'c': 0x3F6826, 'n': 'Royal Palm'}, |
||||||
|
{'c': 0x646F5E, 'n': 'Seaweed'}, |
||||||
|
{'c': 0x476A34, 'n': 'Noble Fir'}, |
||||||
|
{'c': 0x5DFC0A, 'n': 'Green Led'}, |
||||||
|
{'c': 0x435D36, 'n': 'Spinach'}, |
||||||
|
{'c': 0x84BE6A, 'n': 'Frog'}, |
||||||
|
{'c': 0x5B9C64, 'n': 'Emerald'}, |
||||||
|
{'c': 0x3A6629, 'n': 'Circuit Board'}, |
||||||
|
{'c': 0x308014, 'n': 'Sapgreen'}, |
||||||
|
{'c': 0x31B94D, 'n': 'Pool Table'}, |
||||||
|
{'c': 0x55AE3A, 'n': 'Leaf'}, |
||||||
|
{'c': 0x4DBD33, 'n': 'Grass'}, |
||||||
|
{'c': 0x596C56, 'n': 'Snake'}, |
||||||
|
{'c': 0x86C67C, 'n': '100 Euro'}, |
||||||
|
{'c': 0x7BCC70, 'n': 'Night Vision'}, |
||||||
|
{'c': 0xA020F0, 'n': 'Purple'}, |
||||||
|
{'c': 0x9B30FF, 'n': 'Purple'}, |
||||||
|
{'c': 0x912CEE, 'n': 'Purple'}, |
||||||
|
{'c': 0x7D26CD, 'n': 'Purple'}, |
||||||
|
{'c': 0xAA00FF, 'n': 'Purple'}, |
||||||
|
{'c': 0x800080, 'n': 'Purple'}, |
||||||
|
{'c': 0xA74CAB, 'n': 'Turnip'}, |
||||||
|
{'c': 0x8F5E99, 'n': 'Violet'}, |
||||||
|
{'c': 0x816687, 'n': 'Eggplant'}, |
||||||
|
{'c': 0xCC00FF, 'n': 'Grape'}, |
||||||
|
{'c': 0x820BBB, 'n': 'Wild Violet'}, |
||||||
|
{'c': 0x660198, 'n': 'Concord Grape'}, |
||||||
|
{'c': 0x71637D, 'n': 'Garden Plum'}, |
||||||
|
{'c': 0xB272A6, 'n': 'Purple Fish'}, |
||||||
|
{'c': 0x5C246E, 'n': 'Ultramarine Violet'}, |
||||||
|
{'c': 0x5E2D79, 'n': 'Purple Rose'}, |
||||||
|
{'c': 0x683A5E, 'n': 'Sea Urchin'}, |
||||||
|
{'c': 0x91219E, 'n': 'Cobalt Violet Deep'}, |
||||||
|
{'c': 0x8B668B, 'n': 'Plum'}, |
||||||
|
{'c': 0x9932CD, 'n': 'Dark Orchid'}, |
||||||
|
{'c': 0xBF5FFF, 'n': 'Violet Flower'}, |
||||||
|
{'c': 0xBDA0CB, 'n': 'Purple Candy'}, |
||||||
|
{'c': 0x551A8B, 'n': 'Deep Purple'}, |
||||||
|
{'c': 0xB5509C, 'n': 'Thistle'}, |
||||||
|
{'c': 0x871F78, 'n': 'Dark Purple'}, |
||||||
|
{'c': 0x9C6B98, 'n': 'Purple Ink'}, |
||||||
|
{'c': 0xDB70DB, 'n': 'Orchid'}, |
||||||
|
{'c': 0x990099, 'n': 'True Purple'}, |
||||||
|
{'c': 0x8B008B, 'n': 'Darkmagenta'}, |
||||||
|
{'c': 0xB62084, 'n': "Harold's Crayon"}, |
||||||
|
{'c': 0x694489, 'n': 'Purple Rain'}, |
||||||
|
{'c': 0xFFD700, 'n': 'Gold'}]; |
@ -0,0 +1,387 @@ |
|||||||
|
function HuesSettings(defaults) { |
||||||
|
this.core = null; |
||||||
|
// TODO: HTML5 local storage or something
|
||||||
|
} |
||||||
|
|
||||||
|
HuesSettings.prototype.connectCore = function(core) { |
||||||
|
this.core = core; |
||||||
|
}; |
||||||
|
|
||||||
|
//class HuesSettings
|
||||||
|
package
|
||||||
|
{ |
||||||
|
import flash.display.*; |
||||||
|
import flash.net.*; |
||||||
|
|
||||||
|
public class HuesSettings extends Object |
||||||
|
{ |
||||||
|
public function HuesSettings() |
||||||
|
{ |
||||||
|
this.bools = ["imageSmoothing", "blurEnabled", "smartAlign", "autosongShuffle", "blackoutUI"]; |
||||||
|
this.numbs = ["autosongDelay"]; |
||||||
|
super(); |
||||||
|
trace("Settings created"); |
||||||
|
this.callbacks = []; |
||||||
|
this.currentSettings = {}; |
||||||
|
this.setDefaults(); |
||||||
|
this.load(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set autosongDelay(arg1:int):void |
||||||
|
{ |
||||||
|
trace("AutoSong delay:", arg1); |
||||||
|
this.currentSettings["autosongDelay"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set autosongShuffle(arg1:Boolean):void |
||||||
|
{ |
||||||
|
trace("Image scaling:", arg1); |
||||||
|
this.currentSettings["autosongShuffle"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set imagescaling(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Image scaling:", arg1); |
||||||
|
this.currentSettings["imagescaling"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set colors(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Colors:", arg1); |
||||||
|
this.currentSettings["colors"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
internal function setDefaults():void |
||||||
|
{ |
||||||
|
this.currentSettings = {"flashQuality":flash.display.StageQuality.HIGH, "imageSmoothing":true, "blurEnabled":true, "blurAmount":"medium", "blurDecay":"high", "channels":"stereo", "smartAlign":true, "buildups":"once", "blendMode":"hard", "ui":"modern", "autosong":"off", "autosongDelay":5, "autosongShuffle":true, "imagescaling":"on", "colors":"normal", "blackoutUI":false}; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function defaults():void |
||||||
|
{ |
||||||
|
this.setDefaults(); |
||||||
|
this.saveSettings(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function getSettingsFromParameters(arg1:Object):void |
||||||
|
{ |
||||||
|
var loc1:*=undefined; |
||||||
|
var loc2:*=null; |
||||||
|
if (arg1)
|
||||||
|
{ |
||||||
|
var loc3:*=0; |
||||||
|
var loc4:*=arg1; |
||||||
|
for (loc2 in loc4)
|
||||||
|
{ |
||||||
|
loc1 = arg1[loc2]; |
||||||
|
if (this.bools.indexOf(loc2) == -1)
|
||||||
|
{ |
||||||
|
if (this.numbs.indexOf(loc2) != -1)
|
||||||
|
{ |
||||||
|
if (loc1.match(new RegExp("\\d+")))
|
||||||
|
{ |
||||||
|
loc1 = int(loc1); |
||||||
|
if (loc2 == "autosongDelay")
|
||||||
|
{ |
||||||
|
loc1 = Math.max(1, loc1); |
||||||
|
} |
||||||
|
} |
||||||
|
else
|
||||||
|
{ |
||||||
|
loc1 = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else if (loc1 != "true")
|
||||||
|
{ |
||||||
|
if (loc1 != "false")
|
||||||
|
{ |
||||||
|
loc1 = null; |
||||||
|
} |
||||||
|
else
|
||||||
|
{ |
||||||
|
loc1 = false; |
||||||
|
} |
||||||
|
} |
||||||
|
else
|
||||||
|
{ |
||||||
|
loc1 = true; |
||||||
|
} |
||||||
|
if (loc1 == null)
|
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
this.currentSettings[loc2] = loc1; |
||||||
|
} |
||||||
|
this.saveSettings(); |
||||||
|
this.callCallbacks(); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function saveSettings():void |
||||||
|
{ |
||||||
|
var so:flash.net.SharedObject; |
||||||
|
var k:String; |
||||||
|
|
||||||
|
var loc1:*; |
||||||
|
k = null; |
||||||
|
trace("Saving settings!"); |
||||||
|
so = flash.net.SharedObject.getLocal(this.objectName); |
||||||
|
var loc2:*=0; |
||||||
|
var loc3:*=this.currentSettings; |
||||||
|
for (k in loc3)
|
||||||
|
{ |
||||||
|
so.data[k] = this.currentSettings[k]; |
||||||
|
} |
||||||
|
so.data.saved = true; |
||||||
|
try
|
||||||
|
{ |
||||||
|
so.flush(); |
||||||
|
} |
||||||
|
catch (e:Error) |
||||||
|
{ |
||||||
|
trace("Saving settings failed, oh well"); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function load():void |
||||||
|
{ |
||||||
|
var loc1:*=flash.net.SharedObject.getLocal(this.objectName); |
||||||
|
if ("saved" in loc1.data)
|
||||||
|
{ |
||||||
|
trace("Old settings"); |
||||||
|
this.currentSettings = loc1.data; |
||||||
|
} |
||||||
|
else
|
||||||
|
{ |
||||||
|
trace("Defaults"); |
||||||
|
this.defaults(); |
||||||
|
} |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set blackoutUI(arg1:Boolean):void |
||||||
|
{ |
||||||
|
trace("Blackout UI:", arg1); |
||||||
|
this.currentSettings["blackoutUI"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function addCallback(arg1:Function):void |
||||||
|
{ |
||||||
|
if (this.callbacks.indexOf(arg1) == -1)
|
||||||
|
{ |
||||||
|
this.callbacks.push(arg1); |
||||||
|
arg1(); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function callCallbacks():void |
||||||
|
{ |
||||||
|
var loc1:*=undefined; |
||||||
|
this.saveSettings(); |
||||||
|
var loc2:*=0; |
||||||
|
var loc3:*=this.callbacks; |
||||||
|
for each (loc1 in loc3)
|
||||||
|
{ |
||||||
|
loc1(); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function get flashQuality():String |
||||||
|
{ |
||||||
|
return this.currentSettings["flashQuality"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get imageSmoothing():Boolean |
||||||
|
{ |
||||||
|
return this.currentSettings["imageSmoothing"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get blurEnabled():Boolean |
||||||
|
{ |
||||||
|
return this.currentSettings["blurEnabled"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get blurAmount():String |
||||||
|
{ |
||||||
|
return this.currentSettings["blurAmount"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get blurDecay():String |
||||||
|
{ |
||||||
|
return this.currentSettings["blurDecay"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get channels():String |
||||||
|
{ |
||||||
|
return this.currentSettings["channels"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get smartAlign():Boolean |
||||||
|
{ |
||||||
|
return this.currentSettings["smartAlign"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get buildups():String |
||||||
|
{ |
||||||
|
return this.currentSettings["buildups"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get blendMode():String |
||||||
|
{ |
||||||
|
return this.currentSettings["blendMode"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get ui():String |
||||||
|
{ |
||||||
|
return this.currentSettings["ui"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get autosong():String |
||||||
|
{ |
||||||
|
return this.currentSettings["autosong"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get autosongDelay():int |
||||||
|
{ |
||||||
|
return this.currentSettings["autosongDelay"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get autosongShuffle():Boolean |
||||||
|
{ |
||||||
|
return this.currentSettings["autosongShuffle"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get imagescaling():String |
||||||
|
{ |
||||||
|
return this.currentSettings["imagescaling"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get colors():String |
||||||
|
{ |
||||||
|
return this.currentSettings["colors"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function get blackoutUI():Boolean |
||||||
|
{ |
||||||
|
return this.currentSettings["blackoutUI"]; |
||||||
|
} |
||||||
|
|
||||||
|
public function set flashQuality(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Flash quality:", arg1); |
||||||
|
this.currentSettings["flashQuality"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set imageSmoothing(arg1:Boolean):void |
||||||
|
{ |
||||||
|
trace("Image smoothing:", arg1); |
||||||
|
this.currentSettings["imageSmoothing"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set blurEnabled(arg1:Boolean):void |
||||||
|
{ |
||||||
|
trace("Blur:", arg1); |
||||||
|
this.currentSettings["blurEnabled"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set blurAmount(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Blur amount:", arg1); |
||||||
|
this.currentSettings["blurAmount"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set blurDecay(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Blur decay:", arg1); |
||||||
|
this.currentSettings["blurDecay"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set channels(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Channels:", arg1); |
||||||
|
this.currentSettings["channels"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set smartAlign(arg1:Boolean):void |
||||||
|
{ |
||||||
|
trace("Smart align:", arg1); |
||||||
|
this.currentSettings["smartAlign"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set buildups(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Buildups:", arg1); |
||||||
|
this.currentSettings["buildups"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set blendMode(arg1:String):void |
||||||
|
{ |
||||||
|
trace("Blend mode:", arg1); |
||||||
|
this.currentSettings["blendMode"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set ui(arg1:String):void |
||||||
|
{ |
||||||
|
trace("UI:", arg1); |
||||||
|
this.currentSettings["ui"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
public function set autosong(arg1:String):void |
||||||
|
{ |
||||||
|
trace("AutoSong:", arg1); |
||||||
|
this.currentSettings["autosong"] = arg1; |
||||||
|
this.callCallbacks(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
internal var currentSettings:Object; |
||||||
|
|
||||||
|
internal var bools:Array; |
||||||
|
|
||||||
|
internal var numbs:Array; |
||||||
|
|
||||||
|
internal var objectName:*="HuesSettings51"; |
||||||
|
|
||||||
|
internal var callbacks:Array; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,423 @@ |
|||||||
|
/* |
||||||
|
Base UI Class for Hues display. Parent is an element |
||||||
|
to put all your own elements under, but make a div |
||||||
|
underneath so it can be entirely hidden. |
||||||
|
*/ |
||||||
|
function HuesUI(parent) { |
||||||
|
if(!parent) { |
||||||
|
parent = document.getElementById("huesUI"); |
||||||
|
} |
||||||
|
this.root = document.createElement("div"); |
||||||
|
this.root.className = this.constructor.name; |
||||||
|
parent.appendChild(this.root); |
||||||
|
this.root.style.display = "none"; |
||||||
|
|
||||||
|
this.core = null; |
||||||
|
|
||||||
|
this.imageName = null; |
||||||
|
this.imageLink = null; |
||||||
|
|
||||||
|
this.songName = null; |
||||||
|
this.songLink = null; |
||||||
|
|
||||||
|
this.hueName = null; |
||||||
|
|
||||||
|
this.volInput = null; |
||||||
|
this.volLabel = null; |
||||||
|
|
||||||
|
this.initUI(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.initUI = function() { |
||||||
|
var doc = this.root.ownerDocument |
||||||
|
|
||||||
|
var imageName = doc.createElement("div"); |
||||||
|
this.imageName = imageName; |
||||||
|
|
||||||
|
this.imageLink = doc.createElement("a"); |
||||||
|
this.imageLink.target = "_blank"; |
||||||
|
this.imageName.appendChild(this.imageLink); |
||||||
|
|
||||||
|
var songName = doc.createElement("div"); |
||||||
|
this.songName = songName; |
||||||
|
|
||||||
|
this.songLink = doc.createElement("a"); |
||||||
|
this.songLink.target = "_blank"; |
||||||
|
this.songName.appendChild(this.songLink); |
||||||
|
|
||||||
|
var hueName = doc.createElement("div"); |
||||||
|
this.hueName = hueName; |
||||||
|
|
||||||
|
//this.setupVolume(leftBox)
|
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.connectCore = function(core) { |
||||||
|
this.core = core; |
||||||
|
this.root.style.display = "block"; |
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.disconnect = function() { |
||||||
|
this.core = null; |
||||||
|
this.root.style.display = "none"; |
||||||
|
} |
||||||
|
|
||||||
|
// May do nothing, may scale elements if needed etc etc
|
||||||
|
HuesUI.prototype.resize = function() {} |
||||||
|
HuesUI.prototype.modeUpdated = function() {} |
||||||
|
HuesUI.prototype.beat = function() {} |
||||||
|
HuesUI.prototype.updateTime = function() {} |
||||||
|
HuesUI.prototype.blurUpdated = function(x, y) {} |
||||||
|
|
||||||
|
HuesUI.prototype.setSongText = function() { |
||||||
|
var song = this.core.currentSong; |
||||||
|
|
||||||
|
if(!song) |
||||||
|
return; |
||||||
|
|
||||||
|
this.songLink.textContent = song.title.toUpperCase(); |
||||||
|
this.songLink.href = song.source; |
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.setImageText = function() { |
||||||
|
var image = this.core.currentImage; |
||||||
|
|
||||||
|
if(!image) |
||||||
|
return; |
||||||
|
|
||||||
|
this.imageLink.textContent = image.fullname.toUpperCase(); |
||||||
|
this.imageLink.href = image.source; |
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.setColourText = function(colour) { |
||||||
|
var colour = this.core.colours[this.core.colourIndex]; |
||||||
|
|
||||||
|
this.hueName.textContent = colour.n.toUpperCase(); |
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.updateLists = function() { |
||||||
|
var songs = this.core.resourceManager.enabledSongs; |
||||||
|
var images = this.core.resourceManager.enabledImages; |
||||||
|
// TODO display this
|
||||||
|
} |
||||||
|
|
||||||
|
HuesUI.prototype.updateTexts = function() { |
||||||
|
// Timer, beat counter
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
Individual UIs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||||
|
*/ |
||||||
|
|
||||||
|
function RetroUI() { |
||||||
|
this.container = null; |
||||||
|
this.mode = null; |
||||||
|
this.beatBar = null; |
||||||
|
this.beatCount = null; |
||||||
|
this.timer = null; |
||||||
|
this.colourIndex = null; |
||||||
|
this.version = null; |
||||||
|
HuesUI.call(this); |
||||||
|
} |
||||||
|
RetroUI.prototype = Object.create(HuesUI.prototype); |
||||||
|
RetroUI.prototype.constructor = RetroUI; |
||||||
|
|
||||||
|
RetroUI.prototype.initUI = function() { |
||||||
|
HuesUI.prototype.initUI.call(this); |
||||||
|
|
||||||
|
var doc = this.root.ownerDocument; |
||||||
|
|
||||||
|
var container = doc.createElement("div"); |
||||||
|
container.className = "hues-r-container"; |
||||||
|
this.root.appendChild(container); |
||||||
|
this.container = container; |
||||||
|
|
||||||
|
this.mode = doc.createElement("div"); |
||||||
|
container.appendChild(this.mode); |
||||||
|
container.appendChild(this.imageName); |
||||||
|
|
||||||
|
this.timer = doc.createElement("div"); |
||||||
|
this.timer.textContent = "T=$0x0000"; |
||||||
|
container.appendChild(this.timer); |
||||||
|
|
||||||
|
this.beatCount = doc.createElement("div"); |
||||||
|
this.beatCount.textContent = "B=$0x00"; |
||||||
|
container.appendChild(this.beatCount); |
||||||
|
|
||||||
|
this.xBlur = doc.createElement("div"); |
||||||
|
this.xBlur.textContent = "X=$0x00"; |
||||||
|
container.appendChild(this.xBlur); |
||||||
|
|
||||||
|
this.yBlur = doc.createElement("div"); |
||||||
|
this.yBlur.textContent = "Y=$0x00"; |
||||||
|
container.appendChild(this.yBlur); |
||||||
|
|
||||||
|
this.colourIndex = doc.createElement("div"); |
||||||
|
this.colourIndex.textContent = "C=$0x00"; |
||||||
|
container.appendChild(this.colourIndex); |
||||||
|
|
||||||
|
this.version = doc.createElement("div"); |
||||||
|
container.appendChild(this.version); |
||||||
|
|
||||||
|
container.appendChild(this.hueName); |
||||||
|
container.appendChild(this.songName); |
||||||
|
|
||||||
|
this.beatBar = doc.createElement("div"); |
||||||
|
container.appendChild(this.beatBar); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.connectCore = function(core) { |
||||||
|
HuesUI.prototype.connectCore.call(this, core); |
||||||
|
|
||||||
|
this.version.textContent = "V=$" + core.version; |
||||||
|
this.modeUpdated(); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.modeUpdated = function() { |
||||||
|
this.mode.textContent = "M=" + this.core.getCurrentMode(); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.blurUpdated = function(x, y) { |
||||||
|
x = Math.floor(x * 0xFF); |
||||||
|
y = Math.floor(y * 0xFF);; |
||||||
|
this.xBlur.textContent = "X=" + this.intToHex2(x); |
||||||
|
this.yBlur.textContent = "Y=" + this.intToHex2(y); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.setImageText = function() { |
||||||
|
var image = this.core.currentImage; |
||||||
|
|
||||||
|
if(!image) |
||||||
|
return; |
||||||
|
|
||||||
|
this.imageLink.textContent = "I=" + image.name.toUpperCase(); |
||||||
|
this.imageLink.href = image.source; |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.setColourText = function(colour) { |
||||||
|
HuesUI.prototype.setColourText.call(this, colour); |
||||||
|
|
||||||
|
this.colourIndex.textContent = "C=" + this.intToHex2(this.core.colourIndex); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.beat = function() { |
||||||
|
var beats = this.core.getBeatString(); |
||||||
|
var rest = beats.slice(1); |
||||||
|
|
||||||
|
this.beatBar.textContent = ">>" + rest; |
||||||
|
|
||||||
|
this.beatCount.textContent = "B=" + this.intToHex2(this.core.getSafeBeatIndex()); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.updateTime = function(time) { |
||||||
|
time = Math.floor(time * 1000); |
||||||
|
this.timer.textContent = "T=" + this.intToHex4(time); |
||||||
|
} |
||||||
|
|
||||||
|
RetroUI.prototype.intToHex2 = function(num) { |
||||||
|
return '$0x' + ("00"+num.toString(16)).slice(-2); |
||||||
|
} |
||||||
|
RetroUI.prototype.intToHex4 = function(num) { |
||||||
|
return '$0x' + ("0000"+num.toString(16)).slice(-4); |
||||||
|
} |
||||||
|
|
||||||
|
function ModernUI() { |
||||||
|
this.beatBar = null; |
||||||
|
this.beatLeft = null; |
||||||
|
this.beatRight = null; |
||||||
|
this.beatCenter = null; |
||||||
|
this.rightBox = null; |
||||||
|
this.leftBox = null; |
||||||
|
this.controls = null; |
||||||
|
|
||||||
|
HuesUI.call(this); |
||||||
|
} |
||||||
|
ModernUI.prototype = Object.create(HuesUI.prototype); |
||||||
|
ModernUI.prototype.constructor = ModernUI; |
||||||
|
|
||||||
|
ModernUI.prototype.initUI = function() { |
||||||
|
HuesUI.prototype.initUI.call(this); |
||||||
|
|
||||||
|
var doc = this.root.ownerDocument; |
||||||
|
|
||||||
|
this.imageName.className = "hues-m-imagename"; |
||||||
|
this.songName.className = "hues-m-songtitle"; |
||||||
|
|
||||||
|
var controls = doc.createElement("div"); |
||||||
|
controls.className = "hues-m-controls"; |
||||||
|
this.root.appendChild(controls); |
||||||
|
this.controls = controls; |
||||||
|
|
||||||
|
controls.appendChild(this.imageName); |
||||||
|
controls.appendChild(this.songName); |
||||||
|
|
||||||
|
var leftBox = doc.createElement("div"); |
||||||
|
leftBox.className = "hues-m-leftbox"; |
||||||
|
controls.appendChild(leftBox); |
||||||
|
this.leftBox = leftBox; |
||||||
|
|
||||||
|
this.hueName.className = "hues-m-huename"; |
||||||
|
leftBox.appendChild(this.hueName); |
||||||
|
|
||||||
|
var rightBox = doc.createElement("div"); |
||||||
|
rightBox.className = "hues-m-rightbox"; |
||||||
|
controls.appendChild(rightBox); |
||||||
|
this.rightBox = rightBox; |
||||||
|
|
||||||
|
var beatBar = doc.createElement("div"); |
||||||
|
beatBar.className = "hues-m-beatbar"; |
||||||
|
this.root.appendChild(beatBar); |
||||||
|
this.beatBar = beatBar; |
||||||
|
|
||||||
|
var beatLeft = doc.createElement("div"); |
||||||
|
beatLeft.className = "hues-m-beatleft"; |
||||||
|
beatBar.appendChild(beatLeft); |
||||||
|
this.beatLeft = beatLeft; |
||||||
|
|
||||||
|
var beatRight = doc.createElement("div"); |
||||||
|
beatRight.className = "hues-m-beatright"; |
||||||
|
beatBar.appendChild(beatRight); |
||||||
|
this.beatRight = beatRight; |
||||||
|
|
||||||
|
var beatCenter = doc.createElement("div"); |
||||||
|
beatCenter.className = "hues-m-beatcenter"; |
||||||
|
this.root.appendChild(beatCenter); |
||||||
|
this.beatCenter = beatCenter; |
||||||
|
} |
||||||
|
|
||||||
|
ModernUI.prototype.beat = function() { |
||||||
|
var beats = this.core.getBeatString(); |
||||||
|
|
||||||
|
var current = beats[0]; |
||||||
|
var rest = beats.slice(1); |
||||||
|
|
||||||
|
this.beatLeft.textContent = rest; |
||||||
|
this.beatRight.textContent = rest; |
||||||
|
|
||||||
|
|
||||||
|
if (current != ".") { |
||||||
|
while (this.beatCenter.firstElementChild) { |
||||||
|
this.beatCenter.removeChild(this.beatCenter.firstElementChild); |
||||||
|
} |
||||||
|
var span = this.beatCenter.ownerDocument.createElement("span"); |
||||||
|
span.textContent = current; |
||||||
|
this.beatCenter.appendChild(span); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ModernUI.prototype.setSongText = function() { |
||||||
|
HuesUI.prototype.setSongText.call(this); |
||||||
|
|
||||||
|
if(!this.core.currentSong) |
||||||
|
return; |
||||||
|
|
||||||
|
var name = this.songName; |
||||||
|
|
||||||
|
name.className = "hues-m-songtitle" |
||||||
|
if (name.offsetWidth > name.clientWidth) { |
||||||
|
name.className = "hues-m-songtitle small" |
||||||
|
} |
||||||
|
if (name.offsetWidth > name.clientWidth) { |
||||||
|
name.className = "hues-m-songtitle x-small" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ModernUI.prototype.setImageText = function() { |
||||||
|
HuesUI.prototype.setImageText.call(this); |
||||||
|
|
||||||
|
if(!this.core.currentImage) |
||||||
|
return; |
||||||
|
|
||||||
|
var name = this.imageName |
||||||
|
|
||||||
|
name.className = "hues-m-imagename" |
||||||
|
if (name.offsetWidth > name.clientWidth) { |
||||||
|
name.className = "hues-m-imagename small" |
||||||
|
} |
||||||
|
if (name.offsetWidth > name.clientWidth) { |
||||||
|
name.className = "hues-m-imagename x-small" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function WeedUI() { |
||||||
|
RetroUI.call(this); |
||||||
|
|
||||||
|
this.xVariance = 10; |
||||||
|
this.yVariance = 20; |
||||||
|
} |
||||||
|
WeedUI.prototype = Object.create(RetroUI.prototype); |
||||||
|
WeedUI.prototype.constructor = WeedUI; |
||||||
|
|
||||||
|
WeedUI.prototype.initUI = function() { |
||||||
|
RetroUI.prototype.initUI.call(this); |
||||||
|
|
||||||
|
this.container.removeChild(this.beatBar) |
||||||
|
var doc = this.root.ownerDocument; |
||||||
|
|
||||||
|
var beatBar = doc.createElement("div"); |
||||||
|
beatBar.className = "hues-w-beatbar"; |
||||||
|
this.root.appendChild(beatBar); |
||||||
|
this.beatBar = beatBar; |
||||||
|
|
||||||
|
var beatLeft = doc.createElement("div"); |
||||||
|
beatLeft.className = "hues-w-beatleft"; |
||||||
|
beatBar.appendChild(beatLeft); |
||||||
|
this.beatLeft = beatLeft; |
||||||
|
|
||||||
|
var beatRight = doc.createElement("div"); |
||||||
|
beatRight.className = "hues-w-beatright"; |
||||||
|
beatBar.appendChild(beatRight); |
||||||
|
this.beatRight = beatRight; |
||||||
|
} |
||||||
|
|
||||||
|
WeedUI.prototype.beat = function() { |
||||||
|
var beats = this.core.getBeatString(); |
||||||
|
var rest = beats.slice(1); |
||||||
|
|
||||||
|
this.beatLeft.textContent = rest; |
||||||
|
this.beatRight.textContent = rest; |
||||||
|
|
||||||
|
this.beatCount.textContent = "B=" + this.intToHex2(this.core.getSafeBeatIndex()); |
||||||
|
|
||||||
|
if(["x", "o", "X", "O"].indexOf(beats[0]) != -1) { |
||||||
|
var doc = this.root.ownerDocument; |
||||||
|
var beatCenter = doc.createElement("div"); |
||||||
|
beatCenter.className = "hues-w-beataccent"; |
||||||
|
var rot = this.round10(15 - Math.random() * 30); |
||||||
|
var x = this.round10(- this.xVariance / 2 + Math.random() * this.xVariance); |
||||||
|
var y = this.round10(30 - this.yVariance / 2 + Math.random() * this.yVariance); |
||||||
|
var transform = "rotate(" + rot + "deg) translate(" + x + "px," + y + "px)"; |
||||||
|
beatCenter.style.MozTransform = transform; |
||||||
|
beatCenter.style.webkitTransform = transform; |
||||||
|
beatCenter.style.transform = transform; |
||||||
|
beatCenter.textContent = beats[0].toUpperCase(); |
||||||
|
this.root.appendChild(beatCenter); |
||||||
|
window.setTimeout(this.getRemoveBeat(beatCenter), 1500); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
WeedUI.prototype.round10 = function(num) { |
||||||
|
return Math.round(num * 10) / 10; |
||||||
|
} |
||||||
|
|
||||||
|
WeedUI.prototype.getRemoveBeat = function(element) { |
||||||
|
var that = this; |
||||||
|
return function() { |
||||||
|
that.root.removeChild(element); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function XmasUI() { |
||||||
|
ModernUI.call(this); |
||||||
|
|
||||||
|
this.controls.removeChild(this.leftBox); |
||||||
|
this.controls.removeChild(this.rightBox); |
||||||
|
|
||||||
|
this.leftBox = this.rightBox = this.hueName = null; |
||||||
|
|
||||||
|
this.controls.className = "hues-x-controls"; |
||||||
|
this.beatBar.className = "hues-x-beatbar"; |
||||||
|
} |
||||||
|
XmasUI.prototype = Object.create(ModernUI.prototype); |
||||||
|
XmasUI.prototype.constructor = XmasUI; |
||||||
|
|
||||||
|
XmasUI.prototype.setColourText = function(colour) {}; |
@ -0,0 +1,132 @@ |
|||||||
|
function Resources() { |
||||||
|
this.resourcePacks = []; |
||||||
|
|
||||||
|
this.allSongs = []; |
||||||
|
this.allImages = []; |
||||||
|
this.enabledSongs = []; |
||||||
|
this.enabledImages = []; |
||||||
|
|
||||||
|
this.toLoad = 0; |
||||||
|
this.progressState = []; |
||||||
|
this.rToLoad = [] |
||||||
|
this.loadFinishCallback = null; |
||||||
|
this.progressCallback = null; |
||||||
|
} |
||||||
|
|
||||||
|
// Array of URLs to load, and a callback for when we're done
|
||||||
|
// Preserves order of URLs being loaded
|
||||||
|
Resources.prototype.addAll = function(urls, callback, progressCallback) { |
||||||
|
var that = this; |
||||||
|
this.toLoad += urls.length; |
||||||
|
if(progressCallback) { |
||||||
|
this.progressCallback = progressCallback; |
||||||
|
this.progressState = Array.apply(null, Array(urls.length)).map(Number.prototype.valueOf,0); |
||||||
|
} |
||||||
|
if(callback) { |
||||||
|
this.loadFinishCallback = callback; |
||||||
|
} |
||||||
|
for(var i = 0; i < urls.length; i++) { |
||||||
|
var r = new Respack(); |
||||||
|
this.rToLoad.push(r); |
||||||
|
r.loadFromURL(urls[i], function() { |
||||||
|
that.toLoad--; |
||||||
|
if(that.toLoad <= 0) { |
||||||
|
// could use a while() and shift(), but it'd be slower
|
||||||
|
for(var i = 0; i < that.rToLoad.length; i++) { |
||||||
|
that.addPack(that.rToLoad[i]); |
||||||
|
} |
||||||
|
that.rToLoad = []; |
||||||
|
if(that.loadFinishCallback) { |
||||||
|
that.loadFinishCallback(); |
||||||
|
that.loadFinishCallback = null; |
||||||
|
} |
||||||
|
that.progressCallback = null; |
||||||
|
} |
||||||
|
}, this.createProgCallback(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.createProgCallback = function(i) { |
||||||
|
var that = this; |
||||||
|
return function(progress) { |
||||||
|
that.progressState[i] = progress; |
||||||
|
that.updateProgress(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.updateProgress = function() { |
||||||
|
var total = 0; |
||||||
|
for(var i = 0; i < this.progressState.length; i++) { |
||||||
|
total += this.progressState[i]; |
||||||
|
} |
||||||
|
total /= this.progressState.length; |
||||||
|
this.progressCallback(total); |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.addPack = function(pack) { |
||||||
|
console.log("Added", pack.name, "to respacks"); |
||||||
|
this.resourcePacks.push(pack); |
||||||
|
this.addResourcesToArrays(pack); |
||||||
|
this.rebuildEnabled(); |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.addResourcesToArrays = function(pack) { |
||||||
|
this.allImages = this.allImages.concat(pack.images); |
||||||
|
this.allSongs = this.allSongs.concat(pack.songs); |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.rebuildArrays = function() { |
||||||
|
this.allSongs = []; |
||||||
|
this.allImages = []; |
||||||
|
this.allAnimations = []; |
||||||
|
|
||||||
|
for(var i = 0; i < this.resourcePacks.length; i++) { |
||||||
|
this.addResourcesToArrays(this.resourcePacks[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.rebuildEnabled = function() { |
||||||
|
enabledSongs = []; |
||||||
|
enabledImages = []; |
||||||
|
_enabledAnimations = []; |
||||||
|
|
||||||
|
for(var i = 0; i < this.resourcePacks.length; i++) { |
||||||
|
var pack = this.resourcePacks[i]; |
||||||
|
if (pack["enabled"] != true) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
for(var j = 0; j < pack.songs.length; j++) { |
||||||
|
var song = pack.songs[j]; |
||||||
|
if (song.enabled && this.enabledSongs.indexOf(song) == -1) { |
||||||
|
this.enabledSongs.push(song); |
||||||
|
} |
||||||
|
} |
||||||
|
for(var j = 0; j < pack.images.length; j++) { |
||||||
|
var image = pack.images[j]; |
||||||
|
if (image.enabled && this.enabledImages.indexOf(image) == -1) { |
||||||
|
this.enabledImages.push(image); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.removePack = function(pack) { |
||||||
|
var index = this.resourcePacks.indexOf(pack); |
||||||
|
if (index != -1) { |
||||||
|
this.resourcePacks.splice(index, 1); |
||||||
|
this.rebuildArrays(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.removeAllPacks = function() { |
||||||
|
this.resourcePacks = []; |
||||||
|
this.rebuildArrays(); |
||||||
|
} |
||||||
|
|
||||||
|
Resources.prototype.getSongNames = function() { |
||||||
|
var names = [] |
||||||
|
for(var i = 0; i < this.allSongs.length; i++) { |
||||||
|
names.push(this.allSongs[i]); |
||||||
|
} |
||||||
|
return names; |
||||||
|
} |
@ -0,0 +1,494 @@ |
|||||||
|
var debugConsole = false; |
||||||
|
function debug() { |
||||||
|
if(debugConsole) { |
||||||
|
console.log.apply(window.console, arguments); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Respack(url) { |
||||||
|
this.songs = []; |
||||||
|
this.songQueue = []; |
||||||
|
this.images = []; |
||||||
|
this.imageQueue = []; |
||||||
|
|
||||||
|
this.name = "<no name>"; |
||||||
|
this.author = "<unknown>"; |
||||||
|
this.description = "<no description>"; |
||||||
|
this.link = null; |
||||||
|
|
||||||
|
this.size = -1; |
||||||
|
this.enabled = true; |
||||||
|
|
||||||
|
this._songFile = null; |
||||||
|
this._songFileParsed = false; |
||||||
|
this._imageFile = null; |
||||||
|
this._infoFile = null; |
||||||
|
|
||||||
|
this.totalFiles = -1; |
||||||
|
|
||||||
|
this.file = null; |
||||||
|
this._completionCallback = null; |
||||||
|
// For zip parsing progress events
|
||||||
|
this.progressCallback = null; |
||||||
|
this.filesToLoad = 0; |
||||||
|
this.filesLoaded = 0; |
||||||
|
this.loadedFromURL = false; |
||||||
|
|
||||||
|
if(url) |
||||||
|
this.loadFromURL(url); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.audioExtensions = new RegExp("\\.(mp3)$", "i"); |
||||||
|
Respack.prototype.imageExtensions = new RegExp("\\.(png|gif|jpg|jpeg)$", "i"); |
||||||
|
Respack.prototype.animRegex = new RegExp("(.*?)_\\d+$"); |
||||||
|
|
||||||
|
Respack.prototype.updateProgress = function() { |
||||||
|
if(this.progressCallback) { |
||||||
|
var percent = this.filesLoaded / this.filesToLoad; |
||||||
|
if(this.loadedFromURL) { |
||||||
|
percent = (percent / 2) + 0.5; |
||||||
|
} |
||||||
|
this.progressCallback(percent); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.loadFromURL = function(url, callback, progress) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
this.progressCallback = progress; |
||||||
|
this.loadedFromURL = true; |
||||||
|
|
||||||
|
var req = new XMLHttpRequest(); |
||||||
|
req.open('GET', url, true); |
||||||
|
req.responseType = 'blob'; |
||||||
|
req.onload = function() { |
||||||
|
that.loadBlob(req.response, callback); |
||||||
|
}; |
||||||
|
req.onerror = function() { |
||||||
|
console.log("Could not load respack at URL", url); |
||||||
|
} |
||||||
|
req.onprogress = function(event) { |
||||||
|
if (event.lengthComputable) { |
||||||
|
var percent = event.loaded / event.total; |
||||||
|
if(progress) { |
||||||
|
progress(percent / 2); // because of processing too
|
||||||
|
} |
||||||
|
} else { |
||||||
|
// Unable to compute progress information since the total size is unknown
|
||||||
|
} |
||||||
|
} |
||||||
|
req.on |
||||||
|
req.send(); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.loadBlob = function(blob, callback) { |
||||||
|
this._completionCallback = callback; |
||||||
|
var that = this; |
||||||
|
this.size = blob.size; |
||||||
|
this.file = new zip.fs.FS(); |
||||||
|
this.file.importBlob(blob, |
||||||
|
function() { // success
|
||||||
|
that.parseWholeZip(); |
||||||
|
}, |
||||||
|
function(error) { // failure
|
||||||
|
console.log("Error loading respack : ", error.toString()); |
||||||
|
that.file = null; |
||||||
|
}
|
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseWholeZip = function() { |
||||||
|
// TODO might break on bad file
|
||||||
|
console.log("Loading new respack: " + this.file.root.children[0].name); |
||||||
|
|
||||||
|
var entries = this.file.entries; |
||||||
|
|
||||||
|
this.totalFiles = 0; |
||||||
|
// Progress events
|
||||||
|
this.filesToLoad = 0; |
||||||
|
this.filesLoaded = 0; |
||||||
|
|
||||||
|
for(var i = 0; i < entries.length; i++) { |
||||||
|
if(!entries[i].directory && entries[i].name) { |
||||||
|
this.totalFiles++; |
||||||
|
this.parseFile(entries[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
debug("ZIP loader: trying to finish"); |
||||||
|
this.tryFinish(); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseFile = function(file) { |
||||||
|
var name = file.name |
||||||
|
if (name.match(this.audioExtensions)) { |
||||||
|
this.parseSong(file); |
||||||
|
this.filesToLoad++; |
||||||
|
} else if (name.match(this.imageExtensions)) { |
||||||
|
this.parseImage(file); |
||||||
|
this.filesToLoad++; |
||||||
|
} |
||||||
|
else { |
||||||
|
switch(name.toLowerCase()) { |
||||||
|
case "songs.xml": |
||||||
|
this._songFile = file; |
||||||
|
break; |
||||||
|
case "images.xml": |
||||||
|
this._imageFile = file; |
||||||
|
break; |
||||||
|
case "info.xml": |
||||||
|
this._infoFile = file; |
||||||
|
break; |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseSong = function(file) { |
||||||
|
this.songQueue.push(file); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseImage = function(file) { |
||||||
|
this.imageQueue.push(file); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseXML = function() { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
if (this.songs.length > 0) { |
||||||
|
if (this._songFile) { |
||||||
|
this._songFile.getText(function(text) { |
||||||
|
//XML parser will complain about a bare '&'
|
||||||
|
text = text.replace(/&/g, '&amp;'); |
||||||
|
that.parseSongFile(text); |
||||||
|
// Go to next in series
|
||||||
|
that._songFile = null; |
||||||
|
that._songFileParsed = true; |
||||||
|
that.parseXML(); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} else if(!this._songFileParsed) { |
||||||
|
console.log("!!!", "Got songs but no songs.xml!"); |
||||||
|
this._songFileParsed = true; |
||||||
|
} |
||||||
|
} |
||||||
|
if (this.images.length > 0 && this._imageFile) { |
||||||
|
this._imageFile.getText(function(text) { |
||||||
|
text = text.replace(/&/g, '&amp;'); |
||||||
|
that.parseImageFile(text); |
||||||
|
that._imageFile = null; |
||||||
|
that.parseXML(); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (this._infoFile) { |
||||||
|
this._infoFile.getText(function(text) { |
||||||
|
text = text.replace(/&/g, '&amp;'); |
||||||
|
that.parseInfoFile(text); |
||||||
|
that._infoFile = null; |
||||||
|
that.parseXML(); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Finally done!
|
||||||
|
this.file = null; |
||||||
|
console.log("Loaded", this.name, "successfully with", this.songs.length,
|
||||||
|
"songs and", this.images.length, "images."); |
||||||
|
if(this._completionCallback) { |
||||||
|
this._completionCallback(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Save some chars
|
||||||
|
Element.prototype.getTag = function(tag, def) { |
||||||
|
var t = this.getElementsByTagName(tag)[0]; |
||||||
|
return t ? t.textContent : (def ? def : null); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseSongFile = function(text) { |
||||||
|
debug(" - Parsing songFile"); |
||||||
|
|
||||||
|
var oParser = new DOMParser(); |
||||||
|
var oDOM = oParser.parseFromString(text, "text/xml"); |
||||||
|
if(oDOM.documentElement.nodeName !== "songs"){ |
||||||
|
console.log("songs.xml error, corrupt file?") |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var newSongs = []; |
||||||
|
// Not supported in mobile safari
|
||||||
|
// var songsXML = oDOM.documentElement.children;
|
||||||
|
var el = oDOM.documentElement.firstElementChild; |
||||||
|
for(; el; el = el.nextElementSibling) { |
||||||
|
var song = this.getSong(el.attributes[0].value); |
||||||
|
if(song) { |
||||||
|
song.title = el.getTag("title"); |
||||||
|
if(!song.title) { |
||||||
|
song.title = "<no name>"; |
||||||
|
debug(" WARNING!", song.name, "has no title!"); |
||||||
|
} |
||||||
|
|
||||||
|
song.rhythm = el.getTag("rhythm"); |
||||||
|
if(!song.rhythm) { |
||||||
|
song.rhythm = "..no..rhythm.."; |
||||||
|
debug(" WARNING!!", song.name, "has no rhythm!!"); |
||||||
|
} |
||||||
|
|
||||||
|
song.startSilence = el.getTag("startSilence"); |
||||||
|
song.endSilence = el.getTag("endSilence"); |
||||||
|
song.buildupLength = el.getTag("buildupLength"); |
||||||
|
if(song.buildupLength) { |
||||||
|
debug("Using custom BU length:", song.buildupLength); |
||||||
|
} |
||||||
|
song.buildup = el.getTag("buildup"); |
||||||
|
if(song.buildup) { |
||||||
|
debug(" Finding a buildup '" + song.buildup + "' for ", song.name); |
||||||
|
var build = this.getSong(song.buildup); |
||||||
|
if(build) { |
||||||
|
song.buildup = build.sound; |
||||||
|
song.buildupPlayed = false; |
||||||
|
// get rid of the junk
|
||||||
|
this.songs.splice(this.songs.indexOf(build), 1); |
||||||
|
} else { |
||||||
|
debug(" WARNING!", "Didn't find a buildup '" + buildup + "'!"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
song.buildupRhythm = el.getTag("buildupRhythm"); |
||||||
|
song.source = el.getTag("source"); |
||||||
|
|
||||||
|
newSongs.push(song); |
||||||
|
debug(" [I] " + song.name, ": '" + song.title + "' added to songs"); |
||||||
|
} else { |
||||||
|
debug(" WARNING!", "songs.xml: <song> element", i + 1,
|
||||||
|
"- no song '" + el.attributes[0].value + "' found"); |
||||||
|
} |
||||||
|
} |
||||||
|
for(var i = 0; i < this.songs.length; i++) { |
||||||
|
if(newSongs.indexOf(this.songs[i]) == -1) { |
||||||
|
debug(" WARNING!", "We have a file for", song.name, "but no information for it"); |
||||||
|
} |
||||||
|
} |
||||||
|
this.songs = newSongs; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseInfoFile = function(text) { |
||||||
|
debug(" - Parsing infoFile"); |
||||||
|
|
||||||
|
var oParser = new DOMParser(); |
||||||
|
var oDOM = oParser.parseFromString(text, "text/xml"); |
||||||
|
var info = oDOM.documentElement; |
||||||
|
if(info.nodeName !== "info"){ |
||||||
|
console.log("info.xml error, corrupt file?") |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// self reference strings to avoid changing strings twice in future
|
||||||
|
this.name = info.getTag("name", this.name); |
||||||
|
this.author = info.getTag("author", this.author); |
||||||
|
this.description = info.getTag("description", this.description); |
||||||
|
this.link = info.getTag("link", this.link); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseImageFile = function(text) { |
||||||
|
debug(" - Parsing imagefile"); |
||||||
|
|
||||||
|
var oParser = new DOMParser(); |
||||||
|
var oDOM = oParser.parseFromString(text, "text/xml"); |
||||||
|
if(oDOM.documentElement.nodeName !== "images"){ |
||||||
|
console.log("images.xml error, corrupt file?") |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var newImages = []; |
||||||
|
// not in mobile safari
|
||||||
|
// var imagesXML = oDOM.documentElement.children;
|
||||||
|
var el = oDOM.documentElement.firstElementChild; |
||||||
|
for(; el; el = el.nextElementSibling) { |
||||||
|
var image = this.getImage(el.attributes[0].value); |
||||||
|
if(image) { |
||||||
|
image.fullname = el.getTag("fullname"); |
||||||
|
if(!image.fullname) { |
||||||
|
debug(" WARNING!", image.name, "has no full name!"); |
||||||
|
} |
||||||
|
image.source = el.getTag("source"); |
||||||
|
// self reference strings to avoid changing strings twice in future
|
||||||
|
image.align = el.getTag("align", image.align); |
||||||
|
var frameDur = el.getTag("frameDuration"); |
||||||
|
if(frameDur) { |
||||||
|
image.frameDurations = [] |
||||||
|
var strSplit = frameDur.split(","); |
||||||
|
for(var j = 0; j < strSplit.length; j++) { |
||||||
|
image.frameDurations.push(parseInt(strSplit[j])); |
||||||
|
} |
||||||
|
while (image.frameDurations.length < image.bitmaps.length) { |
||||||
|
image.frameDurations.push(image.frameDurations[image.frameDurations.length - 1]); |
||||||
|
} |
||||||
|
debug("Frame durations:", image.frameDurations); |
||||||
|
} |
||||||
|
debug(" [I] " + image.name, ":", image.fullname, "added to images"); |
||||||
|
if (image.bitmap || image.bitmaps) { |
||||||
|
newImages.push(image); |
||||||
|
} |
||||||
|
else { |
||||||
|
debug(" WARNING!!", "Image", image.name, "has no bitmap nor animation frames!"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
debug(" WARNING!", "images.xml: <image> element",
|
||||||
|
i + 1, "- no image '" + el.attributes[0].value + "' found"); |
||||||
|
} |
||||||
|
} |
||||||
|
for(var i = 0; i < this.images.length; i++) { |
||||||
|
var image = this.images[i]; |
||||||
|
// Add all images with no info
|
||||||
|
if(newImages.indexOf(image) == -1) { |
||||||
|
newImages.push(image); |
||||||
|
} |
||||||
|
} |
||||||
|
this.images = newImages; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.containsSong = function(name) { |
||||||
|
return this.getSong(name) !== null; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.containsImage = function(name) { |
||||||
|
return this.getImage(name) !== null; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.getSong = function(name) { |
||||||
|
for(var i = 0; i < this.songs.length; i++) { |
||||||
|
if (name == this.songs[i].name) { |
||||||
|
return this.songs[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.getImage = function(name) { |
||||||
|
for(var i = 0; i < this.images.length; i++) { |
||||||
|
if (name == this.images[i].name) { |
||||||
|
return this.images[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseSongQueue = function() { |
||||||
|
var that = this; |
||||||
|
var songFile = this.songQueue.shift(); |
||||||
|
var name = songFile.name.replace(this.audioExtensions, ""); |
||||||
|
|
||||||
|
debug("parsing song: " + name); |
||||||
|
if (this.containsSong(name)) { |
||||||
|
var oldSong = this.getSong(name); |
||||||
|
debug("WARNING: Song", name, "already exists! Conflict with", name, "and", oldSong.name); |
||||||
|
} else { |
||||||
|
var newSong = {"name":name, |
||||||
|
"title":null, |
||||||
|
"rhythm":null, |
||||||
|
"source":null, |
||||||
|
//"crc":this.quickCRC(file), TODO
|
||||||
|
"sound":null, |
||||||
|
"enabled":true, |
||||||
|
"filename":songFile.name}; |
||||||
|
songFile.getBlob("audio/mpeg3", function(sound) { |
||||||
|
// Because blobs are crap
|
||||||
|
var fr = new FileReader(); |
||||||
|
fr.onload = function() { |
||||||
|
newSong.sound = this.result; |
||||||
|
that.filesLoaded++; |
||||||
|
that.updateProgress(); |
||||||
|
that.tryFinish(); |
||||||
|
}; |
||||||
|
fr.readAsArrayBuffer(sound); |
||||||
|
}); |
||||||
|
this.songs.push(newSong); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.parseImageQueue = function() { |
||||||
|
var imgFile = this.imageQueue.shift(); |
||||||
|
var name = imgFile.name.replace(this.imageExtensions, ""); |
||||||
|
|
||||||
|
if (match = name.match(new RegExp("^(.*)_(\\d+)$"))) { |
||||||
|
var anim = this.getImage(match[1]) |
||||||
|
if(!anim) { // make a fresh one
|
||||||
|
anim = {"name":match[1], |
||||||
|
"fullname":match[1], |
||||||
|
"align":"center", |
||||||
|
//"crc":this.quickCRC(imgFile),
|
||||||
|
"bitmaps":[], |
||||||
|
"frameDurations":[33], |
||||||
|
"source":null, |
||||||
|
"enabled":true, |
||||||
|
"animated":true}; |
||||||
|
this.images.push(anim); |
||||||
|
} |
||||||
|
this.imageLoadStart(imgFile, anim); |
||||||
|
} else if (!this.containsImage(name)) { |
||||||
|
var img = {"name":name, |
||||||
|
"fullname":name, |
||||||
|
"bitmap":null, |
||||||
|
"align":"center", |
||||||
|
//"crc":this.quickCRC(imgFile),
|
||||||
|
"source":null, |
||||||
|
"enabled":true, |
||||||
|
"filename":imgFile.name, |
||||||
|
"animated":false}; |
||||||
|
this.images.push(img); |
||||||
|
this.imageLoadStart(imgFile, img); |
||||||
|
} else { |
||||||
|
existing = this.getImage(name); |
||||||
|
debug("WARNING: Image", name, "already exists! Conflict with", imgFile.name, "and", existing.name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.imageLoadStart = function(imgFile, imageObj) { |
||||||
|
var that = this; |
||||||
|
var extension = imgFile.name.split('.').pop().toLowerCase(); |
||||||
|
var mime; |
||||||
|
switch(extension) { |
||||||
|
case "png": |
||||||
|
mime = "image/png"; |
||||||
|
break; |
||||||
|
case "gif": |
||||||
|
mime = "image/gif"; |
||||||
|
break; |
||||||
|
case "jpg": |
||||||
|
case "jpeg": |
||||||
|
mime = "image/jpeg"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
mime = "application/octet-stream"; |
||||||
|
} |
||||||
|
imgFile.getData64URI(mime, function(image) { |
||||||
|
that.imageLoadComplete(image, imageObj); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.imageLoadComplete = function(imageBmp, imageObj) { |
||||||
|
newImg = new Image(); |
||||||
|
newImg.src = imageBmp; |
||||||
|
if (imageObj.animated) { |
||||||
|
imageObj.bitmaps.push(newImg); |
||||||
|
} else { |
||||||
|
imageObj.bitmap = newImg; |
||||||
|
debug("parsing image:", imageObj.name); |
||||||
|
} |
||||||
|
this.filesLoaded++; |
||||||
|
this.updateProgress(); |
||||||
|
this.tryFinish(); |
||||||
|
} |
||||||
|
|
||||||
|
Respack.prototype.tryFinish = function() { |
||||||
|
if (this.imageQueue.length > 0) { |
||||||
|
this.parseImageQueue(); |
||||||
|
} else if(this.songQueue.length > 0) { |
||||||
|
this.parseSongQueue(); |
||||||
|
} else { |
||||||
|
debug("Finished parsing images/songs, parsing xml files..."); |
||||||
|
this.parseXML(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,258 @@ |
|||||||
|
var LAME_DELAY_START = 2258; |
||||||
|
var LAME_DELAY_END = 1000; |
||||||
|
|
||||||
|
function SoundManager() { |
||||||
|
this.playing = false; |
||||||
|
this.song = null; |
||||||
|
|
||||||
|
/* Lower level audio and timing info */ |
||||||
|
this.bufSource = null; |
||||||
|
this.buffer = null; |
||||||
|
this.context = null; // Audio context, Web Audio API
|
||||||
|
this.startTime = 0; // File start time - 0 is loop start, not build start
|
||||||
|
this.loopStart = 0; // When the build ends, if any
|
||||||
|
this.loopLength = 0; // For calculating beat lengths
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
this.gainNode = null; |
||||||
|
this.mute = false; |
||||||
|
this.lastVol = 1; |
||||||
|
|
||||||
|
// For concatenating our files
|
||||||
|
this.leftToLoad = 0; |
||||||
|
this.tmpBuffer = null; |
||||||
|
this.tmpBuild = null; |
||||||
|
this.onLoadCallback = null; |
||||||
|
|
||||||
|
// In case of API non-support
|
||||||
|
this.canUse = true; |
||||||
|
|
||||||
|
// Check Web Audio API Support
|
||||||
|
try { |
||||||
|
// More info at http://caniuse.com/#feat=audio-api
|
||||||
|
window.AudioContext = window.AudioContext || window.webkitAudioContext; |
||||||
|
this.context = new window.AudioContext(); |
||||||
|
this.gainNode = this.context.createGain(); |
||||||
|
this.gainNode.connect(this.context.destination); |
||||||
|
} catch(e) { |
||||||
|
this.canUse = false; |
||||||
|
} |
||||||
|
|
||||||
|
var that = this; |
||||||
|
window.addEventListener('touchstart', function() { |
||||||
|
|
||||||
|
// create empty buffer
|
||||||
|
var buffer = that.context.createBuffer(1, 1, 22050); |
||||||
|
var source = that.context.createBufferSource(); |
||||||
|
source.buffer = buffer; |
||||||
|
|
||||||
|
// connect to output (your speakers)
|
||||||
|
source.connect( that.context.destination); |
||||||
|
|
||||||
|
// play the file
|
||||||
|
source.noteOn(0); |
||||||
|
|
||||||
|
}, false); |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.playSong = function(song, callback) { |
||||||
|
var that = this; |
||||||
|
if(this.song == song) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.stop(); |
||||||
|
this.song = song; |
||||||
|
|
||||||
|
this.loadBuffer(song, function() { |
||||||
|
// To prevent race condition if you press "next" twice fast
|
||||||
|
if(song == that.song) { |
||||||
|
that.startTime = that.context.currentTime + that.loopStart; |
||||||
|
that.bufSource = that.context.createBufferSource(); |
||||||
|
that.bufSource.buffer = that.buffer; |
||||||
|
that.bufSource.loop = true; |
||||||
|
that.bufSource.loopStart = that.loopStart; |
||||||
|
that.bufSource.loopEnd = that.buffer.duration; |
||||||
|
that.bufSource.connect(that.gainNode); |
||||||
|
|
||||||
|
// Mobile Safari requires offset, even if 0
|
||||||
|
that.bufSource.start(0); |
||||||
|
// offset to after the build
|
||||||
|
//that.startTime = that.context.currentTime + that.loopStart;
|
||||||
|
that.playing = true; |
||||||
|
if(callback) |
||||||
|
callback(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.stop = function() { |
||||||
|
if (this.playing) { |
||||||
|
this.bufSource.stop(); |
||||||
|
this.bufSource.disconnect(); // TODO needed?
|
||||||
|
this.bufSource = null; |
||||||
|
this.playing = false; |
||||||
|
this.startTime = 0; |
||||||
|
this.loopStart = 0; |
||||||
|
this.loopLength = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// In seconds, relative to the loop start
|
||||||
|
SoundManager.prototype.currentTime = function() { |
||||||
|
if(!this.playing) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return this.context.currentTime - this.startTime; |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.displayableTime = function() { |
||||||
|
if(!this.playing) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
var time = this.currentTime(); |
||||||
|
if(time < 0) { |
||||||
|
return 0; |
||||||
|
} else { |
||||||
|
return time % this.loopLength; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.loadBuffer = function(song, callback) { |
||||||
|
if(callback) { |
||||||
|
this.onLoadCallback = callback; |
||||||
|
} |
||||||
|
if(song.buildup) { |
||||||
|
this.loadAudioFile(song, true); |
||||||
|
} |
||||||
|
this.loadAudioFile(song, false); |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.loadAudioFile = function(song, isBuild) { |
||||||
|
this.context.decodeAudioData( |
||||||
|
isBuild ? song.buildup : song.sound, |
||||||
|
this.getAudioCallback(song, isBuild), |
||||||
|
function() { |
||||||
|
console.log('Error decoding audio "' + song.name + '".'); |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/* decodeAudioData nukes our original MP3 array, but we want to keep it around |
||||||
|
for memory saving purposes, so we must duplicate it locally here */ |
||||||
|
SoundManager.prototype.getAudioCallback = function(song, isBuild) { |
||||||
|
var that = this; |
||||||
|
var current = isBuild ? song.buildup : song.sound; |
||||||
|
var copy = current.slice(0); |
||||||
|
return function(buffer) { |
||||||
|
// before the race condition check or we might lose data
|
||||||
|
if(isBuild) { |
||||||
|
song.buildup = copy; |
||||||
|
} else { |
||||||
|
song.sound = copy; |
||||||
|
} |
||||||
|
// race condition prevention
|
||||||
|
if(that.song != song) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if(isBuild) { |
||||||
|
that.tmpBuild = that.trimMP3(buffer); |
||||||
|
} else { |
||||||
|
that.tmpBuffer = that.trimMP3(buffer); |
||||||
|
} |
||||||
|
that.onSongLoad(song); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.onSongLoad = function(song) { |
||||||
|
// if this fails, we need to wait for the other part to load
|
||||||
|
if(this.tmpBuffer && (!song.buildup || this.tmpBuild)) { |
||||||
|
if(song.buildup) { |
||||||
|
this.buffer = this.concatenateAudioBuffers(this.tmpBuild, this.tmpBuffer); |
||||||
|
this.loopStart = this.tmpBuild.duration; |
||||||
|
} else { |
||||||
|
this.buffer = this.tmpBuffer; |
||||||
|
this.loopStart = 0; |
||||||
|
} |
||||||
|
this.loopLength = this.buffer.duration - this.loopStart; |
||||||
|
// free dat memory
|
||||||
|
this.tmpBuild = this.tmpBuffer = null; |
||||||
|
if(this.onLoadCallback) { |
||||||
|
this.onLoadCallback(); |
||||||
|
this.onLoadCallback = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// because MP3 is bad, we nuke silence
|
||||||
|
SoundManager.prototype.trimMP3 = function(buffer) { |
||||||
|
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; |
||||||
|
if(!isFirefox) { |
||||||
|
// Webkit is better than Gecko, clearly
|
||||||
|
return buffer; |
||||||
|
} |
||||||
|
var ret = this.context.createBuffer(buffer.numberOfChannels,
|
||||||
|
buffer.length - LAME_DELAY_START - LAME_DELAY_END, buffer.sampleRate); |
||||||
|
for(var i=0; i<buffer.numberOfChannels; i++) { |
||||||
|
var oldBuf = buffer.getChannelData(i); |
||||||
|
var newBuf = ret.getChannelData(i); |
||||||
|
for(var j=0; j<ret.length; j++) { |
||||||
|
newBuf[j] = oldBuf[LAME_DELAY_START + j]; |
||||||
|
} |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
// This wouldn't be required if Web Audio could do gapless playback properly
|
||||||
|
// Looking at you, Firefox
|
||||||
|
SoundManager.prototype.concatenateAudioBuffers = function(buffer1, buffer2) { |
||||||
|
if (!buffer1 || !buffer2) { |
||||||
|
console.log("no buffers!"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if (buffer1.numberOfChannels != buffer2.numberOfChannels) { |
||||||
|
console.log("number of channels is not the same!"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if (buffer1.sampleRate != buffer2.sampleRate) { |
||||||
|
console.log("sample rates don't match!"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
var tmp = this.context.createBuffer(buffer1.numberOfChannels, |
||||||
|
buffer1.length + buffer2.length, buffer1.sampleRate); |
||||||
|
|
||||||
|
for (var i=0; i<tmp.numberOfChannels; i++) { |
||||||
|
var data = tmp.getChannelData(i); |
||||||
|
data.set(buffer1.getChannelData(i)); |
||||||
|
data.set(buffer2.getChannelData(i),buffer1.length); |
||||||
|
} |
||||||
|
return tmp; |
||||||
|
}; |
||||||
|
|
||||||
|
SoundManager.prototype.setMute = function(mute) { |
||||||
|
if(!this.mute && mute) { // muting
|
||||||
|
this.lastVol = this.gainNode.gain.value; |
||||||
|
this.gainNode.gain.value = 0; |
||||||
|
} else if(this.mute && !mute) { // unmuting
|
||||||
|
this.gainNode.gain.value = this.lastVol; |
||||||
|
} |
||||||
|
this.mute = mute; |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.toggleMute = function() { |
||||||
|
this.setMute(!this.mute); |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.decreaseVolume = function() { |
||||||
|
this.setMute(false); |
||||||
|
val = Math.max(this.gainNode.gain.value - 0.1, 0); |
||||||
|
this.gainNode.gain.value = val; |
||||||
|
} |
||||||
|
|
||||||
|
SoundManager.prototype.increaseVolume = function() { |
||||||
|
this.setMute(false); |
||||||
|
val = Math.min(this.gainNode.gain.value + 0.1, 1); |
||||||
|
this.gainNode.gain.value = val; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"browser": true, |
||||||
|
"laxbreak": true, |
||||||
|
"undef": true, |
||||||
|
"globals": { |
||||||
|
"console": false, |
||||||
|
"escape": false, |
||||||
|
"unescape": false, |
||||||
|
"zip": false |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,64 @@ |
|||||||
|
/// wrapper for pako (https://github.com/nodeca/pako)
|
||||||
|
|
||||||
|
/* globals pako */ |
||||||
|
(function(global) { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
function Codec(isDeflater, options) { |
||||||
|
var newOptions = { raw: true, chunkSize: 1024 * 1024 }; |
||||||
|
if (options && typeof options.level === 'number') |
||||||
|
newOptions.level = options.level; |
||||||
|
this._backEnd = isDeflater? |
||||||
|
new pako.Deflate(newOptions) : |
||||||
|
new pako.Inflate(newOptions); |
||||||
|
this._chunks = []; |
||||||
|
this._dataLength = 0; |
||||||
|
this._backEnd.onData = this._onData.bind(this); |
||||||
|
} |
||||||
|
Codec.prototype._onData = function _onData(chunk) { |
||||||
|
this._chunks.push(chunk); |
||||||
|
this._dataLength += chunk.length; |
||||||
|
}; |
||||||
|
Codec.prototype._fetchData = function _fetchData() { |
||||||
|
var be = this._backEnd; |
||||||
|
if (be.err !== 0) |
||||||
|
throw new Error(be.msg); |
||||||
|
var chunks = this._chunks; |
||||||
|
var data; |
||||||
|
if (chunks.length === 1) |
||||||
|
data = chunks[0]; |
||||||
|
else if (chunks.length > 1) { |
||||||
|
data = new Uint8Array(this._dataLength); |
||||||
|
for (var i = 0, n = chunks.length, off = 0; i < n; i++) { |
||||||
|
var chunk = chunks[i]; |
||||||
|
data.set(chunk, off); |
||||||
|
off += chunk.length; |
||||||
|
} |
||||||
|
} |
||||||
|
chunks.length = 0; |
||||||
|
this._dataLength = 0; |
||||||
|
return data; |
||||||
|
}; |
||||||
|
Codec.prototype.append = function append(bytes, onprogress) { |
||||||
|
this._backEnd.push(bytes, false); |
||||||
|
return this._fetchData(); |
||||||
|
}; |
||||||
|
Codec.prototype.flush = function flush() { |
||||||
|
this._backEnd.push(new Uint8Array(0), true); |
||||||
|
return this._fetchData(); |
||||||
|
}; |
||||||
|
|
||||||
|
function Deflater(options) { |
||||||
|
Codec.call(this, true, options); |
||||||
|
} |
||||||
|
Deflater.prototype = Object.create(Codec.prototype); |
||||||
|
function Inflater() { |
||||||
|
Codec.call(this, false); |
||||||
|
} |
||||||
|
Inflater.prototype = Object.create(Codec.prototype); |
||||||
|
|
||||||
|
// 'zip' may not be defined in z-worker and some tests
|
||||||
|
var env = global.zip || global; |
||||||
|
env.Deflater = env._pako_Deflater = Deflater; |
||||||
|
env.Inflater = env._pako_Inflater = Inflater; |
||||||
|
})(this); |
@ -0,0 +1,153 @@ |
|||||||
|
/* jshint worker:true */ |
||||||
|
(function main(global) { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
if (global.zWorkerInitialized) |
||||||
|
throw new Error('z-worker.js should be run only once'); |
||||||
|
global.zWorkerInitialized = true; |
||||||
|
|
||||||
|
addEventListener("message", function(event) { |
||||||
|
var message = event.data, type = message.type, sn = message.sn; |
||||||
|
var handler = handlers[type]; |
||||||
|
if (handler) { |
||||||
|
try { |
||||||
|
handler(message); |
||||||
|
} catch (e) { |
||||||
|
onError(type, sn, e); |
||||||
|
} |
||||||
|
} |
||||||
|
//for debug
|
||||||
|
//postMessage({type: 'echo', originalType: type, sn: sn});
|
||||||
|
}); |
||||||
|
|
||||||
|
var handlers = { |
||||||
|
importScripts: doImportScripts, |
||||||
|
newTask: newTask, |
||||||
|
append: processData, |
||||||
|
flush: processData, |
||||||
|
}; |
||||||
|
|
||||||
|
// deflater/inflater tasks indexed by serial numbers
|
||||||
|
var tasks = {}; |
||||||
|
|
||||||
|
function doImportScripts(msg) { |
||||||
|
if (msg.scripts && msg.scripts.length > 0) |
||||||
|
importScripts.apply(undefined, msg.scripts); |
||||||
|
postMessage({type: 'importScripts'}); |
||||||
|
} |
||||||
|
|
||||||
|
function newTask(msg) { |
||||||
|
var CodecClass = global[msg.codecClass]; |
||||||
|
var sn = msg.sn; |
||||||
|
if (tasks[sn]) |
||||||
|
throw Error('duplicated sn'); |
||||||
|
tasks[sn] = { |
||||||
|
codec: new CodecClass(msg.options), |
||||||
|
crcInput: msg.crcType === 'input', |
||||||
|
crcOutput: msg.crcType === 'output', |
||||||
|
crc: new Crc32(), |
||||||
|
}; |
||||||
|
postMessage({type: 'newTask', sn: sn}); |
||||||
|
} |
||||||
|
|
||||||
|
// performance may not be supported
|
||||||
|
var now = global.performance ? global.performance.now.bind(global.performance) : Date.now; |
||||||
|
|
||||||
|
function processData(msg) { |
||||||
|
var sn = msg.sn, type = msg.type, input = msg.data; |
||||||
|
var task = tasks[sn]; |
||||||
|
// allow creating codec on first append
|
||||||
|
if (!task && msg.codecClass) { |
||||||
|
newTask(msg); |
||||||
|
task = tasks[sn]; |
||||||
|
} |
||||||
|
var isAppend = type === 'append'; |
||||||
|
var start = now(); |
||||||
|
var output; |
||||||
|
if (isAppend) { |
||||||
|
try { |
||||||
|
output = task.codec.append(input, function onprogress(loaded) { |
||||||
|
postMessage({type: 'progress', sn: sn, loaded: loaded}); |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
delete tasks[sn]; |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} else { |
||||||
|
delete tasks[sn]; |
||||||
|
output = task.codec.flush(); |
||||||
|
} |
||||||
|
var codecTime = now() - start; |
||||||
|
|
||||||
|
start = now(); |
||||||
|
if (input && task.crcInput) |
||||||
|
task.crc.append(input); |
||||||
|
if (output && task.crcOutput) |
||||||
|
task.crc.append(output); |
||||||
|
var crcTime = now() - start; |
||||||
|
|
||||||
|
var rmsg = {type: type, sn: sn, codecTime: codecTime, crcTime: crcTime}; |
||||||
|
var transferables = []; |
||||||
|
if (output) { |
||||||
|
rmsg.data = output; |
||||||
|
transferables.push(output.buffer); |
||||||
|
} |
||||||
|
if (!isAppend && (task.crcInput || task.crcOutput)) |
||||||
|
rmsg.crc = task.crc.get(); |
||||||
|
|
||||||
|
// posting a message with transferables will fail on IE10
|
||||||
|
try { |
||||||
|
postMessage(rmsg, transferables); |
||||||
|
} catch(ex) { |
||||||
|
postMessage(rmsg); // retry without transferables
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function onError(type, sn, e) { |
||||||
|
var msg = { |
||||||
|
type: type, |
||||||
|
sn: sn, |
||||||
|
error: formatError(e) |
||||||
|
}; |
||||||
|
postMessage(msg); |
||||||
|
} |
||||||
|
|
||||||
|
function formatError(e) { |
||||||
|
return { message: e.message, stack: e.stack }; |
||||||
|
} |
||||||
|
|
||||||
|
// Crc32 code copied from file zip.js
|
||||||
|
function Crc32() { |
||||||
|
this.crc = -1; |
||||||
|
} |
||||||
|
Crc32.prototype.append = function append(data) { |
||||||
|
var crc = this.crc | 0, table = this.table; |
||||||
|
for (var offset = 0, len = data.length | 0; offset < len; offset++) |
||||||
|
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF]; |
||||||
|
this.crc = crc; |
||||||
|
}; |
||||||
|
Crc32.prototype.get = function get() { |
||||||
|
return ~this.crc; |
||||||
|
}; |
||||||
|
Crc32.prototype.table = (function() { |
||||||
|
var i, j, t, table = []; // Uint32Array is actually slower than []
|
||||||
|
for (i = 0; i < 256; i++) { |
||||||
|
t = i; |
||||||
|
for (j = 0; j < 8; j++) |
||||||
|
if (t & 1) |
||||||
|
t = (t >>> 1) ^ 0xEDB88320; |
||||||
|
else |
||||||
|
t = t >>> 1; |
||||||
|
table[i] = t; |
||||||
|
} |
||||||
|
return table; |
||||||
|
})(); |
||||||
|
|
||||||
|
// "no-op" codec
|
||||||
|
function NOOP() {} |
||||||
|
global.NOOP = NOOP; |
||||||
|
NOOP.prototype.append = function append(bytes, onprogress) { |
||||||
|
return bytes; |
||||||
|
}; |
||||||
|
NOOP.prototype.flush = function flush() {}; |
||||||
|
})(this); |
@ -0,0 +1,242 @@ |
|||||||
|
/* |
||||||
|
Copyright (c) 2013 Gildas Lormeau. All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer in |
||||||
|
the documentation and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
3. The names of the authors may not be used to endorse or promote products |
||||||
|
derived from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, |
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, |
||||||
|
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
(function() { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var ERR_HTTP_RANGE = "HTTP Range not supported."; |
||||||
|
|
||||||
|
var Reader = zip.Reader; |
||||||
|
var Writer = zip.Writer; |
||||||
|
|
||||||
|
var ZipDirectoryEntry; |
||||||
|
|
||||||
|
var appendABViewSupported; |
||||||
|
try { |
||||||
|
appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0; |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
|
||||||
|
function HttpReader(url) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
function getData(callback, onerror) { |
||||||
|
var request; |
||||||
|
if (!that.data) { |
||||||
|
request = new XMLHttpRequest(); |
||||||
|
request.addEventListener("load", function() { |
||||||
|
if (!that.size) |
||||||
|
that.size = Number(request.getResponseHeader("Content-Length")); |
||||||
|
that.data = new Uint8Array(request.response); |
||||||
|
callback(); |
||||||
|
}, false); |
||||||
|
request.addEventListener("error", onerror, false); |
||||||
|
request.open("GET", url); |
||||||
|
request.responseType = "arraybuffer"; |
||||||
|
request.send(); |
||||||
|
} else |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
var request = new XMLHttpRequest(); |
||||||
|
request.addEventListener("load", function() { |
||||||
|
that.size = Number(request.getResponseHeader("Content-Length")); |
||||||
|
callback(); |
||||||
|
}, false); |
||||||
|
request.addEventListener("error", onerror, false); |
||||||
|
request.open("HEAD", url); |
||||||
|
request.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
getData(function() { |
||||||
|
callback(new Uint8Array(that.data.subarray(index, index + length))); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
HttpReader.prototype = new Reader(); |
||||||
|
HttpReader.prototype.constructor = HttpReader; |
||||||
|
|
||||||
|
function HttpRangeReader(url) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
var request = new XMLHttpRequest(); |
||||||
|
request.addEventListener("load", function() { |
||||||
|
that.size = Number(request.getResponseHeader("Content-Length")); |
||||||
|
if (request.getResponseHeader("Accept-Ranges") == "bytes") |
||||||
|
callback(); |
||||||
|
else |
||||||
|
onerror(ERR_HTTP_RANGE); |
||||||
|
}, false); |
||||||
|
request.addEventListener("error", onerror, false); |
||||||
|
request.open("HEAD", url); |
||||||
|
request.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function readArrayBuffer(index, length, callback, onerror) { |
||||||
|
var request = new XMLHttpRequest(); |
||||||
|
request.open("GET", url); |
||||||
|
request.responseType = "arraybuffer"; |
||||||
|
request.setRequestHeader("Range", "bytes=" + index + "-" + (index + length - 1)); |
||||||
|
request.addEventListener("load", function() { |
||||||
|
callback(request.response); |
||||||
|
}, false); |
||||||
|
request.addEventListener("error", onerror, false); |
||||||
|
request.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
readArrayBuffer(index, length, function(arraybuffer) { |
||||||
|
callback(new Uint8Array(arraybuffer)); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
HttpRangeReader.prototype = new Reader(); |
||||||
|
HttpRangeReader.prototype.constructor = HttpRangeReader; |
||||||
|
|
||||||
|
function ArrayBufferReader(arrayBuffer) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
that.size = arrayBuffer.byteLength; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
callback(new Uint8Array(arrayBuffer.slice(index, index + length))); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
ArrayBufferReader.prototype = new Reader(); |
||||||
|
ArrayBufferReader.prototype.constructor = ArrayBufferReader; |
||||||
|
|
||||||
|
function ArrayBufferWriter() { |
||||||
|
var array, that = this; |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
array = new Uint8Array(); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function writeUint8Array(arr, callback, onerror) { |
||||||
|
var tmpArray = new Uint8Array(array.length + arr.length); |
||||||
|
tmpArray.set(array); |
||||||
|
tmpArray.set(arr, array.length); |
||||||
|
array = tmpArray; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback) { |
||||||
|
callback(array.buffer); |
||||||
|
} |
||||||
|
|
||||||
|
that.init = init; |
||||||
|
that.writeUint8Array = writeUint8Array; |
||||||
|
that.getData = getData; |
||||||
|
} |
||||||
|
ArrayBufferWriter.prototype = new Writer(); |
||||||
|
ArrayBufferWriter.prototype.constructor = ArrayBufferWriter; |
||||||
|
|
||||||
|
function FileWriter(fileEntry, contentType) { |
||||||
|
var writer, that = this; |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
fileEntry.createWriter(function(fileWriter) { |
||||||
|
writer = fileWriter; |
||||||
|
callback(); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
function writeUint8Array(array, callback, onerror) { |
||||||
|
var blob = new Blob([ appendABViewSupported ? array : array.buffer ], { |
||||||
|
type : contentType |
||||||
|
}); |
||||||
|
writer.onwrite = function() { |
||||||
|
writer.onwrite = null; |
||||||
|
callback(); |
||||||
|
}; |
||||||
|
writer.onerror = onerror; |
||||||
|
writer.write(blob); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback) { |
||||||
|
fileEntry.file(callback); |
||||||
|
} |
||||||
|
|
||||||
|
that.init = init; |
||||||
|
that.writeUint8Array = writeUint8Array; |
||||||
|
that.getData = getData; |
||||||
|
} |
||||||
|
FileWriter.prototype = new Writer(); |
||||||
|
FileWriter.prototype.constructor = FileWriter; |
||||||
|
|
||||||
|
zip.FileWriter = FileWriter; |
||||||
|
zip.HttpReader = HttpReader; |
||||||
|
zip.HttpRangeReader = HttpRangeReader; |
||||||
|
zip.ArrayBufferReader = ArrayBufferReader; |
||||||
|
zip.ArrayBufferWriter = ArrayBufferWriter; |
||||||
|
|
||||||
|
if (zip.fs) { |
||||||
|
ZipDirectoryEntry = zip.fs.ZipDirectoryEntry; |
||||||
|
ZipDirectoryEntry.prototype.addHttpContent = function(name, URL, useRangeHeader) { |
||||||
|
function addChild(parent, name, params, directory) { |
||||||
|
if (parent.directory) |
||||||
|
return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new zip.fs.ZipFileEntry(parent.fs, name, params, parent); |
||||||
|
else |
||||||
|
throw "Parent entry is not a directory."; |
||||||
|
} |
||||||
|
|
||||||
|
return addChild(this, name, { |
||||||
|
data : URL, |
||||||
|
Reader : useRangeHeader ? HttpRangeReader : HttpReader |
||||||
|
}); |
||||||
|
}; |
||||||
|
ZipDirectoryEntry.prototype.importHttpContent = function(URL, useRangeHeader, onend, onerror) { |
||||||
|
this.importZip(useRangeHeader ? new HttpRangeReader(URL) : new HttpReader(URL), onend, onerror); |
||||||
|
}; |
||||||
|
zip.fs.FS.prototype.importHttpContent = function(URL, useRangeHeader, onend, onerror) { |
||||||
|
this.entries = []; |
||||||
|
this.root = new ZipDirectoryEntry(this); |
||||||
|
this.root.importHttpContent(URL, useRangeHeader, onend, onerror); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
})(); |
@ -0,0 +1,541 @@ |
|||||||
|
/* |
||||||
|
Copyright (c) 2013 Gildas Lormeau. All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
3. The names of the authors may not be used to endorse or promote products |
||||||
|
derived from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, |
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, |
||||||
|
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
(function() { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var CHUNK_SIZE = 512 * 1024; |
||||||
|
|
||||||
|
var TextWriter = zip.TextWriter, //
|
||||||
|
BlobWriter = zip.BlobWriter, //
|
||||||
|
Data64URIWriter = zip.Data64URIWriter, //
|
||||||
|
Reader = zip.Reader, //
|
||||||
|
TextReader = zip.TextReader, //
|
||||||
|
BlobReader = zip.BlobReader, //
|
||||||
|
Data64URIReader = zip.Data64URIReader, //
|
||||||
|
createReader = zip.createReader, //
|
||||||
|
createWriter = zip.createWriter; |
||||||
|
|
||||||
|
function ZipBlobReader(entry) { |
||||||
|
var that = this, blobReader; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
that.size = entry.uncompressedSize; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback) { |
||||||
|
if (that.data) |
||||||
|
callback(); |
||||||
|
else |
||||||
|
entry.getData(new BlobWriter(), function(data) { |
||||||
|
that.data = data; |
||||||
|
blobReader = new BlobReader(data); |
||||||
|
callback(); |
||||||
|
}, null, that.checkCrc32); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
getData(function() { |
||||||
|
blobReader.readUint8Array(index, length, callback, onerror); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
ZipBlobReader.prototype = new Reader(); |
||||||
|
ZipBlobReader.prototype.constructor = ZipBlobReader; |
||||||
|
ZipBlobReader.prototype.checkCrc32 = false; |
||||||
|
|
||||||
|
function getTotalSize(entry) { |
||||||
|
var size = 0; |
||||||
|
|
||||||
|
function process(entry) { |
||||||
|
size += entry.uncompressedSize || 0; |
||||||
|
entry.children.forEach(process); |
||||||
|
} |
||||||
|
|
||||||
|
process(entry); |
||||||
|
return size; |
||||||
|
} |
||||||
|
|
||||||
|
function initReaders(entry, onend, onerror) { |
||||||
|
var index = 0; |
||||||
|
|
||||||
|
function next() { |
||||||
|
index++; |
||||||
|
if (index < entry.children.length) |
||||||
|
process(entry.children[index]); |
||||||
|
else |
||||||
|
onend(); |
||||||
|
} |
||||||
|
|
||||||
|
function process(child) { |
||||||
|
if (child.directory) |
||||||
|
initReaders(child, next, onerror); |
||||||
|
else { |
||||||
|
child.reader = new child.Reader(child.data, onerror); |
||||||
|
child.reader.init(function() { |
||||||
|
child.uncompressedSize = child.reader.size; |
||||||
|
next(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (entry.children.length) |
||||||
|
process(entry.children[index]); |
||||||
|
else |
||||||
|
onend(); |
||||||
|
} |
||||||
|
|
||||||
|
function detach(entry) { |
||||||
|
var children = entry.parent.children; |
||||||
|
children.forEach(function(child, index) { |
||||||
|
if (child.id == entry.id) |
||||||
|
children.splice(index, 1); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function exportZip(zipWriter, entry, onend, onprogress, totalSize) { |
||||||
|
var currentIndex = 0; |
||||||
|
|
||||||
|
function process(zipWriter, entry, onend, onprogress, totalSize) { |
||||||
|
var childIndex = 0; |
||||||
|
|
||||||
|
function exportChild() { |
||||||
|
var child = entry.children[childIndex]; |
||||||
|
if (child) |
||||||
|
zipWriter.add(child.getFullname(), child.reader, function() { |
||||||
|
currentIndex += child.uncompressedSize || 0; |
||||||
|
process(zipWriter, child, function() { |
||||||
|
childIndex++; |
||||||
|
exportChild(); |
||||||
|
}, onprogress, totalSize); |
||||||
|
}, function(index) { |
||||||
|
if (onprogress) |
||||||
|
onprogress(currentIndex + index, totalSize); |
||||||
|
}, { |
||||||
|
directory : child.directory, |
||||||
|
version : child.zipVersion |
||||||
|
}); |
||||||
|
else |
||||||
|
onend(); |
||||||
|
} |
||||||
|
|
||||||
|
exportChild(); |
||||||
|
} |
||||||
|
|
||||||
|
process(zipWriter, entry, onend, onprogress, totalSize); |
||||||
|
} |
||||||
|
|
||||||
|
function addFileEntry(zipEntry, fileEntry, onend, onerror) { |
||||||
|
function getChildren(fileEntry, callback) { |
||||||
|
if (fileEntry.isDirectory) |
||||||
|
fileEntry.createReader().readEntries(callback); |
||||||
|
if (fileEntry.isFile) |
||||||
|
callback([]); |
||||||
|
} |
||||||
|
|
||||||
|
function process(zipEntry, fileEntry, onend) { |
||||||
|
getChildren(fileEntry, function(children) { |
||||||
|
var childIndex = 0; |
||||||
|
|
||||||
|
function addChild(child) { |
||||||
|
function nextChild(childFileEntry) { |
||||||
|
process(childFileEntry, child, function() { |
||||||
|
childIndex++; |
||||||
|
processChild(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
if (child.isDirectory) |
||||||
|
nextChild(zipEntry.addDirectory(child.name)); |
||||||
|
if (child.isFile) |
||||||
|
child.file(function(file) { |
||||||
|
var childZipEntry = zipEntry.addBlob(child.name, file); |
||||||
|
childZipEntry.uncompressedSize = file.size; |
||||||
|
nextChild(childZipEntry); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
function processChild() { |
||||||
|
var child = children[childIndex]; |
||||||
|
if (child) |
||||||
|
addChild(child); |
||||||
|
else |
||||||
|
onend(); |
||||||
|
} |
||||||
|
|
||||||
|
processChild(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
if (fileEntry.isDirectory) |
||||||
|
process(zipEntry, fileEntry, onend); |
||||||
|
else |
||||||
|
fileEntry.file(function(file) { |
||||||
|
zipEntry.addBlob(fileEntry.name, file); |
||||||
|
onend(); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
function getFileEntry(fileEntry, entry, onend, onprogress, onerror, totalSize, checkCrc32) { |
||||||
|
var currentIndex = 0; |
||||||
|
|
||||||
|
function process(fileEntry, entry, onend, onprogress, onerror, totalSize) { |
||||||
|
var childIndex = 0; |
||||||
|
|
||||||
|
function addChild(child) { |
||||||
|
function nextChild(childFileEntry) { |
||||||
|
currentIndex += child.uncompressedSize || 0; |
||||||
|
process(childFileEntry, child, function() { |
||||||
|
childIndex++; |
||||||
|
processChild(); |
||||||
|
}, onprogress, onerror, totalSize); |
||||||
|
} |
||||||
|
|
||||||
|
if (child.directory) |
||||||
|
fileEntry.getDirectory(child.name, { |
||||||
|
create : true |
||||||
|
}, nextChild, onerror); |
||||||
|
else |
||||||
|
fileEntry.getFile(child.name, { |
||||||
|
create : true |
||||||
|
}, function(file) { |
||||||
|
child.getData(new zip.FileWriter(file, zip.getMimeType(child.name)), nextChild, function(index) { |
||||||
|
if (onprogress) |
||||||
|
onprogress(currentIndex + index, totalSize); |
||||||
|
}, checkCrc32); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
function processChild() { |
||||||
|
var child = entry.children[childIndex]; |
||||||
|
if (child) |
||||||
|
addChild(child); |
||||||
|
else |
||||||
|
onend(); |
||||||
|
} |
||||||
|
|
||||||
|
processChild(); |
||||||
|
} |
||||||
|
|
||||||
|
if (entry.directory) |
||||||
|
process(fileEntry, entry, onend, onprogress, onerror, totalSize); |
||||||
|
else |
||||||
|
entry.getData(new zip.FileWriter(fileEntry, zip.getMimeType(entry.name)), onend, onprogress, checkCrc32); |
||||||
|
} |
||||||
|
|
||||||
|
function resetFS(fs) { |
||||||
|
fs.entries = []; |
||||||
|
fs.root = new ZipDirectoryEntry(fs); |
||||||
|
} |
||||||
|
|
||||||
|
function bufferedCopy(reader, writer, onend, onprogress, onerror) { |
||||||
|
var chunkIndex = 0; |
||||||
|
|
||||||
|
function stepCopy() { |
||||||
|
var index = chunkIndex * CHUNK_SIZE; |
||||||
|
if (onprogress) |
||||||
|
onprogress(index, reader.size); |
||||||
|
if (index < reader.size) |
||||||
|
reader.readUint8Array(index, Math.min(CHUNK_SIZE, reader.size - index), function(array) { |
||||||
|
writer.writeUint8Array(new Uint8Array(array), function() { |
||||||
|
chunkIndex++; |
||||||
|
stepCopy(); |
||||||
|
}); |
||||||
|
}, onerror); |
||||||
|
else |
||||||
|
writer.getData(onend); |
||||||
|
} |
||||||
|
|
||||||
|
stepCopy(); |
||||||
|
} |
||||||
|
|
||||||
|
function addChild(parent, name, params, directory) { |
||||||
|
if (parent.directory) |
||||||
|
return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent); |
||||||
|
else |
||||||
|
throw "Parent entry is not a directory."; |
||||||
|
} |
||||||
|
|
||||||
|
function ZipEntry() { |
||||||
|
} |
||||||
|
|
||||||
|
ZipEntry.prototype = { |
||||||
|
init : function(fs, name, params, parent) { |
||||||
|
var that = this; |
||||||
|
if (fs.root && parent && parent.getChildByName(name)) |
||||||
|
throw "Entry filename already exists."; |
||||||
|
if (!params) |
||||||
|
params = {}; |
||||||
|
that.fs = fs; |
||||||
|
that.name = name; |
||||||
|
that.id = fs.entries.length; |
||||||
|
that.parent = parent; |
||||||
|
that.children = []; |
||||||
|
that.zipVersion = params.zipVersion || 0x14; |
||||||
|
that.uncompressedSize = 0; |
||||||
|
fs.entries.push(that); |
||||||
|
if (parent) |
||||||
|
that.parent.children.push(that); |
||||||
|
}, |
||||||
|
getFileEntry : function(fileEntry, onend, onprogress, onerror, checkCrc32) { |
||||||
|
var that = this; |
||||||
|
initReaders(that, function() { |
||||||
|
getFileEntry(fileEntry, that, onend, onprogress, onerror, getTotalSize(that), checkCrc32); |
||||||
|
}, onerror); |
||||||
|
}, |
||||||
|
moveTo : function(target) { |
||||||
|
var that = this; |
||||||
|
if (target.directory) { |
||||||
|
if (!target.isDescendantOf(that)) { |
||||||
|
if (that != target) { |
||||||
|
if (target.getChildByName(that.name)) |
||||||
|
throw "Entry filename already exists."; |
||||||
|
detach(that); |
||||||
|
that.parent = target; |
||||||
|
target.children.push(that); |
||||||
|
} |
||||||
|
} else |
||||||
|
throw "Entry is a ancestor of target entry."; |
||||||
|
} else |
||||||
|
throw "Target entry is not a directory."; |
||||||
|
}, |
||||||
|
getFullname : function() { |
||||||
|
var that = this, fullname = that.name, entry = that.parent; |
||||||
|
while (entry) { |
||||||
|
fullname = (entry.name ? entry.name + "/" : "") + fullname; |
||||||
|
entry = entry.parent; |
||||||
|
} |
||||||
|
return fullname; |
||||||
|
}, |
||||||
|
isDescendantOf : function(ancestor) { |
||||||
|
var entry = this.parent; |
||||||
|
while (entry && entry.id != ancestor.id) |
||||||
|
entry = entry.parent; |
||||||
|
return !!entry; |
||||||
|
} |
||||||
|
}; |
||||||
|
ZipEntry.prototype.constructor = ZipEntry; |
||||||
|
|
||||||
|
var ZipFileEntryProto; |
||||||
|
|
||||||
|
function ZipFileEntry(fs, name, params, parent) { |
||||||
|
var that = this; |
||||||
|
ZipEntry.prototype.init.call(that, fs, name, params, parent); |
||||||
|
that.Reader = params.Reader; |
||||||
|
that.Writer = params.Writer; |
||||||
|
that.data = params.data; |
||||||
|
if (params.getData) { |
||||||
|
that.getData = params.getData; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ZipFileEntry.prototype = ZipFileEntryProto = new ZipEntry(); |
||||||
|
ZipFileEntryProto.constructor = ZipFileEntry; |
||||||
|
ZipFileEntryProto.getData = function(writer, onend, onprogress, onerror) { |
||||||
|
var that = this; |
||||||
|
if (!writer || (writer.constructor == that.Writer && that.data)) |
||||||
|
onend(that.data); |
||||||
|
else { |
||||||
|
if (!that.reader) |
||||||
|
that.reader = new that.Reader(that.data, onerror); |
||||||
|
that.reader.init(function() { |
||||||
|
writer.init(function() { |
||||||
|
bufferedCopy(that.reader, writer, onend, onprogress, onerror); |
||||||
|
}, onerror); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
ZipFileEntryProto.getText = function(onend, onprogress, checkCrc32, encoding) { |
||||||
|
this.getData(new TextWriter(encoding), onend, onprogress, checkCrc32); |
||||||
|
}; |
||||||
|
ZipFileEntryProto.getBlob = function(mimeType, onend, onprogress, checkCrc32) { |
||||||
|
this.getData(new BlobWriter(mimeType), onend, onprogress, checkCrc32); |
||||||
|
}; |
||||||
|
ZipFileEntryProto.getData64URI = function(mimeType, onend, onprogress, checkCrc32) { |
||||||
|
this.getData(new Data64URIWriter(mimeType), onend, onprogress, checkCrc32); |
||||||
|
}; |
||||||
|
|
||||||
|
var ZipDirectoryEntryProto; |
||||||
|
|
||||||
|
function ZipDirectoryEntry(fs, name, params, parent) { |
||||||
|
var that = this; |
||||||
|
ZipEntry.prototype.init.call(that, fs, name, params, parent); |
||||||
|
that.directory = true; |
||||||
|
} |
||||||
|
|
||||||
|
ZipDirectoryEntry.prototype = ZipDirectoryEntryProto = new ZipEntry(); |
||||||
|
ZipDirectoryEntryProto.constructor = ZipDirectoryEntry; |
||||||
|
ZipDirectoryEntryProto.addDirectory = function(name) { |
||||||
|
return addChild(this, name, null, true); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.addText = function(name, text) { |
||||||
|
return addChild(this, name, { |
||||||
|
data : text, |
||||||
|
Reader : TextReader, |
||||||
|
Writer : TextWriter |
||||||
|
}); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.addBlob = function(name, blob) { |
||||||
|
return addChild(this, name, { |
||||||
|
data : blob, |
||||||
|
Reader : BlobReader, |
||||||
|
Writer : BlobWriter |
||||||
|
}); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.addData64URI = function(name, dataURI) { |
||||||
|
return addChild(this, name, { |
||||||
|
data : dataURI, |
||||||
|
Reader : Data64URIReader, |
||||||
|
Writer : Data64URIWriter |
||||||
|
}); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.addFileEntry = function(fileEntry, onend, onerror) { |
||||||
|
addFileEntry(this, fileEntry, onend, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.addData = function(name, params) { |
||||||
|
return addChild(this, name, params); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.importBlob = function(blob, onend, onerror) { |
||||||
|
this.importZip(new BlobReader(blob), onend, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.importText = function(text, onend, onerror) { |
||||||
|
this.importZip(new TextReader(text), onend, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.importData64URI = function(dataURI, onend, onerror) { |
||||||
|
this.importZip(new Data64URIReader(dataURI), onend, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.exportBlob = function(onend, onprogress, onerror) { |
||||||
|
this.exportZip(new BlobWriter("application/zip"), onend, onprogress, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.exportText = function(onend, onprogress, onerror) { |
||||||
|
this.exportZip(new TextWriter(), onend, onprogress, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.exportFileEntry = function(fileEntry, onend, onprogress, onerror) { |
||||||
|
this.exportZip(new zip.FileWriter(fileEntry, "application/zip"), onend, onprogress, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.exportData64URI = function(onend, onprogress, onerror) { |
||||||
|
this.exportZip(new Data64URIWriter("application/zip"), onend, onprogress, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.importZip = function(reader, onend, onerror) { |
||||||
|
var that = this; |
||||||
|
createReader(reader, function(zipReader) { |
||||||
|
zipReader.getEntries(function(entries) { |
||||||
|
entries.forEach(function(entry) { |
||||||
|
var parent = that, path = entry.filename.split("/"), name = path.pop(); |
||||||
|
path.forEach(function(pathPart) { |
||||||
|
parent = parent.getChildByName(pathPart) || new ZipDirectoryEntry(that.fs, pathPart, null, parent); |
||||||
|
}); |
||||||
|
if (!entry.directory) |
||||||
|
addChild(parent, name, { |
||||||
|
data : entry, |
||||||
|
Reader : ZipBlobReader |
||||||
|
}); |
||||||
|
}); |
||||||
|
onend(); |
||||||
|
}); |
||||||
|
}, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.exportZip = function(writer, onend, onprogress, onerror) { |
||||||
|
var that = this; |
||||||
|
initReaders(that, function() { |
||||||
|
createWriter(writer, function(zipWriter) { |
||||||
|
exportZip(zipWriter, that, function() { |
||||||
|
zipWriter.close(onend); |
||||||
|
}, onprogress, getTotalSize(that)); |
||||||
|
}, onerror); |
||||||
|
}, onerror); |
||||||
|
}; |
||||||
|
ZipDirectoryEntryProto.getChildByName = function(name) { |
||||||
|
var childIndex, child, that = this; |
||||||
|
for (childIndex = 0; childIndex < that.children.length; childIndex++) { |
||||||
|
child = that.children[childIndex]; |
||||||
|
if (child.name == name) |
||||||
|
return child; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function FS() { |
||||||
|
resetFS(this); |
||||||
|
} |
||||||
|
FS.prototype = { |
||||||
|
remove : function(entry) { |
||||||
|
detach(entry); |
||||||
|
this.entries[entry.id] = null; |
||||||
|
}, |
||||||
|
find : function(fullname) { |
||||||
|
var index, path = fullname.split("/"), node = this.root; |
||||||
|
for (index = 0; node && index < path.length; index++) |
||||||
|
node = node.getChildByName(path[index]); |
||||||
|
return node; |
||||||
|
}, |
||||||
|
getById : function(id) { |
||||||
|
return this.entries[id]; |
||||||
|
}, |
||||||
|
importBlob : function(blob, onend, onerror) { |
||||||
|
resetFS(this); |
||||||
|
this.root.importBlob(blob, onend, onerror); |
||||||
|
}, |
||||||
|
importText : function(text, onend, onerror) { |
||||||
|
resetFS(this); |
||||||
|
this.root.importText(text, onend, onerror); |
||||||
|
}, |
||||||
|
importData64URI : function(dataURI, onend, onerror) { |
||||||
|
resetFS(this); |
||||||
|
this.root.importData64URI(dataURI, onend, onerror); |
||||||
|
}, |
||||||
|
exportBlob : function(onend, onprogress, onerror) { |
||||||
|
this.root.exportBlob(onend, onprogress, onerror); |
||||||
|
}, |
||||||
|
exportText : function(onend, onprogress, onerror) { |
||||||
|
this.root.exportText(onend, onprogress, onerror); |
||||||
|
}, |
||||||
|
exportFileEntry : function(fileEntry, onend, onprogress, onerror) { |
||||||
|
this.root.exportFileEntry(fileEntry, onend, onprogress, onerror); |
||||||
|
}, |
||||||
|
exportData64URI : function(onend, onprogress, onerror) { |
||||||
|
this.root.exportData64URI(onend, onprogress, onerror); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
zip.fs = { |
||||||
|
FS : FS, |
||||||
|
ZipDirectoryEntry : ZipDirectoryEntry, |
||||||
|
ZipFileEntry : ZipFileEntry |
||||||
|
}; |
||||||
|
|
||||||
|
zip.getMimeType = function() { |
||||||
|
return "application/octet-stream"; |
||||||
|
}; |
||||||
|
|
||||||
|
})(); |
@ -0,0 +1,966 @@ |
|||||||
|
/* |
||||||
|
Copyright (c) 2013 Gildas Lormeau. All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer in |
||||||
|
the documentation and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
3. The names of the authors may not be used to endorse or promote products |
||||||
|
derived from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, |
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, |
||||||
|
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
(function(obj) { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var ERR_BAD_FORMAT = "File format is not recognized."; |
||||||
|
var ERR_CRC = "CRC failed."; |
||||||
|
var ERR_ENCRYPTED = "File contains encrypted entry."; |
||||||
|
var ERR_ZIP64 = "File is using Zip64 (4gb+ file size)."; |
||||||
|
var ERR_READ = "Error while reading zip file."; |
||||||
|
var ERR_WRITE = "Error while writing zip file."; |
||||||
|
var ERR_WRITE_DATA = "Error while writing file data."; |
||||||
|
var ERR_READ_DATA = "Error while reading file data."; |
||||||
|
var ERR_DUPLICATED_NAME = "File already exists."; |
||||||
|
var CHUNK_SIZE = 512 * 1024; |
||||||
|
|
||||||
|
var TEXT_PLAIN = "text/plain"; |
||||||
|
|
||||||
|
var appendABViewSupported; |
||||||
|
try { |
||||||
|
appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0; |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
|
||||||
|
function Crc32() { |
||||||
|
this.crc = -1; |
||||||
|
} |
||||||
|
Crc32.prototype.append = function append(data) { |
||||||
|
var crc = this.crc | 0, table = this.table; |
||||||
|
for (var offset = 0, len = data.length | 0; offset < len; offset++) |
||||||
|
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF]; |
||||||
|
this.crc = crc; |
||||||
|
}; |
||||||
|
Crc32.prototype.get = function get() { |
||||||
|
return ~this.crc; |
||||||
|
}; |
||||||
|
Crc32.prototype.table = (function() { |
||||||
|
var i, j, t, table = []; // Uint32Array is actually slower than []
|
||||||
|
for (i = 0; i < 256; i++) { |
||||||
|
t = i; |
||||||
|
for (j = 0; j < 8; j++) |
||||||
|
if (t & 1) |
||||||
|
t = (t >>> 1) ^ 0xEDB88320; |
||||||
|
else |
||||||
|
t = t >>> 1; |
||||||
|
table[i] = t; |
||||||
|
} |
||||||
|
return table; |
||||||
|
})(); |
||||||
|
|
||||||
|
// "no-op" codec
|
||||||
|
function NOOP() {} |
||||||
|
NOOP.prototype.append = function append(bytes, onprogress) { |
||||||
|
return bytes; |
||||||
|
}; |
||||||
|
NOOP.prototype.flush = function flush() {}; |
||||||
|
|
||||||
|
function blobSlice(blob, index, length) { |
||||||
|
if (index < 0 || length < 0 || index + length > blob.size) |
||||||
|
throw new RangeError('offset:' + index + ', length:' + length + ', size:' + blob.size); |
||||||
|
if (blob.slice) |
||||||
|
return blob.slice(index, index + length); |
||||||
|
else if (blob.webkitSlice) |
||||||
|
return blob.webkitSlice(index, index + length); |
||||||
|
else if (blob.mozSlice) |
||||||
|
return blob.mozSlice(index, index + length); |
||||||
|
else if (blob.msSlice) |
||||||
|
return blob.msSlice(index, index + length); |
||||||
|
} |
||||||
|
|
||||||
|
function getDataHelper(byteLength, bytes) { |
||||||
|
var dataBuffer, dataArray; |
||||||
|
dataBuffer = new ArrayBuffer(byteLength); |
||||||
|
dataArray = new Uint8Array(dataBuffer); |
||||||
|
if (bytes) |
||||||
|
dataArray.set(bytes, 0); |
||||||
|
return { |
||||||
|
buffer : dataBuffer, |
||||||
|
array : dataArray, |
||||||
|
view : new DataView(dataBuffer) |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// Readers
|
||||||
|
function Reader() { |
||||||
|
} |
||||||
|
|
||||||
|
function TextReader(text) { |
||||||
|
var that = this, blobReader; |
||||||
|
|
||||||
|
function init(callback, onerror) { |
||||||
|
var blob = new Blob([ text ], { |
||||||
|
type : TEXT_PLAIN |
||||||
|
}); |
||||||
|
blobReader = new BlobReader(blob); |
||||||
|
blobReader.init(function() { |
||||||
|
that.size = blobReader.size; |
||||||
|
callback(); |
||||||
|
}, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
blobReader.readUint8Array(index, length, callback, onerror); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
TextReader.prototype = new Reader(); |
||||||
|
TextReader.prototype.constructor = TextReader; |
||||||
|
|
||||||
|
function Data64URIReader(dataURI) { |
||||||
|
var that = this, dataStart; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
var dataEnd = dataURI.length; |
||||||
|
while (dataURI.charAt(dataEnd - 1) == "=") |
||||||
|
dataEnd--; |
||||||
|
dataStart = dataURI.indexOf(",") + 1; |
||||||
|
that.size = Math.floor((dataEnd - dataStart) * 0.75); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback) { |
||||||
|
var i, data = getDataHelper(length); |
||||||
|
var start = Math.floor(index / 3) * 4; |
||||||
|
var end = Math.ceil((index + length) / 3) * 4; |
||||||
|
var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart)); |
||||||
|
var delta = index - Math.floor(start / 4) * 3; |
||||||
|
for (i = delta; i < delta + length; i++) |
||||||
|
data.array[i - delta] = bytes.charCodeAt(i); |
||||||
|
callback(data.array); |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
Data64URIReader.prototype = new Reader(); |
||||||
|
Data64URIReader.prototype.constructor = Data64URIReader; |
||||||
|
|
||||||
|
function BlobReader(blob) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
that.size = blob.size; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function readUint8Array(index, length, callback, onerror) { |
||||||
|
var reader = new FileReader(); |
||||||
|
reader.onload = function(e) { |
||||||
|
callback(new Uint8Array(e.target.result)); |
||||||
|
}; |
||||||
|
reader.onerror = onerror; |
||||||
|
try { |
||||||
|
reader.readAsArrayBuffer(blobSlice(blob, index, length)); |
||||||
|
} catch (e) { |
||||||
|
onerror(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
that.size = 0; |
||||||
|
that.init = init; |
||||||
|
that.readUint8Array = readUint8Array; |
||||||
|
} |
||||||
|
BlobReader.prototype = new Reader(); |
||||||
|
BlobReader.prototype.constructor = BlobReader; |
||||||
|
|
||||||
|
// Writers
|
||||||
|
|
||||||
|
function Writer() { |
||||||
|
} |
||||||
|
Writer.prototype.getData = function(callback) { |
||||||
|
callback(this.data); |
||||||
|
}; |
||||||
|
|
||||||
|
function TextWriter(encoding) { |
||||||
|
var that = this, blob; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
blob = new Blob([], { |
||||||
|
type : TEXT_PLAIN |
||||||
|
}); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function writeUint8Array(array, callback) { |
||||||
|
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { |
||||||
|
type : TEXT_PLAIN |
||||||
|
}); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback, onerror) { |
||||||
|
var reader = new FileReader(); |
||||||
|
reader.onload = function(e) { |
||||||
|
callback(e.target.result); |
||||||
|
}; |
||||||
|
reader.onerror = onerror; |
||||||
|
reader.readAsText(blob, encoding); |
||||||
|
} |
||||||
|
|
||||||
|
that.init = init; |
||||||
|
that.writeUint8Array = writeUint8Array; |
||||||
|
that.getData = getData; |
||||||
|
} |
||||||
|
TextWriter.prototype = new Writer(); |
||||||
|
TextWriter.prototype.constructor = TextWriter; |
||||||
|
|
||||||
|
function Data64URIWriter(contentType) { |
||||||
|
var that = this, data = "", pending = ""; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
data += "data:" + (contentType || "") + ";base64,"; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function writeUint8Array(array, callback) { |
||||||
|
var i, delta = pending.length, dataString = pending; |
||||||
|
pending = ""; |
||||||
|
for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++) |
||||||
|
dataString += String.fromCharCode(array[i]); |
||||||
|
for (; i < array.length; i++) |
||||||
|
pending += String.fromCharCode(array[i]); |
||||||
|
if (dataString.length > 2) |
||||||
|
data += obj.btoa(dataString); |
||||||
|
else |
||||||
|
pending = dataString; |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback) { |
||||||
|
callback(data + obj.btoa(pending)); |
||||||
|
} |
||||||
|
|
||||||
|
that.init = init; |
||||||
|
that.writeUint8Array = writeUint8Array; |
||||||
|
that.getData = getData; |
||||||
|
} |
||||||
|
Data64URIWriter.prototype = new Writer(); |
||||||
|
Data64URIWriter.prototype.constructor = Data64URIWriter; |
||||||
|
|
||||||
|
function BlobWriter(contentType) { |
||||||
|
var blob, that = this; |
||||||
|
|
||||||
|
function init(callback) { |
||||||
|
blob = new Blob([], { |
||||||
|
type : contentType |
||||||
|
}); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function writeUint8Array(array, callback) { |
||||||
|
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { |
||||||
|
type : contentType |
||||||
|
}); |
||||||
|
callback(); |
||||||
|
} |
||||||
|
|
||||||
|
function getData(callback) { |
||||||
|
callback(blob); |
||||||
|
} |
||||||
|
|
||||||
|
that.init = init; |
||||||
|
that.writeUint8Array = writeUint8Array; |
||||||
|
that.getData = getData; |
||||||
|
} |
||||||
|
BlobWriter.prototype = new Writer(); |
||||||
|
BlobWriter.prototype.constructor = BlobWriter; |
||||||
|
|
||||||
|
/** |
||||||
|
* inflate/deflate core functions |
||||||
|
* @param worker {Worker} web worker for the task. |
||||||
|
* @param initialMessage {Object} initial message to be sent to the worker. should contain |
||||||
|
* sn(serial number for distinguishing multiple tasks sent to the worker), and codecClass. |
||||||
|
* This function may add more properties before sending. |
||||||
|
*/ |
||||||
|
function launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror) { |
||||||
|
var chunkIndex = 0, index, outputSize, sn = initialMessage.sn, crc; |
||||||
|
|
||||||
|
function onflush() { |
||||||
|
worker.removeEventListener('message', onmessage, false); |
||||||
|
onend(outputSize, crc); |
||||||
|
} |
||||||
|
|
||||||
|
function onmessage(event) { |
||||||
|
var message = event.data, data = message.data, err = message.error; |
||||||
|
if (err) { |
||||||
|
err.toString = function () { return 'Error: ' + this.message; }; |
||||||
|
onreaderror(err); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (message.sn !== sn) |
||||||
|
return; |
||||||
|
if (typeof message.codecTime === 'number') |
||||||
|
worker.codecTime += message.codecTime; // should be before onflush()
|
||||||
|
if (typeof message.crcTime === 'number') |
||||||
|
worker.crcTime += message.crcTime; |
||||||
|
|
||||||
|
switch (message.type) { |
||||||
|
case 'append': |
||||||
|
if (data) { |
||||||
|
outputSize += data.length; |
||||||
|
writer.writeUint8Array(data, function() { |
||||||
|
step(); |
||||||
|
}, onwriteerror); |
||||||
|
} else |
||||||
|
step(); |
||||||
|
break; |
||||||
|
case 'flush': |
||||||
|
crc = message.crc; |
||||||
|
if (data) { |
||||||
|
outputSize += data.length; |
||||||
|
writer.writeUint8Array(data, function() { |
||||||
|
onflush(); |
||||||
|
}, onwriteerror); |
||||||
|
} else |
||||||
|
onflush(); |
||||||
|
break; |
||||||
|
case 'progress': |
||||||
|
if (onprogress) |
||||||
|
onprogress(index + message.loaded, size); |
||||||
|
break; |
||||||
|
case 'importScripts': //no need to handle here
|
||||||
|
case 'newTask': |
||||||
|
case 'echo': |
||||||
|
break; |
||||||
|
default: |
||||||
|
console.warn('zip.js:launchWorkerProcess: unknown message: ', message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function step() { |
||||||
|
index = chunkIndex * CHUNK_SIZE; |
||||||
|
// use `<=` instead of `<`, because `size` may be 0.
|
||||||
|
if (index <= size) { |
||||||
|
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) { |
||||||
|
if (onprogress) |
||||||
|
onprogress(index, size); |
||||||
|
var msg = index === 0 ? initialMessage : {sn : sn}; |
||||||
|
msg.type = 'append'; |
||||||
|
msg.data = array; |
||||||
|
|
||||||
|
// posting a message with transferables will fail on IE10
|
||||||
|
try { |
||||||
|
worker.postMessage(msg, [array.buffer]); |
||||||
|
} catch(ex) { |
||||||
|
worker.postMessage(msg); // retry without transferables
|
||||||
|
} |
||||||
|
chunkIndex++; |
||||||
|
}, onreaderror); |
||||||
|
} else { |
||||||
|
worker.postMessage({ |
||||||
|
sn: sn, |
||||||
|
type: 'flush' |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
outputSize = 0; |
||||||
|
worker.addEventListener('message', onmessage, false); |
||||||
|
step(); |
||||||
|
} |
||||||
|
|
||||||
|
function launchProcess(process, reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror) { |
||||||
|
var chunkIndex = 0, index, outputSize = 0, |
||||||
|
crcInput = crcType === 'input', |
||||||
|
crcOutput = crcType === 'output', |
||||||
|
crc = new Crc32(); |
||||||
|
function step() { |
||||||
|
var outputData; |
||||||
|
index = chunkIndex * CHUNK_SIZE; |
||||||
|
if (index < size) |
||||||
|
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) { |
||||||
|
var outputData; |
||||||
|
try { |
||||||
|
outputData = process.append(inputData, function(loaded) { |
||||||
|
if (onprogress) |
||||||
|
onprogress(index + loaded, size); |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
onreaderror(e); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (outputData) { |
||||||
|
outputSize += outputData.length; |
||||||
|
writer.writeUint8Array(outputData, function() { |
||||||
|
chunkIndex++; |
||||||
|
setTimeout(step, 1); |
||||||
|
}, onwriteerror); |
||||||
|
if (crcOutput) |
||||||
|
crc.append(outputData); |
||||||
|
} else { |
||||||
|
chunkIndex++; |
||||||
|
setTimeout(step, 1); |
||||||
|
} |
||||||
|
if (crcInput) |
||||||
|
crc.append(inputData); |
||||||
|
if (onprogress) |
||||||
|
onprogress(index, size); |
||||||
|
}, onreaderror); |
||||||
|
else { |
||||||
|
try { |
||||||
|
outputData = process.flush(); |
||||||
|
} catch (e) { |
||||||
|
onreaderror(e); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (outputData) { |
||||||
|
if (crcOutput) |
||||||
|
crc.append(outputData); |
||||||
|
outputSize += outputData.length; |
||||||
|
writer.writeUint8Array(outputData, function() { |
||||||
|
onend(outputSize, crc.get()); |
||||||
|
}, onwriteerror); |
||||||
|
} else |
||||||
|
onend(outputSize, crc.get()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
step(); |
||||||
|
} |
||||||
|
|
||||||
|
function inflate(worker, sn, reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { |
||||||
|
var crcType = computeCrc32 ? 'output' : 'none'; |
||||||
|
if (obj.zip.useWebWorkers) { |
||||||
|
var initialMessage = { |
||||||
|
sn: sn, |
||||||
|
codecClass: 'Inflater', |
||||||
|
crcType: crcType, |
||||||
|
}; |
||||||
|
launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} else |
||||||
|
launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
function deflate(worker, sn, reader, writer, level, onend, onprogress, onreaderror, onwriteerror) { |
||||||
|
var crcType = 'input'; |
||||||
|
if (obj.zip.useWebWorkers) { |
||||||
|
var initialMessage = { |
||||||
|
sn: sn, |
||||||
|
options: {level: level}, |
||||||
|
codecClass: 'Deflater', |
||||||
|
crcType: crcType, |
||||||
|
}; |
||||||
|
launchWorkerProcess(worker, initialMessage, reader, writer, 0, reader.size, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} else |
||||||
|
launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, crcType, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
function copy(worker, sn, reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { |
||||||
|
var crcType = 'input'; |
||||||
|
if (obj.zip.useWebWorkers && computeCrc32) { |
||||||
|
var initialMessage = { |
||||||
|
sn: sn, |
||||||
|
codecClass: 'NOOP', |
||||||
|
crcType: crcType, |
||||||
|
}; |
||||||
|
launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} else |
||||||
|
launchProcess(new NOOP(), reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
// ZipReader
|
||||||
|
|
||||||
|
function decodeASCII(str) { |
||||||
|
var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', |
||||||
|
'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', |
||||||
|
'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', |
||||||
|
'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6', |
||||||
|
'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3', |
||||||
|
'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE', |
||||||
|
'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE', |
||||||
|
'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7', |
||||||
|
'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ]; |
||||||
|
for (i = 0; i < str.length; i++) { |
||||||
|
charCode = str.charCodeAt(i) & 0xFF; |
||||||
|
if (charCode > 127) |
||||||
|
out += extendedASCII[charCode - 128]; |
||||||
|
else |
||||||
|
out += String.fromCharCode(charCode); |
||||||
|
} |
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
function decodeUTF8(string) { |
||||||
|
return decodeURIComponent(escape(string)); |
||||||
|
} |
||||||
|
|
||||||
|
function getString(bytes) { |
||||||
|
var i, str = ""; |
||||||
|
for (i = 0; i < bytes.length; i++) |
||||||
|
str += String.fromCharCode(bytes[i]); |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
function getDate(timeRaw) { |
||||||
|
var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff; |
||||||
|
try { |
||||||
|
return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, |
||||||
|
(time & 0x001F) * 2, 0); |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function readCommonHeader(entry, data, index, centralDirectory, onerror) { |
||||||
|
entry.version = data.view.getUint16(index, true); |
||||||
|
entry.bitFlag = data.view.getUint16(index + 2, true); |
||||||
|
entry.compressionMethod = data.view.getUint16(index + 4, true); |
||||||
|
entry.lastModDateRaw = data.view.getUint32(index + 6, true); |
||||||
|
entry.lastModDate = getDate(entry.lastModDateRaw); |
||||||
|
if ((entry.bitFlag & 0x01) === 0x01) { |
||||||
|
onerror(ERR_ENCRYPTED); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) { |
||||||
|
entry.crc32 = data.view.getUint32(index + 10, true); |
||||||
|
entry.compressedSize = data.view.getUint32(index + 14, true); |
||||||
|
entry.uncompressedSize = data.view.getUint32(index + 18, true); |
||||||
|
} |
||||||
|
if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) { |
||||||
|
onerror(ERR_ZIP64); |
||||||
|
return; |
||||||
|
} |
||||||
|
entry.filenameLength = data.view.getUint16(index + 22, true); |
||||||
|
entry.extraFieldLength = data.view.getUint16(index + 24, true); |
||||||
|
} |
||||||
|
|
||||||
|
function createZipReader(reader, callback, onerror) { |
||||||
|
var inflateSN = 0; |
||||||
|
|
||||||
|
function Entry() { |
||||||
|
} |
||||||
|
|
||||||
|
Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) { |
||||||
|
var that = this; |
||||||
|
|
||||||
|
function testCrc32(crc32) { |
||||||
|
var dataCrc32 = getDataHelper(4); |
||||||
|
dataCrc32.view.setUint32(0, crc32); |
||||||
|
return that.crc32 == dataCrc32.view.getUint32(0); |
||||||
|
} |
||||||
|
|
||||||
|
function getWriterData(uncompressedSize, crc32) { |
||||||
|
if (checkCrc32 && !testCrc32(crc32)) |
||||||
|
onerror(ERR_CRC); |
||||||
|
else |
||||||
|
writer.getData(function(data) { |
||||||
|
onend(data); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function onreaderror(err) { |
||||||
|
onerror(err || ERR_READ_DATA); |
||||||
|
} |
||||||
|
|
||||||
|
function onwriteerror(err) { |
||||||
|
onerror(err || ERR_WRITE_DATA); |
||||||
|
} |
||||||
|
|
||||||
|
reader.readUint8Array(that.offset, 30, function(bytes) { |
||||||
|
var data = getDataHelper(bytes.length, bytes), dataOffset; |
||||||
|
if (data.view.getUint32(0) != 0x504b0304) { |
||||||
|
onerror(ERR_BAD_FORMAT); |
||||||
|
return; |
||||||
|
} |
||||||
|
readCommonHeader(that, data, 4, false, onerror); |
||||||
|
dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength; |
||||||
|
writer.init(function() { |
||||||
|
if (that.compressionMethod === 0) |
||||||
|
copy(that._worker, inflateSN++, reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror); |
||||||
|
else |
||||||
|
inflate(that._worker, inflateSN++, reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror); |
||||||
|
}, onwriteerror); |
||||||
|
}, onreaderror); |
||||||
|
}; |
||||||
|
|
||||||
|
function seekEOCDR(eocdrCallback) { |
||||||
|
// "End of central directory record" is the last part of a zip archive, and is at least 22 bytes long.
|
||||||
|
// Zip file comment is the last part of EOCDR and has max length of 64KB,
|
||||||
|
// so we only have to search the last 64K + 22 bytes of a archive for EOCDR signature (0x06054b50).
|
||||||
|
var EOCDR_MIN = 22; |
||||||
|
if (reader.size < EOCDR_MIN) { |
||||||
|
onerror(ERR_BAD_FORMAT); |
||||||
|
return; |
||||||
|
} |
||||||
|
var ZIP_COMMENT_MAX = 256 * 256, EOCDR_MAX = EOCDR_MIN + ZIP_COMMENT_MAX; |
||||||
|
|
||||||
|
// In most cases, the EOCDR is EOCDR_MIN bytes long
|
||||||
|
doSeek(EOCDR_MIN, function() { |
||||||
|
// If not found, try within EOCDR_MAX bytes
|
||||||
|
doSeek(Math.min(EOCDR_MAX, reader.size), function() { |
||||||
|
onerror(ERR_BAD_FORMAT); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// seek last length bytes of file for EOCDR
|
||||||
|
function doSeek(length, eocdrNotFoundCallback) { |
||||||
|
reader.readUint8Array(reader.size - length, length, function(bytes) { |
||||||
|
for (var i = bytes.length - EOCDR_MIN; i >= 0; i--) { |
||||||
|
if (bytes[i] === 0x50 && bytes[i + 1] === 0x4b && bytes[i + 2] === 0x05 && bytes[i + 3] === 0x06) { |
||||||
|
eocdrCallback(new DataView(bytes.buffer, i, EOCDR_MIN)); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
eocdrNotFoundCallback(); |
||||||
|
}, function() { |
||||||
|
onerror(ERR_READ); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var zipReader = { |
||||||
|
getEntries : function(callback) { |
||||||
|
var worker = this._worker; |
||||||
|
// look for End of central directory record
|
||||||
|
seekEOCDR(function(dataView) { |
||||||
|
var datalength, fileslength; |
||||||
|
datalength = dataView.getUint32(16, true); |
||||||
|
fileslength = dataView.getUint16(8, true); |
||||||
|
if (datalength < 0 || datalength >= reader.size) { |
||||||
|
onerror(ERR_BAD_FORMAT); |
||||||
|
return; |
||||||
|
} |
||||||
|
reader.readUint8Array(datalength, reader.size - datalength, function(bytes) { |
||||||
|
var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes); |
||||||
|
for (i = 0; i < fileslength; i++) { |
||||||
|
entry = new Entry(); |
||||||
|
entry._worker = worker; |
||||||
|
if (data.view.getUint32(index) != 0x504b0102) { |
||||||
|
onerror(ERR_BAD_FORMAT); |
||||||
|
return; |
||||||
|
} |
||||||
|
readCommonHeader(entry, data, index + 6, true, onerror); |
||||||
|
entry.commentLength = data.view.getUint16(index + 32, true); |
||||||
|
entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10); |
||||||
|
entry.offset = data.view.getUint32(index + 42, true); |
||||||
|
filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength)); |
||||||
|
entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename); |
||||||
|
if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/") |
||||||
|
entry.directory = true; |
||||||
|
comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46 |
||||||
|
+ entry.filenameLength + entry.extraFieldLength + entry.commentLength)); |
||||||
|
entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment); |
||||||
|
entries.push(entry); |
||||||
|
index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength; |
||||||
|
} |
||||||
|
callback(entries); |
||||||
|
}, function() { |
||||||
|
onerror(ERR_READ); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}, |
||||||
|
close : function(callback) { |
||||||
|
if (this._worker) { |
||||||
|
this._worker.terminate(); |
||||||
|
this._worker = null; |
||||||
|
} |
||||||
|
if (callback) |
||||||
|
callback(); |
||||||
|
}, |
||||||
|
_worker: null |
||||||
|
}; |
||||||
|
|
||||||
|
if (!obj.zip.useWebWorkers) |
||||||
|
callback(zipReader); |
||||||
|
else { |
||||||
|
createWorker('inflater', |
||||||
|
function(worker) { |
||||||
|
zipReader._worker = worker; |
||||||
|
callback(zipReader); |
||||||
|
}, |
||||||
|
function(err) { |
||||||
|
onerror(err); |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ZipWriter
|
||||||
|
|
||||||
|
function encodeUTF8(string) { |
||||||
|
return unescape(encodeURIComponent(string)); |
||||||
|
} |
||||||
|
|
||||||
|
function getBytes(str) { |
||||||
|
var i, array = []; |
||||||
|
for (i = 0; i < str.length; i++) |
||||||
|
array.push(str.charCodeAt(i)); |
||||||
|
return array; |
||||||
|
} |
||||||
|
|
||||||
|
function createZipWriter(writer, callback, onerror, dontDeflate) { |
||||||
|
var files = {}, filenames = [], datalength = 0; |
||||||
|
var deflateSN = 0; |
||||||
|
|
||||||
|
function onwriteerror(err) { |
||||||
|
onerror(err || ERR_WRITE); |
||||||
|
} |
||||||
|
|
||||||
|
function onreaderror(err) { |
||||||
|
onerror(err || ERR_READ_DATA); |
||||||
|
} |
||||||
|
|
||||||
|
var zipWriter = { |
||||||
|
add : function(name, reader, onend, onprogress, options) { |
||||||
|
var header, filename, date; |
||||||
|
var worker = this._worker; |
||||||
|
|
||||||
|
function writeHeader(callback) { |
||||||
|
var data; |
||||||
|
date = options.lastModDate || new Date(); |
||||||
|
header = getDataHelper(26); |
||||||
|
files[name] = { |
||||||
|
headerArray : header.array, |
||||||
|
directory : options.directory, |
||||||
|
filename : filename, |
||||||
|
offset : datalength, |
||||||
|
comment : getBytes(encodeUTF8(options.comment || "")) |
||||||
|
}; |
||||||
|
header.view.setUint32(0, 0x14000808); |
||||||
|
if (options.version) |
||||||
|
header.view.setUint8(0, options.version); |
||||||
|
if (!dontDeflate && options.level !== 0 && !options.directory) |
||||||
|
header.view.setUint16(4, 0x0800); |
||||||
|
header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true); |
||||||
|
header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true); |
||||||
|
header.view.setUint16(22, filename.length, true); |
||||||
|
data = getDataHelper(30 + filename.length); |
||||||
|
data.view.setUint32(0, 0x504b0304); |
||||||
|
data.array.set(header.array, 4); |
||||||
|
data.array.set(filename, 30); |
||||||
|
datalength += data.array.length; |
||||||
|
writer.writeUint8Array(data.array, callback, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
function writeFooter(compressedLength, crc32) { |
||||||
|
var footer = getDataHelper(16); |
||||||
|
datalength += compressedLength || 0; |
||||||
|
footer.view.setUint32(0, 0x504b0708); |
||||||
|
if (typeof crc32 != "undefined") { |
||||||
|
header.view.setUint32(10, crc32, true); |
||||||
|
footer.view.setUint32(4, crc32, true); |
||||||
|
} |
||||||
|
if (reader) { |
||||||
|
footer.view.setUint32(8, compressedLength, true); |
||||||
|
header.view.setUint32(14, compressedLength, true); |
||||||
|
footer.view.setUint32(12, reader.size, true); |
||||||
|
header.view.setUint32(18, reader.size, true); |
||||||
|
} |
||||||
|
writer.writeUint8Array(footer.array, function() { |
||||||
|
datalength += 16; |
||||||
|
onend(); |
||||||
|
}, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
function writeFile() { |
||||||
|
options = options || {}; |
||||||
|
name = name.trim(); |
||||||
|
if (options.directory && name.charAt(name.length - 1) != "/") |
||||||
|
name += "/"; |
||||||
|
if (files.hasOwnProperty(name)) { |
||||||
|
onerror(ERR_DUPLICATED_NAME); |
||||||
|
return; |
||||||
|
} |
||||||
|
filename = getBytes(encodeUTF8(name)); |
||||||
|
filenames.push(name); |
||||||
|
writeHeader(function() { |
||||||
|
if (reader) |
||||||
|
if (dontDeflate || options.level === 0) |
||||||
|
copy(worker, deflateSN++, reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror); |
||||||
|
else |
||||||
|
deflate(worker, deflateSN++, reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror); |
||||||
|
else |
||||||
|
writeFooter(); |
||||||
|
}, onwriteerror); |
||||||
|
} |
||||||
|
|
||||||
|
if (reader) |
||||||
|
reader.init(writeFile, onreaderror); |
||||||
|
else |
||||||
|
writeFile(); |
||||||
|
}, |
||||||
|
close : function(callback) { |
||||||
|
if (this._worker) { |
||||||
|
this._worker.terminate(); |
||||||
|
this._worker = null; |
||||||
|
} |
||||||
|
|
||||||
|
var data, length = 0, index = 0, indexFilename, file; |
||||||
|
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) { |
||||||
|
file = files[filenames[indexFilename]]; |
||||||
|
length += 46 + file.filename.length + file.comment.length; |
||||||
|
} |
||||||
|
data = getDataHelper(length + 22); |
||||||
|
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) { |
||||||
|
file = files[filenames[indexFilename]]; |
||||||
|
data.view.setUint32(index, 0x504b0102); |
||||||
|
data.view.setUint16(index + 4, 0x1400); |
||||||
|
data.array.set(file.headerArray, index + 6); |
||||||
|
data.view.setUint16(index + 32, file.comment.length, true); |
||||||
|
if (file.directory) |
||||||
|
data.view.setUint8(index + 38, 0x10); |
||||||
|
data.view.setUint32(index + 42, file.offset, true); |
||||||
|
data.array.set(file.filename, index + 46); |
||||||
|
data.array.set(file.comment, index + 46 + file.filename.length); |
||||||
|
index += 46 + file.filename.length + file.comment.length; |
||||||
|
} |
||||||
|
data.view.setUint32(index, 0x504b0506); |
||||||
|
data.view.setUint16(index + 8, filenames.length, true); |
||||||
|
data.view.setUint16(index + 10, filenames.length, true); |
||||||
|
data.view.setUint32(index + 12, length, true); |
||||||
|
data.view.setUint32(index + 16, datalength, true); |
||||||
|
writer.writeUint8Array(data.array, function() { |
||||||
|
writer.getData(callback); |
||||||
|
}, onwriteerror); |
||||||
|
}, |
||||||
|
_worker: null |
||||||
|
}; |
||||||
|
|
||||||
|
if (!obj.zip.useWebWorkers) |
||||||
|
callback(zipWriter); |
||||||
|
else { |
||||||
|
createWorker('deflater', |
||||||
|
function(worker) { |
||||||
|
zipWriter._worker = worker; |
||||||
|
callback(zipWriter); |
||||||
|
}, |
||||||
|
function(err) { |
||||||
|
onerror(err); |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function resolveURLs(urls) { |
||||||
|
var a = document.createElement('a'); |
||||||
|
return urls.map(function(url) { |
||||||
|
a.href = url; |
||||||
|
return a.href; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
var DEFAULT_WORKER_SCRIPTS = { |
||||||
|
deflater: ['z-worker.js', 'deflate.js'], |
||||||
|
inflater: ['z-worker.js', 'inflate.js'] |
||||||
|
}; |
||||||
|
function createWorker(type, callback, onerror) { |
||||||
|
if (obj.zip.workerScripts !== null && obj.zip.workerScriptsPath !== null) { |
||||||
|
onerror(new Error('Either zip.workerScripts or zip.workerScriptsPath may be set, not both.')); |
||||||
|
return; |
||||||
|
} |
||||||
|
var scripts; |
||||||
|
if (obj.zip.workerScripts) { |
||||||
|
scripts = obj.zip.workerScripts[type]; |
||||||
|
if (!Array.isArray(scripts)) { |
||||||
|
onerror(new Error('zip.workerScripts.' + type + ' is not an array!')); |
||||||
|
return; |
||||||
|
} |
||||||
|
scripts = resolveURLs(scripts); |
||||||
|
} else { |
||||||
|
scripts = DEFAULT_WORKER_SCRIPTS[type].slice(0); |
||||||
|
scripts[0] = (obj.zip.workerScriptsPath || '') + scripts[0]; |
||||||
|
} |
||||||
|
var worker = new Worker(scripts[0]); |
||||||
|
// record total consumed time by inflater/deflater/crc32 in this worker
|
||||||
|
worker.codecTime = worker.crcTime = 0; |
||||||
|
worker.postMessage({ type: 'importScripts', scripts: scripts.slice(1) }); |
||||||
|
worker.addEventListener('message', onmessage); |
||||||
|
function onmessage(ev) { |
||||||
|
var msg = ev.data; |
||||||
|
if (msg.error) { |
||||||
|
worker.terminate(); // should before onerror(), because onerror() may throw.
|
||||||
|
onerror(msg.error); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (msg.type === 'importScripts') { |
||||||
|
worker.removeEventListener('message', onmessage); |
||||||
|
worker.removeEventListener('error', errorHandler); |
||||||
|
callback(worker); |
||||||
|
} |
||||||
|
} |
||||||
|
// catch entry script loading error and other unhandled errors
|
||||||
|
worker.addEventListener('error', errorHandler); |
||||||
|
function errorHandler(err) { |
||||||
|
worker.terminate(); |
||||||
|
onerror(err); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function onerror_default(error) { |
||||||
|
console.error(error); |
||||||
|
} |
||||||
|
obj.zip = { |
||||||
|
Reader : Reader, |
||||||
|
Writer : Writer, |
||||||
|
BlobReader : BlobReader, |
||||||
|
Data64URIReader : Data64URIReader, |
||||||
|
TextReader : TextReader, |
||||||
|
BlobWriter : BlobWriter, |
||||||
|
Data64URIWriter : Data64URIWriter, |
||||||
|
TextWriter : TextWriter, |
||||||
|
createReader : function(reader, callback, onerror) { |
||||||
|
onerror = onerror || onerror_default; |
||||||
|
|
||||||
|
reader.init(function() { |
||||||
|
createZipReader(reader, callback, onerror); |
||||||
|
}, onerror); |
||||||
|
}, |
||||||
|
createWriter : function(writer, callback, onerror, dontDeflate) { |
||||||
|
onerror = onerror || onerror_default; |
||||||
|
dontDeflate = !!dontDeflate; |
||||||
|
|
||||||
|
writer.init(function() { |
||||||
|
createZipWriter(writer, callback, onerror, dontDeflate); |
||||||
|
}, onerror); |
||||||
|
}, |
||||||
|
useWebWorkers : true, |
||||||
|
/** |
||||||
|
* Directory containing the default worker scripts (z-worker.js, deflate.js, and inflate.js), relative to current base url. |
||||||
|
* E.g.: zip.workerScripts = './'; |
||||||
|
*/ |
||||||
|
workerScriptsPath : null, |
||||||
|
/** |
||||||
|
* Advanced option to control which scripts are loaded in the Web worker. If this option is specified, then workerScriptsPath must not be set. |
||||||
|
* workerScripts.deflater/workerScripts.inflater should be arrays of urls to scripts for deflater/inflater, respectively. |
||||||
|
* Scripts in the array are executed in order, and the first one should be z-worker.js, which is used to start the worker. |
||||||
|
* All urls are relative to current base url. |
||||||
|
* E.g.: |
||||||
|
* zip.workerScripts = { |
||||||
|
* deflater: ['z-worker.js', 'deflate.js'], |
||||||
|
* inflater: ['z-worker.js', 'inflate.js'] |
||||||
|
* }; |
||||||
|
*/ |
||||||
|
workerScripts : null, |
||||||
|
}; |
||||||
|
|
||||||
|
})(this); |
@ -0,0 +1,49 @@ |
|||||||
|
/// wrapper for zlib-asm (https://github.com/ukyo/zlib-asm)
|
||||||
|
|
||||||
|
/* globals zlib */ |
||||||
|
(function(global) { |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
function Codec(isDeflater, options) { |
||||||
|
this._isDeflater = isDeflater; |
||||||
|
if (options && typeof options.level === 'number') |
||||||
|
this.level = options.level; |
||||||
|
this._inputLength = 0; |
||||||
|
this._input = []; |
||||||
|
} |
||||||
|
Codec.prototype.append = function append(bytes, onprogress) { |
||||||
|
this._inputLength += bytes.length; |
||||||
|
this._input.push(bytes); |
||||||
|
}; |
||||||
|
Codec.prototype.flush = function flush() { |
||||||
|
var bytes; |
||||||
|
var input = this._input; |
||||||
|
if (input.length === 1) |
||||||
|
bytes = input[0]; |
||||||
|
else { |
||||||
|
bytes = new Uint8Array(this._inputLength); |
||||||
|
for (var i = 0, n = input.length, off = 0; i < n; i++) { |
||||||
|
var slice = input[i]; |
||||||
|
bytes.set(slice, off); |
||||||
|
off += slice.length; |
||||||
|
} |
||||||
|
} |
||||||
|
return this._isDeflater ? |
||||||
|
zlib.rawDeflate(bytes, this.level) : |
||||||
|
zlib.rawInflate(bytes); |
||||||
|
}; |
||||||
|
|
||||||
|
function Deflater(options) { |
||||||
|
Codec.call(this, true, options); |
||||||
|
} |
||||||
|
Deflater.prototype = Object.create(Codec.prototype); |
||||||
|
function Inflater() { |
||||||
|
Codec.call(this, false); |
||||||
|
} |
||||||
|
Inflater.prototype = Object.create(Codec.prototype); |
||||||
|
|
||||||
|
// 'zip' may not be defined in z-worker and some tests
|
||||||
|
var env = global.zip || global; |
||||||
|
env.Deflater = env._zlib_asm_Deflater = Deflater; |
||||||
|
env.Inflater = env._zlib_asm_Inflater = Inflater; |
||||||
|
})(this); |
@ -0,0 +1,19 @@ |
|||||||
|
var assets = {} |
||||||
|
|
||||||
|
assets.loadLocal = function(file) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
assets.loadRemote = function(file) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// called by loadRemote/Local
|
||||||
|
assets.decodeZip = function(zip) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// Given a buffer
|
||||||
|
assets.decodeSong = function(index, callback) { |
||||||
|
|
||||||
|
} |
@ -0,0 +1,217 @@ |
|||||||
|
/* README for mon |
||||||
|
* download zip.js from here http://gildas-lormeau.github.io/zip.js/
|
||||||
|
* place all .js from zip.js in lib/ |
||||||
|
* place this file also somewhere in the webserver dir |
||||||
|
* add lib/zip.js, lib/zip-fs.js and this file as scripts |
||||||
|
* call respackInit() from somewhere. |
||||||
|
* Enjoy the filepicker. |
||||||
|
* |
||||||
|
* Limitations: |
||||||
|
* - does not load images yet |
||||||
|
* - cannot unload respacks |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
function respackParseSongs(songsxml){ |
||||||
|
var oParser = new DOMParser(); |
||||||
|
var oDOM = oParser.parseFromString(songsxml, "text/xml"); |
||||||
|
var songList = []; |
||||||
|
if(oDOM.documentElement.nodeName !== "songs"){ |
||||||
|
console.log("Songs.xml error, corrupt file?") |
||||||
|
return songList; |
||||||
|
} |
||||||
|
|
||||||
|
var domsonglist = oDOM.documentElement.children; |
||||||
|
for (var i = 0; i < domsonglist.length; i++){ |
||||||
|
var filename = domsonglist[i].attributes[0].value + ".mp3"; |
||||||
|
var rhythm = domsonglist[i].getElementsByTagName('rhythm')[0].textContent; |
||||||
|
var title = domsonglist[i].getElementsByTagName('title')[0].textContent; |
||||||
|
var source = domsonglist[i].getElementsByTagName('source')[0] && domsonglist[i].getElementsByTagName('source')[0].textContent; |
||||||
|
var buildUp = domsonglist[i].getElementsByTagName('buildup')[0] && domsonglist[i].getElementsByTagName('buildup')[0].textContent; |
||||||
|
var buildUpRhythm = domsonglist[i].getElementsByTagName('buildupRhythm')[0] && domsonglist[i].getElementsByTagName('buildupRhythm')[0].textContent; |
||||||
|
|
||||||
|
if (buildUp && !buildUpRhythm){ |
||||||
|
buildUpRhythm = "."; //Add a empty rhythm
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
var result = { |
||||||
|
pack: packno, |
||||||
|
file: filename, |
||||||
|
name: title, |
||||||
|
source: source, |
||||||
|
rhythm: rhythm |
||||||
|
} |
||||||
|
if(buildUp){ |
||||||
|
result.buildUp = buildUp + ".mp3"; |
||||||
|
result.buildUpRhythm = buildUpRhythm; |
||||||
|
} |
||||||
|
songList[i] = result; |
||||||
|
} |
||||||
|
|
||||||
|
return songList; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function respackFindBuildUpData(song, songsDir, onend){ |
||||||
|
songsDir.children.forEach(function(entry) { |
||||||
|
var buildupFile = song.buildUp; |
||||||
|
if (buildupFile === entry.name) { |
||||||
|
|
||||||
|
entry.getData64URI("audio/mpeg", function(data) { |
||||||
|
|
||||||
|
onend(data); |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function respackFindSongData(song, songsDir, onend){ |
||||||
|
songsDir.children.forEach(function(entry) { |
||||||
|
var songfile = song.file; |
||||||
|
if (songfile === entry.name) { |
||||||
|
|
||||||
|
entry.getData64URI("audio/mpeg", function(data) { |
||||||
|
|
||||||
|
onend(data); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function respackIsLoaded(packname){ |
||||||
|
for (var i = 0; i < respacksLoaded.length; i++) { |
||||||
|
if(respacksLoaded[i].name === packname ){ |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function respackInit(audio) { |
||||||
|
|
||||||
|
//Add array.find() for chrome
|
||||||
|
if (!Array.prototype.find) { |
||||||
|
Array.prototype.find = function(predicate) { |
||||||
|
if (this == null) { |
||||||
|
throw new TypeError('Array.prototype.find called on null or undefined'); |
||||||
|
} |
||||||
|
if (typeof predicate !== 'function') { |
||||||
|
throw new TypeError('predicate must be a function'); |
||||||
|
} |
||||||
|
var list = Object(this); |
||||||
|
var length = list.length >>> 0; |
||||||
|
var thisArg = arguments[1]; |
||||||
|
var value; |
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) { |
||||||
|
value = list[i]; |
||||||
|
if (predicate.call(thisArg, value, i, list)) { |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
zip.workerScriptsPath = 'lib/'; |
||||||
|
respacksLoaded = []; |
||||||
|
packno = 4; |
||||||
|
function addGlobalSong(song){ |
||||||
|
audio.songs[audio.songs.length] = song; |
||||||
|
} |
||||||
|
var fileInput = document.getElementById("respack-input"); |
||||||
|
if(!audio || !audio.songs ){ |
||||||
|
console.log("invalid audio object, bailing..."); |
||||||
|
if(fileInput){ |
||||||
|
fileInput.remove(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function respackHandleZipFile(event) { |
||||||
|
var fileInput = document.getElementById("respack-input"); |
||||||
|
|
||||||
|
var fs = new zip.fs.FS(); |
||||||
|
|
||||||
|
fs.importBlob( fileInput.files[0], function() { |
||||||
|
|
||||||
|
if(fs.root.children.length === 1){ |
||||||
|
rootdir = fs.root.children[0] |
||||||
|
var basename = rootdir.name; |
||||||
|
//Packshit respack is giving me headaches, this "fixes" the directory structure like Packshit.zip/Packshit/Packshit/songs.xml
|
||||||
|
if(rootdir.children.length == 1 && rootdir.children[0].name == basename){ |
||||||
|
rootdir = rootdir.children[0]; |
||||||
|
} |
||||||
|
if(respackIsLoaded(basename)){ |
||||||
|
alert("respack " + basename + " already loaded!"); |
||||||
|
return; |
||||||
|
}else{ |
||||||
|
respacksLoaded[respacksLoaded.length] = {name: basename, packno: packno}; |
||||||
|
} |
||||||
|
console.log("Loading " + basename+".zip"); |
||||||
|
//Find files of iterest
|
||||||
|
var songsXML = rootdir.children.find(function(element,index, array){ |
||||||
|
if( element.name.toLowerCase() === "songs.xml"){ |
||||||
|
return element; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}); |
||||||
|
|
||||||
|
var songsDir = rootdir.children.find(function(element,index, array){ |
||||||
|
if( element.directory && (element.name.toLowerCase() === "songs" || element.name.toLowerCase() === "loops") ){ |
||||||
|
return element; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}); |
||||||
|
songsXML.getText(function(text){ |
||||||
|
text = text.replace(/&/g, '&amp;'); //XML parser will complain about a bare '&', found in the Packshit respack
|
||||||
|
var songList = respackParseSongs(text); |
||||||
|
songList.forEach(function(song, index, arr){ |
||||||
|
if(song.buildUp){ |
||||||
|
respackFindBuildUpData(song, songsDir, function(data){ |
||||||
|
song.buildUp = data; |
||||||
|
|
||||||
|
}); |
||||||
|
} |
||||||
|
respackFindSongData(song, songsDir, function(data){ |
||||||
|
song.file = data; |
||||||
|
addGlobalSong(song); |
||||||
|
}); |
||||||
|
|
||||||
|
if( index === (arr.length-1)){ |
||||||
|
|
||||||
|
packno += 1; |
||||||
|
fs = null; //or somethin
|
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
}, function(error) { |
||||||
|
// onerror callback
|
||||||
|
console.log("Error loading zip file!"); |
||||||
|
}); |
||||||
|
this.value = null; //Chrom*
|
||||||
|
return false; //Firefox
|
||||||
|
} |
||||||
|
if(!fileInput){ |
||||||
|
//create a file input
|
||||||
|
fileInput = document.createElement("input"); |
||||||
|
fileInput.type ="file" |
||||||
|
fileInput.accept="application/zip" |
||||||
|
fileInput.id="respack-input" |
||||||
|
var controls = document.getElementById("controls"); |
||||||
|
controls.appendChild(fileInput); |
||||||
|
|
||||||
|
} |
||||||
|
fileInput.addEventListener('change', respackHandleZipFile, false); |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
waifus = {} |
||||||
|
|
||||||
|
waifus.defaults = { |
||||||
|
respacks: ["respacks/default.zip"], |
||||||
|
preloadBuild: null, |
||||||
|
preloadLoop: null, |
||||||
|
preloadOutro: null, |
||||||
|
customColourSet: null, |
||||||
|
blurQuality: 2, // low/med/high/extreme 0-3
|
||||||
|
|
||||||
|
// UI accessible config
|
||||||
|
// Autosong stuff is a todo
|
||||||
|
smartAlign: true, |
||||||
|
blendMode: "hard-light", // hard-light, TODO: plain, alpha
|
||||||
|
blurAmount: 1, // 0,1,2,3 = off,low,med,high
|
||||||
|
blurDecay: 2, // 0,1,2,3 = slow,med,fast,faster!
|
||||||
|
colourSet: "normal", // normal, pastel, 420, custom
|
||||||
|
// scaleImages, nah
|
||||||
|
blackoutUI: false, |
||||||
|
// channel selection, nah
|
||||||
|
playBuildups: "on" // off, once, on
|
||||||
|
} |
||||||
|
|
||||||
|
waifus.init = function() { |
||||||
|
console.log("Initialising 0x40"); |
||||||
|
// merge defaults and user set stuff
|
||||||
|
waifus.config = waifus.config || {}; |
||||||
|
for(var attr in waifus.defaults) { |
||||||
|
if(!waifus.config[attr]) |
||||||
|
waifus.config[attr] = waifus.defaults[attr]; |
||||||
|
} |
||||||
|
|
||||||
|
waifus.updateBlurQuality(); |
||||||
|
waifus.updateBlurAmount(); |
||||||
|
waifus.updateBlurDecay(); |
||||||
|
waifuCanvas.blendMode = waifus.config.blendMode; |
||||||
|
waifuCanvas.init(); |
||||||
|
|
||||||
|
waifus.initPreloader(); |
||||||
|
if(waifus.config.preloadBuild || waifus.config.preloadLoop || waifus.config.preloadOutro) |
||||||
|
waifus.initPreloadSong(); |
||||||
|
|
||||||
|
for(var i in waifus.config.respacks) { |
||||||
|
waifus.loadRespack(i); |
||||||
|
} |
||||||
|
console.log("Initialisation complete"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
waifus.initPreloadSong = function() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
waifus.initPreloader = function() { |
||||||
|
waifus.preload = []; |
||||||
|
waifus.leftToLoad = waifus.config.respacks.length; |
||||||
|
for(var i in waifus.config.respacks) { |
||||||
|
waifus.preload[i] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
waifus.updatePreloader = function() { |
||||||
|
var total = 0; |
||||||
|
for(var i in waifus.config.respacks) { |
||||||
|
total += waifus.preload[i]; |
||||||
|
} |
||||||
|
total /= waifus.config.respacks.length; |
||||||
|
total = Math.floor(total * 0x40); |
||||||
|
document.getElementById("preloader").innerHTML = '0x' + pad(total.toString(16), 2); |
||||||
|
} |
||||||
|
|
||||||
|
waifus.loadRespack = function(i) { |
||||||
|
var respack = waifus.config.respacks[i]; |
||||||
|
console.log("Loading " + respack); |
||||||
|
var req = new XMLHttpRequest(); |
||||||
|
req.open('GET', respack, true); |
||||||
|
req.responseType = 'arraybuffer'; |
||||||
|
req.onload = function() { |
||||||
|
console.log(respack + " downloaded"); |
||||||
|
waifus.onRespackLoad(req.response); |
||||||
|
}; |
||||||
|
req.onprogress = function(evt) { |
||||||
|
if (evt.lengthComputable) { |
||||||
|
waifus.preload[i] = evt.loaded / evt.total; |
||||||
|
waifus.updatePreloader(); |
||||||
|
} |
||||||
|
} |
||||||
|
req.send(); |
||||||
|
} |
||||||
|
|
||||||
|
waifus.onRespackLoad = function(buff) { |
||||||
|
processRespack(buff); |
||||||
|
waifus.leftToLoad--; |
||||||
|
if(!waifus.leftToLoad) { |
||||||
|
console.log("All zips downloaded"); |
||||||
|
document.getElementById("preloader").style.color = "#0F0"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
waifus.updateBlurQuality = function() { |
||||||
|
var iterations = [5, 15, 31, 65]; |
||||||
|
waifuCanvas.setBlurIterations(iterations[waifus.config.blurQuality]); |
||||||
|
} |
||||||
|
|
||||||
|
waifus.updateBlurAmount = function() { |
||||||
|
var amount = [0, 15, 30, 60]; |
||||||
|
waifuCanvas.setBlurAmount(amount[waifus.config.blurAmount]); |
||||||
|
} |
||||||
|
|
||||||
|
waifus.updateBlurDecay = function() { |
||||||
|
var decay = [10, 15, 25, 45]; |
||||||
|
waifuCanvas.setBlurDecay(decay[waifus.config.blurDecay]); |
||||||
|
} |
Loading…
Reference in new issue