├── .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 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
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 |
57 |
58 |
60 |
61 |
62 |
63 | acousticness
64 | shows how acoustic a song is.
65 |
66 |
67 |
69 |
70 |
71 |
72 | mood
73 | classifies the mood of a song into a spectrum from 0 to 1 and respectively from sad to happy.
74 |
75 |
76 |
78 |
79 |
80 |
81 | energy
82 | represents the intensity and activity of a song.
83 |
84 |
85 |
87 |
88 |
89 |
90 | liveness
91 | registers how live a song is and is able to identify audience sounds in a recording.
92 |
93 |
94 |
96 |
97 |
98 |
99 | danceability
100 | describes how danceable a song is.
101 |
102 |
103 |
105 |
106 |
107 |
108 | speechiness
109 | analyses the song to distinguish between spoken words and singing.
110 |
111 |
112 |
114 |
115 |
116 |
117 | instrumentalness
118 | addresses the parts without singing or spoken words, the instrumental parts, of a song.
119 |
120 |
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 |
Explore
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 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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 |
71 |
72 |
74 |
75 |
76 |
77 | acousticness
78 | shows how acoustic a song is.
79 |
80 |
81 |
83 |
84 |
85 |
86 | mood
87 | classifies the mood of a song into a spectrum from 0 to 1 and respectively from sad to happy.
88 |
89 |
90 |
92 |
93 |
94 |
95 | energy
96 | represents the intensity and activity of a song.
97 |
98 |
99 |
101 |
102 |
103 |
104 | liveness
105 | registers how live a song is and is able to identify audience sounds in a recording.
106 |
107 |
108 |
110 |
111 |
112 |
113 | danceability
114 | describes how danceable a song is.
115 |
116 |
117 |
119 |
120 |
121 |
122 | speechiness
123 | analyses the song to distinguish between spoken words and singing.
124 |
125 |
126 |
128 |
129 |
130 |
131 | instrumentalness
132 | addresses the parts without singing or spoken words, the instrumental parts, of a song.
133 |
134 |
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 |
286 |
287 |
Before you start: to analyse and interpret your musical behaviour correctly you should have a look at the legend.
288 | Click on the "?" in the lower right corner or select "legend" in the menu bar to show it.
289 |
290 |
Got it
291 |
292 |
293 |
294 |
298 |
303 |
304 |
305 |
306 |
307 |
311 |
↓
312 |
↓
313 |
314 |
315 |
316 |
317 |
318 |
x1
319 |
x2
320 |
x4
321 |
x8
322 |
323 |
324 |
325 |
326 |
327 |
?
328 |
Close
329 |
Get songs and genres
330 |
331 |
332 |
333 |
334 |
Sorry, your Browser doesn't support html5.
335 |
SONGS
336 |
338 |
339 |
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 |
--------------------------------------------------------------------------------