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