hylogen-0.1.0.6: app/index.html
<!DOCTYPE html>
<html>
<head>
<title>HYLOGEN</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
#canvas {
width: 100vmax;
height: 100vmax;
background: white;
}
#landing {
width: 100vw;
height: 100vh;
position: fixed;
right: 0;
top: 0;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-family: arial;
line-height: 10em;
pointer-events: none;
}
#bg {
background: black;
width: 100vw;
height: 100vh;
position: fixed;
right: 0;
top: 0;
pointer-events: none;
}
#title {
text-transform: uppercase;
font-style: italic;
font-weight: bold;
font-size: 15vw;
letter-spacing: 0.1em;
text-shadow: 0 0 0.2em white;
pointer-events: auto;
}
@keyframes fadeOut {
0% {opacity: 1;}
100% {opacity: 0;}
}
.removing {
-webkit-animation: fadeOut 1000ms;
-moz-animation: fadeOut 1000ms;
animation: fadeOut 1000ms;
}
a {
text-decoration: inherit;
color: white;
}
a:visited {
color: inherit;
}
</style>
</head>
<body>
<div id="bg"> </div>
<div id="landing">
<div id="title">
<a href="https://github.com/sleexyz/hylogen">
Hylogen
</a>
</div>
</div>
<canvas id="canvas"></canvas>
<script>
const vsSource = `
attribute vec3 aPosition;
varying vec2 uvN;
void main() {
gl_Position = vec4(aPosition, 1.0);
uvN = aPosition.xy;
}
`;
const fsHeader=`
precision mediump float;
const float pi = 3.141592653589793238462643383;
uniform float time;
uniform vec2 mouse;
uniform vec4 audio;
uniform sampler2D backBuffer;
varying vec2 uvN;
vec2 uv() {
return 0.5 * uvN + 0.5;
}
`
const initialFsSource = `
void main() {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
function loadProgram (gl, state, vsSource, fsSource) {
// compileShader :: (gl, source, shaderType) -> Shader
// throws Error on compilation error
function compileShader (gl, source, shaderType) {
//assert(shaderType === gl.FRAGMENT_SHADER || shaderType === g.VERTEXT_SHADER);
let shader = gl.createShader(shaderType);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success) {
console.log(source);
throw "could not compile shader:" + gl.getShaderInfoLog(shader);
}
return shader;
};
let vs = compileShader(gl, vsSource, gl.VERTEX_SHADER);
let fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);
let program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) {
throw ("program failed to link:" + gl.getProgramInfoLog(program));
}
gl.useProgram(program);
// Create a square as a strip of two triangles.
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1,1,0, 1,1,0, -1,-1,0, 1,-1,0 ]), gl.STATIC_DRAW);
// Assign attribute aPosition to each of the square's vertices.
gl.aPosition = gl.getAttribLocation(program, "aPosition");
gl.enableVertexAttribArray(gl.aPosition);
gl.vertexAttribPointer(gl.aPosition, 3, gl.FLOAT, false, 0, 0);
// backBuffer stuff
function createTarget() {
let target = {
texture: gl.createTexture(),
framebuffer: gl.createFramebuffer()
};
// set up framebuffer
gl.bindTexture( gl.TEXTURE_2D, target.texture);
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, WIDTH, HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.bindFramebuffer( gl.FRAMEBUFFER, target.framebuffer);
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target.texture, 0);
// clean up
gl.bindTexture( gl.TEXTURE_2D, null);
gl.bindFramebuffer( gl.FRAMEBUFFER, null);
return target;
}
state.fb[0] = createTarget();
state.fb[1] = createTarget();
// remember the address within the fragment shader of each of my uniforms variables
gl.time = gl.getUniformLocation(program, "time");
gl.mouse = gl.getUniformLocation(program, "mouse");
gl.audio = gl.getUniformLocation(program, "audio");
gl.backBuffer = gl.getUniformLocation(program, "backBuffer");
draw(gl, state);
if (state.animationFrameRequest === null) {
state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));
}
}
function draw (gl, state) {
gl.uniform1f(gl.time, (new Date().getTime() / 1000 - state.time0));
gl.uniform2f(gl.mouse, state.mouse.x, state.mouse.y);
gl.uniform4f(gl.audio, state.audio.low, state.audio.mid, state.audio.upper, state.audio.high);
gl.uniform1i(gl.backBuffer, 0); // Do I need to check for null?
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, state.fb[state.bit].texture);
state.bit = (state.bit + 1) % 2;
gl.bindFramebuffer( gl.FRAMEBUFFER, state.fb[state.bit].framebuffer);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, state.fb[state.bit].texture);
gl.bindFramebuffer( gl.FRAMEBUFFER, null);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
function animate (gl, state) {
draw(gl, state);
state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));
};
function setupState (state, canvas){
state.animationFrameRequest = null;
state.bit = 0
state.fb = [null, null];
state.time0 = new Date() / 1000;
function setMouse (event) {
let r = event.target.getBoundingClientRect();
state.mouse.x = (event.clientX - r.left) / (r.right - r.left) * 2 -1;
state.mouse.y = (event.clientY - r.bottom) / (r.top - r.bottom) * 2 - 1;
};
/* canvas.onmousedown = (event) => setMouse(event, 1); */
/* canvas.onmouseup = (event) => setMouse(event, 0); */
canvas.onmousemove = (event) => setMouse(event);
state.mouse = {x: 0, y: 0};
state.audio = {low: 0.0, mid: 0.0, upper: 0.0, high: 0.0};
state.audioAgain = null;
function onAccept (stream) {
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let source = audioCtx.createMediaStreamSource(stream);
let analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.fftSize = 512;
let bufferLength = analyser.frequencyBinCount;
let dataArray = new Uint8Array(bufferLength);
function toAudio() {
state.audioAgain = requestAnimationFrame(toAudio);
analyser.getByteFrequencyData(dataArray);
// Taken from The_Force
let k = 0, f = 0.0;
let a = 5, b = 11, c = 24, d = bufferLength, i = 0;
for(; i < a; i++) {
f += dataArray[i];
}
f *= .2; // 1/(a-0)
f *= .003921569; // 1/255
state.audio.low = f;
f = 0.0;
for(; i < b; i++) {
f += dataArray[i];
}
f *= .166666667; // 1/(b-a)
f *= .003921569; // 1/255
state.audio.mid = f;
f = 0.0;
for(; i < c; i++) {
f += dataArray[i];
}
f *= .076923077; // 1/(c-b)
f *= .003921569; // 1/255
state.audio.upper = f;
f = 0.0;
for(; i < d; i++) {
f += dataArray[i];
}
f *= .00204918; // 1/(d-c)
f *= .003921569; // 1/255
state.audio.high = f;
};
toAudio();
}
function onFail(e) {
console.error(e);
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
navigator.getUserMedia({audio: true}, onAccept, onFail);
}
// stateful variables
const canvas = document.getElementById("canvas");
let WIDTH = Math.max(window.innerHeight, window.innerWidth);
let HEIGHT = Math.max(window.innerHeight, window.innerWidth);
canvas.width = WIDTH;
canvas.height = HEIGHT;
const gl = canvas.getContext("webgl");
const state = {};
setupState(state, canvas);
loadProgram (gl, state, vsSource, fsHeader + initialFsSource);
function fadeOut(elem) {
elem.className = "removing";
window.setTimeout(function() {
elem.remove();
}, 1000);
}
function connectToHylogen() {
const wsConn = new WebSocket("ws://localhost:8080/");
wsConn.onopen = function() {
const landing = document.getElementById("landing");
if (landing) {
fadeOut(document.getElementById("bg"));
window.setTimeout(() => {fadeOut(landing);}, 500);
}
console.log('websocket opened');
};
wsConn.onclose = function() {
console.log('websocket closed');
window.setTimeout(connectToHylogen, 100);
};
wsConn.onmessage = function (m) {
console.log(m.data);
loadProgram(gl, state, vsSource, fsHeader + m.data);
};
}
connectToHylogen();
</script>
</body>
</html>