Add sweet audio visualiser

Also some minor CSS tweaks
alternate-visualiser
William Toohey 10 years ago
parent a76fc05ca5
commit 57737b0c75
  1. 13
      css/hues-h.css
  2. 22
      css/hues-m.css
  3. 10
      css/hues-r.css
  4. 4
      css/hues-w.css
  5. 11
      css/hues-x.css
  6. 12
      css/style.css
  7. 62
      js/HuesCore.js
  8. 10
      js/HuesSettings.js
  9. 26
      js/HuesUI.js
  10. 129
      js/SoundManager.js

@ -34,6 +34,11 @@
font-size: 13px;
}
.hues-m-beatcenter.hues-h-text.hidden{
transform: translateY(-80px);
-webkit-transform: translateY(-80px);
}
.hues-h-eyes {
background: none;
background-image: url('../img/skull-eyes.png');
@ -77,6 +82,13 @@
z-index: 1;
}
@media (min-width: 768px) {
.hues-m-controls.hues-h-controls.hidden {
transform: translateY(64px);
-webkit-transform: translateY(64px);
}
}
.hues-m-songtitle.hues-h-text, .hues-m-imagename.hues-h-text {
padding: 4px 0px;
margin: 0px 5px;
@ -210,4 +222,5 @@
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
}

@ -25,8 +25,18 @@
}
.hues-m-controls.hidden {
transform: translateY(104px);
-webkit-transform: translateY(104px);
transform: translateY(108px);
-webkit-transform: translateY(108px);
}
.hues-m-visualisercontainer {
position: absolute;
width: 100%;
height: 64px;
bottom:108px;
left:-8px;
right:0px;
margin:0px auto;
}
.hues-m-beatbar {
@ -166,7 +176,6 @@
max-width: 992px;
height: 104px;
margin: 0 auto;
overflow: hidden;
left: 8px;
right: 8px;
color: rgba(255,255,255,0.7);
@ -462,6 +471,10 @@ input[type=range]::-ms-thumb {
.hues-m-controls {
height: 54px;
}
.hues-m-controls.hidden {
transform: translateY(54px);
-webkit-transform: translateY(54px);
}
.hues-m-imagename {
left: 300px;
right: 300px;
@ -499,4 +512,7 @@ input[type=range]::-ms-thumb {
margin: 0 auto;
left: 8px;
}
.hues-m-visualisercontainer {
bottom:58px;
}
}

@ -112,4 +112,14 @@
position: absolute;
right: 35px;
bottom: 45px;
}
.hues-r-visualisercontainer {
transform: scaleY(-1);
-webkit-transform: scaleY(-1);
position: absolute;
width: 100%;
height: 64px;
top: 0;
left: 0;
}

@ -90,4 +90,8 @@
@-webkit-keyframes fallspin {
from {-webkit-transform: rotate(0deg) translate(0px, 0px);
opacity: 1;}
}
.hues-r-visualisercontainer.hues-w-visualisercontainer {
top: 17px;
}

@ -20,6 +20,7 @@
.hues-m-beatbar.hues-x-beatbar {
background: none;
border-style: none;
overflow: visible;
}
.hues-x-light {
@ -96,4 +97,14 @@
left:50%;
margin-left: -1444.5px;
overflow: hidden;
}
.hues-x-visualisercontainer {
transform: scaleY(-1);
-webkit-transform: scaleY(-1);
position: absolute;
width: 100%;
height: 64px;
top: 25px;
left: 0;
}

@ -85,6 +85,15 @@ h1, h2, h3 {
display: none;
}
#visualiser {
position:absolute;
z-index: -1;
}
#visualiser.hidden {
display: none;
}
#preloadHelper {
background-color: #FFF;
width: 100%;
@ -205,6 +214,9 @@ input.tab-input[type="radio"]:checked + label {
.settings-category {
font-size: 12pt;
width: 50%;
float: left;
margin-bottom: 10px;
}
.settings-individual{

@ -62,6 +62,11 @@ function HuesCore(defaults) {
return;
}
this.renderer = new HuesCanvas("waifu", this.soundManager.context, this);
this.visualiser = document.createElement("canvas");
this.visualiser.id = "visualiser";
this.visualiser.height = "64";
this.vCtx = this.visualiser.getContext("2d");
this.uiArray.push(new RetroUI(), new WeedUI(), new ModernUI(), new XmasUI(), new HalloweenUI());
this.settings.connectCore(this);
@ -104,12 +109,55 @@ function HuesCore(defaults) {
this.animationLoop();
}
HuesCore.prototype.resizeVisualiser = function() {
this.soundManager.initVisualiser(this.visualiser.width/2);
}
HuesCore.prototype.updateVisualiser = function() {
if(localStorage["visualiser"] != "on") {
return;
}
var logArrays = this.soundManager.getVisualiserData();
if(!logArrays) {
return;
}
this.vCtx.clearRect(0, 0, this.vCtx.canvas.width, this.vCtx.canvas.height);
var gradient=this.vCtx.createLinearGradient(0,64,0,0);
gradient.addColorStop(1,"rgba(255,255,255,0.6)");
gradient.addColorStop(0,"rgba(20,20,20,0.6)");
this.vCtx.fillStyle = gradient;
var barWidth = 2;
var barHeight;
var x = 0;
for(var a = 0; a < logArrays.length; a++) {
var vals = logArrays[a];
for(var i = 0; i < vals.length; i++) {
var index = 0;
if(logArrays.length == 2 && a == 0) {
index = vals.length - i - 1;
} else {
index = i;
}
barHeight = vals[index]/4;
this.vCtx.fillRect(x,this.vCtx.canvas.height-barHeight,barWidth,barHeight);
x += barWidth;
}
}
}
HuesCore.prototype.animationLoop = function() {
var that = this;
if(!this.soundManager.playing) {
requestAnimationFrame(function() {that.animationLoop();});
return;
}
this.updateVisualiser();
var now = this.soundManager.currentTime();
if(now < 0) {
this.userInterface.updateTime(0);
@ -271,6 +319,9 @@ HuesCore.prototype.songDataUpdated = function() {
HuesCore.prototype.resetAudio = function() {
this.beatIndex = 0;
this.songDataUpdated();
if(localStorage["visualiser"] == "on") {
this.soundManager.initVisualiser(this.visualiser.width/2);
}
};
HuesCore.prototype.randomImage = function() {
@ -565,6 +616,17 @@ HuesCore.prototype.settingsUpdated = function() {
}
break;
}
switch (localStorage["visualiser"]) {
case "off":
document.getElementById("visualiser").className = "hidden";
break;
case "on":
document.getElementById("visualiser").className = "";
if(!this.soundManager.vReady) {
this.soundManager.initVisualiser(this.visualiser.width/2);
}
break;
}
/*if (this.autoSong == "off" && !(this.settings.autosong == "off")) {
console.log("Resetting loopCount since AutoSong was enabled");
this.loopCount = 0;

@ -46,7 +46,8 @@ HuesSettings.prototype.defaultSettings = {
colourSet: "normal",
blackoutUI: "off",
playBuildups: "on",
volume : 0.7
volume: 0.7,
visualiser: "on"
};
// Don't get saved to localStorage
@ -73,7 +74,8 @@ HuesSettings.prototype.settingsCategories = {
"UI Settings" : [
"currentUI",
"colourSet",
"blackoutUI"
"blackoutUI",
"visualiser"
],
"Audio Settings" : [
"playBuildups"
@ -97,6 +99,10 @@ HuesSettings.prototype.settingsOptions = {
name : "Blur Quality",
options : ["low", "medium", "high", "extreme"]
},
visualiser : {
name : "Spectrum analyser",
options : ["on", "off"]
},
currentUI : {
name : "User Interface",
options : ["retro", "v4.20", "modern", "xmas", "hlwn"]

@ -58,6 +58,8 @@ function HuesUI(parent) {
// Put this near the links to song/image lists/ Bottom right alignment
this.listContainer = null;
// Must be dynamic width, 64 pixels high. Will be filled with visualiser
this.visualiserContainer = null;
this.hidden = false;
@ -139,6 +141,7 @@ HuesUI.prototype.initUI = function() {
};
this.listContainer = document.createElement("div");
this.visualiserContainer = document.createElement("div");
this.resizeHandler = function() {
that.resize();
@ -149,6 +152,7 @@ HuesUI.prototype.connectCore = function(core) {
this.core = core;
this.root.style.display = "block";
this.listContainer.appendChild(core.resourceManager.listView);
this.visualiserContainer.appendChild(this.core.visualiser);
window.addEventListener('resize', this.resizeHandler);
this.resizeHandler();
@ -160,6 +164,9 @@ HuesUI.prototype.disconnect = function() {
while (this.listContainer.firstElementChild) {
this.listContainer.removeChild(this.listContainer.firstElementChild);
}
while (this.visualiserContainer.firstElementChild) {
this.visualiserContainer.removeChild(this.visualiserContainer.firstElementChild);
}
window.removeEventListener('resize', this.resizeHandler);
};
@ -345,6 +352,9 @@ RetroUI.prototype.initUI = function() {
this.listContainer.className = "hues-r-listcontainer";
this.root.appendChild(this.listContainer);
this.visualiserContainer.className = "hues-r-visualisercontainer";
this.root.appendChild(this.visualiserContainer);
};
RetroUI.prototype.toggleHide = function(stylename) {
@ -400,6 +410,11 @@ RetroUI.prototype.beat = function() {
this.beatCount.textContent = "B=" + this.intToHex3(this.core.getSafeBeatIndex());
};
RetroUI.prototype.resize = function() {
this.core.visualiser.width = this.visualiserContainer.offsetWidth;
this.core.resizeVisualiser();
};
function WeedUI() {
RetroUI.call(this);
@ -435,6 +450,8 @@ WeedUI.prototype.initUI = function() {
this.imageModeManual.textContent = "ONE";
this.imageModeAuto.textContent = "MANY";
this.visualiserContainer.className += " hues-w-visualisercontainer";
};
WeedUI.prototype.toggleHide = function() {
@ -632,6 +649,9 @@ ModernUI.prototype.initUI = function() {
this.leftInfo = leftInfo;
controls.appendChild(leftInfo);
controls.appendChild(rightInfo);
this.visualiserContainer.className = "hues-m-visualisercontainer";
controls.appendChild(this.visualiserContainer);
var beatBar = document.createElement("div");
beatBar.className = "hues-m-beatbar";
@ -723,6 +743,8 @@ ModernUI.prototype.beat = function() {
ModernUI.prototype.resize = function() {
this.resizeSong();
this.resizeImage();
this.core.visualiser.width = this.controls.offsetWidth;
this.core.resizeVisualiser();
};
ModernUI.prototype.resizeElement = function(el, parent) {
@ -823,6 +845,10 @@ function XmasUI() {
bottomHelper.appendChild(bottom);
wires.appendChild(bottomHelper);
this.root.appendChild(wires);
this.visualiserContainer.className = "hues-x-visualisercontainer";
this.controls.removeChild(this.visualiserContainer);
this.beatBar.appendChild(this.visualiserContainer);
}
XmasUI.prototype = Object.create(ModernUI.prototype);

@ -40,6 +40,18 @@ function SoundManager(core) {
this.gainNode = null;
this.mute = false;
this.lastVol = 1;
// Visualiser
this.vReady = false;
this.vBars = 0;
this.splitter = null;
this.analysers = [];
this.analyserArrays = [];
this.logArrays = [];
this.binCutoffs = [];
this.linBins = 0;
this.logBins = 0;
this.maxBinLin = 0;
// For concatenating our files
this.leftToLoad = 0;
@ -124,9 +136,9 @@ SoundManager.prototype.playSong = function(song, playBuild, callback) {
that.startTime = that.context.currentTime;
}
that.context.resume().then(function() {
that.playing = true;
if(callback)
callback();
that.playing = true;
});
});
} else {
@ -138,9 +150,9 @@ SoundManager.prototype.playSong = function(song, playBuild, callback) {
that.bufSource.start(0, that.loopStart);
that.startTime = that.context.currentTime;
}
that.playing = true;
if(callback)
callback();
that.playing = true;
}
}
});
@ -152,6 +164,7 @@ SoundManager.prototype.stop = function() {
this.bufSource.stop(0);
this.bufSource.disconnect(); // TODO needed?
this.bufSource = null;
this.vReady = false;
this.playing = false;
this.startTime = 0;
this.loopStart = 0;
@ -302,6 +315,118 @@ SoundManager.prototype.concatenateAudioBuffers = function(buffer1, buffer2) {
return tmp;
};
SoundManager.prototype.initVisualiser = function(bars) {
if(!bars) {
return;
}
this.vReady = false;
this.vBars = bars;
for(var i = 0; i < this.analysers.length; i++) {
this.analysers[i].disconnect();
}
if(this.splitter) {
this.splitter.disconnect();
this.splitter = null;
}
this.analysers = [];
this.analyserArrays = [];
this.logArrays = [];
this.binCutoffs = [];
this.linBins = 0;
this.logBins = 0;
this.maxBinLin = 0;
this.attachVisualiser();
}
SoundManager.prototype.attachVisualiser = function() {
if(!this.playing || this.vReady) {
return;
}
var channels = this.bufSource.channelCount;
// In case channel counts change, this is changed each time
this.splitter = this.context.createChannelSplitter(channels);
this.bufSource.connect(this.splitter);
// Split display up into each channel
this.vBars = Math.floor(this.vBars/channels);
for(var i = 0; i < channels; i++) {
var analyser = this.context.createAnalyser();
// big fft buffers are new-ish
try {
analyser.fftSize = 8192;
} catch(err) {
analyser.fftSize = 2048;
}
// Chosen because they look nice, no maths behind it
analyser.smoothingTimeConstant = 0.6;
analyser.minDecibels = -70;
analyser.maxDecibels = -25;
this.analyserArrays.push(new Uint8Array(analyser.frequencyBinCount));
analyser.getByteTimeDomainData(this.analyserArrays[i]);
this.splitter.connect(analyser, i);
this.analysers.push(analyser);
this.logArrays.push(new Uint8Array(this.vBars));
}
var binCount = this.analysers[0].frequencyBinCount;
var binWidth = this.bufSource.buffer.sampleRate / binCount;
// first 2kHz are linear
this.maxBinLin = Math.floor(2000/binWidth);
// Don't stretch the first 2kHz, it looks awful
this.linBins = Math.min(this.maxBinLin, Math.floor(this.vBars/2));
// Only go up to 22KHz
var maxBinLog = Math.floor(22000/binWidth);
var logBins = this.vBars - this.linBins;
console.log("Going to compress", this.maxBinLin,
"bins into", this.linBins, "linear bins");
console.log("Going to compress", maxBinLog-this.linBins,
"bins into", logBins, "logarithmic bins");
var logLow = Math.log2(2000);
var logDiff = Math.log2(22000) - logLow;
for(var i = 0; i < logBins; i++) {
var cutoff = i * (logDiff/logBins) + logLow;
var freqCutoff = Math.pow(2, cutoff);
var binCutoff = Math.floor(freqCutoff / binWidth);
this.binCutoffs.push(binCutoff);
}
this.vReady = true;
}
SoundManager.prototype.sumArray = function(array, low, high) {
var total = 0;
for(var i = low; i <= high; i++) {
total += array[i];
}
return total/(high-low+1);
}
SoundManager.prototype.getVisualiserData = function() {
if(!this.vReady) {
return null;
}
for(var a = 0; a < this.analyserArrays.length; a++) {
var data = this.analyserArrays[a];
var result = this.logArrays[a];
this.analysers[a].getByteFrequencyData(data);
for(var i = 0; i < this.linBins; i++) {
var scaled = Math.round(i * this.maxBinLin / this.linBins);
result[i] = data[scaled];
}
result[this.linBins] = data[this.binCutoffs[0]];
for(var i = this.linBins+1; i < this.vBars; i++) {
var cutoff = i - this.linBins;
result[i] = this.sumArray(data, this.binCutoffs[cutoff-1],
this.binCutoffs[cutoff]);
}
}
return this.logArrays;
}
SoundManager.prototype.setMute = function(mute) {
if(!this.mute && mute) { // muting
this.lastVol = this.gainNode.gain.value;

Loading…
Cancel
Save