Add beatmap editor

master
William Toohey 10 years ago
parent d583638160
commit eae16ce535
  1. BIN
      fonts/HuesExtra.eot
  2. 7
      fonts/HuesExtra.svg
  3. BIN
      fonts/HuesExtra.ttf
  4. BIN
      fonts/HuesExtra.woff
  5. 26
      index.html
  6. 190
      src/css/hues-editor.css
  7. 61
      src/css/style.css
  8. 40
      src/js/HuesCore.js
  9. 949
      src/js/HuesEditor.js
  10. 29
      src/js/HuesSettings.js
  11. 10
      src/js/ResourcePack.js
  12. 48
      src/js/SoundManager.js

Binary file not shown.

@ -11,4 +11,11 @@
<glyph unicode="&#xe901;" glyph-name="play3" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe902;" glyph-name="pause2" d="M128 832h320v-768h-320zM576 832h320v-768h-320z" />
<glyph unicode="&#xe903;" glyph-name="shuffle" d="M768 256h-101.49l-160 160 160 160h101.49v-160l224 224-224 224v-160h-128c-16.974 0-33.252-6.744-45.254-18.746l-178.746-178.744-178.746 178.746c-12 12-28.28 18.744-45.254 18.744h-192v-128h165.49l160-160-160-160h-165.49v-128h192c16.974 0 33.252 6.742 45.254 18.746l178.746 178.744 178.746-178.744c12.002-12.004 28.28-18.746 45.254-18.746h128v-160l224 224-224 224v-160z" />
<glyph unicode="&#xe904;" glyph-name="chain-broken" horiz-adv-x="951" d="M250.857 212.381l-146.286-146.286q-5.714-5.143-13.143-5.143-6.857 0-13.143 5.143-5.143 5.714-5.143 13.143t5.143 13.143l146.286 146.286q5.714 5.143 13.143 5.143t13.143-5.143q5.143-5.714 5.143-13.143t-5.143-13.143zM347.429 188.953v-182.857q0-8-5.143-13.143t-13.143-5.143-13.143 5.143-5.143 13.143v182.857q0 8 5.143 13.143t13.143 5.143 13.143-5.143 5.143-13.143zM219.429 316.953q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143 5.143 13.143 13.143 5.143h182.857q8 0 13.143-5.143t5.143-13.143zM941.714 243.81q0-68.571-48.571-116l-84-83.429q-47.429-47.429-116-47.429-69.143 0-116.571 48.571l-190.857 191.429q-12 12-24 32l136.571 10.286 156-156.571q15.429-15.429 38.857-15.714t38.857 15.143l84 83.429q16 16 16 38.286 0 22.857-16 38.857l-156.571 157.143 10.286 136.571q20-12 32-24l192-192q48-49.143 48-116.571zM589.143 657.524l-136.571-10.286-156 156.571q-16 16-38.857 16-22.286 0-38.857-15.429l-84-83.429q-16-16-16-38.286 0-22.857 16-38.857l156.571-156.571-10.286-137.143q-20 12-32 24l-192 192q-48 49.143-48 116.571 0 68.571 48.571 116l84 83.429q47.429 47.429 116 47.429 69.143 0 116.571-48.571l190.857-191.429q12-12 24-32zM950.857 609.524q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143 5.143 13.143 13.143 5.143h182.857q8 0 13.143-5.143t5.143-13.143zM640 920.381v-182.857q0-8-5.143-13.143t-13.143-5.143-13.143 5.143-5.143 13.143v182.857q0 8 5.143 13.143t13.143 5.143 13.143-5.143 5.143-13.143zM872.571 834.096l-146.286-146.286q-6.286-5.143-13.143-5.143t-13.143 5.143q-5.143 5.714-5.143 13.143t5.143 13.143l146.286 146.286q5.714 5.143 13.143 5.143t13.143-5.143q5.143-5.714 5.143-13.143t-5.143-13.143z" />
<glyph unicode="&#xe905;" glyph-name="chain" horiz-adv-x="951" d="M832 243.81q0 22.857-16 38.857l-118.857 118.857q-16 16-38.857 16-24 0-41.143-18.286 1.714-1.714 10.857-10.571t12.286-12.286 8.571-10.857 7.429-14.571 2-15.714q0-22.857-16-38.857t-38.857-16q-8.571 0-15.714 2t-14.571 7.429-10.857 8.571-12.286 12.286-10.571 10.857q-18.857-17.714-18.857-41.714 0-22.857 16-38.857l117.714-118.286q15.429-15.429 38.857-15.429 22.857 0 38.857 14.857l84 83.429q16 16 16 38.286zM430.286 646.667q0 22.857-16 38.857l-117.714 118.286q-16 16-38.857 16-22.286 0-38.857-15.429l-84-83.429q-16-16-16-38.286 0-22.857 16-38.857l118.857-118.857q15.429-15.429 38.857-15.429 24 0 41.143 17.714-1.714 1.714-10.857 10.571t-12.286 12.286-8.571 10.857-7.429 14.571-2 15.714q0 22.857 16 38.857t38.857 16q8.571 0 15.714-2t14.571-7.429 10.857-8.571 12.286-12.286 10.571-10.857q18.857 17.714 18.857 41.714zM941.714 243.81q0-68.571-48.571-116l-84-83.429q-47.429-47.429-116-47.429-69.143 0-116.571 48.571l-117.714 118.286q-47.429 47.429-47.429 116 0 70.286 50.286 119.429l-50.286 50.286q-49.143-50.286-118.857-50.286-68.571 0-116.571 48l-118.857 118.857q-48 48-48 116.571t48.571 116l84 83.429q47.429 47.429 116 47.429 69.143 0 116.571-48.571l117.714-118.286q47.429-47.429 47.429-116 0-70.286-50.286-119.429l50.286-50.286q49.143 50.286 118.857 50.286 68.571 0 116.571-48l118.857-118.857q48-48 48-116.571z" />
<glyph unicode="&#xe906;" glyph-name="lock" d="M592 512h-16v192c0 105.87-86.13 192-192 192h-128c-105.87 0-192-86.13-192-192v-192h-16c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h544c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48zM192 704c0 35.29 28.71 64 64 64h128c35.29 0 64-28.71 64-64v-192h-256v192z" />
<glyph unicode="&#xe907;" glyph-name="unlocked" d="M768 896c105.87 0 192-86.13 192-192v-192h-128v192c0 35.29-28.71 64-64 64h-128c-35.29 0-64-28.71-64-64v-192h16c26.4 0 48-21.6 48-48v-480c0-26.4-21.6-48-48-48h-544c-26.4 0-48 21.6-48 48v480c0 26.4 21.6 48 48 48h400v192c0 105.87 86.13 192 192 192h128z" />
<glyph unicode="&#xe908;" glyph-name="menu" d="M128 682.667h768v-86h-768v86zM128 384.667v84h768v-84h-768zM128 170.667v86h768v-86h-768z" />
<glyph unicode="&#xe909;" glyph-name="backward2" d="M576 800v-320l320 320v-704l-320 320v-320l-352 352z" />
<glyph unicode="&#xe90a;" glyph-name="forward3" d="M512 96v320l-320-320v704l320-320v320l352-352z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Binary file not shown.

@ -33,21 +33,29 @@
<div id="settingsWindow">
<div id="closeButton">x</div>
<div id="tabs">
<input class="tab-input" type="radio" name="tabs" id="tab1">
<label class="tab-label" for="tab1">RESOURCES</label>
<input class="tab-input" type="radio" name="tabs" id="tab2">
<label class="tab-label" for="tab2">OPTIONS</label>
<input class="tab-input" type="radio" name="tabs" id="tab3" checked>
<label class="tab-label" for="tab3">INFO</label>
<div id="tab1-content" class="tab-content">
<label class="tab-label" for="tab-resources">RESOURCES</label>
<label class="tab-label checked" for="tab-editor">EDITOR</label>
<label class="tab-label" for="tab-options">OPTIONS</label>
<label class="tab-label" for="tab-info">INFO</label>
</div>
<div>
<input class="tab-input" type="radio" name="tabs" id="tab-resources">
<input class="tab-input" type="radio" name="tabs" id="tab-editor" checked>
<input class="tab-input" type="radio" name="tabs" id="tab-options">
<input class="tab-input" type="radio" name="tabs" id="tab-info">
<div id="tab-resources-content" class="tab-content">
<!-- Populated by ResourceManager.js -->
<div id="huesResources"></div>
</div>
<div id="tab2-content" class="tab-content">
<div id="tab-editor-content" class="tab-content">
<!-- Populated by HuesEditor.js -->
<div id="huesEditor"></div>
</div>
<div id="tab-options-content" class="tab-content">
<!-- Populated by HuesSettings.js -->
<div id="huesSettings"></div>
</div>
<div id="tab3-content" class="tab-content">
<div id="tab-info-content" class="tab-content">
<div id="about">
<h1>0x40 Hues of JS, <span id="versionText"><!-- Populated by HuesInfo.js --></span></h1>
<h2>Adapted from the <a target="_blank" href="http://0x40hues.blogspot.com">0x40 Flash</a></h2>

@ -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;
}

@ -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);
}
}

@ -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':

@ -0,0 +1,949 @@
/* Copyright (c) William Toohey <will@mon.im>
*
* 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 = "&block;";
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:&nbsp;", "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 = "&block;";
this.loopEdit._hilight.innerHTML = "&block;";
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 = "&block;";
}
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<br />");
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 &#xe904; for CHAIN-BROKEN
this.createButton("&#xe905;", this.timeLock)
this.buildEdit = this.uiCreateSingleEditor("Buildup&nbsp;", "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 = "&#xe908;"; // 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&nbsp;&nbsp;", "sound", "rhythm", "edit-loop", editArea);
this.buildEdit._hilight.textContent = "[none]";
this.loopEdit._hilight.innerHTML =
'<br />\
Click [LOAD RHYTHM] to load a loop! LAME encoded MP3s work best.<br />\
(LAME is important for seamless MP3 loops)<br />\
<br />\
[DOUBLE] doubles the selected map length by padding it with "."s.<br />\
[HALVE] shortens the map length by removing every other character.<br />\
[<< START] starts the song from the beginning.<br />\
[< LOOP] starts the song from the start of the loop.<br />\
<br />\
You can also add a buildup with [LOAD BUILDUP], or remove it<br />\
with [REMOVE].<br />\
<br />\
[NEW SONG] adds a completely empty song for you to edit, and<br />\
[EDIT CURRENT SONG] takes the current playing song to the editor.<br />\
<br />\
[COPY/SAVE XML] allow for storing the rhythms and easy <br />\
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(/&nbsp;/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);
// <br> 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();
// <br> 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 = "&#xe906;"; // LOCKED
} else {
editor._lockedBtn.innerHTML = "&#xe907;"; // 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("&#xe909;", controls, false, "hues-icon");
speedDown.onclick = changeRate.bind(this, -0.25);
// FORWARD
var speedUp = this.createButton("&#xe90a;", 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 = " <song name=\"" + this.song.name + "\">\n";
result += " <title>" + this.song.title + "</title>\n";
if(this.song.source) {
result += " <source>" + this.song.source + "</source>\n";
}
result += " <rhythm>" + this.song.rhythm + "</rhythm>\n";
if(this.song.buildup) {
result += " <buildup>" + this.song.buildupName + "</buildup>\n";
result += " <buildupRhythm>" + this.song.buildupRhythm + "</buildupRhythm>\n";
}
result += " </song>\n";
return result;
}
HuesEditor.prototype.saveXML = function() {
var xml = this.generateXML();
if(!xml) {
return;
}
var result = "<songs>\n";
result += xml;
result += "</songs>\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);

@ -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() {

@ -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 + "'!");
}
}

@ -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;
}
};

Loading…
Cancel
Save