about summary refs log tree commit diff
path: root/web
diff options
context:
space:
mode:
authorsternenseemann <git@lukasepple.de>2017-09-10 16:13:07 +0200
committersternenseemann <git@lukasepple.de>2017-09-10 16:22:51 +0200
commitd1377b5c82184f4c5c667b49012cb3a26aa0204e (patch)
tree14d2bd4ca82717f81c7b5d5caad83f5d27eed033 /web
parentf1c95a7753f1d33a2c15d8311b094ac59868b852 (diff)
Lots of design improvements & feature additions
- Seed storage
  - added an endpoint for receiving a random seed
  - added an input for manually entering a seed
  - seeds get saved to local storage
- param storage
  - params (maxhops, seed, starting_node) get saved to local storage
  - restored upon reload
- added in browser player which generates a wav based on the seed
- renamed midi format to mid
- improve overall look and usability of the sidebar
  - introduced issues in the dialogs -> TODO
Diffstat (limited to 'web')
-rw-r--r--web/source/custom.css84
-rw-r--r--web/source/index.html41
-rw-r--r--web/source/main.js164
3 files changed, 207 insertions, 82 deletions
diff --git a/web/source/custom.css b/web/source/custom.css
index 9e7c17c..6fc3605 100644
--- a/web/source/custom.css
+++ b/web/source/custom.css
@@ -17,6 +17,7 @@ body {
     color: white;
     background-color: black;
     box-shadow: 0px 0px 20px #111;
+    font-size: 1.2rem;
 }
 
 #sidebar > * {
@@ -26,27 +27,15 @@ body {
     padding-left: 0px;
     padding-right: 0px;
     margin: 0;
-    display: inline-block;
-}
-
-#sidebar button, #sidebar input {
-    height: 3rem;
-}
-
-#sidebar *:nth-child(odd) {
-    background-color: #111;
-}
-
-#sidebar ::placeholder {
-    color:#aaa;
 }
 
-#sidebar *:nth-child(even) {
-    background-color: #000;
+#sidebar button:hover, #sidebar input:hover,
+#sidebar .custom-file:hover, #sidebar select:hover {
+    background-color: #563d7c;
 }
 
-#sidebar button:hover, #sidebar input:hover, #sidebar .custom-file:hover {
-    background-color: #563d7c;
+#sidebar button, #sidebar input, #sidebar .custom-file, #sidebar select {
+  background-color: #000;
 }
 
 #sidebar h1 {
@@ -54,6 +43,7 @@ body {
     padding-top: 0.75rem;
     padding-bottom: 0.75rem;
     text-align: center;
+    background-color: #111;
 }
 
 #sidebar h2 {
@@ -61,6 +51,15 @@ body {
     padding-top: 0.9rem;
     padding-bottom: 0.9rem;
     text-align: center;
+    background-color: #222;
+}
+
+#sidebar select {
+  color: white;
+  border: none;
+  padding: 0.75rem;
+  font-size: 1.2rem;
+  width: auto;
 }
 
 button {
@@ -69,7 +68,7 @@ button {
     background-color:black;
     font-size: 1.2rem;
     margin:0;
-    padding:0;
+    padding:0.75rem;
 }
 
 input[type="number"] {
@@ -78,6 +77,7 @@ input[type="number"] {
     border: none;
     text-align: center;
     font-size: 1.2rem;
+    padding:0.75rem;
 }
 
 .custom-file {
@@ -88,7 +88,6 @@ input[type="number"] {
     height: 3rem;
 }
 
-
 .custom-file input[type="file"] {
     position: relative;
     top:0;
@@ -103,6 +102,7 @@ input[type="number"] {
 }
 
 .custom-file span {
+    text-align: center;
     position: absolute;
     top: 0;
     left: 0;
@@ -112,7 +112,6 @@ input[type="number"] {
     height: 3rem;
     pointer-events: none;
     background-color: transparent !important;
-    text-align: center;
     font-size: 1.2rem;
     line-height: 1.5rem;
     padding-top: 0.75rem;
@@ -148,50 +147,69 @@ input[type="number"] {
     font-size: 1.5rem;
 }
 
-.dialog button.cancel {
+button.cancel {
     background-color: #a23a30;
 }
 
-.dialog button.save {
+button.save {
     background-color: #0ea92f;
 }
 
-.label-input {
+.dialog .multi-inputs {
+  font-size: 1.5rem;
+}
+
+.multi-inputs {
   display: inline-flex;
   flex-direction: row;
+  flex-wrap: nowrap;
+  justify-content: flex-start;
+  align-items: baseline;
   width: 100%;
 }
 
-.label-input > * {
-  flex: auto;
+.multi-inputs > * {
+  flex-grow: 1;
+  flex-basis: auto;
   transition: width 0.7s ease-out;
+  max-height: 100%;
+  text-align: center;
 }
 
-.label-input label {
+.multi-inputs :nth-child(1) {
+  text-align: left;
+}
+
+.multi-inputs label {
   display: inline-block;
   background-color: #333;
-  font-size: 1.5rem;
   padding: 0.75rem;
 }
 
-.label-input input {
+.multi-inputs input {
   display: inline-block;
   color: white;
   background-color: #111;
-  font-size: 1.5rem;
   padding: 0.75rem;
   border: none;
   min-width: 0px;
 }
 
-.label-input span {
+.multi-inputs span {
   display: inline-block;
-  font-size: 1.5rem;
   padding: 0.75rem;
   background-color: #222;
 }
 
-.label-input button {
-    font-size: 1.5rem;
+.multi-inputs button {
     padding: 0.75rem;
 }
+
+#player-container {
+  display: inline-flex;
+  align-items: center;
+}
+
+#player-container > * {
+  flex: auto;
+}
diff --git a/web/source/index.html b/web/source/index.html
index 14c08dc..0e21fcd 100644
--- a/web/source/index.html
+++ b/web/source/index.html
@@ -17,46 +17,65 @@
             <button id="set-starting-node">Set starting node</button>
             <button id="show-starting-node">Show starting node</button>
             <h2>Generate an interpretation</h2>
-            <input type="number" min="0" id="hop-count" placeholder="Max. song length (in notes)">
-            <button id="play">Play</button>
-            <button id="gen-midi">Download MIDI</button>
+            <div class="multi-inputs">
+                <label for="seed">Seed:</label>
+                <input type="number" id="seed">
+            </div>
+            <div class="multi-inputs">
+                <label for="hop-count">Length:</label>
+                <input type="number" min="0" id="hop-count" placeholder="Max. note count">
+            </div>
+            <div id="player-container">
+                <button id="reload-player">&#8634;</button>
+                <audio id="player" controls></audio>
+            </div>
+            <div class="multi-inputs">
+                <button id="download-audio">Download</button>
+                <label for="format">
+                    as
+                </label>
+                <select id="format">
+                    <option value="mid">MIDI</option>
+                    <option value="wav">WAV</option>
+                </select>
+            </div>
             <h2>Load or Save Work</h2>
-            <button id="gen-score">Save</button>
+            <button id="gen-score" class="save">Save</button>
             <label for="upload-score" class="custom-file">
                 <input type="file" id="upload-score" >
                 <span>Load</span>
             </label>
-            <button id="clear-score">Clear</button>
+            <button id="clear-score" class="cancel">Clear</button>
         </div>
         <div id="edge-overlay" class="hidden dialog">
             <h2><span id="edge-operation"></span> edge</h2>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <label for="prob">Probability:</label>
                 <input id="prob" type="number" min="0.0" max="100">
                 <span>%</span>
             </div>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <button class="save" id="edge-save">Save</button>
                 <button class="cancel" id="edge-cancel">Cancel</button>
             </div>
         </div>
         <div id="node-overlay" class="hidden dialog">
             <h2><span id="node-operation"></span> node</h2>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <label for="pitch">Pitch:</label>
                 <select id="pitch"></select>
             </div>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <label for="octave">Octave:</label>
                 <input id="octave" type="number" step="1">
             </div>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <label>Duration:</label>
                 <input min="0" id="numerator" type="number" step="1">
                 <span>/</span>
                 <input min="0" id="denominator" type="number" step="1">
             </div>
-            <div class="label-input">
+            <div class="multi-inputs">
                 <button class="save" id="node-save">Save</button>
                 <button class="cancel" id="node-cancel">Cancel</button>
             </div>
diff --git a/web/source/main.js b/web/source/main.js
index 2dd126b..ab9823e 100644
--- a/web/source/main.js
+++ b/web/source/main.js
@@ -3,6 +3,7 @@ import { Map } from 'immutable';
 // types / internals
 
 const valid_pitches = [
+    'Rest',
     'Cff', 'Cf', 'C',
     'Dff', 'Cs', 'Df',
     'Css', 'D', 'Eff',
@@ -14,10 +15,11 @@ const valid_pitches = [
     'Gs', 'Af', 'Gss',
     'A', 'Bff', 'As',
     'Bf', 'Ass', 'B',
-    'Bs', 'Bss', 'Rest'
+    'Bs', 'Bss'
 ];
 
 const display_pitches = [
+    'Rest',
     'C♯♯', 'C♯', 'C',
     'D♯♯', 'C♭', 'D♯',
     'C𝄫', 'D', 'E♯♯',
@@ -29,7 +31,7 @@ const display_pitches = [
     'G♭', 'A♯', 'G𝄫',
     'A', 'B♯♯', 'A♭',
     'B♯', 'A𝄫', 'B',
-    'B♭', 'B𝄫', 'Rest'
+    'B♭', 'B𝄫'
 ];
 
 function displayPitch(pitch) {
@@ -254,7 +256,7 @@ function downloadFile(content_type, filename, content) {
 var nodeData = Map();
 var edgeData = Map();
 var network = null;
-var starting_node_id = undefined;
+var starting_node_id = null;
 
 
 function showOverlay(id) {
@@ -378,9 +380,11 @@ function handleImport() {
     }
 }
 
-function saveGraphToLocalStorage() {
+function saveDataToLocalStorage() {
     const json = JSON.stringify(collectGraphData(nodeData, edgeData));
+    const params = JSON.stringify(gatherParams());
     localStorage.setItem("score", json)
+    localStorage.setItem("params", params)
 }
 
 function showStartingNode() {
@@ -402,45 +406,118 @@ function setStartingNode() {
     }
 }
 
-function genInterpretation(format) {
-    try {
-        var starting_node_entry = nodeData.get(starting_node_id);
+function fetchInterpretation(params, format) {
+    var jsonRequest = JSON.stringify({
+        graph: collectGraphData(nodeData, edgeData),
+        params: params
+    });
+
+    var myHeaders = new Headers();
+    myHeaders.set('Content-Type', 'application/json');
+
+    var myInit = {
+        method: 'POST',
+        headers: myHeaders,
+        mode: 'cors',
+        body: jsonRequest
+    };
+
+    var myRequest = new Request(`http://localhost:8081/interpretation/${format}`, myInit);
+
+    return fetch(myRequest).then(res => res.blob());
+}
+
+function gatherParams() {
+    var starting_node_entry = nodeData.get(starting_node_id);
+    if(starting_node_entry !== undefined && starting_node_entry !== null) {
         var starting_node = {
             id: starting_node_entry.nodeData.id,
             music: starting_node_entry.music
         };
-    } catch(e) {
-        alert('Set a starting node first!');
-        return;
+    } else {
+        var starting_node = null
     }
 
-    try {
-        var maxhops = document.getElementById('hop-count').value;
+    var maxhops = document.getElementById('hop-count').value;
+    if(maxhops === "" || Number(maxhops) === NaN) {
+        maxhops = null;
+    } else {
+        maxhops = Number(maxhops);
+    }
 
-        var jsonRequest = JSON.stringify({
-            graph: collectGraphData(nodeData, edgeData),
-            params: { maxhops: 100, starting_node: starting_node }
-        });
+    var seed = document.getElementById('seed').value;
+    if(seed === "" || Number(seed) === NaN) {
+        seed = null;
+    } else {
+        seed = Number(seed);
+    }
 
+    return {
+        maxhops:  maxhops,
+        starting_node: starting_node,
+        seed: seed
+    };
+}
 
-        var myHeaders = new Headers();
-        myHeaders.set('Content-Type', 'application/json');
+function completeGatherParams() {
+    var p = gatherParams();
+    if(p.starting_node === null) {
+        alert('Set a starting node first!');
+        return null;
+    }
 
-        var myInit = {
-            method: 'POST',
-            headers: myHeaders,
-            mode: 'cors',
-            body: jsonRequest
-        };
+    if(p.maxhops === null) {
+        alert('Set the maximum amount of hops to a valid number');
+        return null;
+    }
+
+    if(p.seed === null) {
+        // TODO auto generate a random one, let the user confirm before
+        alert('Set the seed to a valid number!');
+        return null;
+    }
 
-        var myRequest = new Request(`http://localhost:8081/interpretation/${format}`, myInit);
+    return p;
+}
 
-        fetch(myRequest).then(res => res.blob()).then(file => {
-            var url = URL.createObjectURL(file);
-            download(url, 'export.midi');
-        });
-    } catch(e) {
-        alert('An error occured while contacting the API: ' + e);
+function importParams(p) {
+    if(p.starting_node !== null) {
+        starting_node_id = p.starting_node.id;
+    }
+    if(p.seed !== null) {
+        document.getElementById('seed').value = p.seed;
+    }
+    if(p.maxhops !== null) {
+        document.getElementById('hop-count').value = p.maxhops;
+    }
+}
+
+function downloadInterpretation(format) {
+    var params = completeGatherParams();
+    if(params != null) {
+        try {
+            fetchInterpretation(params, format).then(file => {
+                var url = URL.createObjectURL(file);
+                download(url, `export.${format}`);
+            });
+        } catch(e) {
+            alert('An error occured while contacting the API: ' + e);
+        }
+    }
+}
+
+function reloadPlayer() {
+    var params = completeGatherParams();
+    if(params !== null) {
+        document.getElementById('player').src = null;
+        try {
+            fetchInterpretation(params, 'wav').then(file => {
+                var url = URL.createObjectURL(file);
+                document.getElementById('player').src = url;
+            });
+        } catch(e) {
+            alert('An error occured while contacting the API: ' + e);
+        }
     }
 }
 
@@ -518,6 +595,14 @@ function init() {
         localStorage.removeItem('score');
     }
 
+    try {
+        const params = localStorage.getItem('params')
+        if(params !== null) {
+            importParams(JSON.parse(params));
+        }
+    } catch(e) {
+        localStorage.removeItem('params');
+    }
 
     const pitch_selector = valid_pitches.map((p, i) =>
         `<option value="${p}">${display_pitches[i]}</option>`)
@@ -525,21 +610,24 @@ function init() {
             acc + v, '');
     document.getElementById('pitch').innerHTML = pitch_selector;
 
-    document.getElementById('gen-midi').onclick =
-        genInterpretation.bind(this, ('midi'));
+    /* event handling, order as in sidebar */
+    document.getElementById('set-starting-node').onclick = setStartingNode;
+    document.getElementById('show-starting-node').onclick = showStartingNode;
+
+    document.getElementById('reload-player').onclick = reloadPlayer;
+    document.getElementById('download-audio').onclick = () => {
+        var format = document.getElementById('format').value;
+        downloadInterpretation(format);
+    };
 
     document.getElementById('gen-score').onclick = () =>
         downloadFile('application/json', 'score.likely.json',
             JSON.stringify(collectGraphData(nodeData, edgeData)));
-
     document.getElementById('upload-score').addEventListener('change',handleImport);
     document.getElementById('clear-score').onclick = () =>
         importGraphData({ nodes: [], edges: []});
 
-    document.getElementById('show-starting-node').onclick = showStartingNode;
-    document.getElementById('set-starting-node').onclick = setStartingNode;
-
-    window.setInterval(saveGraphToLocalStorage, 5000);
+    window.setInterval(saveDataToLocalStorage, 5000);
 }
 
 document.addEventListener('DOMContentLoaded', () => init());