Massive changes, see full text

- 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 images
serial
Will Toohey 10 years ago
parent db16a57656
commit 8cccc393bf
  1. 2
      .gitignore
  2. 313
      css/hues-m.css
  3. 20
      css/hues-r.css
  4. 53
      css/hues-w.css
  5. 30
      css/hues-x.css
  6. 74
      css/style.css
  7. 402
      index.html
  8. 303
      js/HuesCanvas.js
  9. 843
      js/HuesCore.js
  10. 387
      js/HuesSettings.js
  11. 423
      js/HuesUI.js
  12. 132
      js/ResourceManager.js
  13. 494
      js/ResourcePack.js
  14. 258
      js/SoundManager.js
  15. 11
      lib/.jshintrc
  16. 2060
      lib/deflate.js
  17. 2155
      lib/inflate.js
  18. 1002
      lib/mime-types.js
  19. 64
      lib/pako/codecs.js
  20. 153
      lib/z-worker.js
  21. 242
      lib/zip-ext.js
  22. 541
      lib/zip-fs.js
  23. 966
      lib/zip.js
  24. 49
      lib/zlib-asm/codecs.js
  25. 385
      old/index.html
  26. 57
      old/index_ideal.html
  27. 0
      old/js/0x40.js
  28. 19
      old/js/assetManager.js
  29. 0
      old/js/audioUtils.js
  30. 217
      old/js/respack.js
  31. 2
      old/js/waifuCanvas.js
  32. 113
      old/js/waifus.js
  33. 0
      old/style.css
  34. 1886
      orig.txt

2
.gitignore vendored

@ -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;}
}

@ -1,385 +1,45 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" > <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" >
<!--
TODO:
Clean up the damn code
if you're not mon, don't read any further, it's pretty bad JS
Keep arraybuffer in mem, load AudioBuffer as needed?
Volume controls
Prettier ui
Song shuffle
Image pause / manual advance
Different colour palettes
External respacks
Blur into blackout? iunno
-->
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>0x40</title> <title>0x40</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="css/hues-x.css">
<script type="text/javascript" src="0x40.js"></script> <link rel="stylesheet" href="css/hues-m.css">
<script type="text/javascript" src="waifuCanvas.js"></script> <link rel="stylesheet" href="css/hues-r.css">
<script type="text/javascript" src="audioUtils.js"></script> <link rel="stylesheet" href="css/hues-w.css">
<script type="text/javascript" src="js/HuesCore.js"></script>
<script type="text/javascript" src="js/HuesSettings.js"></script>
<script type="text/javascript" src="js/HuesUI.js"></script>
<script type="text/javascript" src="js/HuesCanvas.js"></script>
<script type="text/javascript" src="js/ResourcePack.js"></script>
<script type="text/javascript" src="js/ResourceManager.js"></script>
<script type="text/javascript" src="js/SoundManager.js"></script>
<script type="text/javascript" src="js/lib/zip.js"></script>
<script type="text/javascript" src="js/lib/zip-fs.js"></script>
<script type="text/javascript"> <script type="text/javascript">
//debug
var skipPreloader = false;
var filesLoaded = 0;
var initialLoad = 0;
var preloaderSong = null;
var audio = {
buffer: null,
proceed: true,
songs: rgSongs,
current_song: null,
current_index: 0,
beat_pointer: 0,
}
// Why does this exist? For smooth preloader transition on FF
audio.prep = function() {
var song = audio.songs[audio.current_index];
document.getElementById("songname").innerHTML = song.name
waifuCanvas.clearBlackout();
// stop() destroys the old, must recreate
var newSong = audio.context.createBufferSource()
audio.current_song = newSong;
newSong.buffer = song.buffer;
newSong.loop = true;
newSong.loopStart = song.loopStart;
newSong.loopEnd = song.buffer.duration;
newSong.loopLength = song.loopLength;
newSong.connect(audio.context.destination);
newSong._build = song.buildUpRhythm || "";
newSong._beats = newSong._build + song.rhythm;
newSong._loopWidth = newSong.loopLength / song.rhythm.length;
newSong._buildWidth = newSong._build ? newSong.loopStart / newSong._build.length : 0;
newSong._beatWidth = newSong._buildWidth;
audio.beat_pointer = -newSong._build.length;
updateBeatView(-1);
document.getElementById("timer").innerHTML = "T=0x0000"
document.getElementById("beetAccent").style.animationDuration = newSong._loopWidth * 1.5 + "s";
}
audio.play = function(delay) {
if(!audio.current_song) {
audio.prep();
}
var cur = audio.current_song;
if(cur._playing) {
return;
}
delay = delay ? delay: 0;
cur.start(audio.context.currentTime + delay);
// offset to after the build
cur._startTime = audio.context.currentTime + cur.loopStart + delay;
cur._playing = true;
}
audio.stop = function() {
if (audio.current_song && audio.current_song._playing) {
updateBeatView(0);
audio.current_song.stop();
audio.current_song = null;
}
}
audio.next = function() {
audio.current_index++;
audio.current_index %= audio.songs.length;
audio.playIndex(audio.current_index);
}
audio.prev = function() {
if(audio.current_index-- <= 0) {
audio.current_index = audio.songs.length - 1;
}
audio.playIndex(audio.current_index);
}
audio.playIndex = function(index) {
audio.current_index = index;
loadSong(audio.songs[index], function() {
audio.stop();
audio.play();
});
}
// In seconds, relative to the loop start
function currentTime() {
var cur = audio.current_song;
var time = audio.context.currentTime - cur._startTime;
return time;
}
function wrapBeats(start, length) {
var ret = '';
for(var i=start; i < start+length; i++) {
ret += getBeat(i);
}
return ret;
}
function loopBeat(index) {
return index %
(audio.current_song._beats.length-audio.current_song._build.length);
}
function getBeat(index) {
var cur = audio.current_song;
if(index < 0) {
return cur._beats.charAt(index + cur._build.length);
} else {
return cur._beats.charAt(loopBeat(index) + cur._build.length);
}
}
function timerUpdate() {
if(!audio.current_song || !audio.current_song._playing) {
return;
}
var now = currentTime();
if(now < 0)
return;
now %= audio.current_song.loopLength;
now = parseInt(now * 1000);
document.getElementById("timer").innerHTML = "T=0x" + pad(now.toString(16).toUpperCase(), 4);
}
function animationLoop() {
requestAnimationFrame(animationLoop);
timerUpdate();
waifuCanvas.animationLoop();
var cur = audio.current_song;
if(!cur || !cur._playing) {
return;
}
var now = currentTime();
if(audio.beat_pointer >=0)
cur._beatWidth = cur._loopWidth;
for(var beatTime = audio.beat_pointer * cur._beatWidth; beatTime < now;
beatTime = ++audio.beat_pointer * cur._beatWidth) {
var beat = getBeat(audio.beat_pointer);
var c = RequestNextColor();
handleBeat(beat, audio.beat_pointer, cur);
}
}
function updateBeatView(pointer) {
// future minus accent
var beatLine = wrapBeats(pointer+1, 30);
document.getElementById("beetRight").innerHTML = beatLine;
document.getElementById("beetLeft").innerHTML = beatLine.split("").reverse().join("");
}
function handleBeat(beat, bp, current) {
// we have changed song since we were scheduled
if(current != audio.current_song)
return;
updateBeatView(bp);
// I probably shouldn't have so much on one line
document.getElementById("beatCount").innerHTML = "B=0x" + pad(bp < 0 ? 0: loopBeat(bp).toString(16).toUpperCase(), 4);
if(beat != '.') {
var accent = document.getElementById("beetAccent");
accent.innerHTML = beat;
accent.className = "beetView";
// trigger reflow to restart animation
accent.offsetWidth = accent.offsetWidth;
accent.className = "beetView fade";
switch(beat) {
case 'X':
case 'x':
waifuCanvas.yBlur();
break;
case 'O':
case 'o':
waifuCanvas.xBlur();
break;
case '+':
waifuCanvas.xBlur();
waifuCanvas.blackout();
break;
case '¤':
waifuCanvas.xBlur();
waifuCanvas.blackout(true);
break;
case '|':
waifuCanvas.shortBlackout(current._beatWidth);
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
break;
case ':':
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
break;
case '*':
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
break;
case '=':
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
case '~':
// TODO colour fade
break;
}
if ([".", "+", "|", "¤"].indexOf(beat) == -1) {
waifuCanvas.clearBlackout();
}
if([".", "+", ":", "*", "X", "O", "~", "="].indexOf(beat) == -1) {
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
}
}
}
function onFileLoad(file) {
filesLoaded++;
var percent = Math.floor(filesLoaded / initialLoad * 0x40);
document.getElementById("preloader").innerHTML = '0x' + pad(percent.toString(16), 2);
// Destroy FF lag
if(!skipPreloader && file && file.name == "Madeon - Finale") {
audio.prep();
}
if(filesLoaded >= initialLoad)
onAllLoaded();
}
function onAllLoaded() {
waifuCanvas.init();
console.log("Completed inital load");
animationLoop();
document.getElementById("preloader").style.color = "#0F0";
if(skipPreloader)
return;
var fileProgress = (audio.context.currentTime - preloaderSong.started) % madeonPreload.loopStart;
var timeToFade = madeonPreload.loopStart - fileProgress;
audio.play(preloaderSong.buffer.duration - fileProgress);
setTimeout( function() {
document.getElementById("preloader").className = "loaded"
}, timeToFade * 1000);
// hacky disgusting chrome workaround
if (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)){
preloaderSong.stop();
preloaderSong = chromeHacks(madeonPreload, fileProgress);
} else {
preloaderSong.loop = false;
}
preloaderSong.onended = function() {
// get around issues on mobile where pointer-events don't work
document.getElementById("preloader").style.display = "none";
document.getElementById("waifu").className = "loaded"
updateBeatView(-1);
// free dat memory
preloaderSong = null;
}
}
function chromeHacks(song, time) {
var ret = audio.context.createBufferSource()
ret.buffer = song.buffer;
ret.connect(audio.context.destination);
ret.started = audio.context.currentTime;
ret.start(0, time);
return ret;
}
window.onload = function() { window.onload = function() {
// Check Web Audio API Support zip.workerScriptsPath = "lib/";
try { var defaults = {
// More info at http://caniuse.com/#feat=audio-api respacks : ["./respacks/Defaults_v5.0.zip",
window.AudioContext = window.AudioContext || window.webkitAudioContext; "./respacks/CharPackagev0.03.zip",
audio.context = new window.AudioContext(); "./respacks/HuesMixA.zip"],
} catch(e) { load : true, //debug
audio.proceed = false; autoplay : true // debug
alert('Web Audio API not supported in this browser.'); };
} core = new HuesCore(defaults);
// normal, xBlur, yBlur, songs
initialLoad = waifus.length + audio.songs.length;
// Sexy audio preloader
if(!skipPreloader) {
loadSong(madeonPreload, function(song) {
preloaderSong = audio.context.createBufferSource()
preloaderSong.buffer = song.buffer;
preloaderSong.loop = true;
preloaderSong.loopStart = 0;
preloaderSong.loopEnd = song.loopStart;
preloaderSong.connect(audio.context.destination);
preloaderSong.started = audio.context.currentTime;
preloaderSong.start(0);
});
} else {
document.getElementById("preloader").className = "loaded";
document.getElementById("preloader").style.display = "none";
document.getElementById("waifu").className = "loaded";
}
for(var song in audio.songs) {
loadSong(audio.songs[song], onFileLoad);
}
// preload images
waifuCanvas.preload();
}; };
$(document).keydown(function(e) {
switch(e.which) {
case 37: // left
break;
case 38: // up
audio.next();
break;
case 39: // right
break;
case 40: // down
audio.prev();
break;
default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent the default action (scroll / move caret)
});
</script> </script>
</head> </head>
<body> <body>
<div id="preloader"> <div id="preloadHelper">
0x00 <div id="preloader">
</div> <div id="preMain">0x00</div>
<div class="ui" id="beets"> <div id="preSub"></div>
<div class="beetView" id="beetLeft"></div> </div>
<div class="beetView fade" id="beetAccent">X</div>
<div class="beetView" id="beetRight"></div>
</div> </div>
<div id="huesSettings">TODO</div>
<div id="huesUI"></div>
<canvas id="waifu" width="1280" height="720"></canvas> <canvas id="waifu" width="1280" height="720"></canvas>
<div class="ui" id="controls">
<a href="#" onclick="void(audio.stop());">stop</a>
<a href="#" onclick="void(audio.play());">play</a>
<a href="#" onclick="void(audio.prev());">prev</a>
<a href="#" onclick="void(audio.next());">next</a>
<div id="beatCount">B=0x0000</div>
<div id="timer">T=0x0000</div>
<div id="songname">Madeon - Finale</div>
<div id="waifuName">Megumi</div>
<div id="colourName">white</div>
</div>
</body> </body>
</html> </html>

@ -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;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;amp;');
that.parseImageFile(text);
that._imageFile = null;
that.parseXML();
});
return;
}
if (this._infoFile) {
this._infoFile.getText(function(text) {
text = text.replace(/&/g, '&amp;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,385 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" >
<!--
TODO:
Clean up the damn code
if you're not mon, don't read any further, it's pretty bad JS
Keep arraybuffer in mem, load AudioBuffer as needed?
Volume controls
Prettier ui
Song shuffle
Image pause / manual advance
Different colour palettes
External respacks
Blur into blackout? iunno
-->
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>0x40</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="0x40.js"></script>
<script type="text/javascript" src="waifuCanvas.js"></script>
<script type="text/javascript" src="audioUtils.js"></script>
<script type="text/javascript">
//debug
var skipPreloader = false;
var filesLoaded = 0;
var initialLoad = 0;
var preloaderSong = null;
var audio = {
buffer: null,
proceed: true,
songs: rgSongs,
current_song: null,
current_index: 0,
beat_pointer: 0,
}
// Why does this exist? For smooth preloader transition on FF
audio.prep = function() {
var song = audio.songs[audio.current_index];
document.getElementById("songname").innerHTML = song.name
waifuCanvas.clearBlackout();
// stop() destroys the old, must recreate
var newSong = audio.context.createBufferSource()
audio.current_song = newSong;
newSong.buffer = song.buffer;
newSong.loop = true;
newSong.loopStart = song.loopStart;
newSong.loopEnd = song.buffer.duration;
newSong.loopLength = song.loopLength;
newSong.connect(audio.context.destination);
newSong._build = song.buildUpRhythm || "";
newSong._beats = newSong._build + song.rhythm;
newSong._loopWidth = newSong.loopLength / song.rhythm.length;
newSong._buildWidth = newSong._build ? newSong.loopStart / newSong._build.length : 0;
newSong._beatWidth = newSong._buildWidth;
audio.beat_pointer = -newSong._build.length;
updateBeatView(-1);
document.getElementById("timer").innerHTML = "T=0x0000"
document.getElementById("beetAccent").style.animationDuration = newSong._loopWidth * 1.5 + "s";
}
audio.play = function(delay) {
if(!audio.current_song) {
audio.prep();
}
var cur = audio.current_song;
if(cur._playing) {
return;
}
delay = delay ? delay: 0;
cur.start(audio.context.currentTime + delay);
// offset to after the build
cur._startTime = audio.context.currentTime + cur.loopStart + delay;
cur._playing = true;
}
audio.stop = function() {
if (audio.current_song && audio.current_song._playing) {
updateBeatView(0);
audio.current_song.stop();
audio.current_song = null;
}
}
audio.next = function() {
audio.current_index++;
audio.current_index %= audio.songs.length;
audio.playIndex(audio.current_index);
}
audio.prev = function() {
if(audio.current_index-- <= 0) {
audio.current_index = audio.songs.length - 1;
}
audio.playIndex(audio.current_index);
}
audio.playIndex = function(index) {
audio.current_index = index;
loadSong(audio.songs[index], function() {
audio.stop();
audio.play();
});
}
// In seconds, relative to the loop start
function currentTime() {
var cur = audio.current_song;
var time = audio.context.currentTime - cur._startTime;
return time;
}
function wrapBeats(start, length) {
var ret = '';
for(var i=start; i < start+length; i++) {
ret += getBeat(i);
}
return ret;
}
function loopBeat(index) {
return index %
(audio.current_song._beats.length-audio.current_song._build.length);
}
function getBeat(index) {
var cur = audio.current_song;
if(index < 0) {
return cur._beats.charAt(index + cur._build.length);
} else {
return cur._beats.charAt(loopBeat(index) + cur._build.length);
}
}
function timerUpdate() {
if(!audio.current_song || !audio.current_song._playing) {
return;
}
var now = currentTime();
if(now < 0)
return;
now %= audio.current_song.loopLength;
now = parseInt(now * 1000);
document.getElementById("timer").innerHTML = "T=0x" + pad(now.toString(16).toUpperCase(), 4);
}
function animationLoop() {
requestAnimationFrame(animationLoop);
timerUpdate();
waifuCanvas.animationLoop();
var cur = audio.current_song;
if(!cur || !cur._playing) {
return;
}
var now = currentTime();
if(audio.beat_pointer >=0)
cur._beatWidth = cur._loopWidth;
for(var beatTime = audio.beat_pointer * cur._beatWidth; beatTime < now;
beatTime = ++audio.beat_pointer * cur._beatWidth) {
var beat = getBeat(audio.beat_pointer);
var c = RequestNextColor();
handleBeat(beat, audio.beat_pointer, cur);
}
}
function updateBeatView(pointer) {
// future minus accent
var beatLine = wrapBeats(pointer+1, 30);
document.getElementById("beetRight").innerHTML = beatLine;
document.getElementById("beetLeft").innerHTML = beatLine.split("").reverse().join("");
}
function handleBeat(beat, bp, current) {
// we have changed song since we were scheduled
if(current != audio.current_song)
return;
updateBeatView(bp);
// I probably shouldn't have so much on one line
document.getElementById("beatCount").innerHTML = "B=0x" + pad(bp < 0 ? 0: loopBeat(bp).toString(16).toUpperCase(), 4);
if(beat != '.') {
var accent = document.getElementById("beetAccent");
accent.innerHTML = beat;
accent.className = "beetView";
// trigger reflow to restart animation
accent.offsetWidth = accent.offsetWidth;
accent.className = "beetView fade";
switch(beat) {
case 'X':
case 'x':
waifuCanvas.yBlur();
break;
case 'O':
case 'o':
waifuCanvas.xBlur();
break;
case '+':
waifuCanvas.xBlur();
waifuCanvas.blackout();
break;
case '¤':
waifuCanvas.xBlur();
waifuCanvas.blackout(true);
break;
case '|':
waifuCanvas.shortBlackout(current._beatWidth);
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
break;
case ':':
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
break;
case '*':
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
break;
case '=':
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
case '~':
// TODO colour fade
break;
}
if ([".", "+", "|", "¤"].indexOf(beat) == -1) {
waifuCanvas.clearBlackout();
}
if([".", "+", ":", "*", "X", "O", "~", "="].indexOf(beat) == -1) {
waifuCanvas.setColour(RequestNextColor());
document.getElementById("colourName").innerHTML = colors[nCurrentColor];
// if isFullAuto
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
}
}
}
function onFileLoad(file) {
filesLoaded++;
var percent = Math.floor(filesLoaded / initialLoad * 0x40);
document.getElementById("preloader").innerHTML = '0x' + pad(percent.toString(16), 2);
// Destroy FF lag
if(!skipPreloader && file && file.name == "Madeon - Finale") {
audio.prep();
}
if(filesLoaded >= initialLoad)
onAllLoaded();
}
function onAllLoaded() {
waifuCanvas.init();
console.log("Completed inital load");
animationLoop();
document.getElementById("preloader").style.color = "#0F0";
if(skipPreloader)
return;
var fileProgress = (audio.context.currentTime - preloaderSong.started) % madeonPreload.loopStart;
var timeToFade = madeonPreload.loopStart - fileProgress;
audio.play(preloaderSong.buffer.duration - fileProgress);
setTimeout( function() {
document.getElementById("preloader").className = "loaded"
}, timeToFade * 1000);
// hacky disgusting chrome workaround
if (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)){
preloaderSong.stop();
preloaderSong = chromeHacks(madeonPreload, fileProgress);
} else {
preloaderSong.loop = false;
}
preloaderSong.onended = function() {
// get around issues on mobile where pointer-events don't work
document.getElementById("preloader").style.display = "none";
document.getElementById("waifu").className = "loaded"
updateBeatView(-1);
// free dat memory
preloaderSong = null;
}
}
function chromeHacks(song, time) {
var ret = audio.context.createBufferSource()
ret.buffer = song.buffer;
ret.connect(audio.context.destination);
ret.started = audio.context.currentTime;
ret.start(0, time);
return ret;
}
window.onload = function() {
// Check Web Audio API Support
try {
// More info at http://caniuse.com/#feat=audio-api
window.AudioContext = window.AudioContext || window.webkitAudioContext;
audio.context = new window.AudioContext();
} catch(e) {
audio.proceed = false;
alert('Web Audio API not supported in this browser.');
}
// normal, xBlur, yBlur, songs
initialLoad = waifus.length + audio.songs.length;
// Sexy audio preloader
if(!skipPreloader) {
loadSong(madeonPreload, function(song) {
preloaderSong = audio.context.createBufferSource()
preloaderSong.buffer = song.buffer;
preloaderSong.loop = true;
preloaderSong.loopStart = 0;
preloaderSong.loopEnd = song.loopStart;
preloaderSong.connect(audio.context.destination);
preloaderSong.started = audio.context.currentTime;
preloaderSong.start(0);
});
} else {
document.getElementById("preloader").className = "loaded";
document.getElementById("preloader").style.display = "none";
document.getElementById("waifu").className = "loaded";
}
for(var song in audio.songs) {
loadSong(audio.songs[song], onFileLoad);
}
// preload images
waifuCanvas.preload();
};
$(document).keydown(function(e) {
switch(e.which) {
case 37: // left
break;
case 38: // up
audio.next();
break;
case 39: // right
break;
case 40: // down
audio.prev();
break;
default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent the default action (scroll / move caret)
});
</script>
</head>
<body>
<div id="preloader">
0x00
</div>
<div class="ui" id="beets">
<div class="beetView" id="beetLeft"></div>
<div class="beetView fade" id="beetAccent">X</div>
<div class="beetView" id="beetRight"></div>
</div>
<canvas id="waifu" width="1280" height="720"></canvas>
<div class="ui" id="controls">
<a href="#" onclick="void(audio.stop());">stop</a>
<a href="#" onclick="void(audio.play());">play</a>
<a href="#" onclick="void(audio.prev());">prev</a>
<a href="#" onclick="void(audio.next());">next</a>
<div id="beatCount">B=0x0000</div>
<div id="timer">T=0x0000</div>
<div id="songname">Madeon - Finale</div>
<div id="waifuName">Megumi</div>
<div id="colourName">white</div>
</div>
</body>
</html>

@ -0,0 +1,57 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" >
<!--
TODO:
Keep arraybuffer in mem, load AudioBuffer as needed?
Volume controls
Prettier ui
Song shuffle
Image pause / manual advance
Different colour palettes
-->
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>0x40</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="js/0x40.js"></script>
<script type="text/javascript" src="js/waifus.js"></script>
<script type="text/javascript" src="js/waifuCanvas.js"></script>
<script type="text/javascript" src="js/audioUtils.js"></script>
<script type="text/javascript" src="lib/zip.js"></script>
<script type="text/javascript" src="lib/zip-fs.js"></script>
<script type="text/javascript">
window.onload = function() {
waifus.config = { respacks: ["respacks/Defaults_v5.0.zip",
"respacks/HuesMixA.zip"],
preloadLoop: "madeon_preloop.mp3",
preloadOutro: "madeon_preout.mp3",
firstSong: "Madeon - Finale"
};
waifus.init();
};
</script>
</head>
<body>
<div id="preloader">
0x00
</div>
<div class="ui" id="beets">
<div class="beetView" id="beetLeft"></div>
<div class="beetView fade" id="beetAccent">X</div>
<div class="beetView" id="beetRight"></div>
</div>
<canvas id="waifu" width="1280" height="720"></canvas>
<div class="ui" id="controls">
<a href="#" onclick="void(audio.stop());">stop</a>
<a href="#" onclick="void(audio.play());">play</a>
<a href="#" onclick="void(audio.prev());">prev</a>
<a href="#" onclick="void(audio.next());">next</a>
<div id="beatCount">B=0x0000</div>
<div id="timer">T=0x0000</div>
<div id="songname">Madeon - Finale</div>
<div id="waifuName">Megumi</div>
<div id="colourName">white</div>
</div>
</body>
</html>

@ -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;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);
}

@ -26,7 +26,7 @@ waifuCanvas.init = function() {
canvas = document.getElementById("waifu").getContext("2d"); canvas = document.getElementById("waifu").getContext("2d");
window.addEventListener('resize', waifuCanvas.resize); window.addEventListener('resize', waifuCanvas.resize);
waifuCanvas.resize(); waifuCanvas.resize();
canvas.drawImage(waifuImgs[0], 0, 0); //canvas.drawImage(waifuImgs[0], 0, 0);
} }
waifuCanvas.resize = function() { waifuCanvas.resize = function() {

@ -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]);
}

1886
orig.txt

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save