├── README.md
├── gh-fork-ribbon.min.css
├── glmatrix.js
├── index.html
├── kung10mb.gif
├── screenshot.png
├── script.js
├── shader.fs
├── shader.vs
├── shader2.fs
├── shader2.vs
└── style.css
/README.md:
--------------------------------------------------------------------------------
1 | WebGL Now Playing Hack
2 | ======================
3 |
4 | 
5 |
6 | A example of how to use the newly released [Spotify Connect Web APIs](https://developer.spotify.com/web-api/web-api-connect-endpoint-reference/) to render the currently playing artwork in a slightly different way using WebGL.
7 |
8 | [Click here to open the online version](https://possan.github.io/webgl-spotify-connect-now-playing-screen-example)
9 |
10 | Download the repo, run `python -m SimpleHTTPServer 8000` in the folder that you just downloaded to host a webserver on port 8000, open `http://localhost:8000` in your webgl capable browser. It should ask you for permission to know what you are currently playing and to control playback.
11 |
12 | While it's running, you can click on the progress bar/scrubber to seek in the track, or use your keyboard to control playback, `space` toggles play/pause, `left` skips to the previous track, `right` skips to the next track.
13 |
14 | It uses my [polyserver](https://github.com/possan/polyserver) hack to vectorize the album covers into triangle data for the renderer, please don't overload it :)
15 |
16 | Enjoy.
17 |
--------------------------------------------------------------------------------
/gh-fork-ribbon.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * "Fork me on GitHub" CSS ribbon v0.2.0 | MIT License
3 | * https://github.com/simonwhitaker/github-fork-ribbon-css
4 | */.github-fork-ribbon{width:12.1em;height:12.1em;position:absolute;overflow:hidden;top:0;right:0;z-index:9999;pointer-events:none;font-size:13px;text-decoration:none;text-indent:-999999px}.github-fork-ribbon.fixed{position:fixed}.github-fork-ribbon:before,.github-fork-ribbon:after{position:absolute;display:block;width:15.38em;height:1.54em;top:3.23em;right:-3.23em;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.github-fork-ribbon:before{content:"";padding:.38em 0;background-color:#0c0;background-image:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.15)));background-image:-webkit-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.15));background-image:-moz-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.15));background-image:-ms-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.15));background-image:-o-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.15));background-image:linear-gradient(to bottom,rgba(0,0,0,0),rgba(0,0,0,0.15));-webkit-box-shadow:0 .15em .23em 0 rgba(0,0,0,0.5);-moz-box-shadow:0 .15em .23em 0 rgba(0,0,0,0.5);box-shadow:0 .15em .23em 0 rgba(0,0,0,0.5);pointer-events:auto}.github-fork-ribbon:after{content:attr(title);color:#fff;font:700 1em "Helvetica Neue",Helvetica,Arial,sans-serif;line-height:1.54em;text-decoration:none;text-shadow:0 -.08em rgba(0,0,0,0.5);text-align:center;text-indent:0;padding:.15em 0;margin:.15em 0;border-width:.08em 0;border-style:dotted;border-color:#fff;border-color:rgba(255,255,255,0.7)}.github-fork-ribbon.left-top,.github-fork-ribbon.left-bottom{right:auto;left:0}.github-fork-ribbon.left-bottom,.github-fork-ribbon.right-bottom{top:auto;bottom:0}.github-fork-ribbon.left-top:before,.github-fork-ribbon.left-top:after,.github-fork-ribbon.left-bottom:before,.github-fork-ribbon.left-bottom:after{right:auto;left:-3.23em}.github-fork-ribbon.left-bottom:before,.github-fork-ribbon.left-bottom:after,.github-fork-ribbon.right-bottom:before,.github-fork-ribbon.right-bottom:after{top:auto;bottom:3.23em}.github-fork-ribbon.left-top:before,.github-fork-ribbon.left-top:after,.github-fork-ribbon.right-bottom:before,.github-fork-ribbon.right-bottom:after{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}
--------------------------------------------------------------------------------
/glmatrix.js:
--------------------------------------------------------------------------------
1 | // glMatrix v0.9.5
2 | glMatrixArrayType=typeof Float32Array!="undefined"?Float32Array:typeof WebGLFloatArray!="undefined"?WebGLFloatArray:Array;var vec3={};vec3.create=function(a){var b=new glMatrixArrayType(3);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2]}return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a==c){a[0]+=b[0];a[1]+=b[1];a[2]+=b[2];return a}c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c};
3 | vec3.subtract=function(a,b,c){if(!c||a==c){a[0]-=b[0];a[1]-=b[1];a[2]-=b[2];return a}c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,c){if(!c||a==c){a[0]*=b;a[1]*=b;a[2]*=b;return a}c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c};
4 | vec3.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(g==1){b[0]=c;b[1]=d;b[2]=e;return b}}else{b[0]=0;b[1]=0;b[2]=0;return b}g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1];a=a[2];var g=b[0],f=b[1];b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};vec3.length=function(a){var b=a[0],c=a[1];a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};
5 | vec3.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1];a=a[2]-b[2];b=Math.sqrt(d*d+e*e+a*a);if(!b){c[0]=0;c[1]=0;c[2]=0;return c}b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};var mat3={};
6 | mat3.create=function(a){var b=new glMatrixArrayType(9);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9]}return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};
7 | mat3.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=0;b[4]=a[3];b[5]=a[4];b[6]=a[5];b[7]=0;b[8]=a[6];b[9]=a[7];b[10]=a[8];b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
8 | mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};var mat4={};mat4.create=function(a){var b=new glMatrixArrayType(16);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15]}return b};
9 | mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};
10 | mat4.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};
11 | mat4.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],o=a[11],m=a[12],n=a[13],p=a[14];a=a[15];return m*k*h*e-j*n*h*e-m*f*l*e+g*n*l*e+j*f*p*e-g*k*p*e-m*k*d*i+j*n*d*i+m*c*l*i-b*n*l*i-j*c*p*i+b*k*p*i+m*f*d*o-g*n*d*o-m*c*h*o+b*n*h*o+g*c*p*o-b*f*p*o-j*f*d*a+g*k*d*a+j*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a};
12 | mat4.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],o=a[10],m=a[11],n=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*n,y=k*r-o*n,z=k*s-m*n,C=l*r-o*p,D=l*s-m*p,E=o*s-m*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x);b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+o*v-m*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-n*w+r*t-s*B)*q;b[7]=(k*w-o*t+m*B)*q;b[8]=(f*D-h*z+j*x)*q;
13 | b[9]=(-c*D+d*z-g*x)*q;b[10]=(n*v-p*t+s*A)*q;b[11]=(-k*v+l*t-m*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-n*u+p*B-r*A)*q;b[15]=(k*u-l*B+o*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
14 | mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],i=a[8],j=a[9],k=a[10],l=k*f-h*j,o=-k*g+h*i,m=j*g-f*i,n=c*l+d*o+e*m;if(!n)return null;n=1/n;b||(b=mat3.create());b[0]=l*n;b[1]=(-k*d+e*j)*n;b[2]=(h*d-e*f)*n;b[3]=o*n;b[4]=(k*c-e*i)*n;b[5]=(-h*c+e*g)*n;b[6]=m*n;b[7]=(-j*c+d*i)*n;b[8]=(f*c-d*g)*n;return b};
15 | mat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],o=a[9],m=a[10],n=a[11],p=a[12],r=a[13],s=a[14];a=a[15];var A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14];b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*i+t*o+u*r;c[2]=A*g+B*j+t*m+u*s;c[3]=A*f+B*k+t*n+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*i+x*o+y*r;c[6]=v*g+w*j+x*m+y*s;c[7]=v*f+w*k+x*n+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*i+D*o+E*r;c[10]=z*
16 | g+C*j+D*m+E*s;c[11]=z*f+C*k+D*n+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*i+G*o+b*r;c[14]=q*g+F*j+G*m+b*s;c[15]=q*f+F*k+G*n+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1];b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c};
17 | mat4.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c};
18 | mat4.translate=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[12]=a[0]*d+a[4]*e+a[8]*b+a[12];a[13]=a[1]*d+a[5]*e+a[9]*b+a[13];a[14]=a[2]*d+a[6]*e+a[10]*b+a[14];a[15]=a[3]*d+a[7]*e+a[11]*b+a[15];return a}var g=a[0],f=a[1],h=a[2],i=a[3],j=a[4],k=a[5],l=a[6],o=a[7],m=a[8],n=a[9],p=a[10],r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=i;c[4]=j;c[5]=k;c[6]=l;c[7]=o;c[8]=m;c[9]=n;c[10]=p;c[11]=r;c[12]=g*d+j*e+m*b+a[12];c[13]=f*d+k*e+n*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=i*d+o*e+r*b+a[15];return c};
19 | mat4.scale=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[0]*=d;a[1]*=d;a[2]*=d;a[3]*=d;a[4]*=e;a[5]*=e;a[6]*=e;a[7]*=e;a[8]*=b;a[9]*=b;a[10]*=b;a[11]*=b;return a}c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c};
20 | mat4.rotate=function(a,b,c,d){var e=c[0],g=c[1];c=c[2];var f=Math.sqrt(e*e+g*g+c*c);if(!f)return null;if(f!=1){f=1/f;e*=f;g*=f;c*=f}var h=Math.sin(b),i=Math.cos(b),j=1-i;b=a[0];f=a[1];var k=a[2],l=a[3],o=a[4],m=a[5],n=a[6],p=a[7],r=a[8],s=a[9],A=a[10],B=a[11],t=e*e*j+i,u=g*e*j+c*h,v=c*e*j-g*h,w=e*g*j-c*h,x=g*g*j+i,y=c*g*j+e*h,z=e*c*j+g*h;e=g*c*j-e*h;g=c*c*j+i;if(d){if(a!=d){d[12]=a[12];d[13]=a[13];d[14]=a[14];d[15]=a[15]}}else d=a;d[0]=b*t+o*u+r*v;d[1]=f*t+m*u+s*v;d[2]=k*t+n*u+A*v;d[3]=l*t+p*u+B*
21 | v;d[4]=b*w+o*x+r*y;d[5]=f*w+m*x+s*y;d[6]=k*w+n*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+o*e+r*g;d[9]=f*z+m*e+s*g;d[10]=k*z+n*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[4],g=a[5],f=a[6],h=a[7],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[0]=a[0];c[1]=a[1];c[2]=a[2];c[3]=a[3];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[4]=e*b+i*d;c[5]=g*b+j*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+i*b;c[9]=g*-d+j*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c};
22 | mat4.rotateY=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[4]=a[4];c[5]=a[5];c[6]=a[6];c[7]=a[7];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*-d;c[1]=g*b+j*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+i*b;c[9]=g*d+j*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c};
23 | mat4.rotateZ=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[4],j=a[5],k=a[6],l=a[7];if(c){if(a!=c){c[8]=a[8];c[9]=a[9];c[10]=a[10];c[11]=a[11];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*d;c[1]=g*b+j*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+i*b;c[5]=g*-d+j*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c};
24 | mat4.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=e*2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=e*2/i;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/i;f[10]=-(g+e)/j;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(g*e*2)/j;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b=a*b;return mat4.frustum(-b,b,-a,a,c,d,e)};
25 | mat4.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/i;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/j;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/i;f[14]=-(g+e)/j;f[15]=1;return f};
26 | mat4.lookAt=function(a,b,c,d){d||(d=mat4.create());var e=a[0],g=a[1];a=a[2];var f=c[0],h=c[1],i=c[2];c=b[1];var j=b[2];if(e==b[0]&&g==c&&a==j)return mat4.identity(d);var k,l,o,m;c=e-b[0];j=g-b[1];b=a-b[2];m=1/Math.sqrt(c*c+j*j+b*b);c*=m;j*=m;b*=m;k=h*b-i*j;i=i*c-f*b;f=f*j-h*c;if(m=Math.sqrt(k*k+i*i+f*f)){m=1/m;k*=m;i*=m;f*=m}else f=i=k=0;h=j*f-b*i;l=b*k-c*f;o=c*i-j*k;if(m=Math.sqrt(h*h+l*l+o*o)){m=1/m;h*=m;l*=m;o*=m}else o=l=h=0;d[0]=k;d[1]=h;d[2]=c;d[3]=0;d[4]=i;d[5]=l;d[6]=j;d[7]=0;d[8]=f;d[9]=
27 | o;d[10]=b;d[11]=0;d[12]=-(k*e+i*g+f*a);d[13]=-(h*e+l*g+o*a);d[14]=-(c*e+j*g+b*a);d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4={};quat4.create=function(a){var b=new glMatrixArrayType(4);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3]}return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};
28 | quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a==b){a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return a}b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a==b){a[0]*=1;a[1]*=1;a[2]*=1;return a}b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2];a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)};
29 | quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(f==0){b[0]=0;b[1]=0;b[2]=0;b[3]=0;return b}f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2];a=a[3];var f=b[0],h=b[1],i=b[2];b=b[3];c[0]=d*b+a*f+e*i-g*h;c[1]=e*b+a*h+g*f-d*i;c[2]=g*b+a*i+d*h-e*f;c[3]=a*b-d*f-e*h-g*i;return c};
30 | quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=a[0];var f=a[1],h=a[2];a=a[3];var i=a*d+f*g-h*e,j=a*e+h*d-b*g,k=a*g+b*e-f*d;d=-b*d-f*e-h*g;c[0]=i*a+d*-b+j*-h-k*-f;c[1]=j*a+d*-f+k*-b-i*-h;c[2]=k*a+d*-h+i*-f-j*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=k+g;b[4]=1-(j+e);b[5]=d-f;b[6]=c-h;b[7]=d+f;b[8]=1-(j+l);return b};
31 | quat4.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=0;b[4]=k+g;b[5]=1-(j+e);b[6]=d-f;b[7]=0;b[8]=c-h;b[9]=d+f;b[10]=1-(j+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};quat4.slerp=function(a,b,c,d){d||(d=a);var e=c;if(a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]<0)e=-1*c;d[0]=1-c*a[0]+e*b[0];d[1]=1-c*a[1]+e*b[1];d[2]=1-c*a[2]+e*b[2];d[3]=1-c*a[3]+e*b[3];return d};
32 | quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};
33 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | WebGL Now Playing Hack
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 | Fork me on GitHub
24 |
25 |
26 |
--------------------------------------------------------------------------------
/kung10mb.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/possan/webgl-spotify-connect-now-playing-screen-example/1585d663938ff6f5fa138b852788bbb6c0dc7e5e/kung10mb.gif
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/possan/webgl-spotify-connect-now-playing-screen-example/1585d663938ff6f5fa138b852788bbb6c0dc7e5e/screenshot.png
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------
2 | //
3 | // This is how quick and dirty code looks like, if you're into linting and things, this you better
4 | // leave now...
5 | //
6 | // --------------------------------------------------------------------------------------
7 |
8 | // Global all the things/variables!
9 |
10 | // auth
11 | var CLIENT_ID = '8da32c6e9f9f4edab31faa41d9f10afd';
12 | var SCOPES = [
13 | 'user-read-currently-playing',
14 | 'user-read-playback-state',
15 | 'user-modify-playback-state'
16 | ];
17 | var accessToken;
18 |
19 | // webgl renderer
20 | var gl;
21 | var shaderProgram;
22 | var shaderProgram2;
23 | var mvMatrix = mat4.create();
24 | var pMatrix = mat4.create();
25 | var eyeFrom = vec3.create();
26 | var eyeTo = vec3.create();
27 | var eyeVector = vec3.create();
28 |
29 | // webgl objects
30 | var cubeVertexPositionBuffer;
31 | var cubeVertexColorBuffer;
32 | var cubeVertexIndexBuffer;
33 | var cubeVertexData1Buffer;
34 | var cubeVertexData2Buffer;
35 | var lastTrackPositionUpdate = 0;
36 | var firstTime = 0;
37 | var globalTime = 0;
38 | var state = 'blank';
39 | var stateStart = 0;
40 | var beatValue = 0.0;
41 | var beatValue2 = 0.0;
42 | var beatValue4 = 0.0;
43 | var beatDelta = 0.0;
44 | var rttFramebuffer;
45 | var rttTexture;
46 | var rttDepthFramebuffer;
47 | var rttDepthTexture;
48 | var noiseTexture;
49 | var postVertexPositionBuffer;
50 | var postVertexTextureBuffer;
51 | var postVertexIndexBuffer;
52 |
53 | // player state
54 | var artistName = '';
55 | var albumImageURL = '';
56 | var albumName = '';
57 | var albumURI = '';
58 | var visibleAlbumURI = '';
59 | var trackedTrackURI = '';
60 | var nextVectorData = null;
61 | var trackDuration = 180000;
62 | var trackURI = '';
63 | var trackPosition = 0;
64 | var trackPlaying = false;
65 | var trackName = '';
66 | var trackAnalysis = null;
67 | var trackBeats = [];
68 | var nextTrackBeat = 0;
69 |
70 | // misc ui
71 | var closetimer = 0;
72 |
73 |
74 |
75 | // --------------------------------------------------------------------------------------
76 | // Some polyfills
77 | // --------------------------------------------------------------------------------------
78 |
79 | window.requestAnimFrame = (function() {
80 | return window.requestAnimationFrame ||
81 | window.webkitRequestAnimationFrame ||
82 | window.mozRequestAnimationFrame ||
83 | window.oRequestAnimationFrame ||
84 | window.msRequestAnimationFrame ||
85 | function(callback) {
86 | window.setTimeout(callback, 1000/60);
87 | };
88 | })();
89 |
90 |
91 |
92 | // --------------------------------------------------------------------------------------
93 | // Network code
94 | // --------------------------------------------------------------------------------------
95 |
96 | function createRequest(method, url, onload) {
97 | var request = new XMLHttpRequest();
98 | request.open(method, url);
99 | if (method != 'GET') {
100 | request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
101 | }
102 | request.onerror = function () {};
103 | request.onload = onload.bind(this, request);
104 | return request;
105 | }
106 |
107 | function requestFile(filename, callback) {
108 | createRequest('GET', filename + '?cachebust=' + Date.now(), function(request) {
109 | if (request.status >= 200 && request.status < 400) {
110 | callback(request.responseText);
111 | }
112 | }).send();
113 | }
114 |
115 | function createAuthorizedRequest(method, url, onload) {
116 | var request = createRequest(method, url, onload);
117 | request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
118 | return request;
119 | }
120 |
121 | function _pollCurrentlyPlaying(callback) {
122 | createAuthorizedRequest(
123 | 'GET',
124 | 'https://api.spotify.com/v1/me/player/currently-playing',
125 | function(request) {
126 | if (request.status < 200 || request.status >= 400) {
127 | callback();
128 | return;
129 | }
130 |
131 | var data = JSON.parse(request.responseText);
132 | console.log('got data', data);
133 | if (data.item) {
134 | albumURI = data.item.album.uri;
135 | albumImageURL = data.item.album.images[0].url;
136 | trackName = data.item.name;
137 | albumName = data.item.album.name;
138 | artistName = data.item.artists[0].name;
139 | setNowPlayingTrack(data.item.uri);
140 | trackPosition = data.progress_ms;
141 | trackDuration = data.item.duration_ms;
142 | trackPlaying = data.is_playing
143 | }
144 | callback();
145 | }
146 | ).send();
147 | }
148 |
149 | var pollDebounce = 0;
150 | function pollCurrentlyPlaying(delay) {
151 | if (pollDebounce) {
152 | clearTimeout(pollDebounce);
153 | }
154 | pollDebounce = setTimeout(
155 | _pollCurrentlyPlaying.bind(this, pollCurrentlyPlaying.bind(this)),
156 | delay || 5000);
157 | }
158 |
159 | function getUserInformation(callback) {
160 | createAuthorizedRequest('GET', 'https://api.spotify.com/v1/me', function(request) {
161 | if (request.status < 200 || request.status >= 400) {
162 | callback(null);
163 | return;
164 | }
165 |
166 | console.log('got data', request.responseText);
167 | var data = JSON.parse(request.responseText);
168 | callback(data);
169 | }).send();
170 | }
171 |
172 | function sendPlayCommand(payload) {
173 | createAuthorizedRequest('PUT', 'https://api.spotify.com/v1/me/player/play', function(request) {
174 | if (request.status >= 200 && request.status < 400) {
175 | console.log('play command response', request.responseText);
176 | }
177 | pollCurrentlyPlaying(1500);
178 | }).send(JSON.stringify(payload));
179 | }
180 |
181 | function sendCommand(method, command, querystring) {
182 | console.log('COMMAND: ' + command);
183 | var url = 'https://api.spotify.com/v1/me/player/' + command + (querystring ? ('?' + querystring) : '');
184 | createAuthorizedRequest(method, url, function (request) {
185 | if (request.status >= 200 && request.status < 400) {
186 | console.log('commant response', request.responseText);
187 | }
188 | pollCurrentlyPlaying(1500);
189 | }).send();
190 | }
191 |
192 | function fetchVectors(albumimage, callback) {
193 | createRequest('POST', 'https://ilovepolygons.possan.se/convert', function (request) {
194 | if (request.status >= 200 && request.status < 400) {
195 | nextVectorData = JSON.parse(request.responseText);
196 | callback();
197 | }
198 | }).send('url=' + encodeURIComponent(albumimage) + '&cutoff=10000&threshold=20');
199 | }
200 |
201 | function sendPlayContext(uri, offset) {
202 | sendPlayCommand({
203 | context_uri: uri,
204 | offset: {
205 | position: offset || 0
206 | }
207 | });
208 | }
209 |
210 |
211 |
212 | // --------------------------------------------------------------------------------------
213 | // WebGL Rendering code
214 | // --------------------------------------------------------------------------------------
215 |
216 | function initWebGL(canvas) {
217 | try {
218 | gl = canvas.getContext("webgl", {alpha: true});
219 | gl.viewportWidth = canvas.width;
220 | gl.viewportHeight = canvas.height;
221 | } catch (e) {}
222 |
223 | if (!gl) {
224 | alert("Could not initialise WebGL, sorry :-( *sad panda*");
225 | }
226 |
227 | var downsample = 2; // reduce rendering quality
228 |
229 | function fit() {
230 | var w = document.body.offsetWidth;
231 | var h = document.body.offsetHeight;
232 | canvas.width = Math.floor(w / downsample);
233 | canvas.height = Math.floor(h / downsample);
234 | gl.viewportWidth = canvas.width;
235 | gl.viewportHeight = canvas.height;
236 | }
237 |
238 | window.addEventListener('resize', function (r) {
239 | console.log('window resized.');
240 | fit();
241 | });
242 |
243 | fit();
244 | }
245 |
246 | function initTextureFramebuffer() {
247 | rttFramebuffer = gl.createFramebuffer();
248 | gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
249 | rttFramebuffer.width = 1024;
250 | rttFramebuffer.height = 1024;
251 |
252 | rttTexture = gl.createTexture();
253 | gl.bindTexture(gl.TEXTURE_2D, rttTexture);
254 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
255 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
256 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
257 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
258 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
259 |
260 | var renderbuffer = gl.createRenderbuffer();
261 | gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
262 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);
263 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
264 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
265 |
266 | gl.bindTexture(gl.TEXTURE_2D, null);
267 | gl.bindRenderbuffer(gl.RENDERBUFFER, null);
268 |
269 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
270 | }
271 |
272 | function initTextureFramebuffer2() {
273 | rttDepthFramebuffer = gl.createFramebuffer();
274 | gl.bindFramebuffer(gl.FRAMEBUFFER, rttDepthFramebuffer);
275 | rttDepthFramebuffer.width = 1024;
276 | rttDepthFramebuffer.height = 1024;
277 |
278 | rttDepthTexture = gl.createTexture();
279 | gl.bindTexture(gl.TEXTURE_2D, rttDepthTexture);
280 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
281 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
282 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
283 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
284 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttDepthFramebuffer.width, rttDepthFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
285 |
286 | var renderbuffer = gl.createRenderbuffer();
287 | gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
288 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttDepthFramebuffer.width, rttDepthFramebuffer.height);
289 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttDepthTexture, 0);
290 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
291 |
292 | gl.bindTexture(gl.TEXTURE_2D, null);
293 | gl.bindRenderbuffer(gl.RENDERBUFFER, null);
294 |
295 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
296 | }
297 |
298 | function initNoiseTexture() {
299 | noiseTexture = gl.createTexture();
300 | gl.bindTexture(gl.TEXTURE_2D, noiseTexture);
301 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
302 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
303 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
304 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
305 |
306 | var data = new Uint8Array(128 * 128 * 4);
307 | for(var j=0; j<128*128*4; j++) {
308 | data[j] = Math.floor(Math.random() * 255);
309 | }
310 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 128, 128, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
311 |
312 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttDepthFramebuffer.width, rttDepthFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
313 | gl.bindTexture(gl.TEXTURE_2D, null);
314 | }
315 |
316 | function initWebGLShader2(callback) {
317 | console.log('init shader2...');
318 | shaderProgram2 = gl.createProgram();
319 | requestFile('shader2.vs', function (vscode2) {
320 | requestFile('shader2.fs', function (fscode2) {
321 | var fragmentShader;
322 | var vertexShader;
323 |
324 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
325 | vertexShader = gl.createShader(gl.VERTEX_SHADER);
326 |
327 | gl.shaderSource(fragmentShader, fscode2);
328 | gl.compileShader(fragmentShader);
329 | if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
330 | console.error('Compilation error: ', gl.getShaderInfoLog(fragmentShader));
331 | }
332 |
333 | gl.shaderSource(vertexShader, vscode2);
334 | gl.compileShader(vertexShader);
335 | if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
336 | console.error('Compilation error: ', gl.getShaderInfoLog(vertexShader));
337 | }
338 |
339 | gl.attachShader(shaderProgram2, vertexShader);
340 | gl.attachShader(shaderProgram2, fragmentShader);
341 | gl.linkProgram(shaderProgram2);
342 |
343 | if (!gl.getProgramParameter(shaderProgram2, gl.LINK_STATUS)) {
344 | alert("Could not initialise shaders");
345 | }
346 |
347 | gl.useProgram(shaderProgram2);
348 |
349 | shaderProgram2.vertexPositionAttribute = gl.getAttribLocation(shaderProgram2, "aVertexPosition");
350 | shaderProgram2.vertexTextureAttribute = gl.getAttribLocation(shaderProgram2, "aVertexTexture");
351 | shaderProgram2.tColor = gl.getUniformLocation(shaderProgram2, "tColor");
352 | shaderProgram2.tDepth = gl.getUniformLocation(shaderProgram2, "tDepth");
353 | shaderProgram2.tNoise = gl.getUniformLocation(shaderProgram2, "tNoise");
354 |
355 | shaderProgram2.timeUniform = gl.getUniformLocation(shaderProgram2, "time");
356 | shaderProgram2.beat1Uniform = gl.getUniformLocation(shaderProgram2, "fBeat1");
357 | shaderProgram2.beat2Uniform = gl.getUniformLocation(shaderProgram2, "fBeat2");
358 | shaderProgram2.beat3Uniform = gl.getUniformLocation(shaderProgram2, "fBeat3");
359 |
360 | console.log('shader2 done.');
361 | callback();
362 | });
363 | });
364 | }
365 |
366 | function initWebGLShader1(callback) {
367 | console.log('init shader1...');
368 | shaderProgram = gl.createProgram();
369 | requestFile('shader.vs', function (vscode) {
370 | requestFile('shader.fs', function (fscode) {
371 | var fragmentShader;
372 | var vertexShader;
373 |
374 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
375 | vertexShader = gl.createShader(gl.VERTEX_SHADER);
376 |
377 | gl.shaderSource(fragmentShader, fscode);
378 | gl.compileShader(fragmentShader);
379 | if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
380 | console.error('Compilation error: ', gl.getShaderInfoLog(fragmentShader));
381 | }
382 |
383 | gl.shaderSource(vertexShader, vscode);
384 | gl.compileShader(vertexShader);
385 | if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
386 | console.error('Compilation error: ', gl.getShaderInfoLog(vertexShader));
387 | }
388 |
389 | gl.attachShader(shaderProgram, vertexShader);
390 | gl.attachShader(shaderProgram, fragmentShader);
391 | gl.linkProgram(shaderProgram);
392 |
393 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
394 | alert("Could not initialise shaders");
395 | }
396 |
397 | gl.useProgram(shaderProgram);
398 |
399 | shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
400 | shaderProgram.vertexData1Attribute = gl.getAttribLocation(shaderProgram, "aVertexData1");
401 | shaderProgram.vertexData2Attribute = gl.getAttribLocation(shaderProgram, "aVertexData2");
402 | shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
403 |
404 | shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
405 | shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
406 | shaderProgram.pMatrixUniform2 = gl.getUniformLocation(shaderProgram, "uPMatrix2");
407 | shaderProgram.mvMatrixUniform2 = gl.getUniformLocation(shaderProgram, "uMVMatrix2");
408 |
409 | shaderProgram.eyeVector = gl.getUniformLocation(shaderProgram, "eyeVector");
410 |
411 | shaderProgram.timeUniform = gl.getUniformLocation(shaderProgram, "time");
412 | shaderProgram.progressUniform = gl.getUniformLocation(shaderProgram, "progress");
413 | shaderProgram.wobble1Uniform = gl.getUniformLocation(shaderProgram, "wobble1");
414 | shaderProgram.wobble2Uniform = gl.getUniformLocation(shaderProgram, "wobble2");
415 | shaderProgram.writeDepthUniform = gl.getUniformLocation(shaderProgram, "uWriteDepth");
416 |
417 | console.log('shader1 done.');
418 | callback();
419 | });
420 | });
421 | }
422 |
423 | function setMatrixUniforms() {
424 | gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
425 | gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
426 | gl.uniformMatrix4fv(shaderProgram.pMatrixUniform2, false, pMatrix);
427 | gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform2, false, mvMatrix);
428 | gl.uniform3f(shaderProgram.eyeVector, false, eyeVector[0], eyeVector[1], eyeVector[2]);
429 | }
430 |
431 | function initWebGLBuffers() {
432 | cubeVertexPositionBuffer = gl.createBuffer();
433 | cubeVertexColorBuffer = gl.createBuffer();
434 | cubeVertexIndexBuffer = gl.createBuffer();
435 | cubeVertexData1Buffer = gl.createBuffer();
436 | cubeVertexData2Buffer = gl.createBuffer();
437 |
438 | postVertexPositionBuffer = gl.createBuffer();
439 | postVertexTextureBuffer = gl.createBuffer();
440 | postVertexIndexBuffer = gl.createBuffer();
441 | }
442 |
443 | function updateBuffers(vectordata) {
444 | var vertices = [];
445 | var colors = [];
446 | var cubeVertexIndices = [];
447 | var verticesdata2 = [];
448 | var verticesdata3 = [];
449 |
450 | function addFace(x0, y0, x1, y1, x2, y2, r, g, b) {
451 | var xc = (x0 + x1 + x2) / 3.0;
452 | var yc = (y0 + y1 + y2) / 3.0;
453 |
454 | var R = 0.0;
455 | R += xc * 4.0;
456 | R += -0.05 + Math.random() * 0.1;
457 | R += r - b;
458 |
459 | var dx = (0.0 - xc);
460 | var dy = (0.0 - yc);
461 | var d = Math.sqrt(dx * dx + dy * dy);
462 | var br = 1.0; // Math.max(0.0, d);
463 |
464 | var rx = -1.0 + Math.random() * 2.0;
465 | var ry = d * 0.2 + -1.0 + Math.random() * 2.0;
466 | var rz = d * d; // -1.0 + Math.random() * 2.0;
467 |
468 | // X, Y, 0
469 | vertices.push(x0);
470 | vertices.push(y0);
471 | vertices.push(0.0);
472 |
473 | vertices.push(x1);
474 | vertices.push(y1);
475 | vertices.push(0.0);
476 |
477 | vertices.push(x2);
478 | vertices.push(y2);
479 | vertices.push(0.0);
480 |
481 | // R, G, B, A
482 | colors.push(r * br);
483 | colors.push(g * br);
484 | colors.push(b * br);
485 | colors.push(1.0);
486 |
487 | colors.push(r * br);
488 | colors.push(g * br);
489 | colors.push(b * br);
490 | colors.push(1.0);
491 |
492 | colors.push(r * br);
493 | colors.push(g * br);
494 | colors.push(b * br);
495 | colors.push(1.0);
496 |
497 | // Face 1
498 | cubeVertexIndices.push(cubeVertexIndices.length);
499 | cubeVertexIndices.push(cubeVertexIndices.length);
500 | cubeVertexIndices.push(cubeVertexIndices.length);
501 |
502 | // RANDOMINDEX); PIVOT_X); PIVOT_Y
503 | verticesdata2.push(R);
504 | verticesdata2.push(xc);
505 | verticesdata2.push(yc);
506 | verticesdata2.push(0.0);
507 |
508 | verticesdata2.push(R);
509 | verticesdata2.push(xc);
510 | verticesdata2.push(yc);
511 | verticesdata2.push(0.0);
512 |
513 | verticesdata2.push(R);
514 | verticesdata2.push(xc);
515 | verticesdata2.push(yc);
516 | verticesdata2.push(0.0);
517 |
518 | // RANDOMINDEX); PIVOT_X); PIVOT_Y
519 | verticesdata3.push(rx);
520 | verticesdata3.push(ry);
521 | verticesdata3.push(rz);
522 | verticesdata3.push(0.0);
523 |
524 | verticesdata3.push(rx);
525 | verticesdata3.push(ry);
526 | verticesdata3.push(rz);
527 | verticesdata3.push(0.0);
528 |
529 | verticesdata3.push(rx);
530 | verticesdata3.push(ry);
531 | verticesdata3.push(rz);
532 | verticesdata3.push(0.0);
533 | }
534 |
535 | if (vectordata) {
536 | var scale = 1.0 / vectordata.height;
537 | var xoffset = vectordata.width / 2;
538 | var yoffset = vectordata.height / 2;
539 | for (var i = 0; i < vectordata.tris.length; i++) {
540 | var x = vectordata.tris[i];
541 | addFace(-((x.x0 + xoffset) * scale - 1.0), -((x.y0 + yoffset) * scale - 1.0), -((x.x1 + xoffset) * scale - 1.0), -((x.y1 + yoffset) * scale - 1.0), -((x.x2 + xoffset) * scale - 1.0), -((x.y2 + yoffset) * scale - 1.0),
542 | x.r / 255.0, x.g / 255.0, x.b / 255.0);
543 | }
544 | }
545 |
546 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
547 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
548 | cubeVertexPositionBuffer.itemSize = 3;
549 |
550 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
551 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
552 | cubeVertexColorBuffer.itemSize = 4;
553 |
554 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexData1Buffer);
555 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesdata2), gl.STATIC_DRAW);
556 | cubeVertexData1Buffer.itemSize = 4;
557 |
558 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexData2Buffer);
559 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesdata3), gl.STATIC_DRAW);
560 | cubeVertexData2Buffer.itemSize = 4;
561 |
562 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
563 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
564 | cubeVertexIndexBuffer.numItems = cubeVertexIndices.length;
565 | }
566 |
567 | function updatePostBuffer() {
568 | var vertices = [];
569 | var texture = [];
570 | var indices = [];
571 |
572 | var R = 1.0;
573 |
574 | vertices.push(-R)
575 | vertices.push(-R)
576 | vertices.push(0)
577 | texture.push(0.0)
578 | texture.push(0.0)
579 |
580 | vertices.push( R)
581 | vertices.push(-R)
582 | vertices.push(0)
583 | texture.push(1.0)
584 | texture.push(0.0)
585 |
586 | vertices.push( R)
587 | vertices.push( R)
588 | vertices.push(0)
589 | texture.push(1.0)
590 | texture.push(1.0)
591 |
592 | indices.push(indices.length);
593 | indices.push(indices.length);
594 | indices.push(indices.length);
595 |
596 | vertices.push(-R)
597 | vertices.push(-R)
598 | vertices.push(0)
599 | texture.push(0.0)
600 | texture.push(0.0)
601 |
602 | vertices.push(-R)
603 | vertices.push(R)
604 | vertices.push(0)
605 | texture.push(0.0)
606 | texture.push(1.0)
607 |
608 | vertices.push(R)
609 | vertices.push(R)
610 | vertices.push(0)
611 | texture.push(1.0)
612 | texture.push(1.0)
613 |
614 | indices.push(indices.length);
615 | indices.push(indices.length);
616 | indices.push(indices.length);
617 |
618 | gl.bindBuffer(gl.ARRAY_BUFFER, postVertexPositionBuffer);
619 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
620 | postVertexPositionBuffer.itemSize = 3;
621 |
622 | gl.bindBuffer(gl.ARRAY_BUFFER, postVertexTextureBuffer);
623 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texture), gl.STATIC_DRAW);
624 | postVertexTextureBuffer.itemSize = 2;
625 |
626 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, postVertexIndexBuffer);
627 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
628 | postVertexIndexBuffer.numItems = indices.length;
629 |
630 | }
631 |
632 | function drawScene() {
633 | gl.enable(gl.DEPTH_TEST);
634 |
635 | // gl.useProgram(shaderProgram);
636 |
637 | var fov = 80 + 20 * Math.sin(globalTime / 5000.0);
638 | mat4.perspective(fov, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
639 |
640 | var T = globalTime + 150 * beatDelta;
641 |
642 | eyeFrom = [
643 | 0.0 + 0.3 * Math.sin(T / 1950),
644 | 0.0 + 0.3 * Math.cos(T / 1730),
645 | 0.0 + 0.4 * Math.cos(T / 1463) - 0.6 // + 0.1 * beatValue
646 | ];
647 |
648 | eyeTo = [
649 | 0.0 + 0.1 * Math.sin(T / 2250),
650 | 0.0 + 0.1 * Math.cos(T / 1730),
651 | 0.0 + 0.1 * Math.cos(T / 1963) + 0.0
652 | ];
653 |
654 | vec3.subtract(eyeTo, eyeFrom, eyeVector);
655 | vec3.normalize(eyeVector);
656 |
657 | mat4.lookAt(eyeFrom, eyeTo, [
658 | 0.0 + 0.1 * Math.sin(globalTime / 3650),
659 | 1.0,
660 | 0.0 + 0.1 * Math.cos(globalTime / 2650)
661 | ], mvMatrix);
662 |
663 | setMatrixUniforms();
664 |
665 | if (cubeVertexIndexBuffer && cubeVertexIndexBuffer.numItems > 0) {
666 | gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
667 | gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
668 | gl.enableVertexAttribArray(shaderProgram.vertexData1Attribute);
669 | gl.enableVertexAttribArray(shaderProgram.vertexData2Attribute);
670 |
671 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
672 | gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
673 |
674 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
675 | gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
676 |
677 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexData1Buffer);
678 | gl.vertexAttribPointer(shaderProgram.vertexData1Attribute, cubeVertexData1Buffer.itemSize, gl.FLOAT, false, 0, 0);
679 |
680 | gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexData2Buffer);
681 | gl.vertexAttribPointer(shaderProgram.vertexData2Attribute, cubeVertexData2Buffer.itemSize, gl.FLOAT, false, 0, 0);
682 |
683 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
684 | gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
685 |
686 | gl.disableVertexAttribArray(shaderProgram.vertexData2Attribute);
687 | gl.disableVertexAttribArray(shaderProgram.vertexData1Attribute);
688 | gl.disableVertexAttribArray(shaderProgram.vertexColorAttribute);
689 | gl.disableVertexAttribArray(shaderProgram.vertexPositionAttribute);
690 | }
691 | }
692 |
693 |
694 | function drawScene2() {
695 | // gl.enable(gl.DEPTH_TEST);
696 | // gl.useProgram(shaderProgram2);
697 |
698 | if (postVertexIndexBuffer && postVertexIndexBuffer.numItems > 0) {
699 | gl.enableVertexAttribArray(shaderProgram2.vertexTextureAttribute);
700 | gl.enableVertexAttribArray(shaderProgram2.vertexPositionAttribute);
701 |
702 | gl.bindBuffer(gl.ARRAY_BUFFER, postVertexPositionBuffer);
703 | gl.vertexAttribPointer(shaderProgram2.vertexPositionAttribute, postVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
704 |
705 | gl.bindBuffer(gl.ARRAY_BUFFER, postVertexTextureBuffer);
706 | gl.vertexAttribPointer(shaderProgram2.vertexTextureAttribute, postVertexTextureBuffer.itemSize, gl.FLOAT, false, 0, 0);
707 |
708 | gl.activeTexture(gl.TEXTURE0);
709 | gl.bindTexture(gl.TEXTURE_2D, rttTexture);
710 | gl.uniform1i(shaderProgram2.tColor, 0);
711 | gl.activeTexture(gl.TEXTURE1);
712 | gl.bindTexture(gl.TEXTURE_2D, rttDepthTexture);
713 | gl.uniform1i(shaderProgram2.tDepth, 1);
714 | gl.activeTexture(gl.TEXTURE2);
715 | gl.bindTexture(gl.TEXTURE_2D, noiseTexture);
716 | gl.uniform1i(shaderProgram2.tNoise, 2);
717 |
718 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, postVertexIndexBuffer);
719 | gl.drawElements(gl.TRIANGLES, postVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
720 |
721 | gl.activeTexture(gl.TEXTURE1);
722 | gl.bindTexture(gl.TEXTURE_2D, null)
723 | gl.activeTexture(gl.TEXTURE0);
724 | gl.bindTexture(gl.TEXTURE_2D, null)
725 |
726 | gl.disableVertexAttribArray(shaderProgram2.vertexTextureAttribute);
727 | gl.disableVertexAttribArray(shaderProgram2.vertexPositionAttribute);
728 | }
729 | }
730 |
731 | function tick() {
732 | requestAnimFrame(tick);
733 |
734 | var t = (new Date()).getTime();
735 | if (lastTrackPositionUpdate == 0) {
736 | lastTrackPositionUpdate = t;
737 | }
738 |
739 | var dt = t - lastTrackPositionUpdate;
740 | lastTrackPositionUpdate = t;
741 |
742 | if (trackPlaying) {
743 | trackPosition += dt;
744 | }
745 |
746 | // find beat changes
747 | // console.log('trackPosition', trackPosition);
748 | var i = nextTrackBeat;
749 | while(i < trackBeats.length && trackPosition > trackBeats[i]) {
750 | // console.log('comparing beat #' + i)
751 | i ++;
752 | }
753 | if (i > nextTrackBeat) {
754 | console.log('BEAT #' + i + ' at ' + trackPosition + ' ms')
755 | nextTrackBeat = i;
756 | beatValue = 1.0;
757 | if (i % 2 == 0) beatValue2 = 1.0;
758 | if (i % 4 == 0) beatValue4 = 1.0;
759 | if (i % 2 == 0) beatDelta += 1.0;
760 | }
761 |
762 | var progress = -2.0;
763 | var stateTime = 0;
764 |
765 | if (state == 'blank') {
766 | progress = -2.0;
767 | if (albumURI != visibleAlbumURI) {
768 | console.log('Album URI changed: ' + albumURI);
769 | visibleAlbumURI = albumURI;
770 | console.log('Got album image..');
771 | fetchVectors(albumImageURL, function () {
772 | console.log('Got album vectors..');
773 | updateBuffers(nextVectorData);
774 | nextVectorData = null;
775 | state = 'fadein';
776 | stateTime = 0.0;
777 | stateStart = globalTime;
778 | });
779 | }
780 | } else if (state == 'fadein') {
781 | stateTime = globalTime - stateStart;
782 | progress = -2.0 + stateTime / 7000.0;
783 | if (stateTime > 14000.0) {
784 | console.log('Fade in done.');
785 | state = 'visible';
786 | stateTime = 0.0;
787 | stateStart = globalTime;
788 | }
789 | } else if (state == 'visible') {
790 | progress = 0.0;
791 | if (albumURI != visibleAlbumURI) {
792 | console.log('Fading out...');
793 | state = 'fadeout';
794 | stateTime = 0.0;
795 | stateStart = globalTime;
796 | }
797 | } else if (state == 'fadeout') {
798 | stateTime = globalTime - stateStart;
799 | progress = 0.0 + stateTime / 2500.0;
800 | if (stateTime > 5000.0) {
801 | console.log('Faded out.');
802 | state = 'blank';
803 | stateTime = 0.0;
804 | stateStart = globalTime;
805 | }
806 | }
807 |
808 | var t2 = Math.sin(globalTime / 1000.0) * Math.max(0, 0.3 + 0.5 * Math.sin(globalTime / 4600.0));
809 | var t3 = Math.cos(globalTime / 1300.0) * Math.max(0, 0.3 + 0.5 * Math.cos(globalTime / 5400.0));
810 |
811 | t2 += beatValue * 0.2 * Math.max(0, 0.3 + 0.5 * Math.sin(globalTime / 3600.0));
812 | t3 += beatValue2 * 0.2 * Math.max(0, 0.3 + 0.5 * Math.sin(globalTime / 5100.0));
813 |
814 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
815 |
816 | gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
817 | gl.useProgram(shaderProgram)
818 | gl.uniform1f(shaderProgram.timeUniform, globalTime);
819 | gl.uniform1f(shaderProgram.progressUniform, progress);
820 | gl.uniform1f(shaderProgram.wobble1Uniform, t2);
821 | gl.uniform1f(shaderProgram.wobble2Uniform, t3);
822 | gl.uniform1i(shaderProgram.writeDepthUniform, 0);
823 | // gl.useProgram(null)
824 | // gl.bindTexture(gl.TEXTURE_2D, null);
825 |
826 | gl.viewport(0, 0, rttFramebuffer.width, rttFramebuffer.height);
827 | gl.clearColor(0.0, 0.0, 0.0, 0.0);
828 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
829 | gl.enable(gl.BLEND);
830 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
831 | drawScene();
832 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
833 |
834 | gl.bindFramebuffer(gl.FRAMEBUFFER, rttDepthFramebuffer);
835 | // gl.useProgram(shaderProgram)
836 | gl.uniform1f(shaderProgram.timeUniform, globalTime);
837 | gl.uniform1f(shaderProgram.progressUniform, progress);
838 | gl.uniform1f(shaderProgram.wobble1Uniform, t2);
839 | gl.uniform1f(shaderProgram.wobble2Uniform, t3);
840 | gl.uniform1i(shaderProgram.writeDepthUniform, 1);
841 | // gl.useProgram(null)
842 | // gl.bindTexture(gl.TEXTURE_2D, null);
843 |
844 | gl.viewport(0, 0, rttDepthFramebuffer.width, rttDepthFramebuffer.height);
845 | gl.clearColor(1.0, 1.0, 1.0, 0.0);
846 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
847 | gl.enable(gl.BLEND);
848 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
849 | drawScene();
850 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
851 |
852 | gl.bindTexture(gl.TEXTURE_2D, rttTexture);
853 | gl.generateMipmap(gl.TEXTURE_2D);
854 | gl.bindTexture(gl.TEXTURE_2D, null);
855 |
856 | gl.bindTexture(gl.TEXTURE_2D, rttDepthTexture);
857 | gl.generateMipmap(gl.TEXTURE_2D);
858 | gl.bindTexture(gl.TEXTURE_2D, null);
859 |
860 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
861 | gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
862 | gl.clearColor(0.0, 0.0, 0.0, 1.0);
863 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
864 | gl.uniform1i(shaderProgram.writeDepthUniform, 0);
865 | gl.enable(gl.DEPTH_TEST);
866 | gl.disable(gl.BLEND);
867 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
868 | drawScene();
869 |
870 | gl.clear(gl.DEPTH_BUFFER_BIT);
871 | gl.enable(gl.DEPTH_TEST);
872 | gl.disable(gl.BLEND);
873 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
874 | // gl.blendFunc(gl.ONE, gl.ONE);
875 | gl.useProgram(shaderProgram2)
876 | gl.uniform1f(shaderProgram2.timeUniform, globalTime);
877 | gl.uniform1f(shaderProgram2.beat1Uniform, beatValue);
878 | gl.uniform1f(shaderProgram2.beat2Uniform, beatValue2);
879 | gl.uniform1f(shaderProgram2.beat3Uniform, beatValue4);
880 | drawScene2();
881 |
882 |
883 | var timeNow = new Date().getTime();
884 | if (firstTime == 0) {
885 | firstTime = timeNow;
886 | }
887 | globalTime = timeNow - firstTime;
888 |
889 | beatValue -= 0.015;
890 | if (beatValue < 0.0) {
891 | beatValue = 0.0;
892 | }
893 | beatValue2 -= 0.015;
894 | if (beatValue2 < 0.0) {
895 | beatValue2 = 0.0;
896 | }
897 | beatValue4 -= 0.015;
898 | if (beatValue4 < 0.0) {
899 | beatValue4 = 0.0;
900 | }
901 | }
902 |
903 |
904 |
905 |
906 | // --------------------------------------------------------------------------------------
907 | // DOM UI
908 | // --------------------------------------------------------------------------------------
909 |
910 | function updateTrackPosition() {
911 | // var t = (new Date()).getTime();
912 | // if (lastTrackPositionUpdate == 0) {
913 | // lastTrackPositionUpdate = t;
914 | // }
915 |
916 | // var dt = t - lastTrackPositionUpdate;
917 | // lastTrackPositionUpdate = t;
918 |
919 | // if (trackPlaying) {
920 | // trackPosition += dt;
921 | // }
922 |
923 | var w = trackPosition * 100 / trackDuration;
924 | w = Math.max(Math.min(100, w), 0);
925 | document.getElementById('trackpositionfill').style.width = w + '%';
926 | }
927 |
928 | function hideLogin() {
929 | document.getElementById('biglogin').style.display = 'none';
930 | }
931 |
932 | function showLogin() {
933 | document.getElementById('biglogin').style.display = 'block';
934 | }
935 |
936 | function toast(title, subtitle) {
937 | document.getElementById('text').innerText = title || '';
938 | document.getElementById('text2').innerText = subtitle || '';
939 | document.getElementById('toast').className = 'toast visible';
940 |
941 | clearTimeout(closetimer);
942 | closetimer = setTimeout(function () {
943 | document.getElementById('toast').className = 'toast';
944 | }, 5000);
945 | }
946 |
947 | function fetchTrackAnalysis() {
948 | var id = trackURI.split(':')[2];
949 | createAuthorizedRequest('GET', 'https://api.spotify.com/v1/audio-analysis/' + id, function(request) {
950 | if (request.status < 200 || request.status >= 400) {
951 | // callback(null);
952 | return;
953 | }
954 |
955 | var data = JSON.parse(request.responseText);
956 | console.log('got analysis data', data);
957 | trackAnalysis = data;
958 | trackBeats = data.beats.map(function(x) {
959 | return Math.round(x.start * 1000.0);
960 | });
961 | trackBeats.sort(function(a, b) {
962 | if (a < b) return -1;
963 | if (a > b) return 1;
964 | return 0;
965 | });
966 | console.log('beats', trackBeats);
967 | // callback(data);
968 | }).send();
969 |
970 | }
971 |
972 | function setNowPlayingTrack(uri) {
973 | if (uri == trackURI) {
974 | return;
975 | }
976 |
977 | trackURI = uri;
978 | trackAnalysis = null;
979 | nextTrackBeat = 0;
980 | trackBeats = [];
981 |
982 | toast(trackName, artistName + ' - ' + albumName);
983 | fetchTrackAnalysis();
984 | }
985 |
986 | function login() {
987 | var redirect_uri = location.protocol + '//' + location.host + location.pathname;
988 | var url = 'https://accounts.spotify.com/authorize?client_id=' + CLIENT_ID +
989 | '&redirect_uri=' + encodeURIComponent(redirect_uri) +
990 | '&scope=' + SCOPES.join('%20') +
991 | '&response_type=token';
992 | console.log('login url', url);
993 | location.href = url;
994 | }
995 |
996 | function connect() {
997 | console.log('Connecting with access token: ' + accessToken);
998 | getUserInformation(function(userinfo) {
999 | if (!userinfo) {
1000 | accessToken = '';
1001 | showLogin();
1002 | return;
1003 | }
1004 |
1005 | hideLogin();
1006 | toast('Hello ' + (userinfo.display_name || userinfo.id) + '!', 'Make sure you\'re playing something in Spotify!');
1007 | pollCurrentlyPlaying(2000);
1008 | });
1009 | }
1010 |
1011 | function validateAuthentication() {
1012 | console.log('location.hash', location.hash);
1013 | var lochash = location.hash.substr(1);
1014 | var newAccessToken = lochash.substr(lochash.indexOf('access_token=')).split('&')[0].split('=')[1];
1015 | if (newAccessToken) {
1016 | localStorage.setItem('access_token', newAccessToken);
1017 | accessToken = newAccessToken;
1018 | } else {
1019 | accessToken = localStorage.getItem('access_token');
1020 | }
1021 | if (accessToken) {
1022 | connect();
1023 | } else {
1024 | showLogin();
1025 | }
1026 | }
1027 |
1028 | function initUI() {
1029 | document.getElementById('trackposition').addEventListener('mousedown', function(event) {
1030 | var time = event.offsetX * trackDuration / document.body.offsetWidth;
1031 | trackPosition = time;
1032 | nextTrackBeat = 0;
1033 | sendCommand('PUT', 'seek', 'position_ms='+Math.round(time));
1034 | });
1035 |
1036 | setInterval(updateTrackPosition, 1000);
1037 | }
1038 |
1039 | function initKeyboard() {
1040 | window.addEventListener('keyup', function (event) {
1041 | console.log('key up', event.keyCode);
1042 |
1043 | // some hidden presets '1' .. '0'
1044 | if (event.keyCode == 49) { sendPlayContext('spotify:album:2gaw3G7HBQuz93N8X89JIA', 1); }
1045 | if (event.keyCode == 50) { sendPlayContext('spotify:album:2KWlNb50pLNM11pGqqVdSX'); }
1046 | if (event.keyCode == 51) { sendPlayContext('spotify:album:7xrc6SpiFhcgBaLYbqfB7k', 1); }
1047 | if (event.keyCode == 52) { sendPlayContext('spotify:album:64XdBdXNdguPHzBg8bdk5A'); }
1048 | if (event.keyCode == 53) { sendPlayContext('spotify:album:64XidJaSHIS1XMb4Po77b1', 9); }
1049 | if (event.keyCode == 54) { sendPlayContext('spotify:album:4wJmWEuo2ezowJeJVdQWYS', 1); }
1050 | if (event.keyCode == 55) { sendPlayContext('spotify:album:5uTGqtnYpSRYiFTEuQcmNE', 0); }
1051 | if (event.keyCode == 56) { sendPlayContext('spotify:album:4QNlqYSMYCPiKZfzUfH7jK', 1); }
1052 | if (event.keyCode == 57) { sendPlayContext('spotify:album:29JfxOC3yMXwy3KlX8WFUQ', 1); }
1053 | if (event.keyCode == 48) { sendPlayContext('spotify:album:68zh8sbZPMeJb7GnqomRJS', 0); }
1054 |
1055 | // left
1056 | if (event.keyCode == 37) {
1057 | sendCommand('POST', 'previous');
1058 | }
1059 |
1060 | // right
1061 | if (event.keyCode == 39) {
1062 | sendCommand('POST', 'next');
1063 | }
1064 |
1065 | // space
1066 | if (event.keyCode == 32) {
1067 | if (trackPlaying) {
1068 | trackPlaying = false;
1069 | sendCommand('PUT', 'pause');
1070 | } else {
1071 | trackPlaying = true;
1072 | sendCommand('PUT', 'play');
1073 | }
1074 | }
1075 | });
1076 | }
1077 |
1078 |
1079 |
1080 | // --------------------------------------------------------------------------------------
1081 | // Bootstrapping
1082 | // --------------------------------------------------------------------------------------
1083 |
1084 | function bootstrap() {
1085 | initWebGL(document.getElementById("canvas"));
1086 | initTextureFramebuffer();
1087 | initTextureFramebuffer2();
1088 | initNoiseTexture();
1089 | initWebGLBuffers();
1090 | initKeyboard();
1091 | initUI();
1092 | validateAuthentication();
1093 | initWebGLShader1(function() {
1094 | initWebGLShader2(function() {
1095 | updatePostBuffer();
1096 | tick();
1097 | });
1098 | });
1099 | }
1100 |
1101 | window.addEventListener('load', bootstrap);
1102 |
--------------------------------------------------------------------------------
/shader.fs:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | varying vec4 vColor;
4 | varying vec3 vNormal;
5 | varying vec4 vEyeVector;
6 | varying vec4 vEyePosition;
7 |
8 | uniform mat4 uMVMatrix2;
9 | uniform mat4 uPMatrix2;
10 | uniform int uWriteDepth;
11 |
12 | void main(void) {
13 | vec3 lightDir = normalize(vec3(-0.3, 0.2, 0.1));
14 | vec3 specular = vNormal * vEyeVector.xyz;
15 | float vspec = 5.0 * pow(max(0.0, dot(reflect(-lightDir, vNormal), vEyeVector.xyz)), 3.0);
16 | float bri = 0.0;
17 | if (uWriteDepth == 1) {
18 | gl_FragColor = vec4(vEyePosition.z * 1.0, 1.0, 0.0, 1.0);
19 | } else {
20 | gl_FragColor = vColor + vec4(bri, bri, bri, 0.0);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/shader.vs:
--------------------------------------------------------------------------------
1 | attribute vec3 aVertexPosition;
2 | attribute vec4 aVertexColor;
3 | attribute vec4 aVertexData1;
4 | attribute vec4 aVertexData2;
5 |
6 | uniform float time;
7 | uniform float progress;
8 | uniform float wobble1;
9 | uniform float wobble2;
10 |
11 | uniform vec3 eyeVector;
12 | uniform mat4 uMVMatrix;
13 | uniform mat4 uPMatrix;
14 | // uniform int uWriteDepth;
15 |
16 | varying vec4 vColor;
17 | varying vec3 vNormal;
18 | varying vec4 vEyeVector;
19 | varying vec4 vEyePosition;
20 |
21 | float inOutQuint(float t, float b, float c, float d) {
22 | t /= d;
23 | float ts = t * t;
24 | float tc = ts * t;
25 | return b+c*(6.0*tc*ts + -15.0*ts*ts + 10.0*tc);
26 | }
27 |
28 | mat4 rotationMatrix(vec3 axis, float angle)
29 | {
30 | axis = normalize(axis);
31 | float s = sin(angle);
32 | float c = cos(angle);
33 | float oc = 1.0 - c;
34 |
35 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
36 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
37 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
38 | 0.0, 0.0, 0.0, 1.0);
39 | }
40 |
41 | float snoise(vec2 co) {
42 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
43 | }
44 |
45 | float progresscurve(float normprog) {
46 | if (normprog < -2.0) return -1.0;
47 | if (normprog > 2.0) return 1.0;
48 | if (normprog >= -2.0 && normprog < -0.5) return inOutQuint( normprog + 2.0, 1.0, -1.0, 1.5 );
49 | if (normprog > 0.5) return inOutQuint( normprog - 0.5, 0.0, 1.0, 1.5 );
50 | return 0.0;
51 | }
52 |
53 | void main(void) {
54 | float randomindex = aVertexData1.x;
55 | float pivot_x = aVertexData1.y;
56 | float pivot_y = aVertexData1.z;
57 | float facerandom1 = aVertexData2.x;
58 | float facerandom2 = aVertexData2.y;
59 | float facerandom3 = aVertexData2.z;
60 | float progressrandom = aVertexData1.x;
61 |
62 | float p = progresscurve( progress * 1.0 + progressrandom / 5.0 );
63 |
64 | float w1 = wobble1;
65 | float w2 = wobble2;
66 |
67 | float z = -1.0 * abs(p);
68 | float clean_z = z;
69 |
70 | z *= 0.8;
71 | z += (w1 * aVertexColor.x * facerandom2 / 2.0) * sin(pivot_y * 3.0 + time / 1933.0);
72 | z += (w2 * aVertexColor.y * facerandom3 / 1.0) * cos(pivot_x * 3.0 + time / 1333.0);
73 |
74 | mat4 localrot = rotationMatrix(vec3(facerandom1 + clean_z, facerandom2, facerandom3), clean_z * 100.0 + p * facerandom2 * 30.0);
75 |
76 | vec4 localpos = vec4(aVertexPosition, 1.0) - vec4(pivot_x, pivot_y, 0, 0);
77 | vec3 localnorm = vec3(0, 0, 1.0);
78 |
79 | vec4 rotpos = localpos * localrot;
80 | rotpos += vec4(pivot_x, pivot_y, 0, 0);
81 | rotpos += vec4(0.0, 0.0, z, 0.0);
82 | rotpos += vec4(facerandom1 * clean_z / 1.0, facerandom2 * clean_z / 3.0, 0.0, 0.0);
83 |
84 | vEyePosition = uPMatrix * uMVMatrix * rotpos;
85 | gl_Position = uPMatrix * uMVMatrix * rotpos;// uPMatrix * uMVMatrix * rotpos;
86 |
87 | float aa = max(0.0, 1.0 - abs(clean_z) - abs(progress / 3.0));
88 |
89 | vColor = aVertexColor * vec4(aa, aa, aa, 1.0);
90 | vNormal = vec3(vec4(localnorm, 0.0) * localrot);
91 | vEyeVector = vec4(eyeVector, 0.0) * uPMatrix;
92 | }
93 |
--------------------------------------------------------------------------------
/shader2.fs:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform sampler2D tColor;
4 | uniform sampler2D tDepth;
5 | uniform sampler2D tNoise;
6 |
7 | uniform float time;
8 | uniform float fBeat1;
9 | uniform float fBeat2;
10 | uniform float fBeat3;
11 |
12 | varying vec4 vPosition;
13 | varying vec2 vTexture;
14 |
15 | // inspiration
16 | // https://www.shadertoy.com/view/XdfGDH
17 | // https://www.shadertoy.com/view/4t23Rc
18 | // https://github.com/Jam3/glsl-fast-gaussian-blur/blob/master/13.glsl
19 |
20 | // varying vec3 vEyeVector;
21 |
22 | // vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
23 | // vec4 color = vec4(0.0);
24 | // vec2 off1 = vec2(1.411764705882353) * direction;
25 | // vec2 off2 = vec2(3.2941176470588234) * direction;
26 | // vec2 off3 = vec2(5.176470588235294) * direction;
27 | // color += texture2D(image, uv) * 0.1964825501511404;
28 | // color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
29 | // color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
30 | // color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
31 | // color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
32 | // color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
33 | // color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
34 | // return color;
35 | // }
36 |
37 | // // #ifdef GL_ES
38 | // // precision mediump float;
39 | // // #endif
40 |
41 | float normpdf(in float x, in float sigma) {
42 | return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;
43 | }
44 |
45 | vec4 blurrr(sampler2D image, vec2 position, float width, float height) {
46 | const int mSize = 7;
47 | const int kSize = (mSize-1)/2;
48 | float kernel[mSize];
49 | vec3 final_colour = vec3(0.0);
50 |
51 | //create the 1-D kernel
52 | float sigma = 7.0;
53 | float Z = 0.0;
54 | for (int j = 0; j <= kSize; ++j) {
55 | kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
56 | }
57 |
58 | // get the normalization factor (as the gaussian has been clamped)
59 | for (int j = 0; j < mSize; ++j) {
60 | Z += kernel[j];
61 | }
62 |
63 | //read out the texels
64 | for (int i=-kSize; i <= kSize; ++i) {
65 | for (int j=-kSize; j <= kSize; ++j) {
66 | final_colour +=
67 | kernel[kSize+j] * kernel[kSize+i] *
68 | texture2D(image, (position + (vec2(width * float(i), height * float(j))))).rgb;
69 | }
70 | }
71 |
72 | return vec4(final_colour/(Z*Z), 1.0);
73 | }
74 |
75 | vec4 rgbShift(in sampler2D tex, in vec2 p, in vec4 shift) {
76 | shift *= 2.0*shift.w - 1.0;
77 | vec2 rs = vec2(shift.x,-shift.y);
78 | vec2 gs = vec2(shift.y,-shift.z);
79 | vec2 bs = vec2(shift.z,-shift.x);
80 | float r = texture2D(tex, p+rs, 0.0).x;
81 | float g = texture2D(tex, p+gs, 0.0).y;
82 | float b = texture2D(tex, p+bs, 0.0).z;
83 | return vec4(r,g,b,1.0);
84 | }
85 |
86 | vec4 noise( in vec2 p ) {
87 | return texture2D(tNoise, p, 0.0);
88 | }
89 |
90 | vec4 vec4pow( in vec4 v, in float p ) {
91 | // Don't touch alpha (w), we use it to choose the direction of the shift
92 | // and we don't want it to go in one direction more often than the other
93 | return vec4(pow(v.x,p),pow(v.y,p),pow(v.z,p),v.w);
94 | }
95 |
96 | #define AMPLITUDE 0.1
97 | #define SPEED 0.05
98 |
99 | /*
100 |
101 |
102 |
103 |
104 |
105 |
106 | vec2 uv = fragCoord.xy / iResolution.xy;
107 | vec2 block = floor(fragCoord.xy / vec2(16));
108 | vec2 uv_noise = block / vec2(64);
109 | uv_noise += floor(vec2(iGlobalTime) * vec2(1234.0, 3543.0)) / vec2(64);
110 |
111 | float block_thresh = pow(fract(iGlobalTime * 1236.0453), 2.0) * 0.2;
112 | float line_thresh = pow(fract(iGlobalTime * 2236.0453), 3.0) * 0.7;
113 |
114 | vec2 uv_r = uv, uv_g = uv, uv_b = uv;
115 |
116 | // glitch some blocks and lines
117 | if (GLITCH && (texture(iChannel1, uv_noise).r < block_thresh ||
118 | texture(iChannel1, vec2(uv_noise.y, 0.0)).g < line_thresh)) {
119 |
120 | vec2 dist = (fract(uv_noise) - 0.5) * audioEnvelope;
121 | fragCoord.x -= dist.x * 250.1 * audioEnvelope;
122 | fragCoord.y -= dist.y * 250.2 * audioEnvelope;
123 | }
124 |
125 |
126 | */
127 |
128 | void main(void) {
129 | vec2 p = vTexture.xy;
130 | vec4 n1 = vec4pow(noise(vec2(SPEED*time/4999.0 + sin(time/13100.0),2.0*SPEED*time/8000.0+p.y/(10.0-fBeat3*2.0) )),13.0);
131 |
132 | vec2 bp = vTexture.xy + vec2(0, n1.g * 0.2); // vsync problems
133 |
134 | // vec4 col = texture2D(tColor, bp);
135 | vec4 dep = texture2D(tDepth, bp);
136 |
137 | // col *= col;
138 |
139 | vec4 blurred = blurrr(tColor, bp, 0.003, 0.003);// * abs(0.8 - dep.r));
140 | // vec4 col2 = texture2D(tColor, ((bp - vec2(0.5,0.5)) * vec2(0.998 + 0.03 * fBeat1,1.0 + 0.003 * fBeat1)) + vec2(0.5 + 0.01 * dep.r * fBeat1,0.5));
141 | // vec4 col3 = texture2D(tColor, ((bp - vec2(0.5,0.5)) * vec2(1.002 + 0.03 * fBeat1,1.0 + 0.003 * fBeat1)) + vec2(0.503 - 0.01 * dep.r * fBeat2,0.5));
142 | vec4 o = vec4(0.0, 0.0, 0.0, 1.0);
143 |
144 | float fb = (fBeat3 * fBeat2) + n1.g + 0.3 * sin(time / 3791.0);
145 | float ifb = 1.0 - fb;
146 |
147 | vec4 shift =
148 | vec4pow(noise(vec2(SPEED*time/7000.0 + p.x / 300.0,2.0*SPEED*time/9000.0+p.y/(25.0-fBeat1*5.0) )), 13.0)
149 | * fBeat1
150 | * vec4(AMPLITUDE, AMPLITUDE, AMPLITUDE, 1.0);
151 |
152 | o += rgbShift(tColor, p, shift) * ifb;
153 | o += blurred * fb;
154 | o += blurred * (0.04 + fBeat3 * 0.1); // always a little blur
155 | o -= texture2D(tNoise, (p + bp) * 5.0 + vec2(time / 700.0, time / 90.0)).r * vec4(1.0,1.0,1.0,1.0) * n1.g * n1.r;
156 | o *= (1.0 + 3.0 * texture2D(tNoise, (p + bp) * 9.0 + vec2(0, time / 19.0)).r * n1.b);
157 |
158 | vec2 pc = 1.0 * (p - vec2(0.5, 0.5));
159 | pc *= pc;
160 | float l = length(pc);
161 |
162 | // o *= vec4(1.0,1.0,1.0,1.0) * (1.0 - l * 5.0);
163 | o *= vec4(1.0,1.0,1.0,1.0) * (1.0 - l * 3.0);
164 |
165 | // o += vec4(0.1, 0.1, 0.1, 1.0) * (1.0 - l * 7.0) * texture2D(tNoise, (p + bp) * 9.0 + vec2(0, time / 19.0)).r;
166 |
167 | gl_FragColor = o;
168 | }
169 |
--------------------------------------------------------------------------------
/shader2.vs:
--------------------------------------------------------------------------------
1 | attribute vec3 aVertexPosition;
2 | attribute vec2 aVertexTexture;
3 |
4 | varying vec2 vTexture;
5 | varying vec4 vPosition;
6 |
7 | void main(void) {
8 | vPosition = vec4(aVertexPosition, 1);
9 | vTexture = vec2(aVertexTexture);
10 | gl_Position = vec4(aVertexPosition, 1); // uPMatrix * uMVMatrix * rotpos;
11 | }
12 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | border: 0;
3 | margin: 0;
4 | padding: 0;
5 | overflow: hidden;
6 | }
7 |
8 | body {
9 | border: 0;
10 | margin: 0;
11 | padding: 0;
12 | overflow: hidden;
13 | background-color: #000;
14 | }
15 |
16 | canvas {
17 | width: 100%;
18 | height: 100%;
19 | border: 0;
20 | }
21 |
22 | .login {
23 | position: absolute;
24 | right: 10px;
25 | top: 10px;
26 | font: 8pt Arial;
27 | color: rgba(255,255,255,0.33);
28 | }
29 |
30 | .biglogin {
31 | display: none;
32 | position: absolute;
33 | top: 50%;
34 | width: 100%;
35 | text-align: center;
36 | }
37 |
38 | .biglogin a {
39 | margin: 0 auto;
40 | font: 18pt "Helvetica Neue", "Helvetica", Arial;
41 | padding: 20px 40px;
42 | border-radius: 100px;
43 | background-color: rgb(30,200,20);
44 | box-shadow: 0 0 100px rgba(30,230,20,0.9);
45 | color: #fff;
46 | display: inline-block;
47 | text-align: center;;
48 | cursor: pointer;
49 | transition: all 0.1s ease-in-out;
50 | }
51 |
52 | .biglogin a:hover {
53 | transform: scale(1.1);
54 | }
55 |
56 | div.toast {
57 | background-color: rgba(0,0,0,0.5);
58 | position: absolute;
59 | left: 0;
60 | bottom: 0;
61 | right: 0;
62 | text-align: center;
63 | padding: 40px;
64 | transform: translateY(100px);
65 | transition: all 1.0s ease-in-out;
66 | opacity: 0.0;
67 | }
68 |
69 | div.toast.visible {
70 | transform: translateY(0);
71 | opacity: 1.0;
72 | }
73 |
74 | div.toast #text {
75 | display: block;
76 | font-size: 25pt;
77 | font-family: "Helvetica Neue", Helvetica;
78 | font-weight: 100;
79 | color: #fff;
80 | }
81 |
82 | div.toast #text2 {
83 | display: block;
84 | font-size: 15pt;
85 | font-family: "Helvetica Neue", Helvetica;
86 | font-weight: 100;
87 | color: #ccc;
88 | }
89 |
90 | div.trackposition {
91 | position: absolute;
92 | left: 10px;
93 | bottom: 10px;
94 | right: 10px;
95 | height: 5px;
96 | background-color: rgba(0,0,0,0.4);
97 | border-radius: 10px;
98 | }
99 |
100 | div.trackposition div.fill {
101 | transition: width 0.1s ease-in-out;
102 | background-color: #1ed660;
103 | position: absolute;
104 | left: 0;
105 | height: 5px;
106 | border-radius: 10px;
107 | }
108 |
--------------------------------------------------------------------------------