diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8a03b5f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.sketch/*
\ No newline at end of file
diff --git a/css/index.css b/css/index.css
new file mode 100644
index 0000000..031ca26
--- /dev/null
+++ b/css/index.css
@@ -0,0 +1,115 @@
+/*
+Quentin Mazars-Simon
+@quentinms
+*/
+
+*{
+ margin: 0;
+ padding: 0;
+}
+
+body{
+ font-family: "Helvetica Neue", Helvetica, Arial;
+ font-weight: 100;
+}
+
+#main_panel{
+
+}
+
+
+nav {
+ width: 250px;
+ float: left;
+ display: inline;
+ margin: 10px 10px 10px 100px;
+ padding: 0;
+
+}
+
+nav ul {
+ list-style: none;
+ text-align: center;
+}
+
+nav ul li {
+ border-bottom: 1px solid black;
+}
+
+nav ul li a{
+ text-decoration: none;
+ color: inherit;
+ font-size: 30px;
+
+}
+
+h1{
+ font-size: 45px;
+ font-weight: 100;
+ text-align: center;
+ margin: 20px;
+}
+
+#newSongForm{
+ float: left;
+ font-size: 20px;
+ margin: 5%;
+ display: none;
+}
+
+#newSongForm input{
+ font-size: 20px;
+ width: 400px;
+ font-family: "Helvetica Neue";
+ font-weight: 100;
+
+}
+#newSongForm button{
+
+ font-size: 20px;
+ width: 100px;
+ font-family: "Helvetica Neue";
+ font-weight: 100;
+ margin: 20px auto 20px auto;
+
+ position: relative;
+ left: 25%;
+}
+
+#content{
+
+
+}
+
+#illustration{
+ width: 500px;
+ height: 500px;
+ margin: auto;
+ background: url('http://lorempixel.com/500/500/abstract/');
+
+ background-repeat: no-repeat;
+ background-position: center center;
+
+ /*
+ If do not want to always be squared
+ background-size: contain;
+ */
+}
+
+#playImage{
+ margin: 200px;
+}
+
+#time{
+ width: 500px;
+ height: 20px;
+ margin: auto;
+ text-align: right;
+}
+
+#waveform {
+ width: 500px;
+ height: 75px;
+ margin: auto;
+}
+
diff --git a/img/pause.png b/img/pause.png
new file mode 100644
index 0000000..f768ed5
Binary files /dev/null and b/img/pause.png differ
diff --git a/img/play.png b/img/play.png
new file mode 100644
index 0000000..3925aa5
Binary files /dev/null and b/img/play.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d8e7f6e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ (image + waveform).
+ $("#content").bind('click',function(){stream.togglePause(); togglePause();})
+
+ //Display time indication when the user hovers the waveform
+ $('#waveform').bind('mouseenter', function() {
+ this.iid = setInterval(function() {
+
+ secP = '0'+parseInt(stream.position / 1000)%60;
+ secP = secP.slice(-2);
+ minP = parseInt(stream.position / 1000 / 60)%60;
+
+ secD = '0'+parseInt(stream.duration / 1000)%60;
+ secD = secD.slice(-2);
+ minD = parseInt(stream.duration / 1000 / 60)%60;
+
+ $("#time").text(minP+':'+secP+' / '+minD+':'+secD);
+ }, 500);
+ }).bind('mouseleave', function(){
+ $("#time").text("");
+ this.iid && clearInterval(this.iid);
+ });
+
+
+
+
+ });
+});
+
+}
+
+
+var selectSong = function(url, tag){
+
+ //Reset a bunch of stuff
+
+ window.ImgCount = 0;
+ window.tumblrImages = [];
+ window.paused = true;
+ $("#playImage").attr('src','img/play.png');
+ $("#newSongForm").hide();
+ $("#content").show();
+ $('#waveform').empty();
+ $('#waveform').unbind('mouseenter').unbind('mouseleave');
+ $("#content").unbind('click');
+
+ if(window.stream != null){
+ window.stream.destruct();
+ }
+
+
+ //We get the new song id, initialize the stream, the waveform and get the images from Tumblr
+ SC.get('/resolve', { url: url }, function(track){
+ initSoundCloud(track.id);
+ })
+
+ initTumblr(tag);
+
+}
+
+/*Utility functions*/
+
+var togglePause = function(){
+
+ if(window.paused){
+ $("#playImage").attr('src','img/pause.png');
+ } else {
+ $("#playImage").attr('src','img/play.png');
+ }
+ window.paused = ! window.paused;
+}
+
+var showForm = function(){
+ $("#content").hide();
+ $("#newSongForm").show();
+}
+
+var hideForm = function(){
+ $("#newSongForm").hide();
+ $("#content").show();
+}
+
+//Generates the menu based on the list of songs
+var generateNav = function(songs){
+
+
+ $('nav').append($("
"));
+
+ for(var i = 0; i < songs.length; i++){
+
+ var item = $("
"+songs[i].title+"");
+ $('nav ul').append(item);
+
+ }
+
+ var item = $("
New Song...");
+ $('nav ul').append(item);
+
+}
+
+/*Main*/
+
+var songs = [ {
+ "title": "Color Theory",
+ "artist": "Tea Leigh",
+ "url": "https://soundcloud.com/tea-leigh/color-theory-1",
+ "tag": "Color Theory"
+ },
+
+ {
+ "title": "Clouds",
+ "artist": "Marekhemmann",
+ "url": "https://soundcloud.com/marekhemmann/clouds",
+ "tag": "Clouds"
+ } ,
+ {
+ "title": "Yonkers",
+ "artist": "Tyler The Creator",
+ "url": "https://soundcloud.com/diamondmedia360/tyler-the-creator-yonkers-prod",
+ "tag": "Yonkers"
+ } ,
+ {
+ "title": "L.O.V.E",
+ "artist": "Onra",
+ "url": "https://soundcloud.com/onra/l-o-v-e",
+ "tag": "Love"
+ } ,
+ {
+ "title": "Icarus",
+ "artist": "Madeon",
+ "url": "https://soundcloud.com/madeon/madeon-icarus",
+ "tag": "Icarus"
+ } ,
+ {
+ "title": "Hours",
+ "artist": "Tycho",
+ "url": "https://soundcloud.com/tycho/tycho-hours",
+ "tag": "Hours"
+ } ,
+ {
+ "title": "New Theory",
+ "artist": "RAC",
+ "url": "https://soundcloud.com/rac/washed-out-new-theory-rac-mix",
+ "tag": "New Theory"
+ } ,
+ {
+ "title": "Too Insistent",
+ "artist": "Inertiamusic",
+ "url": "https://soundcloud.com/inertiamusic/the-do-too-insistent",
+ "tag": "Too Insistent"
+ } ,
+ {
+ "title": "If U Got It",
+ "artist": "Chris Malinchak",
+ "url": "https://soundcloud.com/chrismalinchak/if-u-got-it",
+ "tag": "If U Got It"
+ } ,
+ {
+ "title": "Pharao Black Magic",
+ "artist": "Future Classic",
+ "url": "https://soundcloud.com/futureclassic/pharao-black-magic-hermes",
+ "tag": "Pharao Black Magic"
+ }
+
+ ];
+
+
+$(document).ready(function() {
+
+ //Authenticate with SoundCloud
+ SC.initialize({
+ client_id: "16be599a525a2df3fc4b5a20da9927c4"
+ });
+
+ var ImgCount = 0;
+ var tumblrImages = [];
+ var paused = true;
+
+$("#illustration").mouseenter(function(){$("#playImage").show();})
+$("#illustration").mouseleave(function(){$("#playImage").hide();})
+
+
+generateNav(songs);
+
+selectSong(songs[0].url,songs[0].tag);
+
+ });
\ No newline at end of file
diff --git a/js/waveform.js b/js/waveform.js
new file mode 100644
index 0000000..5a0482d
--- /dev/null
+++ b/js/waveform.js
@@ -0,0 +1,253 @@
+(function() {
+ var JSONP, Waveform,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+ window.Waveform = Waveform = (function() {
+
+ Waveform.name = 'Waveform';
+
+ function Waveform(options) {
+ this.redraw = __bind(this.redraw, this);
+ this.container = options.container;
+ this.canvas = options.canvas;
+ this.data = options.data || [];
+ this.outerColor = options.outerColor || "transparent";
+ this.innerColor = options.innerColor || "#000000";
+ this.interpolate = true;
+ if (options.interpolate === false) {
+ this.interpolate = false;
+ }
+ if (this.canvas == null) {
+ if (this.container) {
+ this.canvas = this.createCanvas(this.container, options.width || this.container.clientWidth, options.height || this.container.clientHeight);
+ } else {
+ throw "Either canvas or container option must be passed";
+ }
+ }
+ this.patchCanvasForIE(this.canvas);
+ this.context = this.canvas.getContext("2d");
+ this.width = parseInt(this.context.canvas.width, 10);
+ this.height = parseInt(this.context.canvas.height, 10);
+ if (options.data) {
+ this.update(options);
+ }
+ }
+
+ Waveform.prototype.setData = function(data) {
+ return this.data = data;
+ };
+
+ Waveform.prototype.setDataInterpolated = function(data) {
+ return this.setData(this.interpolateArray(data, this.width));
+ };
+
+ Waveform.prototype.setDataCropped = function(data) {
+ return this.setData(this.expandArray(data, this.width));
+ };
+
+ Waveform.prototype.update = function(options) {
+ if (options.interpolate != null) {
+ this.interpolate = options.interpolate;
+ }
+ if (this.interpolate === false) {
+ this.setDataCropped(options.data);
+ } else {
+ this.setDataInterpolated(options.data);
+ }
+ return this.redraw();
+ };
+
+ Waveform.prototype.redraw = function() {
+ var d, i, middle, t, _i, _len, _ref, _results;
+ this.clear();
+ this.context.fillStyle = this.innerColor;
+ middle = this.height / 2;
+ i = 0;
+ _ref = this.data;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ d = _ref[_i];
+ t = this.width / this.data.length;
+ if (typeof this.innerColor === "function") {
+ this.context.fillStyle = this.innerColor(i / this.width, d);
+ }
+ this.context.clearRect(t * i, middle - middle * d, t, middle * d * 2);
+ this.context.fillRect(t * i, middle - middle * d, t, middle * d * 2);
+ _results.push(i++);
+ }
+ return _results;
+ };
+
+ Waveform.prototype.clear = function() {
+ this.context.fillStyle = this.outerColor;
+ this.context.clearRect(0, 0, this.width, this.height);
+ return this.context.fillRect(0, 0, this.width, this.height);
+ };
+
+ Waveform.prototype.patchCanvasForIE = function(canvas) {
+ var oldGetContext;
+ if (typeof window.G_vmlCanvasManager !== "undefined") {
+ canvas = window.G_vmlCanvasManager.initElement(canvas);
+ oldGetContext = canvas.getContext;
+ return canvas.getContext = function(a) {
+ var ctx;
+ ctx = oldGetContext.apply(canvas, arguments);
+ canvas.getContext = oldGetContext;
+ return ctx;
+ };
+ }
+ };
+
+ Waveform.prototype.createCanvas = function(container, width, height) {
+ var canvas;
+ canvas = document.createElement("canvas");
+ container.appendChild(canvas);
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ };
+
+ Waveform.prototype.expandArray = function(data, limit, defaultValue) {
+ var i, newData, _i, _ref;
+ if (defaultValue == null) {
+ defaultValue = 0.0;
+ }
+ newData = [];
+ if (data.length > limit) {
+ newData = data.slice(data.length - limit, data.length);
+ } else {
+ for (i = _i = 0, _ref = limit - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+ newData[i] = data[i] || defaultValue;
+ }
+ }
+ return newData;
+ };
+
+ Waveform.prototype.linearInterpolate = function(before, after, atPoint) {
+ return before + (after - before) * atPoint;
+ };
+
+ Waveform.prototype.interpolateArray = function(data, fitCount) {
+ var after, atPoint, before, i, newData, springFactor, tmp;
+ newData = new Array();
+ springFactor = new Number((data.length - 1) / (fitCount - 1));
+ newData[0] = data[0];
+ i = 1;
+ while (i < fitCount - 1) {
+ tmp = i * springFactor;
+ before = new Number(Math.floor(tmp)).toFixed();
+ after = new Number(Math.ceil(tmp)).toFixed();
+ atPoint = tmp - before;
+ newData[i] = this.linearInterpolate(data[before], data[after], atPoint);
+ i++;
+ }
+ newData[fitCount - 1] = data[data.length - 1];
+ return newData;
+ };
+
+ Waveform.prototype.optionsForSyncedStream = function(options) {
+ var innerColorWasSet, that;
+ if (options == null) {
+ options = {};
+ }
+ innerColorWasSet = false;
+ that = this;
+ return {
+ whileplaying: this.redraw,
+ whileloading: function() {
+ var stream;
+ if (!innerColorWasSet) {
+ stream = this;
+ that.innerColor = function(x, y) {
+ if (x < stream.position / stream.durationEstimate) {
+ return options.playedColor || "rgba(255, 102, 0, 0.8)";
+ } else if (x < stream.bytesLoaded / stream.bytesTotal) {
+ return options.loadedColor || "rgba(0, 0, 0, 0.8)";
+ } else {
+ return options.defaultColor || "rgba(0, 0, 0, 0.4)";
+ }
+ };
+ innerColorWasSet = true;
+ }
+ return this.redraw;
+ }
+ };
+ };
+
+ Waveform.prototype.dataFromSoundCloudTrack = function(track) {
+ var _this = this;
+ return JSONP.get("http://waveformjs.org/w", {
+ url: track.waveform_url
+ }, function(data) {
+ return _this.update({
+ data: data
+ });
+ });
+ };
+
+ return Waveform;
+
+ })();
+
+ JSONP = (function() {
+ var config, counter, encode, head, jsonp, key, load, query, setDefaults, window;
+ load = function(url) {
+ var done, head, script;
+ script = document.createElement("script");
+ done = false;
+ script.src = url;
+ script.async = true;
+ script.onload = script.onreadystatechange = function() {
+ if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
+ done = true;
+ script.onload = script.onreadystatechange = null;
+ if (script && script.parentNode) {
+ return script.parentNode.removeChild(script);
+ }
+ }
+ };
+ if (!head) {
+ head = document.getElementsByTagName("head")[0];
+ }
+ return head.appendChild(script);
+ };
+ encode = function(str) {
+ return encodeURIComponent(str);
+ };
+ jsonp = function(url, params, callback, callbackName) {
+ var key, query;
+ query = ((url || "").indexOf("?") === -1 ? "?" : "&");
+ params = params || {};
+ for (key in params) {
+ if (params.hasOwnProperty(key)) {
+ query += encode(key) + "=" + encode(params[key]) + "&";
+ }
+ }
+ jsonp = "json" + (++counter);
+ window[jsonp] = function(data) {
+ callback(data);
+ try {
+ delete window[jsonp];
+ } catch (_error) {}
+ return window[jsonp] = null;
+ };
+ load(url + query + (callbackName || config["callbackName"] || "callback") + "=" + jsonp);
+ return jsonp;
+ };
+ setDefaults = function(obj) {
+ var config;
+ return config = obj;
+ };
+ counter = 0;
+ head = void 0;
+ query = void 0;
+ key = void 0;
+ window = this;
+ config = {};
+ return {
+ get: jsonp,
+ init: setDefaults
+ };
+ })();
+
+}).call(this);
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..789e2ef
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,17 @@
+# Soundcloud Video Clip
+
+## A Soundcloud-Tumblr Mashup
+
+This project is a collaboration between students from ECAL and EPFL.
+The image displayed next to the song changes each time a comment occurs during the song.
+The illustration are fetched from [this Tumblr](http://soundcloudvideoclip.tumblr.com) according to the tag of the song.
+
+### Demo
+[http://www.quentin.ms/SoundcloudVideo/index.html](http://www.quentin.ms/SoundcloudVideo/index.html)
+
+### Used libraries
+* [jQuery](jquery.com)
+* [Soundcloud API](http://developers.soundcloud.com/docs/api/sdks#javascript)
+* [waveform.js](waveformjs.org)
+
+Made by Quentin Mazars-Simon and Belinda
\ No newline at end of file