0x40 Hues of JS,
diff --git a/src/css/hues-editor.css b/src/css/hues-editor.css
new file mode 100644
index 0000000..aafe324
--- /dev/null
+++ b/src/css/hues-editor.css
@@ -0,0 +1,190 @@
+#huesEditor {
+ position: relative;
+ display: flex !important;
+ flex-direction: column;
+ max-width: calc(100% - 10px);
+ width: 1000px;
+
+ min-height: 400px;
+ margin: 5px;
+
+ font-size: 13px;
+}
+
+#huesEditor > hr {
+ width: 100%;
+}
+
+#edit-titlebuttons {
+ margin: -2px 0 2px -2px;
+ height: 15px;
+}
+
+#edit-topbar {
+ display: flex;
+ height: 60px;
+}
+
+
+#edit-info {
+ flex-grow: 1;
+}
+
+#edit-imports {
+ padding-top: 6px;
+}
+
+#edit-songstats {
+ margin-top: 5px;
+ margin-left: -8px;
+}
+
+.edit-songstat-value {
+ position: absolute;
+ right: 2px;
+}
+
+.edit-label {
+ display: flex;
+ margin: 12px auto;
+}
+
+.edit-textbox-container {
+ display: flex;
+ margin-top: -2px;
+ flex-grow: 1;
+}
+
+.edit-textbox {
+ flex-grow: 1;
+ font-family: 'PetMe64Web';
+ font-size: 7pt;
+ margin-right: 5px;
+ border-color: rgb(0,0,0);
+ border-width: 1px;
+ border-style: solid;
+}
+
+#edit-timelock {
+ display: flex;
+ align-items: center;
+ position: absolute;
+ margin-top: 4px;
+}
+
+#edit-timelock:before {
+ position: absolute;
+ z-index: -1;
+ content : "";
+ left : 8px;
+ top : 0;
+ height: 100%;
+ width : 5px;
+ border-left: 3px #666 solid;
+ border-top: 3px #666 solid;
+ border-bottom: 3px #666 solid;
+}
+
+#edit-timelock.unlocked:before {
+ border-left: 3px #666 dashed;
+}
+
+#edit-timelock > .hues-button {
+ /* because of the pseudo element this one can't be transparent */
+ background-color: rgb(171,171,171) !important;
+
+}
+
+#edit-timelock:hover > .hues-button {
+ background-color: rgb(236,236,236) !important;
+}
+
+
+#edit-area {
+ flex-grow: 1;
+ /*height: initial !important;*/
+ margin: 0 auto;
+ width: 100%;
+}
+
+.edit-area-header {
+ padding-bottom: 8px;
+ margin-left: 15px;
+}
+
+.beat-count {
+ font-size: 10px;
+}
+
+.edit-area-right-header {
+ position: absolute;
+ right: 25px;
+}
+
+.edit-box {
+ position: relative;
+ overflow-y : scroll;
+ background-color: white;
+ margin: auto 20px;
+}
+
+.beatmap {
+ height: inherit;
+ width: inherit;
+ position: relative;
+}
+
+.beat-hilight {
+ position: absolute;
+ color: rgba(127,127,127,0.5);
+ visibility: visible;
+ text-align: center;
+}
+
+.beat-hilight.hidden {
+ visibility: hidden;
+}
+
+#edit-build, #edit-loop {
+ height: auto;
+}
+
+#edit-resize-handle-container {
+ width: 100%;
+ height: 20px;
+ cursor: row-resize;
+ overflow: hidden;
+}
+
+#edit-resize-handle {
+ transform: scale(10.0, 1.0);
+ font-size: 20px;
+ text-align:center;
+ color: #999;
+}
+
+#edit-resize-handle:hover {
+ color: #000;
+}
+
+#edit-controls {
+ height: 25px !important;
+}
+
+#edit-waveform {
+ width: 100%;
+ height: 50px;
+}
+
+/* Make it as invisible as we can */
+#edit-copybox {
+ position : fixed;
+ top : 0;
+ left : 0;
+ width : 2em;
+ height : 2em;
+ padding : 0;
+ border : none;
+ outline : none;
+ background : transparent;
+}
\ No newline at end of file
diff --git a/src/css/style.css b/src/css/style.css
index 85f52aa..be1ac3a 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -154,6 +154,7 @@ h1, h2, h3 {
#tabs {
margin: -1px;
padding-top: 22px;
+ display: flex;
}
input[type=radio] {
@@ -161,48 +162,49 @@ input[type=radio] {
}
.tab-label{
+ flex-grow: 1;
cursor: pointer;
- display: table-cell;
padding: 10px;
border: 2px solid black;
- width: 1%;
text-align: center;
}
-label.tab-label:hover {
- background: rgba(255,255,255,0.3);
+.tab-label.checked {
+ border-bottom: 0px;
}
-input.tab-input[type="radio"]:checked + label {
- border-bottom: 0px;
+l.tab-label:hover {
+ background: rgba(255,255,255,0.3);
}
.tab-content {
display: none;
}
-#tab1:checked ~ #tab1-content,
-#tab2:checked ~ #tab2-content,
-#tab3:checked ~ #tab3-content,
-#tab4:checked ~ #tab4-content {
+#tab-resources:checked ~ #tab-resources-content,
+#tab-editor:checked ~ #tab-editor-content,
+#tab-options:checked ~ #tab-options-content,
+#tab-info:checked ~ #tab-info-content {
display: block;
}
#settingsHelper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
position: absolute;
- left: 50%;
- top: 50%;
- width: 0;
- height: 0;
+ margin-top: -15px;
+ width: 100%;
+ height: 100%;
}
#settingsWindow {
position: relative;
z-index: 9;
- width: 640px;
- height: 470px;
- margin-left: -320px;
- margin-top: -235px;
+ min-width: 640px;
+ min-height: 470px;
+ max-height: calc(100% - 50px);
+ margin: 10px;
background: rgba(200,200,200, 0.7);
border-color: black;
@@ -212,6 +214,9 @@ input.tab-input[type="radio"]:checked + label {
#huesSettings {
padding: 5px;
+ width: 630px;
+ display: flex;
+ flex-wrap: wrap;
}
#closeButton {
@@ -292,7 +297,7 @@ label.settings-label:hover {
font-size: 10px;
margin: 3px 2px;
padding: 3px;
- background: rgba(127,127,127, 0.5);
+ background-color: rgba(127,127,127, 0.5);
border-color: rgb(0,0,0);
border-width: 1px;
border-style: solid;
@@ -331,4 +336,22 @@ label.settings-label:hover {
.hues-button.disabled:hover {
background-color: rgba(127,127,127, 0.5);
+}
+
+.hues-button.glow {
+ animation-name: glow;
+ animation-duration: 2s;
+ animation-iteration-count: infinite;
+}
+
+@keyframes glow {
+ from {
+ background-color: rgba(127,127,127, 0.5);
+ }
+ 50% {
+ background-color: rgba(200,220,200, 0.5);
+ }
+ to {
+ background-color: rgba(127,127,127, 0.5);
+ }
}
\ No newline at end of file
diff --git a/src/js/HuesCore.js b/src/js/HuesCore.js
index c4546b6..3530069 100644
--- a/src/js/HuesCore.js
+++ b/src/js/HuesCore.js
@@ -290,6 +290,10 @@ HuesCore.prototype.animationLoop = function() {
this.callEventListeners("frame");
};
+HuesCore.prototype.recalcBeatIndex = function() {
+ this.beatIndex = Math.floor(this.soundManager.clampedTime() / this.beatLength);
+};
+
HuesCore.prototype.getBeatIndex = function() {
if(!this.soundManager.playing) {
return 0;
@@ -298,7 +302,7 @@ HuesCore.prototype.getBeatIndex = function() {
} else {
return this.beatIndex % this.currentSong.rhythm.length;
}
-}
+};
HuesCore.prototype.getSafeBeatIndex = function() {
var index = this.getBeatIndex();
@@ -329,19 +333,17 @@ HuesCore.prototype.setSongByName = function(name) {
var songs = this.resourceManager.enabledSongs;
for(var i = 0; i < songs.length; i++) {
if(songs[i].title == name) {
- this.setSong(i);
- return;
+ return this.setSong(i);
}
}
- this.setSong(0); // fallback
+ return this.setSong(0); // fallback
};
/* To set songs via reference instead of index - used in HuesEditor */
HuesCore.prototype.setSongOject = function(song) {
for(var i = 0; i < this.resourceManager.enabledSongs.length; i++) {
if(this.resourceManager.enabledSongs[i] === song) {
- this.setSong(i);
- return;
+ return this.setSong(i);
}
}
}
@@ -375,7 +377,7 @@ HuesCore.prototype.setSong = function(index) {
}
}
this.setInvert(false);
- this.soundManager.playSong(this.currentSong, this.doBuildup)
+ return this.soundManager.playSong(this.currentSong, this.doBuildup)
.then(() => {
this.resetAudio();
this.fillBuildup();
@@ -393,20 +395,22 @@ HuesCore.prototype.updateBeatLength = function() {
HuesCore.prototype.fillBuildup = function() {
this.updateBeatLength();
- var buildBeats = Math.floor(this.soundManager.buildLength / this.beatLength);
- if(buildBeats < 1) {
- buildBeats = 1;
- }
- if (this.currentSong.buildupRhythm === null) {
+ if (!this.currentSong.buildupRhythm) {
this.currentSong.buildupRhythm = "";
}
- if (this.currentSong.buildupRhythm.length < buildBeats) {
- console.log("Filling buildup beatmap");
- while (this.currentSong.buildupRhythm.length < buildBeats) {
- this.currentSong.buildupRhythm = this.currentSong.buildupRhythm + ".";
+ if(this.currentSong.buildup) {
+ var buildBeats = Math.floor(this.soundManager.buildLength / this.beatLength);
+ if(buildBeats < 1) {
+ buildBeats = 1;
+ }
+ if (this.currentSong.buildupRhythm.length < buildBeats) {
+ console.log("Filling buildup beatmap");
+ while (this.currentSong.buildupRhythm.length < buildBeats) {
+ this.currentSong.buildupRhythm = this.currentSong.buildupRhythm + ".";
+ }
}
+ console.log("Buildup length:", buildBeats);
}
- console.log("Buildup length:", buildBeats);
if(this.doBuildup) {
this.beatIndex = -this.currentSong.buildupRhythm.length;
} else {
@@ -633,7 +637,7 @@ HuesCore.prototype.beater = function(beat) {
break;
}
}
- this.renderer.doColourFade(fadeLen * this.beatLength);
+ this.renderer.doColourFade((fadeLen * this.beatLength) / this.soundManager.playbackRate);
this.randomColour(true);
break;
case 'I':
diff --git a/src/js/HuesEditor.js b/src/js/HuesEditor.js
new file mode 100644
index 0000000..db0cf5f
--- /dev/null
+++ b/src/js/HuesEditor.js
@@ -0,0 +1,949 @@
+/* Copyright (c) William Toohey
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+(function(window, document) {
+"use strict";
+
+function HuesEditor(core) {
+ this.buildEditSize = 80; // pixels, including header
+ this.buildEdit = null;
+ this.loopEdit = null;
+ this.editArea = null;
+ this.wrapAt = 16;
+
+ this.hilightWidth = 0;
+ this.hilightHeight = 0;
+
+ this.undoBuffer = [];
+ this.redoBuffer = [];
+
+ // for storing respacks created with "new"
+ this.respack = null;
+ // when we're actually following the playing song
+ this.linked = false;
+
+ this.core = core;
+ this.root = document.getElementById("huesEditor");
+ if(!this.root) {
+ return;
+ }
+ if(!core.settings.defaults.noUI) {
+ this.initUI();
+ core.addEventListener("beat", this.onBeat.bind(this));
+ core.addEventListener("newsong", this.onNewSong.bind(this));
+ }
+}
+
+HuesEditor.prototype.initUI = function() {
+ var titleButtons = document.createElement("div");
+ titleButtons.id = "edit-titlebuttons";
+ this.root.appendChild(titleButtons);
+ this.saveBtn = this.createButton("Save XML", titleButtons, true);
+ this.saveBtn.onclick = this.saveXML.bind(this);
+ this.copyBtn = this.createButton("Copy XML", titleButtons, true);
+ this.copyBtn.onclick = this.copyXML.bind(this);
+ this.undoBtn = this.createButton("Undo", titleButtons, true);
+ this.redoBtn = this.createButton("Redo", titleButtons, true);
+ var help = this.createButton("Help?", titleButtons);
+ help.style.backgroundColor = "rgba(0,160,0,0.3)";
+ help.onclick = function() {
+ window.open("http://0x40hues.blogspot.com/p/0x40-hues-creation-tutorial.html", '_blank');
+ };
+
+ this.topBar = document.createElement("div");
+ this.topBar.id = "edit-topbar";
+ this.root.appendChild(this.topBar);
+
+ this.uiCreateInfo();
+ this.uiCreateImport();
+ this.root.appendChild(document.createElement("hr"));
+ this.uiCreateEditArea();
+ this.uiCreateControls();
+ this.uiCreateVisualiser();
+
+ window.addEventListener('resize', this.resize.bind(this));
+ // Fix Chrome rendering - redraw on tab load
+ document.getElementById("tab-editor").addEventListener("change", this.resize.bind(this));
+ this.resize();
+};
+
+HuesEditor.prototype.resize = function() {
+ this.root.style.height = (window.innerHeight - 200) + "px";
+ var boxHeight = this.editArea.offsetHeight;
+ var bHeadHeight = this.buildEdit._header.offsetHeight;
+ var lHeadHeight = this.loopEdit._header.offsetHeight;
+ var handleHeight = this.resizeHandle.offsetHeight;
+ var minHeight = bHeadHeight;
+ var maxHeight = boxHeight - handleHeight - lHeadHeight - bHeadHeight;
+ var buildHeight = Math.min(maxHeight, Math.max(minHeight, this.buildEditSize - handleHeight));
+ this.buildEdit.style.height = buildHeight + "px";
+ this.buildEdit._box.style.height = (buildHeight - bHeadHeight) + "px";
+ var loopHeight = maxHeight - buildHeight + lHeadHeight;
+ this.loopEdit.style.height = loopHeight + "px";
+ this.loopEdit._box.style.height = (loopHeight - lHeadHeight) + "px";
+
+ // For window resizing down situation
+ if(this.editArea.offsetHeight != boxHeight) {
+ this.resize();
+ }
+
+ // Resize the time lock
+ this.timeLock.style.height = (buildHeight + handleHeight) + "px";
+
+ // Save to fix Chrome rendering and to enable right click to seek
+ var hilight = document.createElement("div");
+ hilight.className = "beat-hilight";
+ hilight.innerHTML = "█";
+ this.root.appendChild(hilight);
+ this.hilightWidth = hilight.clientWidth;
+ this.hilightHeight = hilight.clientHeight;
+ this.root.removeChild(hilight);
+}
+
+HuesEditor.prototype.createTextInput = function(label, id, subtitle, parent) {
+ var div = document.createElement("div");
+ div.className = "edit-label";
+ var caption = document.createElement("label");
+ caption.innerHTML = label;
+ caption.htmlFor = id;
+ div.appendChild(caption);
+ var container = document.createElement("span");
+ container.className = "edit-textbox-container";
+ var input = document.createElement("input");
+ input.className = "edit-textbox";
+ input.type = "text";
+ input.id = id;
+ input.value = subtitle;
+ container.appendChild(input);
+ div.appendChild(container);
+
+ parent.appendChild(div);
+
+ return input;
+}
+
+HuesEditor.prototype.createButton = function(label, parent, disabled, extraClass) {
+ var button = document.createElement("span");
+ button.className = "hues-button";
+ if(disabled) {
+ button.className += " disabled";
+ }
+ if(extraClass) {
+ button.className += " " + extraClass;
+ }
+ button.innerHTML = label.toUpperCase();
+ parent.appendChild(button);
+ return button;
+}
+
+HuesEditor.prototype.uiCreateInfo = function() {
+ var info = document.createElement("div");
+ this.topBar.appendChild(info);
+ info.id = "edit-info";
+
+ var songUpdate = function(name) {
+ if(!this.song ) {
+ return;
+ }
+ this.song[name] = this[name].value;
+ if(this.song != this.core.currentSong) {
+ return;
+ }
+ this.core.callEventListeners("newsong", this.song);
+ }
+
+ this.title = this.createTextInput("Title:", "edit-title", "Song name", info);
+ this.title.oninput = songUpdate.bind(this, "title");
+ this.source = this.createTextInput("Link: ", "edit-source", "Source link", info);
+ this.source.oninput = songUpdate.bind(this, "source");
+};
+
+HuesEditor.prototype.onNewSong = function(song) {
+ if(this.linked) {
+ if(song == this.song) {
+ // Because you can "edit current" before it loads
+ this.updateInfo();
+ } else {
+ this.linked = false;
+ // Clear beat hilight
+ this.buildEdit._hilight.innerHTML = "█";
+ this.loopEdit._hilight.innerHTML = "█";
+ this.buildEdit._hilight.className = "beat-hilight hidden";
+ this.loopEdit._hilight.className = "beat-hilight hidden";
+ }
+ }
+}
+
+HuesEditor.prototype.onBeat = function(map, index) {
+ if(!this.song || this.core.currentSong != this.song) {
+ return;
+ }
+ var editor;
+ if(index < 0) {
+ index += this.core.currentSong.buildupRhythm.length;
+ editor = this.buildEdit;
+ this.loopEdit._hilight.className = "beat-hilight hidden";
+ } else {
+ editor = this.loopEdit;
+ if(this.song.buildup) {
+ this.buildEdit._hilight.className = "beat-hilight hidden";
+ }
+ }
+ editor._hilight.className = "beat-hilight";
+ var offsetX = index % editor._breakAt;
+ var offsetY = Math.floor(index / editor._breakAt);
+ // Not computing width/height here due to Chrome bug
+ editor._hilight.style.left = (offsetX * this.hilightWidth) + "px";
+ editor._hilight.style.top = (offsetY * this.hilightHeight) + "px";
+}
+
+HuesEditor.prototype.reflow = function(editor, map) {
+ if(!map) { // NOTHING TO SEE HERE
+ editor._beatmap.textContent = "";
+ editor._hilight.textContent = "[none]";
+ editor._hilight.className = "beat-hilight";
+ editor._hilight.style.top = "0";
+ editor._hilight.style.left = "0";
+ editor._beatCount.textContent = "0 beats";
+ return;
+ } else {
+ editor._hilight.innerHTML = "█";
+ }
+ var charWidth = editor._hilight.clientWidth;
+ var charsPerLine = Math.floor(editor._beatmap.clientWidth / charWidth);
+ // if it's too long to wrap, just give up
+ var wrap = Math.min(this.wrapAt, charsPerLine);
+ charsPerLine -= charsPerLine % wrap;
+ editor._beatCount.textContent = map.length + " beats";
+ // http://stackoverflow.com/a/27012001
+ var regex = new RegExp("(.{" + charsPerLine + "})", "g");
+ editor._beatmap.innerHTML = map.replace(regex, "$1
");
+ editor._breakAt = charsPerLine;
+}
+
+HuesEditor.prototype.loadAudio = function(editor) {
+ if(editor._fileInput.files.length < 1) {
+ return;
+ }
+ // Disable load button TODO
+ var file = editor._fileInput.files[0];
+ // load audio
+ this.blobToArrayBuffer(file)
+ .then(buffer => {
+ // First load, go fresh, get the core synced up
+ this.newSong(this.song);
+
+ this.song[editor._sound] = buffer;
+ // make empty map if needed
+ if(!this.getText(editor)) {
+ this.setText(editor, "x...o...x...o...");
+ }
+ // Do we have a loop to play?
+ if(this.song.sound) {
+ // Force refresh
+ return this.core.soundManager.playSong(this.song, true, true);
+ }
+ }).then(() => {
+ this.updateInfo();
+ this.reflow(editor, this.getText(editor));
+ this.core.updateBeatLength();
+ // We may have to go backwards in time
+ this.core.recalcBeatIndex();
+ }).catch(() => {
+ alert("Couldn't load song! Is it a LAME encoded MP3?");
+ });
+}
+
+HuesEditor.prototype.blobToArrayBuffer = function(blob) {
+ return new Promise((resolve, reject) => {
+ var fr = new FileReader();
+ fr.onload = () => {
+ resolve(fr.result);
+ };
+ fr.onerror = () => {
+ reject(new Error("File read failed!"));
+ };
+ fr.readAsArrayBuffer(blob);
+ });
+}
+
+HuesEditor.prototype.newSong = function(song) {
+ if(!song) {
+ song = {"name":"Name",
+ "title":"Title",
+ "rhythm":"",
+ "source":"",
+ "sound":null,
+ "enabled":true,
+ "filename":null,
+ "charsPerBeat": null};
+ if(!this.respack) {
+ this.respack = new Respack();
+ this.respack.name = "Editor Respack";
+ this.respack.author = "You!";
+ this.respack.description = "An internal resourcepack for editing new songs";
+ this.core.resourceManager.addPack(this.respack);
+ }
+ this.respack.songs.push(song);
+ this.core.resourceManager.rebuildArrays();
+ this.core.resourceManager.rebuildEnabled();
+ this.core.setSongOject(song);
+ }
+ // Clear instructions
+ this.buildEdit._hilight.className = "beat-hilight hidden";
+ this.loopEdit._hilight.className = "beat-hilight hidden";
+
+ // Clear helpful glows
+ this.newSongBtn.classList.remove("glow");
+ this.fromSongBtn.classList.remove("glow");
+
+ this.clearUndoRedo();
+
+ this.song = song;
+ this.reflow(this.buildEdit, song.buildupRhythm);
+ this.reflow(this.loopEdit, song.rhythm);
+ this.title.value = song.title;
+ this.source.value = song.source;
+
+ // Unlock beatmap lengths
+ this.setLocked(this.buildEdit, 0);
+ this.setLocked(this.loopEdit, 0);
+
+ this.linked = true;
+ this.updateInfo();
+}
+
+HuesEditor.prototype.updateInfo = function() {
+ if(!this.linked) {
+ return;
+ }
+
+ var loopLen = this.core.soundManager.loopLength;
+ var buildLen = this.core.soundManager.buildLength;
+ var beatLen = (loopLen / this.song.rhythm.length) * 1000;
+
+ this.loopLen.textContent = loopLen.toFixed(2);
+ this.buildLen.textContent = buildLen.toFixed(2);
+ this.beatLen.textContent = beatLen.toFixed(2);
+
+ // Avoid a bunch of nested elses
+ this.seekStart.className = "hues-button disabled";
+ this.seekLoop.className = "hues-button disabled";
+ this.saveBtn.className = "hues-button disabled";
+ this.copyBtn.className = "hues-button disabled";
+ this.buildEdit._removeBtn.className = "hues-button disabled";
+ this.loopEdit._removeBtn.className = "hues-button disabled";
+
+ if(this.song) {
+ this.saveBtn.className = "hues-button";
+ this.copyBtn.className = "hues-button";
+ if(this.song.sound) {
+ this.seekLoop.className = "hues-button";
+ this.loopEdit._removeBtn.className = "hues-button";
+ if(this.song.buildup) {
+ this.seekStart.className = "hues-button";
+ this.buildEdit._removeBtn.className = "hues-button";
+ }
+ }
+ }
+}
+
+HuesEditor.prototype.pushUndo = function(name, editor, oldText, newText) {
+ if(oldText == newText) {
+ return;
+ }
+ this.redoBuffer = [];
+
+ this.undoBuffer.push({songVar: name, editor: editor, text: oldText});
+ while(this.undoBuffer.length > 50) {
+ this.undoBuffer.pop();
+ }
+ this.updateUndoUI();
+}
+
+HuesEditor.prototype.undo = function() {
+ this.undoRedo(this.undoBuffer, this.redoBuffer);
+}
+
+HuesEditor.prototype.redo = function() {
+ this.undoRedo(this.redoBuffer, this.undoBuffer);
+}
+
+HuesEditor.prototype.undoRedo = function(from, to) {
+ if(from.length == 0 || !this.song) {
+ return;
+ }
+ // Remove old data
+ var fromData = from.pop();
+ // Make restore from current
+ to.push({songVar: fromData.songVar, editor: fromData.editor, text: this.song[fromData.songVar]});
+ // Restore to editor
+ this.song[fromData.songVar] = fromData.text;
+ this.reflow(fromData.editor, this.song[fromData.songVar]);
+ this.updateUndoUI();
+ this.updateHalveDoubleButtons();
+ this.core.updateBeatLength();
+ this.core.recalcBeatIndex();
+}
+
+HuesEditor.prototype.clearUndoRedo = function() {
+ this.undoBuffer = [];
+ this.redoBuffer = [];
+ this.updateUndoUI();
+}
+
+HuesEditor.prototype.updateUndoUI = function() {
+ this.undoBtn.className = "hues-button disabled";
+ this.undoBtn.onclick = null;
+ this.redoBtn.className = "hues-button disabled";
+ this.redoBtn.onclick = null;
+
+ if(this.undoBuffer.length > 0) {
+ this.undoBtn.classList.remove("disabled");
+ this.undoBtn.onclick = this.undo.bind(this);
+ }
+ if(this.redoBuffer.length > 0) {
+ this.redoBtn.classList.remove("disabled");
+ this.redoBtn.onclick = this.redo.bind(this);
+ }
+}
+
+HuesEditor.prototype.halveBeats = function(editor) {
+ if(!this.song || this.getText(editor).length < 2) {
+ return;
+ }
+ this.setText(editor, this.song[editor._rhythm].replace(/(.)./g, "$1"));
+}
+
+HuesEditor.prototype.doubleBeats = function(editor) {
+ if(!this.song || this.getText(editor).length == 0) {
+ return;
+ }
+ this.setText(editor, this.song[editor._rhythm].replace(/(.)/g, "$1."));
+}
+
+HuesEditor.prototype.uiCreateImport = function() {
+ var imports = document.createElement("div");
+ this.topBar.appendChild(imports);
+ imports.id = "edit-imports";
+
+ var songEdits = document.createElement("div");
+ imports.appendChild(songEdits);
+ var newSongBtn = this.createButton("New song", songEdits, false, "glow");
+ newSongBtn.onclick = () => {
+ this.newSong();
+ };
+ this.newSongBtn = newSongBtn;
+ var fromSong = this.createButton("Edit current song", songEdits, false, "glow");
+ fromSong.onclick = () => {
+ if(this.core.currentSong) {
+ this.newSong(this.core.currentSong);
+ }
+ };
+ this.fromSongBtn = fromSong;
+
+ var songInfos = document.createElement("div");
+ songInfos.className = "settings-individual";
+ songInfos.id = "edit-songstats";
+ imports.appendChild(songInfos);
+
+ this.loopLen = this.uiCreateSongStat("Loop length (s):", "0.00", songInfos);
+ this.buildLen = this.uiCreateSongStat("Build length (s):", "0.00", songInfos);
+ this.beatLen = this.uiCreateSongStat("Beat length (ms):", "0.00", songInfos);
+};
+
+HuesEditor.prototype.uiCreateSongStat = function(name, value, parent) {
+ var container = document.createElement("div");
+ parent.appendChild(container);
+ var label = document.createElement("span");
+ label.textContent = name;
+ container.appendChild(label);
+ var valueSpan = document.createElement("span");
+ valueSpan.textContent = value;
+ valueSpan.className = "edit-songstat-value";
+ container.appendChild(valueSpan);
+ return valueSpan;
+}
+
+HuesEditor.prototype.uiCreateEditArea = function() {
+ var editArea = document.createElement("div");
+ this.editArea = editArea;
+ editArea.id = "edit-area";
+ this.root.appendChild(editArea);
+
+ // Lock build/loop lengths
+ this.timeLock = document.createElement("div");
+ editArea.appendChild(this.timeLock);
+ this.timeLock.id = "edit-timelock";
+ this.timeLock.className = "hues-icon";
+ // CHAIN, use for CHAIN-BROKEN
+ this.createButton("", this.timeLock)
+ this.buildEdit = this.uiCreateSingleEditor("Buildup ", "buildup", "buildupRhythm", "edit-build", editArea);
+
+ // drag handle
+ var handleContainer = document.createElement("div");
+ handleContainer.id = "edit-resize-handle-container";
+ editArea.appendChild(handleContainer);
+ var handle = document.createElement("div");
+ handle.id = 'edit-resize-handle';
+ handle.className = 'hues-icon';
+ handle.innerHTML = ""; // DRAG HANDLE
+ handleContainer.appendChild(handle);
+ this.resizeHandle = handleContainer;
+
+ handleContainer.addEventListener("mousedown", (e) => {
+ e.preventDefault();
+
+ var resizer = (e) => {
+ var editTop = this.editArea.getBoundingClientRect().top;
+ this.buildEditSize = Math.floor(e.clientY - editTop);
+ this.resize();
+ };
+
+ var mouseup = function(e) {
+ document.removeEventListener("mousemove", resizer);
+ document.removeEventListener("mouseup", mouseup);
+ };
+
+ document.addEventListener("mousemove", resizer);
+ document.addEventListener("mouseup", mouseup);
+ });
+
+ this.loopEdit = this.uiCreateSingleEditor("Rhythm ", "sound", "rhythm", "edit-loop", editArea);
+
+ this.buildEdit._hilight.textContent = "[none]";
+ this.loopEdit._hilight.innerHTML =
+ '
\
+ Click [LOAD RHYTHM] to load a loop! LAME encoded MP3s work best.
\
+ (LAME is important for seamless MP3 loops)
\
+
\
+ [DOUBLE] doubles the selected map length by padding it with "."s.
\
+ [HALVE] shortens the map length by removing every other character.
\
+ [<< START] starts the song from the beginning.
\
+ [< LOOP] starts the song from the start of the loop.
\
+
\
+ You can also add a buildup with [LOAD BUILDUP], or remove it
\
+ with [REMOVE].
\
+
\
+ [NEW SONG] adds a completely empty song for you to edit, and
\
+ [EDIT CURRENT SONG] takes the current playing song to the editor.
\
+
\
+ [COPY/SAVE XML] allow for storing the rhythms and easy
\
+ inclusion into a Resource Pack!';
+};
+
+HuesEditor.prototype.uiCreateSingleEditor = function(title, soundName, rhythmName, id, parent) {
+ var container = document.createElement("div");
+ container.id = id;
+ parent.appendChild(container);
+
+ var header = document.createElement("div");
+ header.className = "edit-area-header";
+ container.appendChild(header);
+
+ var nameLabel = document.createElement("span");
+ header.appendChild(nameLabel);
+ nameLabel.innerHTML = title;
+ var beatCount = document.createElement("span");
+ header.appendChild(beatCount);
+ beatCount.className = "beat-count";
+ beatCount.textContent = "0 beats";
+ container._lockedBtn = this.createButton("", header, false, "hues-icon");
+ container._lockedBtn.onclick = () => {
+ if(container._locked) {
+ this.setLocked(container, 0);
+ } else {
+ var textLen = this.getText(container).length;
+ this.setLocked(container, textLen);
+ }
+ };
+
+ var rightHeader = document.createElement("span");
+ rightHeader.className = "edit-area-right-header";
+ header.appendChild(rightHeader);
+
+ container._halveBtn = this.createButton("Halve", rightHeader);
+ container._halveBtn.onclick = this.halveBeats.bind(this, container)
+ container._doubleBtn = this.createButton("Double", rightHeader);
+ container._doubleBtn.onclick = this.doubleBeats.bind(this, container)
+
+ var fileInput = document.createElement("input");
+ fileInput.type ="file";
+ fileInput.accept="audio/mpeg3";
+ fileInput.multiple = false;
+ fileInput.onchange = this.loadAudio.bind(this, container);
+ var load = this.createButton("Load " + title.replace(/ /g,""), rightHeader);
+ load.onclick = () => {fileInput.click()};
+
+ container._removeBtn = this.createButton("Remove", rightHeader, true);
+
+ var editBox = document.createElement("div");
+ editBox.className = "edit-box";
+ var beatmap = document.createElement("div");
+ beatmap.className = "beatmap";
+ beatmap.contentEditable = true;
+ beatmap.spellcheck = false;
+ beatmap.oninput = this.textUpdated.bind(this, container);
+
+ var beatHilight = document.createElement("div");
+ beatHilight.className = "beat-hilight";
+
+ editBox.appendChild(beatHilight);
+ editBox.appendChild(beatmap);
+ container.appendChild(editBox);
+
+ container._header = header;
+ container._beatCount = beatCount;
+ container._box = editBox;
+ container._beatmap = beatmap;
+ container._hilight = beatHilight;
+ container._fileInput = fileInput;
+
+ container._sound = soundName;
+ container._rhythm = rhythmName;
+
+ // Are we in insert mode? Default = no
+ this.setLocked(container, 0);
+
+ return container;
+}
+
+HuesEditor.prototype.textUpdated = function(editor) {
+ if(!this.song || !this.song[editor._sound]) {
+ this.reflow(editor, "");
+ return;
+ }
+ // Space at start of line is nonbreaking, get it with \u00a0
+ var input = editor._beatmap.textContent.replace(/ |\u00a0/g, "");
+ if(input.length == 0) {
+ input = ".";
+ }
+ this.setText(editor, input);
+}
+
+HuesEditor.prototype.getText = function(editor) {
+ if(!this.song || !this.song[editor._rhythm]) {
+ return "";
+ } else {
+ return this.song[editor._rhythm];
+ }
+};
+
+HuesEditor.prototype.setText = function(editor, text) {
+ if(!this.song || !this.song[editor._sound]) {
+ this.reflow(editor, "");
+ return;
+ }
+ var caret = this.getCaret(editor._beatmap);
+ if(editor._locked) {
+ caret = Math.min(editor._locked, caret);
+ if(text.length > editor._locked) {
+ // Works for pastes too! Removes the different between sizes from the caret position
+ text = text.slice(0, caret) + text.slice(caret + (text.length - editor._locked), text.length)
+ } else {
+ while(text.length < editor._locked) {
+ text += ".";
+ }
+ }
+ }
+ this.pushUndo(editor._rhythm, editor, this.song[editor._rhythm], text);
+ this.song[editor._rhythm] = text
+ this.reflow(editor, this.song[editor._rhythm]);
+ this.setCaret(editor._beatmap, caret);
+ this.updateHalveDoubleButtons(editor);
+
+ this.core.updateBeatLength();
+ // We may have to go backwards in time
+ this.core.recalcBeatIndex();
+ this.updateInfo();
+}
+
+HuesEditor.prototype.getCaret = function(editable) {
+ var caret = 0;
+ var sel = window.getSelection();
+ if (sel.rangeCount) {
+ var range = sel.getRangeAt(0);
+ //
elements are empty, and pastes do weird things.
+ // So don't go up in multiples of 2 for getCaret
+ for(var i = 0; i < editable.childNodes.length; i++) {
+ if (range.commonAncestorContainer == editable.childNodes[i]) {
+ caret += range.endOffset;
+ return caret;
+ } else {
+ caret += editable.childNodes[i].textContent.length;
+ }
+ }
+ }
+ return 0;
+}
+
+HuesEditor.prototype.setCaret = function(editable, caret) {
+ var range = document.createRange();
+ var sel = window.getSelection();
+ //
elements mean children go up in multiples of 2
+ for(var i = 0; i < editable.childNodes.length; i+= 2) {
+ var textLen = editable.childNodes[i].textContent.length;
+ if(caret > textLen) {
+ caret -= textLen;
+ } else {
+ range.setStart(editable.childNodes[i], caret);
+ range.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ break;
+ }
+ }
+}
+
+HuesEditor.prototype.updateHalveDoubleButtons = function(editor) {
+ editor._halveBtn.className = "hues-button disabled";
+ editor._doubleBtn.className = "hues-button disabled";
+
+ if(!editor._locked) {
+ var txtLen = this.getText(editor).length;
+ if(txtLen > 0) {
+ editor._doubleBtn.className = "hues-button";
+ }
+ if(txtLen > 1) {
+ editor._halveBtn.className = "hues-button";
+ }
+ }
+}
+
+HuesEditor.prototype.setLocked = function(editor, locked) {
+ editor._locked = locked;
+ if(locked) {
+ editor._lockedBtn.innerHTML = ""; // LOCKED
+ } else {
+ editor._lockedBtn.innerHTML = ""; // UNLOCKED
+ }
+ this.updateHalveDoubleButtons(editor);
+}
+
+HuesEditor.prototype.uiCreateControls = function() {
+ var controls = document.createElement("div");
+ controls.id = "edit-controls";
+ this.root.appendChild(controls);
+
+ this.seekStart = this.createButton("<< Start", controls, true);
+ this.seekStart.onclick = () => {
+ this.core.soundManager.seek(-this.core.soundManager.buildLength);
+ };
+ this.seekLoop = this.createButton("< Loop", controls, true);
+ this.seekLoop.onclick = () => {
+ this.core.soundManager.seek(0);
+ };
+
+ var playRateLab = document.createElement("span");
+ playRateLab.className = "settings-individual";
+ playRateLab.textContent = "1.00x";
+ controls.appendChild(playRateLab);
+
+ var changeRate = function(change) {
+ var rate = this.core.soundManager.playbackRate;
+ rate += change;
+ this.core.soundManager.setRate(rate);
+ // In case it gets clamped, check
+ var newRate = this.core.soundManager.playbackRate;
+ playRateLab.textContent = newRate.toFixed(2) + "x";
+ }
+
+ // BACKWARD
+ var speedDown = this.createButton("", controls, false, "hues-icon");
+ speedDown.onclick = changeRate.bind(this, -0.25);
+ // FORWARD
+ var speedUp = this.createButton("", controls, false, "hues-icon");
+ speedUp.onclick = changeRate.bind(this, 0.25);
+
+ var wrapLab = document.createElement("span");
+ wrapLab.className = "settings-individual";
+ wrapLab.textContent = "New line at beat ";
+ controls.appendChild(wrapLab);
+
+ var wrapAt = document.createElement("input");
+ wrapAt.className = "settings-input";
+ wrapAt.value = this.wrapAt;
+ wrapAt.type = "text";
+ wrapAt.oninput = () => {
+ wrapAt.value = wrapAt.value.replace(/\D/g,'');
+ if(wrapAt.value == "" || wrapAt.value < 1) {
+ wrapAt.value = "";
+ return;
+ }
+ this.wrapAt = parseInt(wrapAt.value);
+ this.reflow(this.buildEdit, this.song.buildupRhythm);
+ this.reflow(this.loopEdit, this.song.rhythm);
+
+ };
+ controls.appendChild(wrapAt);
+};
+
+HuesEditor.prototype.uiCreateVisualiser = function() {
+ // TODO placeholder
+ var waveDiv = document.createElement("div");
+ waveDiv.id = "edit-waveform";
+ this.root.appendChild(waveDiv);
+};
+
+HuesEditor.prototype.generateXML = function() {
+ if(!this.song) {
+ return null;
+ }
+ // Yes, this is just a bunch of strings. Simple XML, simple method.
+
+ var result = " \n";
+ result += " " + this.song.title + "\n";
+ if(this.song.source) {
+ result += " " + this.song.source + "\n";
+ }
+ result += " " + this.song.rhythm + "\n";
+ if(this.song.buildup) {
+ result += " " + this.song.buildupName + "\n";
+ result += " " + this.song.buildupRhythm + "\n";
+ }
+ result += " \n";
+ return result;
+}
+
+HuesEditor.prototype.saveXML = function() {
+ var xml = this.generateXML();
+ if(!xml) {
+ return;
+ }
+ var result = "\n";
+ result += xml;
+ result += "\n";
+
+ // http://stackoverflow.com/a/18197341
+ var element = document.createElement('a');
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
+ element.setAttribute('download', "0x40Hues - " + this.song.name + ".xml");
+
+ element.style.display = 'none';
+ document.body.appendChild(element);
+
+ element.click();
+
+ document.body.removeChild(element);
+}
+
+// http://stackoverflow.com/a/30810322
+HuesEditor.prototype.copyXML = function() {
+ var text = this.generateXML();
+
+ // Clicking when disabled
+ if(!text) {
+ return;
+ }
+
+ var textArea = document.createElement("textarea");
+ textArea.id = "edit-copybox";
+
+ textArea.value = text;
+
+ document.body.appendChild(textArea);
+
+ textArea.select();
+
+ var success;
+
+ try {
+ success = document.execCommand('copy');
+ } catch (err) {
+ success = false;
+ }
+
+ document.body.removeChild(textArea);
+ if(success) {
+ alert("Beatmap XML copied to clipboard!");
+ } else {
+ alert("Copy failed! Try saving instead");
+ }
+
+}
+
+ function drawWaveform(buffer) {
+ var ca = document.querySelector("#waveform");
+ var c = ca.getContext("2d");
+
+ var samples = buffer.getChannelData(0);
+ var width = (buffer.duration * secondWidth) | 0;
+
+ ca.width = width;
+ ca.height = 128;
+
+ c.strokeStyle = "black";
+
+ var samplesInColumn = (samples.length / width) | 0;
+
+ // console.log(samplesInColumn)
+
+ var t, b;
+ for (var i = 0; i < width; i++) {
+ var pmin = 999;
+ var pmax = 0;
+
+ var nmin = 0;
+ var nmax = -999;
+ for (var j = 0; j < samplesInColumn; j++) {
+ var s = samples[i*samplesInColumn+j]
+ // console.log(s)
+ if (s > 0 && s < pmin) { pmin = s;}
+ else if (s > 0 && s > pmax) { pmax = s;}
+
+ else if (s < 0 && s < nmin) { nmin = s;}
+ else if (s < 0 && s > nmax) { nmax = s;}
+ };
+
+ // console.log(pmax, pmin, nmax, nmin)
+ var ch = ca.height;
+
+ // t = (pmax) * ch/2
+ // b = (2+nmin) * ch/2
+
+ t = (1-pmax) * ch/2
+ b = ch - (1+nmin) * ch/2
+
+
+
+ c.strokeStyle = "black";
+ c.beginPath()
+ c.moveTo(i, t)
+ c.lineTo(i, b)
+ c.stroke();
+
+ // t = (1-pmax) * ch/2
+ // b = ch - (1+nmin) * ch/2
+ // c.strokeStyle = "green";
+ // c.beginPath()
+ // c.moveTo(i, t)
+ // c.lineTo(i, b)
+ // c.stroke();
+ // pixel(c, i, t, [255,0,0,255])
+ // pixel(c, i, b, [0,0,255,255])
+ // console.log(pmin, nmax)
+ };
+
+ }
+
+window.HuesEditor = HuesEditor;
+
+})(window, document);
\ No newline at end of file
diff --git a/src/js/HuesSettings.js b/src/js/HuesSettings.js
index fdca3af..df45f3f 100644
--- a/src/js/HuesSettings.js
+++ b/src/js/HuesSettings.js
@@ -238,6 +238,18 @@ function HuesSettings(defaults) {
// because we still care about the main window
document.getElementById("closeButton").onclick = this.hide.bind(this);
+ // we also care about tabs looking nice.
+ var tabs = document.getElementsByClassName("tab-label");
+ for(var i = 0; i < tabs.length; i++) {
+ var check = document.getElementById(tabs[i].htmlFor);
+ check._label = tabs[i];
+ check.addEventListener("change", function() { // Uses 'this' of check!
+ for(var i = 0; i < tabs.length; i++) {
+ tabs[i].className = "tab-label";
+ }
+ this._label.className = "tab-label checked";
+ });
+ }
if(!this.defaults.noUI) {
this.initUI();
}
@@ -251,7 +263,8 @@ HuesSettings.prototype.connectCore = function(core) {
HuesSettings.prototype.show = function() {
if(this.core)
this.core.hideLists();
- this.window.style.display = "block";
+ this.window.style.display = "-webkit-flex";
+ this.window.style.display = "flex";
};
HuesSettings.prototype.hide = function() {
@@ -260,32 +273,30 @@ HuesSettings.prototype.hide = function() {
HuesSettings.prototype.toggle = function() {
if(this.window.style.display == "none") {
- this.window.style.display = "block";
- if(this.core)
- this.core.hideLists();
+ this.show();
} else {
- this.window.style.display = "none";
+ this.hide();
}
};
HuesSettings.prototype.showRespacks = function() {
this.show();
- document.getElementById("tab1").checked = true;
+ document.getElementById("tab-resources").click();
};
HuesSettings.prototype.showEditor = function() {
this.show();
- document.getElementById("tab2").checked = true;
+ document.getElementById("tab-editor").click();
};
HuesSettings.prototype.showOptions = function() {
this.show();
- document.getElementById("tab3").checked = true;
+ document.getElementById("tab-options").click();
};
HuesSettings.prototype.showInfo = function() {
this.show();
- document.getElementById("tab4").checked = true;
+ document.getElementById("tab-info").click();
};
HuesSettings.prototype.initUI = function() {
diff --git a/src/js/ResourcePack.js b/src/js/ResourcePack.js
index ecd3e48..3602a85 100644
--- a/src/js/ResourcePack.js
+++ b/src/js/ResourcePack.js
@@ -421,17 +421,17 @@ Respack.prototype.parseSongFile = function(text) {
if(song.buildupLength) {
debug("Using custom BU length:", song.buildupLength);
}
- song.buildup = el.getTag("buildup");
- if(song.buildup) {
- debug(" Finding a buildup '" + song.buildup + "' for ", song.name);
- var build = this.getSong(song.buildup);
+ song.buildupName = el.getTag("buildup");
+ if(song.buildupName) {
+ debug(" Finding a buildup '" + song.buildupName + "' for ", song.name);
+ var build = this.getSong(song.buildupName);
if(build) {
song.buildup = build.sound;
song.buildupPlayed = false;
// get rid of the junk
this.songs.splice(this.songs.indexOf(build), 1);
} else {
- debug(" WARNING!", "Didn't find a buildup '" + song.buildup + "'!");
+ debug(" WARNING!", "Didn't find a buildup '" + song.buildupName + "'!");
}
}
diff --git a/src/js/SoundManager.js b/src/js/SoundManager.js
index 95f5899..fb45301 100644
--- a/src/js/SoundManager.js
+++ b/src/js/SoundManager.js
@@ -25,6 +25,7 @@
function SoundManager(core) {
this.core = core;
this.playing = false;
+ this.playbackRate = 1;
this.song = null;
this.initPromise = null;
@@ -130,9 +131,10 @@ SoundManager.prototype.init = function() {
return this.initPromise;
}
-SoundManager.prototype.playSong = function(song, playBuild) {
+SoundManager.prototype.playSong = function(song, playBuild, forcePlay) {
var p = Promise.resolve();
- if(this.song == song) {
+ // Editor forces play on audio updates
+ if(this.song == song && !forcePlay) {
return p;
}
this.stop();
@@ -173,8 +175,6 @@ SoundManager.prototype.playSong = function(song, playBuild) {
}
return this.context.resume();
- }).then(() => {
- this.playing = true;
}).catch(error => {
// Just to ignore it if the song was invalid
// Log it in case it's something weird
@@ -202,8 +202,20 @@ SoundManager.prototype.stop = function() {
}
};
+SoundManager.prototype.setRate = function(rate) {
+ // Double speed is more than enough. Famous last words?
+ rate = Math.max(Math.min(rate, 2), 0.25);
+
+ var time = this.clampedTime();
+ this.playbackRate = rate;
+ this.seek(time);
+}
+
SoundManager.prototype.seek = function(time) {
- console.log("Seeking to " + time);
+ if(!this.song) {
+ return;
+ }
+ //console.log("Seeking to " + time);
// Clamp the blighter
time = Math.min(Math.max(time, -this.buildLength), this.loopLength);
@@ -211,6 +223,7 @@ SoundManager.prototype.seek = function(time) {
this.loopSource = this.context.createBufferSource();
this.loopSource.buffer = this.loop;
+ this.loopSource.playbackRate.value = this.playbackRate;
this.loopSource.loop = true;
this.loopSource.loopStart = 0;
this.loopSource.loopEnd = this.loopLength;
@@ -219,14 +232,17 @@ SoundManager.prototype.seek = function(time) {
if(time < 0) {
this.buildSource = this.context.createBufferSource();
this.buildSource.buffer = this.buildup;
+ this.buildSource.playbackRate.value = this.playbackRate;
this.buildSource.connect(this.gainNode);
this.buildSource.start(0, this.buildLength + time);
- this.loopSource.start(this.context.currentTime - time);
+ this.loopSource.start(this.context.currentTime - (time / this.playbackRate));
} else {
this.loopSource.start(0, time);
}
- this.startTime = this.context.currentTime - time;
+ this.startTime = this.context.currentTime - (time / this.playbackRate);
+ this.playing = true;
+ this.core.recalcBeatIndex();
}
// In seconds, relative to the loop start
@@ -234,18 +250,24 @@ SoundManager.prototype.currentTime = function() {
if(!this.playing) {
return 0;
}
- return this.context.currentTime - this.startTime;
+ return (this.context.currentTime - this.startTime) * this.playbackRate;
};
-SoundManager.prototype.displayableTime = function() {
- if(!this.playing) {
- return 0;
- }
+SoundManager.prototype.clampedTime = function() {
var time = this.currentTime();
+
+ if(time > 0) {
+ time %= this.loopLength;
+ }
+ return time;
+}
+
+SoundManager.prototype.displayableTime = function() {
+ var time = this.clampedTime();
if(time < 0) {
return 0;
} else {
- return time % this.loopLength;
+ return time;
}
};