├── 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 | ![Obligatory 10mb gif](kung10mb.gif) 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 |
Sign in to Spotify
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 | --------------------------------------------------------------------------------