Add preloader, canvas implementation test, original source for reference, cleanup js

serial
William Toohey 10 years ago
parent 0c28b13314
commit bf556b1996
  1. 25
      0x40.js
  2. 124
      audioUtils.js
  3. 317
      index_canvas.html
  4. 1886
      orig.txt
  5. 92
      style_canvas.css
  6. 81
      waifuCanvas.js

@ -521,15 +521,15 @@ var waifus = [
fullname: "Lain Iwakura", fullname: "Lain Iwakura",
align: "center" align: "center"
}, },
{ //{
name: "Lain", // name: "Lain",
file: "Lain (anim).gif", // file: "Lain (anim).gif",
source: "http://0x40Hues.blogspot.com/Sources/Defaults#Lain", // source: "http://0x40Hues.blogspot.com/Sources/Defaults#Lain",
source_other: "http://myanimelist.net/character/2219/Lain_Iwakura", // source_other: "http://myanimelist.net/character/2219/Lain_Iwakura",
fullname: "Lain Iwakura", // fullname: "Lain Iwakura",
align: "left", // align: "left",
frameDuration: 83 // frameDuration: 83
}, //},
{ {
name: "Lala-Ru", name: "Lala-Ru",
file: "Lala-Ru.png", file: "Lala-Ru.png",
@ -1116,6 +1116,11 @@ var waifus = [
} }
]; ];
var madeonPreload = {
file: "songs/prebuild_Finale.mp3",
buildUp: "songs/preprebuild_Finale.mp3"
}
var rgSongs = [ var rgSongs = [
// Pack: Default HQ // Pack: Default HQ
{ {
@ -1393,7 +1398,7 @@ var rgSongs = [
}, },
]; ];
var nCurrentColor = 0; var nCurrentColor = 63; // start white
var nCurrentWaifu = 0; var nCurrentWaifu = 0;
var nColorX, nColorY, nColorZ; var nColorX, nColorY, nColorZ;

@ -0,0 +1,124 @@
// callback is given a populated song object
function loadSong(song, callback) {
if(song.buffer) {
callback(song);
return;
}
if(song.isLoading) {
return; // we're already trying to load this
}
song.isLoading = true;
song.tmpBuf = {};
if(song.buildUp) {
loadAudioFile(song, true, callback);
}
loadAudioFile(song, false, callback);
}
function loadAudioFile(song, isBuild, callback) {
var filename = isBuild ? song.buildUp : song.file;
var req = new XMLHttpRequest();
req.open('GET', filename, true);
req.responseType = 'arraybuffer';
req.onload = function() {
audio.context.decodeAudioData(
req.response,
function(buffer) {
if(isBuild) {
song.tmpBuf.build = trimSilence(buffer);
} else {
song.tmpBuf.loop = trimSilence(buffer);
}
onSongLoad(song, callback);
},
function() {
console.log('Error decoding audio "' + filename + '".');
}
);
};
req.send();
}
function onSongLoad(song, callback) {
// if this fails, we need to wait for the other part to load
if(song.tmpBuf.loop && (!song.buildUp || song.tmpBuf.build)) {
if(song.buildUp) {
song.buffer = concatenateAudioBuffers(song.tmpBuf.build, song.tmpBuf.loop);
song.loopStart = song.tmpBuf.build.duration;
} else {
song.buffer = song.tmpBuf.loop;
song.loopStart = 0;
}
song.loopLength = song.buffer.duration - song.loopStart;
// free dat memory
song.tmpBuf = null;
song.isLoading = false;
callback(song);
}
}
// because MP3 is bad
function trimSilence(buffer) {
// how much silence we have
var minSilence = buffer.length;
var maxSilence = 0;
for(var i=0; i<buffer.numberOfChannels; i++) {
var tmp = buffer.getChannelData(i);
for(var j=0; j < tmp.length; j++) {
// end of silence
if(tmp[j] != 0) {
if(j < minSilence) {
minSilence = j;
}
break;
}
}
// because just padding 1 end isn't enough for this codec
for(var j=tmp.length-1; j >= 0 ; j--) {
if(tmp[j] != 0) {
if(j > maxSilence) {
maxSilence = j;
}
break;
}
}
}
// 1152 = one frame, makes the sync better because ID3 tags
// take up that space or some garbage
var ret = audio.context.createBuffer(buffer.numberOfChannels, maxSilence-minSilence-1152, 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[minSilence + j + 1152];
}
}
return ret;
}
function concatenateAudioBuffers(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 = audio.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;
};

@ -0,0 +1,317 @@
<!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
Fine tune blur timing
Volume controls
Prettier ui
Song shuffle
Image pause
Keyboard shortcuts
Different colour palettes
External respacks
Change short blackout to beat length / 1.7
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_canvas.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" src="sizeof.compressed.js"></script>
<script type="text/javascript">
//debug
var skipPreloader = true;
var filesLoaded = 0;
var initialLoad = 0;
var preloaderSong = null;
var audio = {
proceed: true,
songs: rgSongs,
current_song: null,
current_index: 0,
beat_pointer: 0,
}
audio.play = function() {
if(audio.current_song && audio.current_song._playing) {
return;
}
var song = audio.songs[audio.current_index];
document.getElementById("songname").innerHTML = song.name
document.getElementById("blackout").style.display = "none";
// 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;
audio.beat_pointer = -newSong._build.length;
newSong._loopWidth = newSong.loopLength / song.rhythm.length;
newSong._buildWidth = newSong._build ? newSong.loopStart / newSong._build.length : 0;
newSong._beatWidth = newSong._buildWidth;
document.getElementById("beets").innerHTML = wrapBeats(0, 100);
document.getElementById("timer").innerHTML = "T=0x0000"
newSong.start(0);
// offset to after the build
newSong._startTime = audio.context.currentTime + newSong.loopStart;
newSong._playing = true;
}
audio.stop = function() {
if (audio.current_song && audio.current_song._playing) {
audio.current_song.stop();
audio.current_song._playing = false;
audio.current_song._startTime = 0;
}
}
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();
var cur = audio.current_song;
if(!cur || !cur._playing) {
// DEBUG
waifuCanvas.animationLoop();
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);
handleBeat(beat, audio.beat_pointer, RequestNextColor(), nCurrentColor, cur);
}
waifuCanvas.animationLoop();
}
function handleBeat(beat, bp, c, cc, current) {
// we have changed song since we were scheduled
if(current != audio.current_song)
return;
var beatLine = wrapBeats(bp+1, 100);
document.getElementById("beets").innerHTML = beatLine; // add reversed bit later
// 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 != '.') {
if(beat == '|') {
shortBlackout = true;
document.getElementById("blackout").style.display = "block";
return;
}
if(beat == '+') {
document.getElementById("blackout").style.display = "block";
return;
} else {
document.getElementById("blackout").style.display = "none";
}
if(beat != ':' && beat != 'O' && beat != 'X') {
waifuCanvas.newWaifu();
document.getElementById("waifuName").innerHTML = waifus[nCurrentWaifu].name;
}
if(beat != '*' && beat != 'X' && beat != 'O') {
document.getElementById("waifuColour").style.backgroundColor = c;
document.getElementById("colourName").innerHTML = colors[cc];
}
if(beat.toLowerCase() == 'o') {
waifuCanvas.xBlur();
}
if(beat.toLowerCase() == 'x') {
waifuCanvas.yBlur();
}
}
if(shortBlackout) {
document.getElementById("blackout").style.display = "none";
shortBlackout = false;
}
}
function onFileLoad() {
filesLoaded++;
var percent = Math.floor(filesLoaded / initialLoad * 0x40);
document.getElementById("preloader").innerHTML = '0x' + pad(percent.toString(16), 2);
if(filesLoaded >= initialLoad)
onAllLoaded();
}
function onAllLoaded() {
waifuCanvas.init();
console.log("Completed inital load");
animationLoop();
document.getElementById("preloader").style.color = "#0F0";
if(skipPreloader)
return;
var timeToFade = madeonPreload.loopStart -((audio.context.currentTime - preloaderSong.started) % madeonPreload.loopStart);
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, madeonPreload.loopStart - timeToFade);
} else {
preloaderSong.loop = false;
}
preloaderSong.onended = function() {
document.getElementById("waifu").className = "loaded"
audio.playIndex(0);
}
}
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("waifu").className = "loaded";
}
for(var song in audio.songs) {
loadSong(audio.songs[song], onFileLoad);
}
// preload images
waifuCanvas.preload();
};
</script>
</head>
<body>
<div id="preloader">
0x00
</div>
<div 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"></div>
<div id="beets"></div>
<div id="waifuName"></div>
<div id="colourName">white</div>
</div>
<div id="colours">
<div id="blackout"></div>
<div id="waifuColour"></div>
</div>
<canvas id="waifu" width="1280" height="720"></canvas>
</body>
</html>

1886
orig.txt

File diff suppressed because it is too large Load Diff

@ -0,0 +1,92 @@
body {
background-color:#ffffff;
height: 100%;
min-height: 100%;
margin: 0px;
}
#waifu {
width: 100%;
#position: absolute;
padding: 0;
top: 0;
left: 0;
z-index: -10;
opacity: 0;
}
#waifu.loaded {
opacity: 1;
animation-name: fadein;
animation-duration: 0.5s;
-webkit-animation-name: fadein;
-webkit-animation-duration: 0.5s;
}
#waifuColour {
opacity: 0.7;
mix-blend-mode: hard-light;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -5;
}
#blackout {
background-color: #000;
display: none;
pointer-events:none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
#preloader {
background-color: #FFF;
width: 100%;
height: 100%;
display:flex;
justify-content:center;
align-items:center;
font-family: monospace;
font-size: 30pt;
position: absolute;
top: 0;
left: 0;
z-index: 10;
}
#preloader.loaded {
opacity: 0;
pointer-events:none;
animation-name: fadeout;
animation-duration: 3s;
-webkit-animation-name: fadeout;
-webkit-animation-duration: 3s;
}
#controls {
font-family: monospace;
background-color:#ffffff;
width:800px;
#position: absolute;
top: 10px;
left: 10px;
z-index: 2;
}
@keyframes fadein {
from {opacity: 0;}
to {opacity: 1;}
}
@keyframes fadeout {
from {opacity: 1;}
to {opacity: 0;}
}

@ -0,0 +1,81 @@
var canvas;
var needsRedraw = false;
var waifuImgs = new Array();
var blurTime = 8;
var blurIterations = 20;
var blurMin = -blurIterations/2;
var blurMax = blurIterations/2;
var blurDistance = 4;
var xBlur = 0;
var yBlur = 0;
var shortBlackout = false;
waifuCanvas = {};
waifuCanvas.init = function() {
canvas = document.getElementById("waifu").getContext("2d");
canvas.drawImage(waifuImgs[0], 0, 0);
}
waifuCanvas.preload = function() {
for(var waifu in waifus) {
newImg = new Image();
newImg.onload = onFileLoad;
newImg.src = 'images/' + waifus[waifu].file;
waifuImgs[waifu] = newImg;
}
}
waifuCanvas.redraw = function() {
canvas.clearRect(0,0,1280,720);
if(xBlur) {
canvas.globalAlpha = 1/blurIterations;
for(var i=blurMin; i<blurMax; i++) {
canvas.drawImage(waifuImgs[nCurrentWaifu], blurDistance * i * xBlur, 0);
}
} else if(yBlur) {
canvas.globalAlpha = 1/blurIterations;
for(var i=blurMin; i<blurMax; i++) {
canvas.drawImage(waifuImgs[nCurrentWaifu], 0, blurDistance * i * yBlur);
}
} else {
canvas.globalAlpha = 1;
canvas.drawImage(waifuImgs[nCurrentWaifu], 0, 0);
}
needsRedraw = false;
}
waifuCanvas.animationLoop = function() {
if(xBlur) {
xBlur--;
waifuCanvas.redraw();
}
else if(yBlur) {
yBlur--;
waifuCanvas.redraw();
} else if(needsRedraw){
waifuCanvas.redraw();
}
}
waifuCanvas.newWaifu = function() {
GetRandomWaifu(); // increments the waifu counter
needsRedraw = true;
}
waifuCanvas.xBlur = function() {
xBlur = blurTime;
yBlur = 0;
needsRedraw = true;
}
waifuCanvas.yBlur = function() {
yBlur = blurTime;
xBlur = 0;
needsRedraw = true;
}
Loading…
Cancel
Save