├── .eslintrc.js ├── .gitignore ├── README.md ├── controllers ├── auth.js ├── load.js └── login.js ├── docs └── images │ ├── design.jpg │ ├── playlist.png │ ├── visualization1.png │ ├── visualization2.png │ └── visualization3.png ├── download_folder └── bachelorthesis-klangspektrum.pdf ├── models └── user.js ├── package.json ├── public ├── app.js ├── css │ ├── fonts.css │ └── styles.css ├── fonts │ ├── geomanist-bold-italic-webfont.eot │ ├── geomanist-bold-italic-webfont.svg │ ├── geomanist-bold-italic-webfont.ttf │ ├── geomanist-bold-italic-webfont.woff │ ├── geomanist-bold-italic-webfont.woff2 │ ├── geomanist-bold-webfont.eot │ ├── geomanist-bold-webfont.svg │ ├── geomanist-bold-webfont.ttf │ ├── geomanist-bold-webfont.woff │ ├── geomanist-bold-webfont.woff2 │ ├── geomanist-light-italic-webfont.eot │ ├── geomanist-light-italic-webfont.svg │ ├── geomanist-light-italic-webfont.ttf │ ├── geomanist-light-italic-webfont.woff │ ├── geomanist-light-italic-webfont.woff2 │ ├── geomanist-light-webfont.eot │ ├── geomanist-light-webfont.svg │ ├── geomanist-light-webfont.ttf │ ├── geomanist-light-webfont.woff │ ├── geomanist-light-webfont.woff2 │ ├── geomanist-medium-italic-webfont.eot │ ├── geomanist-medium-italic-webfont.svg │ ├── geomanist-medium-italic-webfont.ttf │ ├── geomanist-medium-italic-webfont.woff │ ├── geomanist-medium-italic-webfont.woff2 │ ├── geomanist-medium-webfont.eot │ ├── geomanist-medium-webfont.svg │ ├── geomanist-medium-webfont.ttf │ ├── geomanist-medium-webfont.woff │ ├── geomanist-medium-webfont.woff2 │ ├── geomanist-regular-italic-webfont.eot │ ├── geomanist-regular-italic-webfont.svg │ ├── geomanist-regular-italic-webfont.ttf │ ├── geomanist-regular-italic-webfont.woff │ ├── geomanist-regular-italic-webfont.woff2 │ ├── geomanist-regular-webfont.eot │ ├── geomanist-regular-webfont.svg │ ├── geomanist-regular-webfont.ttf │ ├── geomanist-regular-webfont.woff │ ├── geomanist-regular-webfont.woff2 │ └── geomanist_webfont_license.pdf ├── img │ └── player_ichons.svg └── landingpage.js ├── routes ├── api.js ├── index.js └── klangviz.js ├── server.js ├── src ├── Api │ ├── api.js │ ├── sort.js │ └── start.js ├── ITF │ └── interfaceEvents.js ├── Viz │ ├── Controller │ │ ├── mouseEvents.js │ │ ├── scaleEvents.js │ │ └── timeEvents.js │ ├── Model │ │ └── klangobject.js │ ├── View │ │ ├── Innerobject.js │ │ └── display.js │ ├── init.json │ └── sketch.js └── index.js ├── views ├── imprint.html ├── index.html └── klangviz.html └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "google", 3 | "parserOptions": { 4 | "ecmaVersion": 5, 5 | "sourceType": "module" 6 | }, 7 | "rules": { 8 | "semi": 2 9 | } 10 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | README.md 4 | public/lib 5 | public/mylib 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KLANGSPEKTRUM 2 | https://developer.spotify.com/showcase/item/klangspektrum/ 3 | 4 | ## [Click here for the latest version (2.x)](https://github.com/schwamic/klangspektrum2) 5 | -------------------------------------------------------------------------------- /controllers/auth.js: -------------------------------------------------------------------------------- 1 | var querystring = require('querystring'); 2 | var new_request = require('request'); 3 | 4 | exports.get = function (request, response) { 5 | var client_id = '...'; 6 | var client_secret = '...'; 7 | var redirect_uri = 'http://www.klangspektrum.digital/api/callback'; // REDIRECT URI 8 | var stateKey = 'spotify_auth_state'; 9 | var code = request.query.code || null; 10 | var state = request.query.state || null; 11 | var storedState = request.cookies ? request.cookies[stateKey] : null; 12 | if (state === null || state !== storedState) { 13 | response.redirect('/klangviz/#' + 14 | querystring.stringify({ 15 | error: 'state_mismatch' 16 | })); 17 | } else { 18 | var authOptions = getAuthOptionsCallback(code, redirect_uri, client_id, client_secret); 19 | new_request.post(authOptions, function (error, res, body) { 20 | if (!error && res.statusCode === 200) { 21 | var access_token = body.access_token; 22 | var refresh_token = body.refresh_token; 23 | 24 | // send tokens to client 25 | response.redirect('/klangviz?' + 26 | querystring.stringify({ 27 | access_token: access_token, 28 | refresh_token: refresh_token 29 | })); 30 | } else { 31 | response.redirect('/klangviz/#' + 32 | querystring.stringify({ 33 | error: 'invalid_token' 34 | })); 35 | } 36 | }); 37 | } 38 | }; 39 | 40 | var getAuthOptionsCallback = function (code, redirect_uri, client_id, client_secret) { 41 | var authOptions = { 42 | url: 'https://accounts.spotify.com/api/token', 43 | form: { 44 | code: code, 45 | redirect_uri: redirect_uri, 46 | grant_type: 'authorization_code' 47 | }, 48 | headers: { 49 | 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) 50 | }, 51 | json: true 52 | }; 53 | return authOptions; 54 | }; 55 | -------------------------------------------------------------------------------- /controllers/load.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var guard = require('when/guard'); 3 | var request_promise = require('request-promise'); 4 | require('../models/user'); 5 | var mongoose = require('mongoose'); 6 | 7 | // Init 8 | var KlangUser = mongoose.model('KlangUser'); 9 | var guardedRequest = guard(guard.n(1), request_promise); 10 | 11 | 12 | exports.getMe = function (request, response) { 13 | console.log('Start requestMe'); 14 | let access_token = request.params.access_token; 15 | let user = request.params.user; 16 | let user_id, user_image; 17 | let options = { 18 | url: 'https://api.spotify.com/v1/me', 19 | headers: { 'Authorization': 'Bearer ' + access_token }, 20 | json: true 21 | }; 22 | 23 | request_promise(options) 24 | .then(function (result) { 25 | user_id = result.id; 26 | user_image = result.images; 27 | 28 | // add to db 29 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 30 | if (err) throw err; 31 | myuser.name = user_id; 32 | myuser.image = user_image; 33 | myuser.save(); 34 | let message = { 'user_id': user_id, 'user_image': user_image }; 35 | response.json(message); 36 | }); 37 | }) 38 | .catch(function (err) { 39 | response.json({ 'error': 'getMe' }); 40 | }); 41 | }; 42 | 43 | 44 | exports.getSavedTracks = function (request, response) { 45 | console.log('Start requestSavedTracks'); 46 | let access_token = request.params.access_token; 47 | let user = request.params.user; 48 | let offset = parseInt(request.params.offset); 49 | let count = parseInt(request.params.count); 50 | let first_time = request.params.first_time; 51 | var track_list = []; 52 | 53 | if (first_time == 'true') { 54 | let options = { 55 | url: `https://api.spotify.com/v1/me/tracks?limit=50&offset=${offset}`, 56 | headers: { 'Authorization': 'Bearer ' + access_token }, 57 | json: true 58 | }; 59 | 60 | request_promise(options) 61 | .then(function (result) { 62 | if (result.total > 0) { 63 | // RESULT 64 | // db + res 65 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 66 | if (err) throw err; 67 | let limit = 50; 68 | count = Math.ceil(result.total / limit); 69 | offset++; 70 | for (let item of result.items) { 71 | let trackID = item.track.id; 72 | let artistID = item.track.artists[0].id; 73 | let tupel = { 'trackID': trackID, 'artistID': artistID }; 74 | myuser.stracks.push(tupel); 75 | track_list.push(item); 76 | }; 77 | myuser.save(function () { 78 | response.json({ 'count': count, 'offset': offset, 'mydata': track_list }); 79 | }); 80 | }); 81 | 82 | } else { 83 | // No stracks available 84 | response.json({ 'count': count, 'offset': offset, 'mydata': track_list }); 85 | } 86 | }) 87 | .catch(function (err) { 88 | response.json({ 'error': 'getSavedTracks' }); 89 | }); 90 | } else { 91 | let arr = []; 92 | let step = ((offset + 10) - count) < 0 ? (offset + 10) : count; 93 | for (let i = offset; i < step; i++) { 94 | arr.push(i * 50); 95 | }; 96 | offset = step; 97 | 98 | Promise.all(arr.map(element => { 99 | let options = { 100 | url: `https://api.spotify.com/v1/me/tracks?limit=50&offset=${element}`, 101 | headers: { 'Authorization': 'Bearer ' + access_token }, 102 | json: true 103 | }; 104 | return guardedRequest(options); 105 | })) 106 | .then(result => { 107 | // RESULT 108 | // db + res 109 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 110 | if (err) throw err; 111 | for (let i = 0; i < result.length; i++) { 112 | for (let item of result[i].items) { 113 | let trackID = item.track.id; 114 | let artistID = item.track.artists[0].id; 115 | let tupel = { 'trackID': trackID, 'artistID': artistID }; 116 | myuser.stracks.push(tupel); 117 | track_list.push(item); 118 | } 119 | }; 120 | myuser.save(function () { 121 | response.json({ 'count': count, 'offset': offset, 'mydata': track_list }); 122 | }); 123 | }); 124 | }) 125 | .catch(err => { 126 | response.json({ 'error': 'requestAllSavedTracks' }); 127 | }); 128 | } 129 | }; 130 | 131 | 132 | exports.getPlaylists = function (request, response) { 133 | console.log('Start requestPlaylist'); 134 | let access_token = request.params.access_token; 135 | let user = request.params.user; 136 | let offset = parseInt(request.params.offset); 137 | let count = parseInt(request.params.count); 138 | let first_time = request.params.first_time; 139 | let user_id = request.params.user_id; 140 | let playlist_list = []; 141 | 142 | if (first_time == 'true') { 143 | let options = { 144 | url: `https://api.spotify.com/v1/users/${user_id}/playlists?limit=50&offset=${offset}`, 145 | headers: { 'Authorization': 'Bearer ' + access_token }, 146 | json: true 147 | }; 148 | 149 | request_promise(options) 150 | .then(function (result) { 151 | // RESULT 152 | if (result.total > 0) { 153 | // db + res 154 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 155 | if (err) throw err; 156 | 157 | var limit = 50; 158 | count = Math.ceil(result.total / limit); 159 | offset++; 160 | for (let item of result.items) { 161 | let href = item.href; 162 | let total = item.tracks.total; 163 | let id = item.owner.id; 164 | let tupel = { 'href': href, 'total': total, 'id': id }; 165 | playlist_list.push(tupel); 166 | myuser.playlists.push(tupel); 167 | }; 168 | myuser.save(function () { 169 | response.json({ 'count': count, 'offset': offset, 'mydata': playlist_list }); 170 | }); 171 | }); 172 | // No playlists available 173 | } else { 174 | response.json({ 'count': count, 'offset': offset, 'mydata': playlist_list }); 175 | } 176 | }) 177 | .catch(function (err) { 178 | response.json({ 'error': 'requestPlaylists' }); 179 | }); 180 | } else { 181 | let arr = []; 182 | let step = ((offset + 10) - count) < 0 ? (offset + 10) : count; 183 | for (let i = offset; i < step; i++) { 184 | arr.push(i * 50); 185 | }; 186 | offset = step; 187 | 188 | Promise.all(arr.map(element => { 189 | let options = { 190 | url: `https://api.spotify.com/v1/users/${user_id}/playlists?limit=50&offset=${element}`, 191 | headers: { 'Authorization': 'Bearer ' + access_token }, 192 | json: true 193 | }; 194 | return guardedRequest(options); 195 | })) 196 | .then(result => { 197 | // DO STUFF WITH RESULT 198 | for (var i = 0; i < result.length; i++) { 199 | for (let item of result[i].items) { 200 | let href = item.href; 201 | let total = item.tracks.total; 202 | let tupel = { 'href': href, 'total': total }; 203 | playlist_list.push(tupel); 204 | } 205 | }; 206 | 207 | // add to db 208 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 209 | if (err) throw err; 210 | let arr = myuser.playlists; 211 | myuser.playlists = arr.concat(playlist_list); 212 | myuser.save(); 213 | response.json({ 'count': count, 'offset': offset, 'mydata': playlist_list }); 214 | }); 215 | }) 216 | .catch(err => { 217 | response.json({ 'error': 'requestPlaylists' }); 218 | }); 219 | } 220 | }; 221 | 222 | 223 | exports.getPlaylistTracks = function (request, response) { 224 | console.log('Start requestPlaylistTracks'); 225 | let access_token = request.params.access_token; 226 | let user = request.params.user; 227 | let pindex = parseInt(request.params.pindex); 228 | let tasks = []; 229 | let playlisttracks_list = []; 230 | let limit = 100; 231 | let playlist; 232 | 233 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 234 | if (err) throw err; 235 | playlist = myuser.playlists[pindex]; 236 | let href = playlist.href; 237 | let total = playlist.total; 238 | 239 | for (let offset = 0; offset < total; offset += limit) { 240 | let options = { 241 | url: `${href}?limit=100&offset=${offset}`, 242 | headers: { 'Authorization': 'Bearer ' + access_token }, 243 | json: true 244 | }; 245 | tasks.push(options); 246 | }; 247 | 248 | Promise.all(tasks.map(options => { 249 | return guardedRequest(options); 250 | })) 251 | .then(result => { 252 | // RESULT 253 | // db + res 254 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 255 | if (err) throw err; 256 | 257 | for (let i = 0; i < result.length; i++) { 258 | for (let item of result[i].tracks.items) { 259 | let trackID = item.track.id; 260 | let artistID = item.track.artists[0].id; 261 | let tupel = { 'trackID': trackID, 'artistID': artistID }; 262 | playlisttracks_list.push(item); 263 | myuser.ptracks.push(tupel); 264 | } 265 | }; 266 | myuser.ownPlaylist.push(pindex); 267 | myuser.save(function () { 268 | response.json({ 'mydata': playlisttracks_list }); 269 | }); 270 | }); 271 | }) 272 | .catch(err => { 273 | response.json({ 'error': 'requestPlaylistTracks' }); 274 | }); 275 | }); 276 | }; 277 | 278 | 279 | exports.createAllTracks = function (request, response) { 280 | let user = request.params.user; 281 | let stracks, ptracks, alltracks; 282 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 283 | if (err) throw err; 284 | stracks = myuser.stracks; 285 | ptracks = myuser.ptracks; 286 | alltracks = stracks.concat(ptracks); 287 | myuser.alltracks = alltracks; 288 | myuser.save(function (err) { 289 | if (err) { 290 | response.json({ 'message': 'error' }); 291 | console.log(err); 292 | } 293 | else { 294 | response.json({ 'message': 'OK' }); 295 | } 296 | }); 297 | }); 298 | }; 299 | 300 | 301 | exports.getFeatures = function (request, response) { 302 | console.log('Start getFeatures'); 303 | let access_token = request.params.access_token; 304 | let user = request.params.user; 305 | let offset = parseInt(request.params.offset); 306 | let tasks = []; 307 | let features_list = []; 308 | let limit = 50; 309 | let url = 'https://api.spotify.com/v1/audio-features?ids='; 310 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 311 | if (err) throw err; 312 | let alltracks_list = myuser.alltracks; 313 | let count = alltracks_list.length; 314 | let step = ((offset + 500) - count) < 0 ? (offset + 500) : count; 315 | for (var i = offset; i < step; i++) { 316 | if (((i + 1) % limit) == 0 || ((step - i) == 1)) { 317 | url += '' + alltracks_list[i].trackID; 318 | 319 | var options = { 320 | url: url, 321 | headers: { 'Authorization': 'Bearer ' + access_token }, 322 | json: true 323 | }; 324 | url = 'https://api.spotify.com/v1/audio-features?ids='; 325 | tasks.push(options); 326 | } else { 327 | url += '' + alltracks_list[i].trackID + ','; 328 | }; 329 | }; 330 | offset = step; 331 | Promise.all(tasks.map(options => { 332 | return guardedRequest(options); 333 | })) 334 | .then(result => { 335 | for (let items of result) { 336 | for (let child of items.audio_features) { 337 | features_list.push(child); 338 | } 339 | }; 340 | // res 341 | response.json({ 'count': count, 'offset': offset, 'mydata': features_list }); 342 | }) 343 | .catch(err => { 344 | console.log(err); 345 | response.json({ 'error': 'requestFeatures' }); 346 | }); 347 | }); 348 | } 349 | 350 | //CHANGE 351 | exports.getArtists = function (request, response) { 352 | console.log('Start getArtists'); 353 | let access_token = request.params.access_token; 354 | let user = request.params.user; 355 | let offset = parseInt(request.params.offset); 356 | let tasks = []; 357 | let artist_list = []; 358 | let limit = 50; 359 | let url = 'https://api.spotify.com/v1/artists?ids='; 360 | KlangUser.findOne({ user: `${user}` }, function (err, myuser) { 361 | if (err) throw err; 362 | let alltracks_list = myuser.alltracks; 363 | let count = alltracks_list.length; 364 | let step = ((offset + 500) - count) < 0 ? (offset + 500) : count; 365 | for (var i = offset; i < step; i++) { 366 | if (((i + 1) % limit) == 0 || ((step - i) == 1)) { 367 | url += '' + alltracks_list[i].artistID; 368 | 369 | var options = { 370 | url: url, 371 | headers: { 'Authorization': 'Bearer ' + access_token }, 372 | json: true 373 | }; 374 | url = 'https://api.spotify.com/v1/artists?ids='; 375 | tasks.push(options); 376 | } else { 377 | url += '' + alltracks_list[i].artistID + ','; 378 | }; 379 | }; 380 | offset = step; 381 | Promise.all(tasks.map(options => { 382 | return guardedRequest(options); 383 | })) 384 | .then(result => { 385 | for (let items of result) { 386 | for (let child of items.artists) { 387 | artist_list.push({ name: (child == null ? 'Unknown' : child.name),'genres': (child == null ? [] : child.genres) }); 388 | } 389 | }; 390 | //add to db + res 391 | response.json({ 'count': count, 'offset': offset, 'mydata': artist_list }); 392 | }) 393 | .catch(err => { 394 | console.log(err); 395 | response.json({ 'error': 'requestArtists' }); 396 | }); 397 | }); 398 | }; 399 | -------------------------------------------------------------------------------- /controllers/login.js: -------------------------------------------------------------------------------- 1 | var querystring = require('querystring'); 2 | require('../models/user'); 3 | var mongoose = require('mongoose'); 4 | 5 | // Init 6 | var KlangUser = mongoose.model('KlangUser'); 7 | 8 | exports.get = function (request, response) { 9 | var client_id = '...'; 10 | var redirect_uri = 'http://www.klangspektrum.digital/api/callback'; // CALLBACK URI 11 | var stateKey = 'spotify_auth_state'; 12 | var scope = 'user-read-private user-read-email playlist-read-private playlist-read-collaborative user-library-read user-top-read'; 13 | var state = generateRandomString(16); 14 | 15 | // save user in db 16 | var current_user = new KlangUser({'user':request.params.user}); 17 | current_user.save(); 18 | 19 | response.cookie(stateKey, state); 20 | response.redirect('https://accounts.spotify.com/authorize?' + 21 | querystring.stringify({ 22 | response_type: 'code', 23 | client_id: client_id, 24 | scope: scope, 25 | redirect_uri: redirect_uri, 26 | state: state, 27 | show_dialog: true 28 | })); 29 | }; 30 | 31 | var generateRandomString = function (length) { 32 | var text = ''; 33 | var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 34 | for (var i = 0; i < length; i++) { 35 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 36 | } 37 | return text; 38 | }; 39 | -------------------------------------------------------------------------------- /docs/images/design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/docs/images/design.jpg -------------------------------------------------------------------------------- /docs/images/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/docs/images/playlist.png -------------------------------------------------------------------------------- /docs/images/visualization1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/docs/images/visualization1.png -------------------------------------------------------------------------------- /docs/images/visualization2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/docs/images/visualization2.png -------------------------------------------------------------------------------- /docs/images/visualization3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/docs/images/visualization3.png -------------------------------------------------------------------------------- /download_folder/bachelorthesis-klangspektrum.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/download_folder/bachelorthesis-klangspektrum.pdf -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var userSchema = new Schema({ 5 | created: { 6 | type: Date, 7 | default: Date.now 8 | }, 9 | user: String, 10 | access_token: String, 11 | refresh_token: String, 12 | name: String, 13 | stracks: [], 14 | ptracks: [], 15 | alltracks: [], 16 | image: [], 17 | playlists: [], 18 | ownPlaylist: [] 19 | }); 20 | 21 | mongoose.model('KlangUser', userSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "klangspektrum_ba", 3 | "version": "1.0.0", 4 | "babel": { 5 | "presets": [ 6 | "es2015" 7 | ] 8 | }, 9 | "description": "Bachelorthesis", 10 | "main": "server.js", 11 | "scripts": { 12 | "start": "node server.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/..." 17 | }, 18 | "author": "Michael Schwarz", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/..." 22 | }, 23 | "homepage": "https://github.com/...", 24 | "dependencies": { 25 | "babel": "^6.5.2", 26 | "babel-core": "^6.21.0", 27 | "babel-loader": "^6.2.10", 28 | "babel-preset-es2015": "^6.18.0", 29 | "cookie-parser": "^1.4.3", 30 | "cors": "^2.8.1", 31 | "express": "^4.14.0", 32 | "gridfs-stream": "^1.1.1", 33 | "mongoose": "^4.7.3", 34 | "p5": "^0.5.5", 35 | "querystring": "^0.2.0", 36 | "request": "^2.79.0", 37 | "request-promise": "^4.1.1", 38 | "underscore": "^1.8.3", 39 | "underscore.string": "^3.3.4", 40 | "webpack": "^1.14.0", 41 | "webpack-dev-middleware": "^1.9.0", 42 | "whatwg-fetch": "^2.0.2", 43 | "when": "^3.7.7" 44 | }, 45 | "devDependencies": { 46 | "eslint": "^3.14.1", 47 | "eslint-config-google": "^0.7.1" 48 | }, 49 | "engines": { 50 | "node": "4.6.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Klangspektrum - by Michael Schwarz 3 | Hochschule Augsburg 2016/2017 4 | */ 5 | 6 | 'use-strict'; 7 | 8 | /** 9 | * Helper 10 | */ 11 | 12 | var allKlangobj; 13 | // timelist 14 | var datelist = []; 15 | var t = 0; 16 | // playlist 17 | var active_node_outside = null; 18 | // scale 19 | var scale_value = 1; 20 | -------------------------------------------------------------------------------- /public/css/fonts.css: -------------------------------------------------------------------------------- 1 | /*LIGHT*/ 2 | @font-face { 3 | font-family: Geomanist; 4 | src: url('/fonts/geomanist-light-italic-webfont.eot'); 5 | src: url('/fonts/geomanist-light-italic-webfont.eot?#iefix') format('embedded-opentype'), 6 | url('/fonts/geomanist-light-italic-webfont.woff2') format('woff2'), 7 | url('/fonts/geomanist-light-italic-webfont.woff') format('woff'), 8 | url('/fonts/geomanist-light-italic-webfont.ttf') format('truetype'), 9 | url('/fonts/geomanist-light-italic-webfont.svg#geomanistitalic') format('svg'); 10 | font-weight: 100; 11 | font-style: italic; 12 | } 13 | @font-face { 14 | font-family: Geomanist; 15 | src: url('/fonts/geomanist-light-webfont.eot'); 16 | src: url('/fonts/geomanist-light-webfont.eot?#iefix') format('embedded-opentype'), 17 | url('/fonts/geomanist-light-webfont.woff2') format('woff2'), 18 | url('/fonts/geomanist-light-webfont.woff') format('woff'), 19 | url('/fonts/geomanist-light-webfont.ttf') format('truetype'), 20 | url('/fonts/geomanist-light-webfont.svg#geomanistitalic') format('svg'); 21 | font-weight: 100; 22 | font-style: normal; 23 | } 24 | /*RERGULAR*/ 25 | @font-face { 26 | font-family: Geomanist; 27 | src: url('/fonts/geomanist-regular-italic-webfont.eot'); 28 | src: url('/fonts/geomanist-regular-italic-webfont.eot?#iefix') format('embedded-opentype'), 29 | url('/fonts/geomanist-regular-italic-webfont.woff2') format('woff2'), 30 | url('/fonts/geomanist-regular-italic-webfont.woff') format('woff'), 31 | url('/fonts/geomanist-regular-italic-webfont.ttf') format('truetype'), 32 | url('/fonts/geomanist-regular-italic-webfont.svg#geomanistitalic') format('svg'); 33 | font-weight: 400; 34 | font-style: italic; 35 | } 36 | @font-face { 37 | font-family: Geomanist; 38 | src: url('/fonts/geomanist-regular-webfont.eot'); 39 | src: url('/fonts/geomanist-regular-webfont.eot?#iefix') format('embedded-opentype'), 40 | url('/fonts/geomanist-regular-webfont.woff2') format('woff2'), 41 | url('/fonts/geomanist-regular-webfont.woff') format('woff'), 42 | url('/fonts/geomanist-regular-webfont.ttf') format('truetype'), 43 | url('/fonts/geomanist-regular-webfont.svg#geomanistitalic') format('svg'); 44 | font-weight: 400; 45 | font-style: normal; 46 | } 47 | /*MEDIUM*/ 48 | @font-face { 49 | font-family: Geomanist; 50 | src: url('/fonts/geomanist-medium-italic-webfont.eot'); 51 | src: url('/fonts/geomanist-medium-italic-webfont.eot?#iefix') format('embedded-opentype'), 52 | url('/fonts/geomanist-medium-italic-webfont.woff2') format('woff2'), 53 | url('/fonts/geomanist-medium-italic-webfont.woff') format('woff'), 54 | url('/fonts/geomanist-medium-italic-webfont.ttf') format('truetype'), 55 | url('/fonts/geomanist-medium-italic-webfont.svg#geomanistitalic') format('svg'); 56 | font-weight: 700; 57 | font-style: italic; 58 | } 59 | @font-face { 60 | font-family: Geomanist; 61 | src: url('/fonts/geomanist-medium-webfont.eot'); 62 | src: url('/fonts/geomanist-medium-webfont.eot?#iefix') format('embedded-opentype'), 63 | url('/fonts/geomanist-medium-webfont.woff2') format('woff2'), 64 | url('/fonts/geomanist-medium-webfont.woff') format('woff'), 65 | url('/fonts/geomanist-medium-webfont.ttf') format('truetype'), 66 | url('/fonts/geomanist-medium-webfont.svg#geomanistitalic') format('svg'); 67 | font-weight: 700; 68 | font-style: normal; 69 | } 70 | /*BOLD*/ 71 | @font-face { 72 | font-family: Geomanist; 73 | src: url('/fonts/geomanist-bold-italic-webfont.eot'); 74 | src: url('/fonts/geomanist-bold-italic-webfont.eot?#iefix') format('embedded-opentype'), 75 | url('/fonts/geomanist-bold-italic-webfont.woff2') format('woff2'), 76 | url('/fonts/geomanist-bold-italic-webfont.woff') format('woff'), 77 | url('/fonts/geomanist-bold-italic-webfont.ttf') format('truetype'), 78 | url('/fonts/geomanist-bold-italic-webfont.svg#geomanistitalic') format('svg'); 79 | font-weight: 900; 80 | font-style: italic; 81 | } 82 | @font-face { 83 | font-family: Geomanist; 84 | src: url('/fonts/geomanist-bold-webfont.eot'); 85 | src: url('/fonts/geomanist-bold-webfont.eot?#iefix') format('embedded-opentype'), 86 | url('/fonts/geomanist-bold-webfont.woff2') format('woff2'), 87 | url('/fonts/geomanist-bold-webfont.woff') format('woff'), 88 | url('/fonts/geomanist-bold-webfont.ttf') format('truetype'), 89 | url('/fonts/geomanist-bold-webfont.svg#geomanistitalic') format('svg'); 90 | font-weight: 900; 91 | font-style: normal; 92 | } -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | font-smoothing: antialiased; 5 | } 6 | 7 | body { 8 | height: 100%; 9 | width: 100vw; 10 | margin: 0px; 11 | padding: 0px; 12 | background: white; 13 | font-family: 'Geomanist', sans-serif; 14 | font-weight: 400; 15 | margin: 0; 16 | padding: 0; 17 | letter-spacing: 1.1px; 18 | word-spacing: 1.2px; 19 | } 20 | 21 | .white_background { 22 | background: grey !important; 23 | } 24 | 25 | p { 26 | -moz-hyphens: auto !important; 27 | -o-hyphens: auto !important; 28 | -webkit-hyphens: auto !important; 29 | -ms-hyphens: auto !important; 30 | hyphens: auto !important; 31 | } 32 | 33 | b { 34 | font-weight: 900; 35 | } 36 | 37 | .logo_svg { 38 | width: 300px; 39 | } 40 | 41 | .logo_svg_small { 42 | width: 118px; 43 | height: 45px !important; 44 | } 45 | 46 | .logo_viz { 47 | position: absolute; 48 | top: 0; 49 | right: 25px; 50 | padding-top: 22px; 51 | } 52 | 53 | #greez_name { 54 | opacity: 0; 55 | transition: opacity 0.5s; 56 | -moz-transition: opacity 0.5s; 57 | -webkit-transition: opacity 0.5s; 58 | -o-transition: opacity 0.5s; 59 | } 60 | 61 | #value_5 { 62 | position: absolute; 63 | top: 210px; 64 | left: 100px; 65 | } 66 | 67 | #value_50 { 68 | position: absolute; 69 | top: 157px; 70 | left: 100px; 71 | } 72 | 73 | #value_1 { 74 | position: absolute; 75 | top: 354px; 76 | left: 100px; 77 | } 78 | 79 | #value_0 { 80 | position: absolute; 81 | left: 100px; 82 | top: 394px; 83 | } 84 | 85 | .text_top { 86 | padding-top: 6px; 87 | } 88 | 89 | .innertext_top { 90 | padding-top: 25px; 91 | padding-bottom: 20px; 92 | } 93 | 94 | .text_bottom { 95 | padding-bottom: 20px; 96 | } 97 | 98 | .circle_5 { 99 | position: relative; 100 | left: 14px; 101 | } 102 | 103 | .c_wrapper { 104 | display: block; 105 | padding-bottom: 20px; 106 | } 107 | 108 | #farbcode { 109 | padding-top: 104px; 110 | } 111 | 112 | .legend_inner circle { 113 | fill: white; 114 | } 115 | 116 | .legend_inner ul { 117 | padding-top: 7px; 118 | padding-left: 15px; 119 | list-style: none; 120 | width: 720px; 121 | columns: 2; 122 | -webkit-columns: 2; 123 | -moz-columns: 2; 124 | } 125 | 126 | .legend_inner li { 127 | -webkit-column-break-inside: avoid; 128 | page-break-inside: avoid; 129 | break-inside: avoid; 130 | } 131 | 132 | .legend_inner svg { 133 | position: relative; 134 | top: 5px; 135 | } 136 | 137 | .legend_inner b { 138 | letter-spacing: 1.3x; 139 | } 140 | 141 | .legend_li { 142 | font-weight: 900; 143 | letter-spacing: 1.5px; 144 | margin: 0; 145 | padding-left: 5px; 146 | padding-top: 15px; 147 | display: inline-block; 148 | } 149 | 150 | .legend_li_text { 151 | margin: 0; 152 | position: relative; 153 | max-width: 300px; 154 | padding-left: 33px; 155 | line-height: 26px; 156 | } 157 | 158 | #circle_0 { 159 | fill: white; 160 | -webkit-animation: myfirst 0.21s infinite; 161 | -webkit-animation-direction: alternate; 162 | animation: myfirst 0.21s infinite; 163 | animation-direction: alternate; 164 | animation-timing-function: ease-in-out; 165 | } 166 | 167 | #circle_1 { 168 | fill: white; 169 | -webkit-animation: myfirst 1.15s infinite; 170 | -webkit-animation-direction: alternate; 171 | animation: myfirst 1.15s infinite; 172 | animation-direction: alternate; 173 | animation-timing-function: ease-in-out; 174 | } 175 | 176 | @keyframes myfirst { 177 | 0% { 178 | transform: scale(0.7) 179 | } 180 | 100% { 181 | transform: scale(1) 182 | } 183 | } 184 | 185 | #about_content { 186 | cursor: default; 187 | line-height: 26px; 188 | display: none; 189 | z-index: 6000; 190 | position: relative; 191 | padding-bottom: 75px; 192 | padding-top: 23px; 193 | width: 100%; 194 | } 195 | 196 | #about_content a { 197 | color: white; 198 | cursor: pointer; 199 | } 200 | 201 | #about_content .h2_title{ 202 | padding-bottom: 2px; 203 | } 204 | 205 | #legend_content { 206 | line-height: 26px; 207 | display: none; 208 | z-index: 6000; 209 | position: relative; 210 | padding-bottom: 75px; 211 | padding-top: 23px; 212 | width: 100%; 213 | } 214 | 215 | .legend_inner { 216 | cursor: default; 217 | display: block; 218 | font-size: 16px; 219 | color: white; 220 | text-align: left; 221 | margin: 0 auto; 222 | font-weight: 400; 223 | margin-bottom: 50px; 224 | width: 720px; 225 | letter-spacing: 1.1px; 226 | word-spacing: 1.2px; 227 | padding-top: 25px; 228 | opacity: 0; 229 | position: relative; 230 | left: -30px; 231 | transition: opacity 0.3s, padding-top 0.5s; 232 | -moz-transition: opacity 0.3s, left 0.5s; 233 | -webkit-transition: opacity 0.3s, left 0.5s; 234 | -o-transition: opacity 0.3s, left 0.5s; 235 | } 236 | 237 | #legend_content .h2_title{ 238 | padding-bottom: 2px; 239 | } 240 | 241 | .h2_title { 242 | cursor: default; 243 | position: relative; 244 | color: white; 245 | font-weight: 400; 246 | width: 200px; 247 | font-size: 18px; 248 | text-align: right; 249 | padding-bottom: 5px; 250 | border-bottom: 1px solid white; 251 | } 252 | 253 | #main { 254 | display: flex; 255 | align-items: center; 256 | height: 100vh; 257 | line-height: 26px; 258 | font-size: 16px; 259 | width: 100vw; 260 | } 261 | 262 | #main > div { 263 | width: 100%; 264 | padding-bottom: 55px; 265 | } 266 | 267 | .main_title { 268 | color: grey; 269 | text-align: left; 270 | display: block; 271 | margin: 0 auto; 272 | font-weight: 400; 273 | text-align: center; 274 | margin-bottom: 30px; 275 | width: 720px; 276 | letter-spacing: 1.1px; 277 | word-spacing: 1.2px; 278 | } 279 | 280 | .main_text { 281 | color: grey; 282 | text-align: left; 283 | display: block; 284 | margin: 0 auto; 285 | font-weight: 400; 286 | margin-bottom: 15px; 287 | width: 720px; 288 | letter-spacing: 1.1px; 289 | word-spacing: 1.2px; 290 | } 291 | 292 | .white_text { 293 | color: white !important; 294 | } 295 | 296 | #login_button { 297 | margin: 0 auto; 298 | margin-top: 55px; 299 | border: 1px solid white; 300 | color: white; 301 | transition-timing-function: ease-in-out; 302 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 303 | transition: color 0.2s, background-color 0.2s; 304 | -moz-transition: color 0.2s, background-color 0.2s; 305 | -webkit-transition: color 0.2s, background-color 0.2s; 306 | -o-transition: color 0.2s, background-color 0.2s; 307 | } 308 | 309 | #login_button:hover { 310 | background-color: white; 311 | color: grey; 312 | } 313 | 314 | button { 315 | letter-spacing: 0.8px; 316 | word-spacing: 1.2px; 317 | height: 35px; 318 | width: 214px; 319 | background-color: transparent; 320 | border: 1px solid grey; 321 | display: block; 322 | cursor: default; 323 | font-family: 'Geomanist', sans-serif; 324 | font-weight: 400; 325 | color: grey; 326 | font-size: 16px; 327 | text-decoration: none; 328 | transition-timing-function: ease-in-out; 329 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 330 | transition: color 0.2s, background-color 0.2s; 331 | -moz-transition: color 0.2s, background-color 0.2s; 332 | -webkit-transition: color 0.2s, background-color 0.2s; 333 | -o-transition: color 0.2s, background-color 0.2s; 334 | } 335 | 336 | button:hover { 337 | background-color: grey; 338 | color: white; 339 | } 340 | 341 | button:focus { 342 | outline: 0; 343 | } 344 | 345 | 346 | /* Menuicon*/ 347 | 348 | #nav-icon1 { 349 | width: 60px; 350 | height: 45px; 351 | position: absolute; 352 | z-index: 8000; 353 | margin-left: 22px; 354 | margin-top: 22px; 355 | top: 0px; 356 | -webkit-transform: rotate(0deg); 357 | -moz-transform: rotate(0deg); 358 | -o-transform: rotate(0deg); 359 | transform: rotate(0deg); 360 | -webkit-transition: .5s ease-in-out; 361 | -moz-transition: .5s ease-in-out; 362 | -o-transition: .5s ease-in-out; 363 | transition: .5s ease-in-out; 364 | cursor: default; 365 | } 366 | 367 | .white_span span { 368 | background: white !important; 369 | } 370 | 371 | #span_center { 372 | width: 100% !important; 373 | left: -30px !important; 374 | } 375 | 376 | #nav-icon1 span { 377 | display: block; 378 | position: absolute; 379 | height: 1px; 380 | width: 50%; 381 | background: grey; 382 | opacity: 1; 383 | left: 0; 384 | -webkit-transform: rotate(0deg); 385 | -moz-transform: rotate(0deg); 386 | -o-transform: rotate(0deg); 387 | transform: rotate(0deg); 388 | -webkit-transition: .25s ease-in-out; 389 | -moz-transition: .25s ease-in-out; 390 | -o-transition: .25s ease-in-out; 391 | transition: .25s ease-in-out; 392 | } 393 | 394 | #nav-icon1 span:nth-child(1) { 395 | top: 0px; 396 | } 397 | 398 | #nav-icon1 span:nth-child(2) { 399 | top: 9px; 400 | } 401 | 402 | #nav-icon1 span:nth-child(3) { 403 | top: 18px; 404 | } 405 | 406 | #nav-icon1.open span:nth-child(1) { 407 | top: 9px; 408 | -webkit-transform: rotate(45deg); 409 | -moz-transform: rotate(45deg); 410 | -o-transform: rotate(45deg); 411 | transform: rotate(45deg); 412 | } 413 | 414 | #nav-icon1.open span:nth-child(2) { 415 | opacity: 0; 416 | left: -90px !important; 417 | } 418 | 419 | #nav-icon1.open span:nth-child(3) { 420 | top: 9px; 421 | -webkit-transform: rotate(-45deg); 422 | -moz-transform: rotate(-45deg); 423 | -o-transform: rotate(-45deg); 424 | transform: rotate(-45deg); 425 | } 426 | 427 | #nav-icon1:hover #span_center { 428 | left: -25px !important; 429 | } 430 | 431 | #nav_imprint{ 432 | display: block; 433 | margin-top: 100px; 434 | font-size: 10px; 435 | text-decoration: none; 436 | color: white; 437 | cursor: pointer; 438 | } 439 | 440 | #nav_start svg{ 441 | margin: 50px; 442 | } 443 | 444 | #nav_imprint:hover{ 445 | font-weight: 700; 446 | } 447 | 448 | /* Categorie and value - display*/ 449 | 450 | #display_cv { 451 | position: absolute; 452 | left: 250px; 453 | font-weight: 900; 454 | top: 8px; 455 | font-size: 9px; 456 | font-family: 'Geomanist', sans-serif; 457 | letter-spacing: 1.5px; 458 | } 459 | 460 | #display_cv h1 { 461 | font-weight: 900; 462 | } 463 | 464 | #display_cv h2 { 465 | font-weight: 900; 466 | } 467 | 468 | .cv_child { 469 | display: inline-block; 470 | } 471 | 472 | .cv_child h1 { 473 | margin-bottom: -2px; 474 | } 475 | 476 | #hover_cv { 477 | position: relative; 478 | margin-bottom: -2px; 479 | left: -4px; 480 | } 481 | 482 | 483 | /* LoadingContainer */ 484 | 485 | #loadingContainer { 486 | height: 100vh; 487 | width: 100vw; 488 | background: grey; 489 | position: absolute; 490 | z-index: 10000; 491 | color: white; 492 | opacity: 1; 493 | display: flex; 494 | align-items: center; 495 | } 496 | 497 | #loadingContainer b { 498 | font-weight: 700; 499 | font-style: italic; 500 | } 501 | 502 | #loadingContainer p { 503 | width: 100vw; 504 | font-size: 16px; 505 | text-align: center; 506 | letter-spacing: 1.1px; 507 | word-spacing: 1.2px; 508 | } 509 | 510 | #loadingBar { 511 | position: absolute; 512 | top: 98.5%; 513 | left: 0px; 514 | height: 1.5%; 515 | background: white; 516 | width: 0vw; 517 | transition-timing-function: ease-in-out; 518 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 519 | transition: width 0.4s; 520 | -moz-transition: width 0.4s; 521 | -webkit-transition: width 0.4s; 522 | -o-transition: width 0.4s; 523 | } 524 | 525 | 526 | /* Player and Playlist */ 527 | 528 | audio { 529 | display: none; 530 | } 531 | 532 | #infoContainer { 533 | display: flex; 534 | align-items: center; 535 | position: absolute; 536 | z-index: 300; 537 | top: 0%; 538 | width: 100%; 539 | height: auto; 540 | min-height: 100%; 541 | background: grey; 542 | transition-timing-function: ease-in-out; 543 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 544 | transition: top 0.2s, height 0.2s, min-height 0.2s; 545 | -moz-transition: top 0.2s, height 0.2s, min-height 0.2s; 546 | -webkit-transition: top 0.2s, height 0.2s, min-height 0.2s; 547 | -o-transition: top 0.2s, height 0.2s, min-height 0.2s; 548 | } 549 | 550 | .smallIC { 551 | top: 98.5% !important; 552 | display: flex !important; 553 | height: 10% !important; 554 | min-height: 10% !important; 555 | align-items: center !important; 556 | } 557 | 558 | .bigIC { 559 | top: 90% !important; 560 | display: flex !important; 561 | height: 10% !important; 562 | min-height: 10% !important; 563 | align-items: center !important; 564 | } 565 | 566 | .bigIC .info { 567 | display: none; 568 | } 569 | 570 | .smallIC .info { 571 | display: none; 572 | } 573 | 574 | .info { 575 | opacity: 0; 576 | width: 100vw; 577 | font-size: 16px; 578 | padding-top: 135px; 579 | transition-timing-function: ease-in-out; 580 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 581 | transition: opacity 0.3s, padding-top 0.5s; 582 | -moz-transition: opacity 0.3s, padding-top 0.5s; 583 | -webkit-transition: opacity 0.3s, padding-top 0.5s; 584 | -o-transition: opacity 0.3s, padding-top 0.5s; 585 | } 586 | 587 | #info_legende a { 588 | cursor: default; 589 | } 590 | 591 | #info_legende { 592 | position: absolute; 593 | top: -40px; 594 | right: 22px; 595 | color: grey; 596 | cursor: default; 597 | } 598 | 599 | #info_legende:hover { 600 | font-weight: 700; 601 | } 602 | 603 | .info #categorie { 604 | font-size: 30px; 605 | font-weight: 900; 606 | padding-top: 70px; 607 | width: 482px; 608 | margin: 0 auto; 609 | padding-bottom: 13px; 610 | color: white; 611 | letter-spacing: 2.8px; 612 | } 613 | 614 | .info #value { 615 | font-size: 23px; 616 | font-weight: 900; 617 | padding-bottom: 50px; 618 | color: white; 619 | margin: 0 auto; 620 | width: 482px; 621 | letter-spacing: 2.8px; 622 | } 623 | 624 | .info_h2 { 625 | position: relative; 626 | font-size: 18px; 627 | width: 200px; 628 | font-weight: 400; 629 | color: white; 630 | text-align: right; 631 | padding-bottom: 4px; 632 | border-bottom: 1px solid white; 633 | } 634 | 635 | #playlist { 636 | -moz-column-count: 1; 637 | -moz-column-gap: 10px; 638 | -webkit-column-count: 1; 639 | -webkit-column-gap: 10px; 640 | width: 720px; 641 | margin: 0 auto; 642 | margin-top: 45px; 643 | column-count: 1; 644 | column-gap: 10px; 645 | list-style: none; 646 | padding-bottom: 50px; 647 | } 648 | 649 | #playlist li { 650 | font-weight: 400; 651 | border-bottom: 1px solid white; 652 | line-height: 26px; 653 | display: block; 654 | } 655 | 656 | #playlist li a:hover p { 657 | font-weight: 700; 658 | font-size: 16.5px; 659 | margin-bottom: 15.5px !important; 660 | } 661 | 662 | #playlist li a { 663 | color: white; 664 | cursor: default; 665 | letter-spacing: 1.1px; 666 | word-spacing: 1.2px; 667 | text-decoration: none; 668 | } 669 | 670 | #playlist li a p { 671 | display: inline-block; 672 | } 673 | 674 | #playlist li a .name { 675 | width: 577px; 676 | } 677 | 678 | #playlist li a .id { 679 | width: 60px; 680 | position: relative; 681 | left: -19px; 682 | display: inline-block; 683 | } 684 | 685 | #playlist li a .icon1 { 686 | display: inline-block; 687 | position: relative; 688 | left: 15px; 689 | top: 5px; 690 | opacity: 0; 691 | } 692 | 693 | #playlist li a .icon2 { 694 | display: inline-block; 695 | position: relative; 696 | left: -5px; 697 | top: 5px; 698 | opacity: 0; 699 | } 700 | 701 | #playlist .current-song a { 702 | font-weight: 700; 703 | } 704 | 705 | #playlist .current-song p { 706 | font-size: 16.5px; 707 | margin-bottom: 15.5px !important; 708 | } 709 | 710 | #closeInfoContainer { 711 | position: absolute; 712 | right: 0px; 713 | top: 0px; 714 | color: white; 715 | border: 1px solid white; 716 | margin: 20px; 717 | width: 59px; 718 | font-size: 12px; 719 | height: 24px; 720 | display: none; 721 | transition: color 0.2s; 722 | -moz-transition: color 0.2s; 723 | -webkit-transition: color 0.2s; 724 | -o-transition: color 0.2s; 725 | } 726 | 727 | #openInfoContainer { 728 | color: white; 729 | border: 1px solid white; 730 | margin: 0 auto; 731 | margin-top: 0px; 732 | transition: color 0.2s; 733 | -moz-transition: color 0.2s; 734 | -webkit-transition: color 0.2s; 735 | -o-transition: color 0.2s; 736 | display: none; 737 | } 738 | 739 | .player_icon { 740 | width: 20px; 741 | height: 20px; 742 | } 743 | 744 | .shape-icon polygon, rect { 745 | fill: white; 746 | stroke: white; 747 | } 748 | 749 | .shape-icon circle { 750 | stroke: white; 751 | } 752 | 753 | .hrefNull { 754 | opacity: 0.6; 755 | } 756 | 757 | 758 | /* Genrelist */ 759 | 760 | #genrelist { 761 | width: 720px; 762 | margin: 0 auto; 763 | margin-top: 60px; 764 | list-style: none; 765 | letter-spacing: 1.1px; 766 | word-spacing: 1.2px; 767 | color: white; 768 | margin-bottom: 125px; 769 | line-height: 26px; 770 | text-align: left; 771 | padding-left: 25px; 772 | } 773 | 774 | #genrelist li a { 775 | color: black; 776 | text-decoration: none; 777 | } 778 | 779 | 780 | /* Timelist */ 781 | 782 | #timeWrapper { 783 | height: 100vh; 784 | position: absolute; 785 | top: 0; 786 | display: flex; 787 | align-items: center; 788 | } 789 | 790 | #timeContainer { 791 | display: table-cell; 792 | vertical-align: middle; 793 | } 794 | 795 | #timeScrollbar { 796 | width: 100px; 797 | height: 390px; 798 | overflow: hidden; 799 | } 800 | 801 | #timelist { 802 | position: relative; 803 | top: 0; 804 | width: 120px; 805 | height: 390px; 806 | overflow: scroll; 807 | } 808 | 809 | #timelist li { 810 | position: relative; 811 | left: -46px; 812 | line-height: 30px; 813 | width: 100px; 814 | display: block; 815 | transition-timing-function: ease-in-out; 816 | transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); 817 | opacity: 1; 818 | transition: left 0.2s; 819 | -moz-transition: left 0.2s; 820 | -webkit-transition: left 0.2s; 821 | -o-transition: left 0.2s; 822 | } 823 | 824 | #timelist li:hover { 825 | left: 2px; 826 | } 827 | 828 | #timelist li a { 829 | font-size: 16px; 830 | font-weight: 400; 831 | color: grey; 832 | padding-left: 4px; 833 | text-decoration: none; 834 | cursor: default; 835 | } 836 | 837 | #timelist .current-time a { 838 | color: grey; 839 | font-weight: bold !important; 840 | } 841 | 842 | #timelist .current-time { 843 | position: relative; 844 | top: 1px !important; 845 | left: 2px !important; 846 | } 847 | 848 | #arrow_up { 849 | display: none; 850 | position: relative; 851 | transform: rotate(180deg); 852 | top: -200px; 853 | left: -89px; 854 | color: grey; 855 | cursor: default; 856 | opacity: 1; 857 | transition: opacity 0.3s; 858 | -moz-transition: opacity 0.3s; 859 | -webkit-transition: opacity 0.3s; 860 | -o-transition: opacity 0.3s; 861 | } 862 | 863 | #arrow_down { 864 | display: none; 865 | position: relative; 866 | top: 200px; 867 | left: -100px; 868 | color: grey; 869 | cursor: default; 870 | opacity: 1; 871 | transition: opacity 0.3s; 872 | -moz-transition: opacity 0.3s; 873 | -webkit-transition: opacity 0.3s; 874 | -o-transition: opacity 0.3s; 875 | } 876 | 877 | 878 | /* Scalelist */ 879 | 880 | #scaleContainer { 881 | position: absolute; 882 | top: 0; 883 | right: 22px; 884 | height: 100vh; 885 | display: table; 886 | } 887 | 888 | #scaleList { 889 | width: 27px; 890 | display: table-cell; 891 | vertical-align: middle; 892 | text-align: right; 893 | } 894 | 895 | #scaleList p { 896 | height: 21px; 897 | } 898 | 899 | .scaleButton { 900 | font-weight: 400; 901 | font-size: 16px; 902 | cursor: default; 903 | color: grey; 904 | } 905 | 906 | .sortButton { 907 | position: relative; 908 | top: 25px; 909 | } 910 | 911 | .scaleButton:hover { 912 | font-weight: 700; 913 | } 914 | 915 | .current_scale { 916 | font-weight: bold !important; 917 | } 918 | 919 | .current_sort { 920 | font-weight: bold !important; 921 | } 922 | 923 | 924 | /* Navigation */ 925 | 926 | .sidenav { 927 | height: 100%; 928 | width: 0; 929 | position: fixed; 930 | z-index: 5000; 931 | top: 0; 932 | left: 0; 933 | background-color: rgb(160, 160, 160); 934 | overflow-x: hidden; 935 | padding-top: 120px; 936 | transition: 0.2s; 937 | } 938 | 939 | .sidenav a { 940 | height: 22px; 941 | position: relative; 942 | left: -20px; 943 | opacity: 0; 944 | border-bottom: 1px solid white; 945 | width: 102px; 946 | text-align: right; 947 | padding: 23px 0px 5px 20px; 948 | text-decoration: none; 949 | font-size: 18px; 950 | font-weight: 400; 951 | color: white; 952 | display: block; 953 | transition: left 0.4s, opacity 0.4s; 954 | cursor: default; 955 | } 956 | 957 | .sidenav a:hover, .offcanvas a:focus { 958 | font-weight: 700; 959 | } 960 | 961 | .modal_info { 962 | cursor: default; 963 | position: absolute; 964 | height: 100vh; 965 | width: 100vw; 966 | display: none; 967 | display: flex; 968 | align-items: center; 969 | z-index: 9000; 970 | } 971 | 972 | .modal_content { 973 | background: white; 974 | padding: 16px; 975 | padding-top: 0px; 976 | width: 600px; 977 | margin: 0 auto; 978 | opacity: 0.8; 979 | color: grey; 980 | } 981 | 982 | .modal_content p { 983 | text-align: center; 984 | } 985 | 986 | .modal_content button { 987 | margin: 0 auto; 988 | margin-top: 50px; 989 | } 990 | 991 | #font_fix_a { 992 | font-weight: 700; 993 | position: absolute; 994 | top: 0px; 995 | right: 0px; 996 | opacity: 0; 997 | display: inline; 998 | font-style: normal; 999 | font-family: 'Geomanist', sans-serif; 1000 | } 1001 | 1002 | #font_fix_b { 1003 | font-weight: 900; 1004 | position: absolute; 1005 | top: 0px; 1006 | opacity: 0; 1007 | right: 0px; 1008 | display: inline; 1009 | font-style: normal; 1010 | font-family: 'Geomanist', sans-serif; 1011 | } 1012 | 1013 | #nav_imprint{ 1014 | display: block; 1015 | margin-top: 100px; 1016 | font-size: 10px; 1017 | color: white; 1018 | cursor: pointer; 1019 | } 1020 | 1021 | #nav_start svg{ 1022 | margin: 50px; 1023 | } 1024 | /* Large screens ----------- */ 1025 | 1026 | @media only screen and (min-width: 1824px) { 1027 | /* Styles */ 1028 | #display_cv { 1029 | top: 8px; 1030 | font-size: 12px; 1031 | } 1032 | } -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-italic-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-italic-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-italic-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-italic-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-bold-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-light-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-italic-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-light-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-italic-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-light-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-italic-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-light-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-italic-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-light-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-italic-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-italic-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-italic-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-italic-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-medium-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-medium-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-italic-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-italic-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-italic-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-italic-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-webfont.eot -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-webfont.woff -------------------------------------------------------------------------------- /public/fonts/geomanist-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist-regular-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/geomanist_webfont_license.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwamic/klangspektrum/479f92c87251d7e577cb34fcfc4accda365489cf/public/fonts/geomanist_webfont_license.pdf -------------------------------------------------------------------------------- /public/img/player_ichons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/landingpage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Klangspektrum - by Michael Schwarz 3 | Hochschule Augsburg 2016/2017 4 | */ 5 | 6 | 'use-strict'; 7 | var domain; 8 | window.onload = function () { 9 | $('#nav-icon1').css('position', 'fixed'); 10 | domain = document.location.host; 11 | }; 12 | 13 | document.getElementById('login_button').addEventListener('click', function () { 14 | var user = createUser(); 15 | localStorage.setItem('user_id', user); 16 | window.location.href = `http://${domain}/api/login/${user}`; 17 | }); 18 | 19 | function createUser() { 20 | return Math.random().toString(36).substr(2, 20); 21 | }; 22 | 23 | /*##########################################################################################################################################*/ 24 | // Menu 25 | $('#nav-icon1').click(function () { 26 | if ($(this).hasClass('open')) { 27 | $(this).removeClass('open'); 28 | closeNav(); 29 | } else { 30 | $(this).addClass('open'); 31 | openNav(); 32 | } 33 | 34 | }); 35 | 36 | /*##########################################################################################################################################*/ 37 | // Navigation 38 | function openNav() { 39 | $('#mySidenav').css('width', '200px'); 40 | 41 | setTimeout(function () { 42 | $('#mySidenav').find('a').css('left', '0px'); 43 | $('#mySidenav').find('a').css('opacity', '1'); 44 | }, 100); 45 | }; 46 | 47 | function closeNav() { 48 | $('#mySidenav').css('width', '0px'); 49 | $('#mySidenav').find('a').css('left', '-20px'); 50 | $('#mySidenav').find('a').css('opacity', '0'); 51 | $('#nav-icon1').css('position', 'fixed'); 52 | // Legend 53 | $('#mySidenav a').css('display', 'block'); 54 | $('#legend_content').css('display', 'none'); 55 | $('#about_content').css('display', 'none'); 56 | $('#main').css('display', 'flex'); 57 | $('.legend_inner').css('opacity', 0); 58 | $('.legend_inner').css('left', -30); 59 | }; 60 | 61 | // Legend 62 | $('#nav_legend').click(function (e) { 63 | e.preventDefault(); 64 | $('#mySidenav').css('width', '100vw'); 65 | $('#mySidenav a').css('display', 'none'); 66 | $('#legend_content').css('display', 'block'); 67 | $(window).scrollTop(0); 68 | $('#nav-icon1').css('position', 'absolute'); 69 | setTimeout(function () { 70 | $('.legend_inner').css('opacity', 1); 71 | $('.legend_inner').css('left', 0); 72 | $('#main').css('display', 'none'); 73 | }, 200); 74 | }); 75 | 76 | // About 77 | $('#nav_about').click(function (e) { 78 | e.preventDefault(); 79 | $('#mySidenav').css('width', '100vw'); 80 | $('#mySidenav a').css('display', 'none'); 81 | $('#main').css('display', 'none'); 82 | $('#about_content').css('display', 'block'); 83 | $('body').css('overflow', 'scroll'); 84 | $(window).scrollTop(0); 85 | setTimeout(function () { 86 | $('.legend_inner').css('opacity', 1); 87 | $('.legend_inner').css('left', 0); 88 | $('#canvasContainer').css('display', 'none'); 89 | $('#infoContainer').css('display', 'none'); 90 | }, 200); 91 | }); 92 | 93 | /*##########################################################################################################################################*/ 94 | //Main-arrow 95 | $(window).scroll(function () { 96 | if ($(this).scrollTop() > 30) { 97 | $('#arrow_main').css('opacity', '0'); 98 | } else { 99 | $('#arrow_main').css('opacity', '1'); 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const path = require('path'); 4 | 5 | var login = require('../controllers/login'); 6 | var auth = require('../controllers/auth'); 7 | var load = require('../controllers/load'); 8 | 9 | router.get('/login/:user', login.get); 10 | router.get('/callback', auth.get); 11 | router.get('/loadMe/:access_token/:user', load.getMe); 12 | router.get('/loadSavedTracks/:access_token/:user/:offset/:count/:first_time', load.getSavedTracks); 13 | router.get('/loadPlaylists/:access_token/:user/:offset/:count/:first_time/:user_id', load.getPlaylists); 14 | router.get('/loadPlaylistTracks/:access_token/:user/:user_id/:pindex', load.getPlaylistTracks); 15 | router.get('/loadAllTracks/:user', load.createAllTracks); 16 | router.get('/loadFeatures/:access_token/:user/:offset', load.getFeatures); 17 | router.get('/loadArtists/:access_token/:user/:offset', load.getArtists); 18 | 19 | router.get('/sortData', (request, response) => { 20 | // sort data and send to client -> callfunction with client_id!! 21 | var data = { 'data': 200 }; 22 | setTimeout(function () { 23 | response.json(data); 24 | }, 10000); 25 | }); 26 | 27 | router.get('/startApp', (request, response) => { 28 | // call client function 29 | setTimeout(function () { 30 | response.json(data); 31 | }, 10000); 32 | }); 33 | 34 | router.get('/logout', (request, response) => { 35 | // Auth. delete and redirect 36 | response.redirect('/'); 37 | }); 38 | 39 | router.get('/download', (request, response) => { 40 | var file = __dirname + '/../download_folder/bachelorthesis-klangspektrum.pdf'; 41 | response.download(file); // Set disposition and send it. 42 | }); 43 | 44 | module.exports = router; 45 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const path = require('path'); 4 | 5 | router.get('/', (request, response) => { 6 | response.sendFile(path.join(__dirname + '/../views/index.html')); 7 | }); 8 | 9 | router.get('/imprint', (request, response) => { 10 | response.sendFile(path.join(__dirname + '/../views/imprint.html')); 11 | }); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /routes/klangviz.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const path = require('path'); 4 | 5 | router.get('/', (request, response) => { 6 | // show loading + request data 7 | response.sendFile(path.join(__dirname + '/../views/klangviz.html')); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | 3 | // Imports 4 | var express = require('express'); 5 | var cookieParser = require('cookie-parser'); 6 | var cors = require('cors'); 7 | var webpack = require('webpack'); 8 | var config = require('./webpack.config'); 9 | var mongoose = require('mongoose'); 10 | 11 | // Init 12 | var compiler = webpack(config); 13 | var app = express(); 14 | var server = require('http').createServer(app); 15 | var port = process.env.PORT || 4000; 16 | 17 | // Middleware 18 | app.use(require('webpack-dev-middleware')(compiler, { 19 | noInfo: true, 20 | publicPath: config.output.publicPath 21 | })); 22 | app.use(cookieParser()); 23 | app.use(cors()); 24 | 25 | // Static folder 26 | app.use(express.static('./public')); 27 | 28 | // Routes 29 | var index = require('./routes/index'); 30 | var klangviz = require('./routes/klangviz'); 31 | var api = require('./routes/api'); 32 | app.use('/', index); 33 | app.use('/klangviz', klangviz); 34 | app.use('/api', api); 35 | 36 | // Error-Handling 37 | app.use(function (req, res) { 38 | res.type('text/plain'); 39 | res.status(404); 40 | res.send('404 - Not Found'); 41 | }); 42 | app.use(function (req, res, next) { 43 | console.error(err.stack); 44 | res.type('text/plain'); 45 | res.status(500); 46 | res.send('500 - Internal Error'); 47 | }); 48 | 49 | // Init database connection 50 | mongoose.Promise = global.Promise; 51 | 52 | // Build the connection string 53 | var dbURI = '...'; 54 | var user = '...'; 55 | var pwd = '...'; 56 | 57 | // Create the database connection 58 | mongoose.connect(`mongodb://${user}:${pwd}@${dbURI}`); 59 | 60 | // When successfully connected 61 | mongoose.connection.on('connected', function () { 62 | console.log('Mongoose default connection open to ' + dbURI); 63 | }); 64 | 65 | // If the connection throws an error 66 | mongoose.connection.on('error', function (err) { 67 | console.log('Mongoose default connection error: ' + err); 68 | }); 69 | 70 | // When the connection is disconnected 71 | mongoose.connection.on('disconnected', function () { 72 | console.log('Mongoose default connection disconnected'); 73 | }); 74 | 75 | // If the Node process ends, close the Mongoose connection 76 | process.on('SIGINT', function () { 77 | mongoose.connection.close(function () { 78 | console.log('Mongoose default connection disconnected through app termination'); 79 | process.exit(0); 80 | }); 81 | }); 82 | 83 | process.on('uncaughtException', (err) => { 84 | console.log('uncaughtException: ' + err); 85 | process.exit(1); 86 | }); 87 | 88 | // Server 89 | server.listen(port, (err) => { 90 | if (err) { 91 | console.log(err); 92 | return; 93 | }; 94 | console.log(`Server listening at port: ${port}`); 95 | }); 96 | -------------------------------------------------------------------------------- /src/Api/api.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | import 'whatwg-fetch'; // Polyfill 3 | var sortData = require('./sort').sortData; 4 | 5 | function api(a, r, u, d) { 6 | var access_token = a; 7 | var refresh_token = r; 8 | var user = u; 9 | var domain = d; 10 | var p = document.getElementById('loaderfeed'); 11 | var gn = document.getElementById('greez_name'); 12 | var user_id; 13 | var user_abo; 14 | var user_image; 15 | var first_time = 'true'; 16 | var offset = 0; 17 | var count = 0; 18 | var atracks = []; 19 | var plists = []; 20 | var pindex = 0; 21 | var pindex_list = []; 22 | var features_list = []; 23 | var artists_list = []; 24 | 25 | this.loadMe = function () { 26 | // console.log('Request to load me'); 27 | p.innerHTML = `load me`; 28 | // FETCH - api/loadMe/:access_token/:user' 29 | let result = fetch(`http://${domain}/api/loadMe/${access_token}/${user}`); 30 | result.then(function (res) { 31 | return res.json(); 32 | }).then(function (json) { 33 | user_id = json.user_id; 34 | user_image = json.user_image; 35 | gn.innerHTML = `Hello ${user_id}, this could take a little while.`; 36 | setTimeout(function () { 37 | gn.style.opacity = 1; 38 | $('#loadingBar').css('width', '15vw'); 39 | loadSavedTracks(); 40 | }, 200); 41 | // console.log(json); 42 | }); 43 | }; 44 | 45 | var loadSavedTracks = function () { 46 | // console.log('Request to load saved-tracks'); 47 | p.innerHTML = `load saved-tracks`; 48 | // FETCH - api/loadSavedTracks/:access_token/:user/:offset/:count/:first_time/ 49 | let result = fetch(`http://${domain}/api/loadSavedTracks/${access_token}/${user}/${offset}/${count}/${first_time}`); 50 | result.then(function (res) { 51 | return res.json(); 52 | }).then(function (json) { 53 | first_time = 'false'; 54 | atracks = atracks.concat(json.mydata); 55 | if (json.count == json.offset) { 56 | offset = 0; 57 | count = 0; 58 | first_time = 'true'; 59 | // console.log(`Finished loading stracks - ${atracks.length} songs`); 60 | $('#loadingBar').css('width', '30vw'); 61 | loadPlaylists(); 62 | } else { 63 | offset = json.offset; 64 | count = json.count; 65 | // console.log('offset:' + offset + ', count:' + count); 66 | loadSavedTracks(); 67 | } 68 | }); 69 | }; 70 | 71 | var loadPlaylists = function () { 72 | // console.log('Request to load playlists '); 73 | p.innerHTML = `load playlists`; 74 | // FETCH - api/loadSavedTracks/:access_token/:user/:offset/:count/:first_time/:user_id 75 | let result = fetch(`http://${domain}/api/loadPlaylists/${access_token}/${user}/${offset}/${count}/${first_time}/${user_id}`); 76 | result.then(function (res) { 77 | return res.json(); 78 | }).then(function (json) { 79 | first_time = 'false'; 80 | plists = plists.concat(json.mydata); 81 | if (json.count == json.offset) { 82 | offset = 0; 83 | count = 0; 84 | first_time = 'true'; 85 | // console.log(`Finished loading plists - ${plists.length} playlists`); 86 | // Check playlists - added_by 87 | for (let i = 0; i < plists.length; i++) { 88 | if (plists[i].id == user_id) { 89 | pindex_list.push(i); 90 | } 91 | } 92 | // console.log(pindex_list); 93 | $('#loadingBar').css('width', '45vw'); 94 | loadPlaylistTracks(); 95 | } else { 96 | offset = json.offset; 97 | count = json.count; 98 | // console.log('offset:' + offset + ', count:' + count) 99 | loadPlaylists(); 100 | } 101 | }); 102 | }; 103 | 104 | var loadPlaylistTracks = function () { 105 | // console.log('Request to load playlist tracks '); 106 | p.innerHTML = `load playlist-tracks`; 107 | // FETCH - api/loadPlaylistTracks/:access_token/:user/:user_id/:pindex' 108 | let result = fetch(`http://${domain}/api/loadPlaylistTracks/${access_token}/${user}/${user_id}/${pindex_list[pindex]}`); 109 | result.then(function (res) { 110 | return res.json(); 111 | }).then(function (json) { 112 | pindex++; 113 | // check result 114 | let state = (json.mydata != undefined) ? ((json.mydata.length > 0) ? true : false) : false; 115 | if (state) { 116 | atracks = atracks.concat(json.mydata); 117 | } 118 | if (pindex_list[pindex] == undefined) { 119 | // console.log(`Finished loading ptracks - ${atracks.length} songs`); 120 | $('#loadingBar').css('width', '60vw'); 121 | createAllTracks(); 122 | } else { 123 | // console.log('pindex:' + pindex + ', pindex_list-length:' + pindex_list.length); 124 | loadPlaylistTracks(); 125 | } 126 | }); 127 | }; 128 | 129 | var createAllTracks = function () { 130 | // console.log('Request to create all tracks'); 131 | let result = fetch(`http://${domain}/api/loadAllTracks/${user}`); 132 | result.then(function () { 133 | loadFeatures(); 134 | // console.log('Finished joining'); 135 | }); 136 | }; 137 | 138 | var loadFeatures = function () { 139 | // console.log('Request to load all features'); 140 | p.innerHTML = `load features`; 141 | let result = fetch(`http://${domain}/api/loadFeatures/${access_token}/${user}/${offset}`); 142 | result.then(function (res) { 143 | return res.json(); 144 | }).then(function (json) { 145 | features_list = features_list.concat(json.mydata); 146 | // console.log(json.offset); 147 | // console.log(json.error); 148 | if (json.count == json.offset) { 149 | // console.log('finished features - ' + features_list.length); 150 | offset = 0; 151 | count = 0; 152 | p.innerHTML = `load artists`; 153 | $('#loadingBar').css('width', '75vw'); 154 | loadArtists(); 155 | } else { 156 | offset = json.offset; 157 | count = json.count; 158 | // console.log('offset:' + offset + ', count:' + count) 159 | loadFeatures(); 160 | } 161 | }); 162 | }; 163 | 164 | var loadArtists = function () { 165 | // console.log('Request to load all artists'); 166 | let result = fetch(`http://${domain}/api/loadArtists/${access_token}/${user}/${offset}`); 167 | result.then(function (res) { 168 | return res.json(); 169 | }).then(function (json) { 170 | artists_list = artists_list.concat(json.mydata); 171 | // console.log(json.error); 172 | if (json.count == json.offset) { 173 | // console.log('finished artists - ' + artists_list.length); 174 | offset = 0; 175 | count = 0; 176 | p.innerHTML = `sort data`; 177 | $('#loadingBar').css('width', '90vw'); 178 | setTimeout(function () { 179 | sortData(atracks, features_list, artists_list); 180 | }, 500); 181 | } else { 182 | offset = json.offset; 183 | count = json.count; 184 | // console.log('offset:' + offset + ', count:' + count) 185 | loadArtists(); 186 | } 187 | }); 188 | }; 189 | 190 | document.getElementById('logout').addEventListener('click', function (event) { 191 | // console.log('logout'); 192 | event.preventDefault(); 193 | access_token = null; 194 | refresh_token = null; 195 | user = null; 196 | localStorage.clear(); 197 | window.location.href = `http://${domain}/api/logout`; 198 | }); 199 | }; 200 | 201 | export { api as default }; 202 | -------------------------------------------------------------------------------- /src/Api/sort.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | var startViz = require('./start').startViz; 3 | 4 | function sortData(atracks, features_list, artists_list) { 5 | var i; 6 | var songDateFeatureValueMap = {}; 7 | var sortedSongs = {}; 8 | 9 | for (var i = 0; i < atracks.length; i++) { 10 | // Check === undefined -> undefined and == undefined -> null 11 | if (!(atracks[i] == undefined || features_list[i] == undefined || artists_list[i] == undefined)) { 12 | var l_song = { 13 | added_at: atracks[i].added_at, 14 | artist: artists_list[i].name, 15 | title: atracks[i].track.name, 16 | preview_url: atracks[i].track.preview_url, 17 | genre: (artists_list[i].genres == undefined) ? [] : artists_list[i].genres, 18 | features: { 19 | 'acousticness': features_list[i].acousticness, 20 | 'danceability': features_list[i].danceability, 21 | 'energy': features_list[i].energy, 22 | 'instrumentalness': features_list[i].instrumentalness, 23 | 'liveness': features_list[i].liveness, 24 | 'speechiness': features_list[i].speechiness, 25 | 'mood': features_list[i].valence, 26 | } 27 | }; 28 | var l_song_id = l_song.id || i, 29 | l_features = l_song.features, 30 | l_date = new Date(l_song.added_at), 31 | l_month = l_date.getFullYear() + '-' + (((l_date.getMonth() + 1) < 10) ? ('0' + (l_date.getMonth() + 1)) : (l_date.getMonth() + 1)), 32 | l_entry = sortedSongs[l_month]; 33 | 34 | l_song.id = l_song_id; 35 | 36 | songDateFeatureValueMap[l_song_id] = []; 37 | 38 | if (l_entry == null) 39 | l_entry = sortedSongs[l_month] = {}; 40 | 41 | for (var l_feature in l_features) { 42 | // if (l_features.hasOwnProperty(l_feature)) 43 | var l_value = '' + Math.ceil(l_features[l_feature] * 100) / 100; 44 | 45 | var l_entry_feature = l_entry[l_feature]; 46 | if (l_entry_feature == null) 47 | l_entry_feature = l_entry[l_feature] = {}; 48 | 49 | var l_entry_value = l_entry_feature[l_value]; 50 | if (l_entry_value == null) 51 | l_entry_value = l_entry_feature[l_value] = { 52 | songs: {}, 53 | friends: [], 54 | valueID: -1 55 | }; 56 | 57 | l_entry_value.songs[l_song_id] = l_song; 58 | 59 | var l_song_dfv = songDateFeatureValueMap[l_song_id]; 60 | if (l_song_dfv == null) 61 | l_song_dfv = songDateFeatureValueMap[l_song_id] = []; 62 | 63 | l_song_dfv.push({ 64 | month: l_month, 65 | feature: l_feature, 66 | value: l_value, 67 | songID: l_song_id, 68 | valueID: -1 69 | }); 70 | } 71 | }; 72 | }; 73 | // set valueID 74 | for (let m in sortedSongs) { 75 | var month = sortedSongs[m]; 76 | let counter = 0; 77 | for (let f in month) { 78 | var feature = sortedSongs[m][f]; 79 | for (let v in feature) { 80 | var value = sortedSongs[m][f][v]; 81 | value.valueID = counter++; 82 | } 83 | } 84 | } 85 | 86 | // create friends 87 | for (let k in songDateFeatureValueMap) { 88 | // if (songDateFeatureValueMap.hasOwnProperty(k)) 89 | var l_songs_dfv = songDateFeatureValueMap[k]; 90 | for (let i = 0, n = l_songs_dfv.length; i < n; i++) { 91 | l_song_dfv = l_songs_dfv[i]; 92 | l_song_dfv.valueID = sortedSongs[l_song_dfv.month][l_song_dfv.feature][l_song_dfv.value].valueID; 93 | for (let ele in l_songs_dfv) { 94 | sortedSongs[l_song_dfv.month][l_song_dfv.feature][l_song_dfv.value].friends.push(l_songs_dfv[ele]); 95 | } 96 | } 97 | } 98 | $('#loadingBar').css('width', '100vw'); 99 | allKlangobj = sortedSongs; 100 | startViz(); 101 | }; 102 | 103 | module.exports = { sortData: sortData }; 104 | -------------------------------------------------------------------------------- /src/Api/start.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | var p5 = require('p5'); 3 | import sketch from './../Viz/sketch'; 4 | 5 | function startViz() { 6 | // console.log("startViz"); 7 | // start sketch 8 | new p5(sketch); 9 | }; 10 | 11 | module.exports = { startViz: startViz }; 12 | -------------------------------------------------------------------------------- /src/ITF/interfaceEvents.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | var playAndGenreList = require('./../Viz/View/Innerobject').playAndGenreList; 3 | 4 | 5 | function klanginterface() { 6 | /*##########################################################################################################################################*/ 7 | // Menu 8 | $('#nav-icon1').click(function () { 9 | if ($(this).hasClass('open')) { 10 | $(this).removeClass('open'); 11 | if ($('#infoContainer').hasClass('smallIC') || $('#infoContainer').hasClass('bigIC')) { 12 | $('#nav-icon1').find('span').css('background', 'grey'); 13 | } 14 | closeNav(); 15 | } else { 16 | $(this).addClass('open'); 17 | $('#nav-icon1').find('span').css('background', 'white'); 18 | openNav(); 19 | } 20 | }); 21 | 22 | /*##########################################################################################################################################*/ 23 | // Modal 24 | $('#close_modal').click(function () { 25 | $('.modal_info').css('display', 'none'); 26 | }); 27 | 28 | /*##########################################################################################################################################*/ 29 | // Navigation 30 | function openNav() { 31 | $('#mySidenav').css('width', '200px'); 32 | setTimeout(function () { 33 | $('#mySidenav').find('a').css('left', '0px'); 34 | $('#mySidenav').find('a').css('opacity', '1'); 35 | }, 100); 36 | }; 37 | 38 | function closeNav() { 39 | $('#mySidenav').css('width', '0px'); 40 | $('#mySidenav').find('a').css('left', '-20px'); 41 | $('#mySidenav').find('a').css('opacity', '0'); 42 | $('#mySidenav a').css('display', 'block'); 43 | $('#legend_content').css('display', 'none'); 44 | $('#about_content').css('display', 'none'); 45 | if ($('#infoContainer').hasClass('smallIC') || $('#infoContainer').hasClass('bigIC')) { 46 | $('#canvasContainer').css('display', 'block'); 47 | $(window).scrollTop(0); 48 | $('body').css('overflow', 'hidden'); 49 | }; 50 | $('#infoContainer').css('display', 'flex'); 51 | $('.legend_inner').css('opacity', 0); 52 | $('.legend_inner').css('left', -30); 53 | }; 54 | 55 | /*##########################################################################################################################################*/ 56 | // Legend 57 | $('#nav_legend').click(function (e) { 58 | e.preventDefault(); 59 | $('#mySidenav').css('width', '100vw'); 60 | $('#mySidenav a').css('display', 'none'); 61 | $('#legend_content').css('display', 'block'); 62 | $('body').css('overflow', 'scroll'); 63 | $(window).scrollTop(0); 64 | setTimeout(function () { 65 | $('.legend_inner').css('opacity', 1); 66 | $('.legend_inner').css('left', 0); 67 | $('#canvasContainer').css('display', 'none'); 68 | $('#infoContainer').css('display', 'none'); 69 | }, 200); 70 | }); 71 | 72 | $('#info_legende').click(function (e) { 73 | e.preventDefault(); 74 | $('#mySidenav').css('width', '100vw'); 75 | $('#mySidenav a').css('display', 'none'); 76 | $('#legend_content').css('display', 'block'); 77 | $('body').css('overflow', 'scroll'); 78 | $('#nav-icon1').find('span').css('background', 'white'); 79 | $('#nav-icon1').addClass('open'); 80 | $(window).scrollTop(0); 81 | setTimeout(function () { 82 | $('.legend_inner').css('opacity', 1); 83 | $('.legend_inner').css('left', 0); 84 | $('#canvasContainer').css('display', 'none'); 85 | $('#infoContainer').css('display', 'none'); 86 | }, 200); 87 | }); 88 | 89 | /*##########################################################################################################################################*/ 90 | // About 91 | $('#nav_about').click(function (e) { 92 | e.preventDefault(); 93 | $('#mySidenav').css('width', '100vw'); 94 | $('#mySidenav a').css('display', 'none'); 95 | $('#main').css('display', 'none'); 96 | $('#about_content').css('display', 'block'); 97 | $('body').css('overflow', 'scroll'); 98 | $(window).scrollTop(0); 99 | setTimeout(function () { 100 | $('.legend_inner').css('opacity', 1); 101 | $('.legend_inner').css('left', 0); 102 | $('#canvasContainer').css('display', 'none'); 103 | $('#infoContainer').css('display', 'none'); 104 | }, 200); 105 | }); 106 | 107 | /*##########################################################################################################################################*/ 108 | // Info-Container 109 | $('#openInfoContainer').click(function (e) { 110 | $('#infoContainer').removeClass('bigIC'); 111 | $('#openInfoContainer').css('display', 'none'); 112 | $('body').css('overflow', 'scroll'); 113 | $('#nav-icon1').find('span').css('background', 'white'); 114 | setTimeout(function () { 115 | $('.info').css('opacity', '1'); 116 | $('.info').css('padding-top', '70px'); 117 | $('#canvasContainer').css('display', 'none'); 118 | $('#closeInfoContainer').css('display', 'block'); 119 | // update 120 | playAndGenreList(active_node_outside); 121 | }, 200); 122 | }); 123 | 124 | $('#closeInfoContainer').click(function (e) { 125 | $('#canvasContainer').css('display', 'block'); 126 | $(window).scrollTop(0); 127 | $('body').css('overflow', 'hidden'); 128 | $('#infoContainer').addClass('bigIC'); 129 | $('.info').css('opacity', '0'); 130 | $('.info').css('padding-top', '90px'); 131 | $('#audioPlayer')[0].pause(); 132 | 133 | $('#playlist li').find('.icon1').css('opacity', '0'); 134 | $('#playlist li').find('.icon2').css('opacity', '0'); 135 | $('#playlist li').find('.id').css('opacity', '1'); 136 | $('#playlist li').removeClass('current-song'); 137 | 138 | $('#closeInfoContainer').css('display', 'none'); 139 | if ($('#nav-icon1').hasClass('open')) { 140 | // do nothing 141 | } else { 142 | $('#nav-icon1').find('span').css('background', 'grey'); 143 | } 144 | setTimeout(function () { 145 | $('#openInfoContainer').css('display', 'block'); 146 | }, 80); 147 | }); 148 | 149 | $('#openInfoContainer').hover(function () { 150 | let color = $('#infoContainer').css('backgroundColor'); 151 | $(this).css('background', 'white'); 152 | $(this).css('color', `${color}`); 153 | $('#closeInfoContainer').css('background', `${color}`); 154 | }, function () { 155 | let color = $('#infoContainer').css('backgroundColor'); 156 | $(this).css('background', `${color}`); 157 | $(this).css('color', 'white'); 158 | }); 159 | 160 | $('#closeInfoContainer').hover(function () { 161 | let color = $('#infoContainer').css('backgroundColor'); 162 | $(this).css('background', 'white'); 163 | $(this).css('color', `${color}`); 164 | }, function () { 165 | let color = $('#infoContainer').css('backgroundColor'); 166 | $(this).css('background', `${color}`); 167 | $(this).css('color', 'white'); 168 | }); 169 | 170 | /*##########################################################################################################################################*/ 171 | // Finished 172 | }; 173 | 174 | function loadingFinished() { 175 | $('#loadingContainer').animate({ 176 | opacity: '0' 177 | }, 500, function () { 178 | $('#loadingContainer').css('display', 'none'); 179 | $('.logo_viz').css('z-index', '1'); 180 | }); 181 | }; 182 | 183 | module.exports = { klanginterface: klanginterface, loadingFinished: loadingFinished }; 184 | -------------------------------------------------------------------------------- /src/Viz/Controller/mouseEvents.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | /** Sort- and pressedEvents */ 3 | 4 | var updateDOM = require('./../View/display').updateDOM; 5 | 6 | function nodeClick(p, h1, h2, active_node, all_nodes_normal, active_view, master_list, t) { 7 | // check node and friends 8 | if (active_node != null) { 9 | // clear all 10 | active_view = true; 11 | for (let ele of master_list[t]) { 12 | ele.friendship_state = false; 13 | all_nodes_normal = true; 14 | } 15 | // set active friends 16 | master_list[t][active_node.id].friendship_state = true; 17 | master_list[t][active_node.id].root = true; 18 | for (let f of active_node.friends) { 19 | master_list[t][f.valueID].friendship_state = true; 20 | } 21 | all_nodes_normal = false; 22 | // display 23 | updateDOM(h1, h2, 'click_active', active_node, p); 24 | } else { 25 | // clear all 26 | active_view = false; 27 | for (let ele of master_list[t]) { 28 | ele.friendship_state = false; 29 | ele.root = false; 30 | all_nodes_normal = true; 31 | } 32 | // display 33 | updateDOM(h1, h2, 'click_passiv'); 34 | } 35 | return [active_view, all_nodes_normal]; 36 | }; 37 | 38 | function mouseHover(p, node, index, osc, active_node, active_view, start_pos) { 39 | // check node 40 | let node_size = ((node.size / 2) * scale_value) * node.s < 5 ? 5 : ((node.size / 2) * scale_value) * node.s; 41 | if (p.dist(p.mouseX, p.mouseY, node.posx, node.posy) < node_size && (active_node == node || active_node == null)) { 42 | switch (active_view) { 43 | case true: 44 | if (node.friendship_state) { 45 | node.hover_state = true; 46 | start_pos = index; 47 | active_node = node; 48 | 49 | osc.freq(p.map(node.value, 0, 1, 500, 5000)); 50 | if (node.osc_status) { 51 | osc.start(); 52 | node.osc_status = false; 53 | } else { 54 | osc.stop(); 55 | } 56 | } 57 | break; 58 | case false: 59 | node.hover_state = true; 60 | start_pos = index; 61 | active_node = node; 62 | osc.freq(p.map(node.value, 0, 1, 500, 5000)); 63 | if (node.osc_status) { 64 | osc.start(); 65 | node.osc_status = false; 66 | } else { 67 | osc.stop(); 68 | } 69 | break; 70 | } 71 | } else { 72 | if (node == active_node) { 73 | active_node = null; 74 | osc.stop(); 75 | node.osc_status = true; 76 | } 77 | node.hover_state = false; 78 | } 79 | return [active_node, start_pos]; 80 | }; 81 | 82 | module.exports = { nodeClick: nodeClick, mouseHover: mouseHover }; 83 | -------------------------------------------------------------------------------- /src/Viz/Controller/scaleEvents.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | 3 | function scaleListener() { 4 | $('#scale05').on('click', function () { 5 | $(this).parent().children().removeClass('current_scale'); 6 | $(this).addClass('current_scale'); 7 | scale_value = 0.5; 8 | }); 9 | 10 | $('#scale1').on('click', function () { 11 | $(this).parent().children().removeClass('current_scale'); 12 | $(this).addClass('current_scale'); 13 | scale_value = 1; 14 | }); 15 | 16 | $('#scale2').on('click', function () { 17 | $(this).parent().children().removeClass('current_scale'); 18 | $(this).addClass('current_scale'); 19 | scale_value = 2; 20 | }); 21 | 22 | $('#scale4').on('click', function () { 23 | $(this).parent().children().removeClass('current_scale'); 24 | $(this).addClass('current_scale'); 25 | scale_value = 4; 26 | }); 27 | }; 28 | 29 | module.exports = { scaleListener: scaleListener }; -------------------------------------------------------------------------------- /src/Viz/Controller/timeEvents.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | 3 | function createTimelist() { 4 | // CREATE 5 | var ul = document.getElementById('timelist'); 6 | var current_time = 0; 7 | for (let i = 0; i < datelist.length; i++) { 8 | let li = document.createElement('li'); 9 | let a = document.createElement('a'); 10 | let date_array = datelist[i].split('-'); 11 | let y = date_array[0]; 12 | let m = date_array[1]; 13 | let text = document.createTextNode(`${y} — ${m}`); 14 | a.appendChild(text); 15 | li.appendChild(a); 16 | ul.appendChild(li); 17 | let length = datelist.length > 12 ? 12 * 30 : datelist.length * 30; 18 | $('#timeScrollbar').css('height', length + 'px'); 19 | $('#timelist').css('height', length + 'px'); 20 | if (datelist.length > 12) { 21 | $('#arrow_up').css('display', 'block'); 22 | $('#arrow_down').css('display', 'block'); 23 | } 24 | }; 25 | var time_ele_start = $(`#timelist li:nth-child(${1})`).offset().top; 26 | var time_ele_end = time_ele_start + 330; // 11*30 max-length 27 | $(`#timelist li:nth-child(1)`).addClass('current-time'); 28 | 29 | // EVENTS 30 | $('#timelist li a').click(function (e) { 31 | e.preventDefault(); 32 | if ($(this).parent().hasClass('current-time')) { 33 | // do nothing 34 | } else { 35 | $('#timelist li').removeClass('current-time'); 36 | $(this).parent().addClass('current-time'); 37 | current_time = $(this).parent().index(); 38 | } 39 | t = current_time; 40 | 41 | $('#openInfoContainer').css('display', 'none'); 42 | $('#noActiveNode').css('display', 'block'); 43 | }); 44 | 45 | $('#timelist li').click(function (e) { 46 | $('#openInfoContainer').css('display', 'none'); 47 | $('#noActiveNode').css('display', 'block'); 48 | }); 49 | 50 | $('#timelist').on('scroll', function () { 51 | for (let i = 1; i <= datelist.length; i++) { 52 | let pos = $(`#timelist li:nth-child(${i})`).offset().top; 53 | 54 | // startPos - 7 && endPos + 7 && ele height = 30px ---> max 8 dates 55 | if (time_ele_start <= pos && pos <= time_ele_end) { 56 | $(`#timelist li:nth-child(${i})`).css('opacity', 1); 57 | } else { 58 | $(`#timelist li:nth-child(${i})`).css('opacity', 0); 59 | } 60 | }; 61 | $('#arrow_up').css('opacity', '0'); 62 | $('#arrow_down').css('opacity', '0'); 63 | setTimeout(function () { 64 | $('#arrow_up').css('display', 'none'); 65 | $('#arrow_down').css('display', 'none'); 66 | }, 500); 67 | setTimeout(function () { 68 | $('#arrow_up').css('display', 'none'); 69 | $('#arrow_down').css('display', 'none'); 70 | }, 500); 71 | }); 72 | }; 73 | 74 | module.exports = { createTimelist: createTimelist }; 75 | -------------------------------------------------------------------------------- /src/Viz/Model/klangobject.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | function Klangobject(p, songs, index, w, h, xoff1, xoff2, xoff3, size, value, categorie, mycolor, friends, target_pos) { 3 | this.songs = songs; 4 | this.w = w; 5 | this.h = h; 6 | this.s = 0; 7 | this.id = index; 8 | this.value = value; 9 | this.categorie = categorie; 10 | this.friendship_state = false; 11 | this.size = size; 12 | this.map_v = p.map(value, 0, 1, 0.15, 0.7); 13 | this.friends = friends; 14 | this.osc_status = true; 15 | this.active_color = p.color(p.red(mycolor), p.green(mycolor), p.blue(mycolor), 150); 16 | this.passiv_color = p.color(220, 220, 220, 200); 17 | this.normal_color = p.color(20, 20, 20, 150); 18 | this.state_move = true; 19 | this.hover_state = false; 20 | this.xoff1 = xoff1; 21 | this.xoff2 = xoff2; 22 | this.xoff3 = xoff3; 23 | this.posx = 0; 24 | this.posy = 0; 25 | this.old_posy; 26 | this.old_xoff1; 27 | this.target_pos = target_pos; 28 | this.target = (this.h / 11) * target_pos; 29 | this.active = false; 30 | this.easing = 0.05; 31 | this.root = false; 32 | 33 | this.move = function () { 34 | if (this.state_move && !this.hover_state) { 35 | this.posx = p.map(p.noise(this.xoff1), 0, 1, 90, this.w); 36 | this.posy = p.map(p.noise(this.xoff2), 0, 1, 0, this.h); 37 | this.xoff1 += 0.002; 38 | this.xoff2 += 0.002; 39 | this.old_posy = this.posy; 40 | this.old_xoff1 = this.xoff1; 41 | } 42 | }; 43 | 44 | this.move_to_active = function () { 45 | this.active = true; 46 | if (this.state_move && !this.hover_state) { 47 | let direction = this.target - this.posy; 48 | this.posy += direction * this.easing; 49 | this.posx = p.map(p.noise(this.xoff1), 0, 1, 90, this.w); 50 | this.xoff1 += 0.002; 51 | } 52 | }; 53 | 54 | this.move_to_passiv = function () { 55 | if (this.state_move && !this.hover_state) { 56 | let direction = this.old_posy - this.posy; 57 | this.posy += direction * this.easing; 58 | if (Math.abs(direction) < 1) { 59 | this.active = false; 60 | } 61 | this.posx = p.map(p.noise(this.xoff1), 0, 1, 90, this.w); 62 | this.xoff1 += 0.002; 63 | } 64 | }; 65 | }; 66 | 67 | export { Klangobject as default }; 68 | -------------------------------------------------------------------------------- /src/Viz/View/Innerobject.js: -------------------------------------------------------------------------------- 1 | 'use-strict' 2 | var _ = require('underscore'); 3 | var s = require('underscore.string'); 4 | 5 | function playAndGenreList(node) { 6 | // CREATE 7 | $("#audioPlayer")[0].pause(); 8 | var ul = document.getElementById("playlist"); 9 | while (ul.firstChild) { 10 | ul.removeChild(ul.firstChild); 11 | }; 12 | 13 | var ul_genre = document.getElementById("genrelist"); 14 | let genres = []; 15 | while (ul_genre.firstChild) { 16 | ul_genre.removeChild(ul_genre.firstChild); 17 | }; 18 | 19 | if (node != null && node != undefined) { 20 | let count = 1; 21 | 22 | for (var s_id in active_node_outside.songs) { 23 | let song = active_node_outside.songs[s_id]; 24 | let li = document.createElement("li"); 25 | let a = document.createElement("a"); 26 | let use = document.createElement("use"); 27 | let p1 = document.createElement("p"); 28 | let p2 = document.createElement("p"); 29 | let p3 = document.createElement("p"); 30 | 31 | if (song.preview_url == null) { 32 | li.className = "hrefNull"; 33 | }; 34 | a.href = `${song.preview_url}`; 35 | p1.className = 'id'; 36 | p2.className = 'name'; 37 | p3.className = "time"; 38 | a.innerHTML = ''; 39 | let id = document.createTextNode(`${count++}`); 40 | let text = document.createTextNode(`${song.title} - ${song.artist}`); 41 | let time = document.createTextNode(`0:30`); 42 | p1.appendChild(id); 43 | p2.appendChild(text); 44 | p3.appendChild(time); 45 | a.appendChild(p1); 46 | a.appendChild(p2); 47 | a.appendChild(p3); 48 | li.appendChild(a); 49 | ul.appendChild(li); 50 | 51 | for (let genre of song.genre) { 52 | genres.push(genre); 53 | } 54 | } 55 | genres = s.toSentence(_.uniq(genres)); 56 | let li_g = document.createElement("li"); 57 | let p_g = document.createElement("p"); 58 | let text_g = document.createTextNode(genres + '.'); 59 | p_g.appendChild(text_g); 60 | li_g.appendChild(p_g); 61 | ul_genre.appendChild(li_g); 62 | audioPlayer(); 63 | } 64 | } 65 | 66 | function audioPlayer() { 67 | $("#audioPlayer")[0].src = $('#playlist li a')[0]; 68 | $('#playlist li a').click(function (e) { 69 | e.preventDefault(); 70 | if ($(this).attr('href') != 'null') { 71 | if ($(this).parent()[0].className == '') { 72 | $('#audioPlayer')[0].src = this; 73 | $("#audioPlayer")[0].play(); 74 | $('#playlist li').removeClass('current-song'); 75 | $(this).parent().addClass('current-song'); 76 | 77 | $('#playlist li').find(".icon1").css('opacity', '0'); 78 | $('#playlist li').find(".icon2").css('opacity', '0'); 79 | $('#playlist li').find(".id").css('opacity', '1'); 80 | $(this).find(".icon2").css('opacity', '1'); 81 | $(this).find(".id").css('opacity', '0'); 82 | } else { 83 | $("#audioPlayer")[0].pause(); 84 | $(this).find(".icon1").css('opacity', '1'); 85 | $(this).find(".icon2").css('opacity', '0'); 86 | $('#playlist li').removeClass('current-song'); 87 | } 88 | } 89 | }); 90 | 91 | $('#playlist li a').hover(function () { 92 | if ($(this).attr('href') != 'null') { 93 | if ($(this).parent()[0].className == '') { 94 | $(this).find(".icon1").css('opacity', '1'); 95 | $(this).find(".icon2").css('opacity', '0'); 96 | $(this).find(".id").css('opacity', '0'); 97 | } 98 | } 99 | }, function () { 100 | if ($(this).parent()[0].className == '') { 101 | $(this).find(".icon1").css('opacity', '0'); 102 | $(this).find(".icon2").css('opacity', '0'); 103 | $(this).find(".id").css('opacity', '1'); 104 | } 105 | }); 106 | 107 | $('#audioPlayer')[0].addEventListener('ended', function () { 108 | $('#playlist li').removeClass('current-song'); 109 | $('#playlist li').find(".icon1").css('opacity', '0'); 110 | $('#playlist li').find(".icon2").css('opacity', '0'); 111 | $('#playlist li').find(".id").css('opacity', '1'); 112 | }); 113 | }; 114 | 115 | module.exports = { playAndGenreList: playAndGenreList } -------------------------------------------------------------------------------- /src/Viz/View/display.js: -------------------------------------------------------------------------------- 1 | function displayNode(p, node, all_nodes_normal, showAll) { 2 | p.push(); 3 | p.noFill(); 4 | if (node.hover_state || node.friendship_state || showAll) { 5 | p.fill(node.active_color); // active color 6 | } else { 7 | if (all_nodes_normal) { 8 | p.fill(node.normal_color); // passiv color 9 | } else { 10 | p.fill(node.passiv_color); 11 | } 12 | } 13 | p.strokeWeight(0); 14 | node.s = p.map(p.sin(node.xoff3), -1, 1, 1, 1 + (node.size > 5 ? 0.2 : 0.25)); 15 | node.y = node.s; 16 | node.xoff3 += node.map_v; 17 | p.ellipse(node.posx, node.posy, node.s * node.size * scale_value, node.s * node.size * scale_value); 18 | p.pop() 19 | } 20 | 21 | function displayActiveNode(p, active_node, h3, h4) { 22 | if (active_node != null) { 23 | h3.html(active_node.categorie); 24 | h3.style('color', active_node.active_color) 25 | h4.html(active_node.value); 26 | h4.style('color', active_node.active_color); 27 | } else { 28 | h3.html(""); 29 | h4.html(""); 30 | } 31 | } 32 | 33 | function updateDOM(h1, h2, state, active_node, p) { 34 | switch (state) { 35 | case 'time': 36 | h1.html(""); 37 | h2.html(""); 38 | $('#infoContainer').css('background', `grey`); 39 | $('#infoContainer').removeClass('bigIC'); 40 | $('#infoContainer').addClass('smallIC'); 41 | $('.active_color').css('color', `grey`); 42 | $('#scaleList p:last-child').removeClass('current_sort'); 43 | break; 44 | case 'sort': 45 | h1.html(""); 46 | h2.html(""); 47 | $('#infoContainer').css('background', `grey`); 48 | $('#infoContainer').removeClass('bigIC'); 49 | $('#infoContainer').addClass('smallIC'); 50 | $('.active_color').css('color', `grey`); 51 | $('#openInfoContainer').css('display', `none`); 52 | $('#scaleList p:last-child').addClass('current_sort'); 53 | break; 54 | case 'click_active': 55 | h1.html(active_node.categorie); 56 | h1.style('color', active_node.active_color) 57 | h2.html(active_node.value); 58 | h2.style('color', active_node.active_color); 59 | h1.style('padding-right', '50px'); 60 | h1.style('padding-left', '1px'); 61 | h2.style('padding-left', '1px'); 62 | $('#categorie').html(active_node.categorie); 63 | $('#value').html(active_node.value); 64 | $('#openInfoContainer').css('background', `${active_node.active_color}`); 65 | $('#openInfoContainer').css('display', `block`); 66 | $('.active_color').css('color', `${active_node.active_color}`); 67 | $('#infoContainer').css('background', `rgb(${p.red(active_node.active_color)},${p.green(active_node.active_color)},${p.blue(active_node.active_color)})`); 68 | $('#scaleList p:last-child').removeClass('current_sort'); 69 | 70 | 71 | $('#infoContainer').removeClass('smallIC'); 72 | $('#infoContainer').addClass('bigIC'); 73 | 74 | break; 75 | case 'click_passiv': 76 | h1.html(""); 77 | h2.html(""); 78 | h1.style('padding-right', '0px'); 79 | h1.style('padding-left', '0px'); 80 | h2.style('padding-left', '0px'); 81 | $('#infoContainer').css('background', `grey`); 82 | $('.active_color').css('color', `grey`); 83 | $('#openInfoContainer').css('display', `none`); 84 | $('#scaleList p:last-child').removeClass('current_sort'); 85 | 86 | $('#infoContainer').removeClass('bigIC'); 87 | $('#infoContainer').addClass('smallIC'); 88 | 89 | break; 90 | default: 91 | console.log("error"); 92 | } 93 | } 94 | 95 | module.exports = { displayActiveNode: displayActiveNode, displayNode: displayNode, updateDOM: updateDOM } 96 | -------------------------------------------------------------------------------- /src/Viz/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors":[ 3 | {"r":239, "g":174, "b":44}, 4 | {"r":40, "g":132, "b":197}, 5 | {"r":227, "g":9, "b":72}, 6 | {"r":116, "g":78, "b":197}, 7 | {"r":171, "g":54, "b":20}, 8 | {"r":181, "g":214, "b":109}, 9 | {"r":124, "g":214, "b":211} 10 | ], 11 | "framerate": 20 12 | } 13 | -------------------------------------------------------------------------------- /src/Viz/sketch.js: -------------------------------------------------------------------------------- 1 | import Klangobject from './Model/klangobject'; 2 | var displayNode = require('./View/display').displayNode; 3 | var displayActiveNode = require('./View/display').displayActiveNode; 4 | var updateDOM = require('./View/display').updateDOM; 5 | var nodeClick = require('./Controller/mouseEvents').nodeClick; 6 | var mouseHover = require('./Controller/mouseEvents').mouseHover; 7 | var createTimelist = require('./Controller/timeEvents').createTimelist; 8 | var scaleListener = require('./Controller/scaleEvents').scaleListener; 9 | var klanginterface = require('./../ITF/interfaceEvents').klanginterface; 10 | var loadingFinished = require('./../ITF/interfaceEvents').loadingFinished; 11 | var p5 = require('p5'); 12 | 13 | import './../../public/mylib/p5.dom'; 14 | import './../../public/mylib/p5.sound'; 15 | 16 | var sketch = function (p, data) { 17 | var canvas; 18 | var w, h; 19 | var osc; 20 | var color_nodes; 21 | var start_pos = null; 22 | var active_node; 23 | var master_list; 24 | var active_list = []; 25 | var h1, h2, h3, h4; 26 | var showAll = false; 27 | var data; 28 | 29 | // controller 30 | var all_nodes_normal = true; 31 | var active_view = false; 32 | 33 | // view 34 | var lately_pos = null; 35 | 36 | p.setup = function () { 37 | p.frameRate(20); //max framerate 20 38 | 39 | // Init Elements 40 | create_dom(); 41 | osc = new p5.Oscillator(); 42 | osc.setType('sine'); 43 | osc.amp(1); 44 | 45 | color_nodes = [ 46 | p.color(239, 174, 44), // yellow 47 | p.color(40, 132, 197), // blue 48 | p.color(227, 44, 78), // magenta 49 | p.color(116, 78, 197), // purple 50 | p.color(105, 193, 135), // green 51 | p.color(252, 102, 18), // orange 52 | p.color(70, 206, 196) // cyan 53 | ]; 54 | 55 | master_list = []; 56 | let master_element; 57 | 58 | for (var m in allKlangobj) { 59 | var month = allKlangobj[m]; 60 | let index = 0; 61 | datelist.push(m); 62 | master_element = []; 63 | for (var f in month) { 64 | let target_pos = get_position(f); 65 | var feature = month[f]; 66 | let c = f; 67 | for (var k in feature) { 68 | var songs = feature[k]['songs']; 69 | let s = Object.keys(feature[k]['songs']).length; 70 | let v = parseFloat(k); 71 | let f = feature[k]['friends']; 72 | let c_node = get_color(c); 73 | //p, songs, index, w, h, xoff1, xoff2, xoff3, size, value, categorie, mycolor, map_v, scale_v, friends, target_pos 74 | master_element.push(new Klangobject(p, songs, index++, w, h, p.random(0, 1000000), p.random(0, 1000000), p.random(0, 1000000), s * 1.5, v, c, c_node, f, target_pos)); 75 | } 76 | target_pos++; 77 | } 78 | master_list.push(master_element); 79 | } 80 | 81 | // Init timelist and scalelist 82 | createTimelist(); 83 | scaleListener(); 84 | 85 | // Init interface 86 | klanginterface(); 87 | 88 | // Show all 89 | loadingFinished(); 90 | 91 | // Feedback 92 | /* 93 | console.log('raw data' + allKlangobj); 94 | console.log('klangobjects' + master_list); 95 | console.log('circles:' + master_list[t].length); 96 | */ 97 | }; 98 | 99 | p.draw = function () { 100 | p.background(255); 101 | active_list = []; 102 | 103 | for (let i = 0; i < master_list[t].length; i++) { 104 | let status = mouseHover(p, master_list[t][i], i, osc, active_node, active_view, start_pos); 105 | active_node = status[0]; 106 | start_pos = status[1]; 107 | if (master_list[t][i] == active_node || master_list[t][i].friendship_state == true || showAll) { 108 | active_list.push(master_list[t][i]); 109 | master_list[t][i].move_to_active(); 110 | } else { 111 | if (master_list[t][i].active == true) { 112 | master_list[t][i].move_to_passiv(); 113 | } else { 114 | master_list[t][i].move(); 115 | } 116 | displayNode(p, master_list[t][i], all_nodes_normal, showAll); 117 | } 118 | } 119 | 120 | for (let i = 0; i < active_list.length; i++) { 121 | displayNode(p, active_list[i], all_nodes_normal, showAll); 122 | } 123 | displayActiveNode(p, active_node, h3, h4); 124 | }; 125 | 126 | // Init Helper 127 | function create_dom() { 128 | w = window.innerWidth; 129 | h = window.innerHeight; 130 | canvas = p.createCanvas(w, h); 131 | canvas.parent('canvasContainer'); 132 | canvas.mousePressed(pressed_handler); 133 | 134 | let time = document.getElementById('timelist'); 135 | time.addEventListener('mouseup', time_handler); 136 | 137 | let sortButton = p.createElement('p', 'sort'); 138 | sortButton.addClass('scaleButton'); 139 | sortButton.addClass('sortButton'); 140 | sortButton.parent('scaleList'); 141 | sortButton.mousePressed(sort_handler); 142 | 143 | h1 = p.createElement('h1', ' '); 144 | h1.parent('click_cv'); 145 | h2 = p.createElement('h2', ' '); 146 | h2.parent('click_cv'); 147 | 148 | h3 = p.createElement('h1', ' '); 149 | h3.parent('hover_cv'); 150 | h4 = p.createElement('h2', ' '); 151 | h4.parent('hover_cv'); 152 | }; 153 | 154 | // Helper 155 | function get_color(categorie) { 156 | let color; 157 | switch (categorie) { 158 | case 'acousticness': 159 | color = color_nodes[0]; 160 | break; 161 | case 'liveness': 162 | color = color_nodes[1]; 163 | break; 164 | case 'danceability': 165 | color = color_nodes[2]; 166 | break; 167 | case 'energy': 168 | color = color_nodes[3]; 169 | break; 170 | case 'mood': 171 | color = color_nodes[4]; 172 | break; 173 | case 'instrumentalness': 174 | color = color_nodes[5]; 175 | break; 176 | case 'speechiness': 177 | color = color_nodes[6]; 178 | break; 179 | default: 180 | color = p.color(255, 0, 0); 181 | console.log('error'); 182 | } 183 | return color; 184 | } 185 | 186 | function get_position(categorie) { 187 | let target_pos; 188 | let gap = 2.4; // responsive -> if w > 2000 -> gap = 4 .... 189 | switch (categorie) { 190 | case 'acousticness': 191 | target_pos = 0 + gap; 192 | break; 193 | case 'liveness': 194 | target_pos = 1 + gap; 195 | break; 196 | case 'danceability': 197 | target_pos = 2 + gap; 198 | break; 199 | case 'energy': 200 | target_pos = 3 + gap; 201 | break; 202 | case 'mood': 203 | target_pos = 4 + gap; 204 | break; 205 | case 'instrumentalness': 206 | target_pos = 5 + gap; 207 | break; 208 | case 'speechiness': 209 | target_pos = 6 + gap; 210 | break; 211 | default: 212 | color = p.color(255, 0, 0); 213 | console.log('error'); 214 | } 215 | return target_pos; 216 | } 217 | 218 | // mouse events +++++++++++++++++++++ 219 | function pressed_handler() { 220 | let states = nodeClick(p, h1, h2, active_node, all_nodes_normal, active_view, master_list, t); 221 | active_view = states[0]; 222 | all_nodes_normal = states[1]; 223 | active_node_outside = active_node; 224 | showAll = false; 225 | } 226 | 227 | function time_handler() { 228 | for (let ele of master_list[t]) { 229 | ele.friendship_state = false; 230 | ele.root = false; 231 | all_nodes_normal = true; 232 | }; 233 | active_view = false; 234 | showAll = false; 235 | updateDOM(h1, h2, 'time'); 236 | } 237 | 238 | function sort_handler() { 239 | showAll = true; 240 | active_view = false; 241 | updateDOM(h1, h2, 'sort'); 242 | } 243 | 244 | // scaleEvents 245 | // timeEvents 246 | }; 247 | 248 | export { sketch as default }; 249 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Klangspektrum - by Michael Schwarz 3 | Hochschule Augsburg 2016/2017 4 | */ 5 | 6 | 'use-strict'; 7 | import api from './Api/api'; 8 | 9 | window.onload = function () { 10 | let access_token = getQueryString('access_token'); 11 | let refresh_token = getQueryString('refresh_token'); 12 | let user = localStorage.getItem("user_id"); 13 | let domain = document.location.host; 14 | $('body').css('overflow', 'hidden'); 15 | 16 | // Start api-calls 17 | var client_api = new api(access_token, refresh_token, user, domain); 18 | client_api.loadMe(); 19 | }; 20 | 21 | // Helper 22 | var getQueryString = function (field, url) { 23 | var href = url ? url : window.location.href; 24 | var reg = new RegExp('[?&]' + field + '=([^&#]*)', 'i'); 25 | var string = reg.exec(href); 26 | return string ? string[1] : null; 27 | }; 28 | -------------------------------------------------------------------------------- /views/imprint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Klangspektrum 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 106 |

107 | Impressum
Angaben gemäß § 5 TMG:
108 |
Michael Schwarz
Baumgartnerstraße 23
86161 Augsburg
109 |
Telefon: 0151 52481302
E-Mail: hallo@michaelschwarz.digital
110 |
Haftungsausschluss (Disclaimer)
Haftung für Inhalte
111 |
Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen 112 | verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte 113 | fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen 114 | zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine 115 | diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden 116 | von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen. 117 |


Haftung für Links
118 |
Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb 119 | können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der 120 | jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung 121 | auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine 122 | permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung 123 | nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen. 124 |


Urheberrecht 125 |
126 |
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. 127 | Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen 128 | der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den 129 | privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, 130 | werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie 131 | trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden 132 | von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen. 133 |

134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Klangspektrum 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | LEGEND 40 | ABOUT 41 |
42 | 43 | 44 | 49 | 50 | 51 |
52 |

COLOUR CODE

53 |
54 |

The data visualisation shows your music archive where only the songs are shown which you added to your playlists. 55 | Every song is described by seven categories. Each category has its own colour:

56 | 121 |
122 |

PRESENTATION

123 |
124 |

Every category consists of 100 values between 0 and 1, in which 1 stands for the highest intensity. 125 | Each value is displayed as an own circle. The number of songs which share the same value in the same category define the diameter of the circle:

126 | 128 | 129 | 130 | 131 | 133 | 134 | 135 | 136 |
137 |

= 50 songs with the same value in the same category

138 |

= 5 songs with the same value in the same category

139 |
140 |

The closer the value reaches to 1, the higher the frequency in sound and scaling gets:

141 | 143 | 144 | 145 | 146 | 148 | 149 | 150 | 151 |
152 |

= 1

153 |

= 0

154 |
155 |
156 |

INTERACTION

157 |
158 |

159 | Click
When a circle gets clicked on its related circles are shown in their particular category colour. 160 | Circles that are unrelated to the selected circle retreat into the background. 161 |

162 |

163 | Hover
A circle shows his music attributes when active and hovered over. 164 |

165 |
166 |
167 | 168 |
169 |

KLANGSPEKTRUM

170 |
171 |

We are living in an increaslingly digitised world but most of us barely have any background knowledge about digital processes. Due to the digital age streaming services gradually evolve. Music streaming services like Spotify massively changed the music industry and the musical behaviour of their users. Current studies show that the individual musical behaviour turns away more and more from criteria such as genre or artists. The once established criteria seem to get replaced by intelligent algorithms that work in the background. 172 |

173 | Spotify is for most of its users what you call a "black box", which means that people without any expert knowledge are not able to understand what kind of processes are going on in the technical background of the application. This interactive data visualisation was created to make Spotify more transparent for its users. 174 |

175 | Klangspektrum enables Spotify users to analyse their musical behaviour based on simplyfied algorithmically calculated song attributes. The user should be able to establish a relation between already familiar parameters such as songs and genres and the abstract song values. Metaphorically speaking the observer transforms into an algorithm to analyse his /her own music profile.

176 | bachelorthesis-klangspektrum.pdf 177 | imprint 178 |
179 |
180 | 181 |
182 |
183 |
184 | 185 | 186 | 187 | 189 | 190 | 191 | 192 | 193 | 195 | 196 | 198 | 200 | 209 | 219 | 229 | 231 | 233 | 241 | 245 | 247 | 248 | 249 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 |
267 |

268 | The digital age has long arrived in the music industry and brought a lot of change for listeners as well as artists. 269 | Current studies show that the individual musical behaviour increaslingly turns away from criteria such as genre or artists. 270 | The once established criteria seem to get replaced by intelligent algorithms that work in the background. In the following application you can gain insights into these background porcesses. 271 |

272 | 273 |
274 |
275 | 276 | 277 |

1

278 |

7

279 | 280 | 281 | -------------------------------------------------------------------------------- /views/klangviz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Klangspektrum 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | LEGEND 53 | ABOUT 54 | LOGOUT 55 |
56 | 57 | 58 | 63 | 64 | 65 |
66 |

COLOUR CODE

67 |
68 |

The data visualisation shows your music archive where only the songs are shown which you added to your playlists. 69 | Every song is described by seven categories. Each category has its own colour:

70 | 135 |
136 |

PRESENTATION

137 |
138 |

Every category consists of 100 values between 0 and 1, in which 1 stands for the highest intensity. 139 | Each value is displayed as an own circle. The number of songs which share the same value in the same category define the diameter of the circle:

140 | 142 | 143 | 144 | 145 | 147 | 148 | 149 | 150 |
151 |

= 50 songs with the same value in the same category

152 |

= 5 songs with the same value in the same category

153 |
154 |

The closer the value reaches to 1, the higher the frequency in sound and scaling gets:

155 | 157 | 158 | 159 | 160 | 162 | 163 | 164 | 165 |
166 |

= 1

167 |

= 0

168 |
169 |
170 |

INTERACTION

171 |
172 |

173 | Click
When a circle gets clicked on its related circles are shown in their particular category colour. 174 | Circles that are unrelated to the selected circle retreat into the background. 175 |

176 |

177 | Hover
A circle shows his music attributes when active and hovered over. 178 |

179 |
180 |
181 | 182 |
183 |

KLANGSPEKTRUM

184 |
185 |

We are living in an increaslingly digitised world but most of us barely have any background knowledge about digital processes. Due to the digital age streaming services gradually evolve. Music streaming services like Spotify massively changed the music industry and the musical behaviour of their users. Current studies show that the individual musical behaviour turns away more and more from criteria such as genre or artists. The once established criteria seem to get replaced by intelligent algorithms that work in the background. 186 |

187 | Spotify is for most of its users what you call a "black box", which means that people without any expert knowledge are not able to understand what kind of processes are going on in the technical background of the application. This interactive data visualisation was created to make Spotify more transparent for its users. 188 |

189 | Klangspektrum enables Spotify users to analyse their musical behaviour based on simplyfied algorithmically calculated song attributes. The user should be able to establish a relation between already familiar parameters such as songs and genres and the abstract song values. Metaphorically speaking the observer transforms into an algorithm to analyse his /her own music profile.

190 | bachelorthesis-klangspektrum.pdf 191 | imprint 192 |
193 |
194 | 195 |
196 | 197 |
198 | 199 | 200 | 202 | 203 | 204 | 205 | 206 | 208 | 209 | 211 | 213 | 222 | 232 | 242 | 244 | 246 | 254 | 258 | 260 | 261 | 262 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 |
279 |
280 |

klanguser

281 |

282 |
283 |
284 |
285 | 293 | 294 |
295 |
296 |
297 |
298 |
299 |
300 | 301 |
302 |
303 |
304 | 305 | 306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 | 315 | 316 |
317 |
318 |

x1

319 |

x2

320 |

x4

321 |

x8

322 |
323 |
324 | 325 | 326 |
327 |
?
328 | 329 | 330 |
331 |

332 |

333 |
334 | 335 |

SONGS

336 |
    337 |
338 |
339 |
340 |

GENRE

341 |
    342 |
343 |
344 |
345 |
346 |
347 | 348 | 349 | 351 | 352 | 354 | 355 | 357 | 359 | 368 | 378 | 388 | 390 | 392 | 400 | 404 | 406 | 407 | 408 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 |
425 | 426 | 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: require.resolve('./src/index.js'), 6 | output: { 7 | path: path.resolve(__dirname, 'public'), 8 | filename: 'bundle.js' 9 | }, 10 | watch: true, 11 | devtool: 'sourcemap', 12 | module: { 13 | loaders: [{ 14 | test: /\.js$/, 15 | exclude: /node_modules/, 16 | loader: ['babel'], 17 | query: { 18 | presets: ['es2015'] 19 | } 20 | }] 21 | } 22 | }; 23 | --------------------------------------------------------------------------------