Skip to content

Instantly share code, notes, and snippets.

@ryanmead
Last active March 1, 2017 06:52
Show Gist options
  • Select an option

  • Save ryanmead/e92662185be02a9dfca2bf6e0adf2f40 to your computer and use it in GitHub Desktop.

Select an option

Save ryanmead/e92662185be02a9dfca2bf6e0adf2f40 to your computer and use it in GitHub Desktop.
rm 101
<p>under construction, come back later</p>
<div id="boom-box">
<div id="loader" class="control-group"><h2>file</h2>
<select id="patternLoader"></select>
<a id="save-pattern" class="control-button">log pattern</a>
</div>
<div id="transport" class="control-group"><h2>transport</h2>
<a id="play" class="control-button">play</a>
tempo <input id="tempo" type="range" min="30.0" max="160.0" step="1"> <span id="showTempo">--</span>&nbsp;bpm
16th note swing <input id="swing" type="range" min="1" max="1.33" step="0.01" value="1">
</div>
<div id="melody" class="control-group"><h2>melody</h2>
<div class="seq-group">
<div class="pitch-adjust up"><div class="triangle-up"></div></div>
<div class="seq-pad">--</div>
<div class="pitch-adjust down"><div class="triangle-down"></div></div>
</div>
</div>
<div id="hihat" class="control-group"><h2>hihat</h2><div class="perc-pad"></div></div>
<div id="snare" class="control-group"><h2>snare</h2><div class="perc-pad"></div></div>
<div id="kick" class="control-group"><h2>kick</h2><div class="perc-pad"></div></div>
<div id="patches" class="control-group"><h2>patch selector</h2>
<select id="melodySounds">
<option>chainsaw</option>
<option>organism</option>
</select><label>snare</label>
<select id="snareSounds">
<option>boring</option>
<option>overblown</option>
<option>d n b</option>
</select><label>snare</label>
</div>
<div id="mixer" class="control-group"><h2>mixer</h2>
<div class="mix-channel">
<div class="vert-sliders">
<input id="melody-mix-level" type="range" min="0" max="1" value="0" step="0.01"> level
<input id="melody-accent" type="range" min="0" max="3" value="0" step="0.01"> accent
</div>
<div class="channel-label">melody</div>
</div>
<div class="mix-channel">
<div class="vert-sliders">
<input id="kick-mix-level" type="range" min="0" max="1" value="0" step="0.01"> level
<input id="kick-accent" type="range" min="0" max="3" value="0" step="0.01"> accent
</div>
<div class="channel-label">kick</div>
</div>
<div class="mix-channel">
<div class="vert-sliders">
<input id="snare-mix-level" type="range" min="0" max="1" value="0" step="0.01"> level
<input id="snare-accent" type="range" min="0" max="3" value="0" step="0.01"> accent
</div>
<div class="channel-label">snare</div>
</div>
<div class="mix-channel">
<div class="vert-sliders">
<input id="hi-hat-mix-level" type="range" min="0" max="1" value="0" step="0.01"> level
<input id="hh-accent" type="range" min="0" max="3" value="0.1" step="0.01"> accent
</div>
<div class="channel-label">hihat</div>
</div>
</div>
</div>
<p>With acknowledgement and thanks to Chris Wilson for metronome.js, which is available <a href="https://github.com/cwilso">on his github</a> and of which this is a fork.</p>

rm 101

Under construction. It's a step sequencer (or at least it will be someday).

Underlying metronome based on //github.com/cwilso/metronome by Chris Wilson.

A Pen by ryan on CodePen.

License.

var beatbox = (function() { //MAIN
// shim the requestAnimationFrame API with a setTimeout fallback
//Chris
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})(); 
//timer worker by Chris Wilson; as a BLOB here b/c I couldn't figure out how to interact with a worker module in another pen
var metronomeWorkerBlob = new Blob(["var timerID=null;var interval=100;self.onmessage=function(e){ if (e.data==\"start\"){timerID=setInterval(function(){postMessage(\"tick\");},interval) } else if (e.data.interval) { console.log(\"setting interval\"); interval=e.data.interval; console.log(\"interval=\"+interval); if (timerID) { clearInterval(timerID); timerID=setInterval(function(){postMessage(\"tick\");},interval) } } else if (e.data==\"stop\") { console.log(\"stopping\"); clearInterval(timerID); timerID=null; }};postMessage('messageWorker last line');"]);
//original vars defined by Chris Wilson
var audioContext = null;
var isPlaying = false; // Are we currently playing?
var startTime; // The start time of the entire sequence.
var currentStep; // What step is currently the last scheduled?
var tempo; // tempo (in beats per minute)
var lookahead = 20.0; // How frequently to call scheduling function (in milliseconds)
var scheduleAheadTime = 0.13; // How far ahead to schedule audio (sec)
// calculated from lookahead and overlaps
// next interval (if timer is late)
var nextStepTime = 0.0; // when the next step is due.
//var hiHatResolution; // 0 == 16th, 1 == 8th, 2 == quarter note
var lastStepDrawn = -1; // the last "box" we drew on the screen
var stepsInQueue = []; // the steps that have been put into the web audio,
// and may or may not have played yet. {step, time}
var timerWorker = null; // The Web Worker used to fire timer messages
var unlocked = false;
//new vars since I forked it
var melodyPads = [], kickPads = [], snarePads = [], hhPads = [];
var midiNoteFreqs = [16.35, 17.32, 18.35, 19.45, 20.60, 21.83, 23.12, 24.50, 25.96, 27.50, 29.14, 30.87, 32.70, 34.65, 36.71, 38.89, 41.20, 43.65, 46.25, 49.00, 51.91, 55.00, 58.27, 61.74, 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.83, 110.00, 116.54, 123.47, 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94, 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, 523.25, 554.37, 587.33, 622.25, 659.25, 698.46, 739.99, 783.99, 830.61, 880.00, 932.33, 987.77, 1046.50, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760.00, 1864.66, 1975.53, 2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520.00, 3729.31, 3951.07, 4186.01, 4434.92, 4698.63, 4978.03, 5274.04, 5587.65, 5919.91, 6271.93, 6644.88, 7040.00, 7458.62, 7902.13];
var numSteps = 1;
var forceRestart = false;
//var toggledPad = -1; // for mouseover to toggle on/off multiple pads
//stores state of clicked-on pad
//under construction
var patternLoader = document.getElementById("patternLoader");
var tempoSlider = document.getElementById("tempo");
var swingControl = document.getElementById("swing");
var hiHatBuffer, snareBuffer;
var hhMixLevel = document.getElementById("hi-hat-mix-level");
var hhAccent = document.getElementById("hh-accent");
var melodyMixLevel = document.getElementById("melody-mix-level");
var melodyAccent = document.getElementById("melody-accent");
var snareMixLevel = document.getElementById("snare-mix-level");
var snareAccent = document.getElementById("snare-accent");
var kickMixLevel = document.getElementById("kick-mix-level");
var kickAccent = document.getElementById("kick-accent");
document.onclick = function(evt, yadda) { //general click event handler
if (evt.target.className.indexOf("seq-pad")!==-1) {
togglePad(evt.target, "seq-pad"); //toggle pad on and off
} else if (evt.target.className.indexOf("perc-pad")!==-1) {
togglePad(evt.target, "perc-pad"); //toggle pad on and off
} else if (evt.target.className.indexOf("pitch-adjust")!==-1) {
adjustPitch(evt.target.parentElement, (evt.target.className.indexOf("up")!==-1)); //go up or down a semitone
} else if (evt.target.className.indexOf("triangle")!==-1) {
adjustPitch(evt.target.parentElement.parentElement, (evt.target.parentElement.className.indexOf("up")!==-1)); //passthru for triangle shapes
} else {
switch (evt.target.id) {
case "play": //clear output "field"
//igniteIOS();
transport();
break;
case "save-pattern":
logPattern();
break;
default: //clicked elsewhere
}
}
};
document.onkeypress = function (e) { //general keypress event handler
e = e || window.event;
if (e.keyCode===32) { //toggle play/pause when press spacebar
transport();
}
};
patternLoader.onchange = function() {
loadPattern(event.target.selectedIndex);
}
tempoSlider.oninput = function() {
setTempo(event.target.value);
}
/*
document.addEventListener("mouseover", function(evt) { //mouseover event handler
if ((evt.target.className.indexOf("seq-pad")!==-1)&&(evt.buttons === 1)) {
togglePad(evt.target); //toggle pad on and off
}
});
document.addEventListener("touchmove", function(evt) { //touchmove event handler; under construction
if (evt.target.className.indexOf("seq-pad")!==-1) {
togglePad(evt.target); //toggle pad on and off
}
});
*/
var patterns = [
{'title':'pattern snapshot','numSteps':32,'bpm':128,'globalSwing':'1.04','melodyNotes':['42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','42','44','42','42','45','42'],'playNotes':[false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false],'melodyAccents':[false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,true,false],'melodyLevel':'0.25','melodyAccentLevel':'0.4','playSnare':[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,true],'snareAccents':[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],'snareLevel':'0.4','snareAccentLevel':'1','playHh':[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false],'hhAccents':[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],'hhLevel':'0.4','hhAccentLevel':'1','playKick':[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],'kickAccents':[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],'kickLevel':'0.47','kickAccentLevel':'0.4'},
{"title":"Pass the Dutchie","numSteps":16,"bpm":80,"globalSwing":"1.07","melodyNotes":["48","64","62","45","48","45","48","63","55","62","55","62","50","62","62","65"],"playNotes":[true,false,false,true,true,true,true,false,true,false,true,false,true,false,false,false],"melodyAccents":[true,false,false,false,true,false,false,false,true,false,true,false,true,false,false,false],"melodyLevel":"0.3","melodyAccentLevel":"0.4","playSnare":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true],"hhAccents":[true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false],"hhLevel":"0.2","hhAccentLevel":"2","playKick":[true,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false],"kickAccents":[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],"kickLevel":"0.4","kickAccentLevel":"0.4"},
{"title":"Fried Green Tomatoes","numSteps":16,"bpm":120,"globalSwing":"1","melodyNotes":["57","57","69","55","52","69","52","50","52","69","52","50","52","69","55","69"],"playNotes":[true,true,false,true,true,false,true,true,true,false,true,true,true,false,true,false],"melodyAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"melodyLevel":"0.4","melodyAccentLevel":"0.4","playSnare":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareAccents":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,true],"hhAccents":[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false],"hhLevel":"0.26","hhAccentLevel":"1.3","playKick":[true,false,false,false,true,false,false,false,true,false,false,true,true,false,true,false],"kickAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"kickLevel":"0.4","kickAccentLevel":"0.4"},
{
"title" : "kick me",
"numSteps" : 16,
"bpm" : 120,
"globalSwing" : 1,
"melodyNotes" : [69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69],
"playNotes" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyLevel" : 0.4,
"melodyAccentLevel" : 0.4,
"playSnare" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"snareAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"snareLevel" : 0.4,
"snareAccentLevel" : 1,
"playHh" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"hhAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"hhLevel" : 0.4,
"hhAccentLevel" : 1,
"playKick" : [true, false, false, false, true, false, false, false, true, false, false, true, true, false, true, false],
"kickAccents" : [true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false],
"kickLevel" : 0.4,
"kickAccentLevel" : 0.4
},
{"title":"solo snare","numSteps":16,"bpm":120,"globalSwing":"1","melodyNotes":["69","69","69","69","69","69","69","69","69","69","69","69","69","69","69","69"],"playNotes":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"melodyAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"melodyLevel":"0.4","melodyAccentLevel":"0.4","playSnare":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareAccents":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"hhAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"hhLevel":"0.26","hhAccentLevel":"1.3","playKick":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"kickAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"kickLevel":"0.4","kickAccentLevel":"0.4"},
{
"title":"4 on floor",
"numSteps":16,
"bpm":120,
"globalSwing":"1",
"melodyNotes" : [69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69],
"playNotes" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyLevel":"0.4",
"melodyAccentLevel":"0.4",
"playSnare":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],
"snareAccents":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],
"snareLevel":"0.4",
"snareAccentLevel":"1",
"playHh":[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,true],
"hhAccents":[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false],
"hhLevel":"0.26",
"hhAccentLevel":"1.3",
"playKick":[true,false,false,false,true,false,false,false,true,false,false,true,true,false,true,false],
"kickAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],
"kickLevel":"0.4",
"kickAccentLevel":"0.4"
},
{"title":"dreaming","numSteps":32,"bpm":128,"globalSwing":"1.04","melodyNotes":["62","55","67","67","67","55","65","55","67","55","67","65","67","65","67","69","70","55","70","69","70","55","70","69","70","55","70","72","70","69","67","65"],"playNotes":[true,false,true,true,true,false,true,false,true,false,true,true,true,true,true,true,true,false,true,true,true,false,true,true,true,false,true,true,true,true,true,true],"melodyAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"melodyLevel":"0.25","melodyAccentLevel":"0.4","playSnare":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,true],"snareAccents":[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false],"hhAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"hhLevel":"0.4","hhAccentLevel":"1","playKick":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"kickAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"kickLevel":"0.47","kickAccentLevel":"0.4"},
{
"title" : "empty24",
"numSteps" : 24,
"bpm" : 120,
"globalSwing" : 1,
"melodyNotes" : [69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69],
"playNotes" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"melodyLevel" : 0.4,
"melodyAccentLevel" : 0.4,
"playSnare" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"snareAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"snareLevel" : 0.4,
"snareAccentLevel" : 1,
"playHh" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"hhAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"hhLevel" : 0.4,
"hhAccentLevel" : 1,
"playKick" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"kickAccents" : [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"kickLevel" : 0.4,
"kickAccentLevel" : 0.4
},
{"title":"Owl's Moving Asshole","numSteps":24,"bpm":"41","globalSwing":"1","melodyNotes":["62","55","55","60","58","57","58","55","62","55","58","62","67","55","66","67","69","65","65","55","55","50","55","58"],"playNotes":[true,false,false,true,true,true,true,false,false,true,true,true,true,false,false,true,true,true,true,false,false,true,true,true],"melodyAccents":[true,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,false,false,true,false,false],"melodyLevel":"0.49","melodyAccentLevel":"0.53","playSnare":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"snareAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[false,true,true,false,true,true,false,true,true,false,true,true,false,true,true,false,true,true,false,true,true,false,true,true],"hhAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"hhLevel":"0.4","hhAccentLevel":"1","playKick":[true,false,false,false,false,true,true,false,false,false,false,false,true,false,false,false,false,true,true,false,false,false,false,false],"kickAccents":[true,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false],"kickLevel":"0.04","kickAccentLevel":"0.4"},
{"title":"whatevs","numSteps":16,"bpm":120,"globalSwing":"1.12","melodyNotes":["69","67","69","69","69","64","64","64","69","69","67","67","67","69","69","69"],"playNotes":[true,true,false,false,false,false,true,false,false,false,false,true,true,false,false,false],"melodyAccents":[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"melodyLevel":"0.15","melodyAccentLevel":"1.3","playSnare":[false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false],"snareAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],"snareLevel":"0.4","snareAccentLevel":"1","playHh":[false,false,false,false,false,false,true,true,false,false,false,false,false,false,true,false],"hhAccents":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false],"hhLevel":"0.4","hhAccentLevel":"1","playKick":[true,false,false,true,true,false,false,false,true,false,false,true,true,false,false,false],"kickAccents":[true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false],"kickLevel":"0.46","kickAccentLevel":"0.4"}
];
window.addEventListener("load", initBeatbox());
function adjustPitch(melodyPadGroup, up) {
var midiNote = melodyPadGroup.firstElementChild.nextElementSibling.dataset.midiNote;
if (up&&(midiNoteFreqs.length-midiNote>1)) {
//increment pitch
melodyPadGroup.firstElementChild.nextElementSibling.dataset.midiNote++;
} else if (!up&&(midiNote>0)) { //must be down
melodyPadGroup.firstElementChild.nextElementSibling.dataset.midiNote--;
}
melodyPadGroup.firstElementChild.nextElementSibling.innerText = (noteName(melodyPadGroup.firstElementChild.nextElementSibling.dataset.midiNote));
}
function draw() {
//created by Chris
var thisStep = lastStepDrawn;
while (stepsInQueue.length && stepsInQueue[0].time < audioContext.currentTime) {
thisStep = stepsInQueue[0].step;
stepsInQueue.splice(0,1); // remove step from queue
}
if (lastStepDrawn != thisStep) { // We only need to draw if the step has advanced
//should maybe use html5 attrs not class names for the following?
melodyPads[thisStep].className += " current";
melodyPads[thisStep].previousElementSibling.className += " current";
melodyPads[thisStep].nextElementSibling.className += " current";
kickPads[thisStep].className += " current";
snarePads[thisStep].className += " current";
hhPads[thisStep].className += " current";
if (lastStepDrawn!==-1) { //don't change prev pad if this is the first step
melodyPads[lastStepDrawn].className = melodyPads[lastStepDrawn].className.replace(" current", "");
melodyPads[lastStepDrawn].previousElementSibling.className = melodyPads[lastStepDrawn].previousElementSibling.className.replace(" current", "");
melodyPads[lastStepDrawn].nextElementSibling.className = melodyPads[lastStepDrawn].nextElementSibling.className.replace(" current", "");
kickPads[lastStepDrawn].className = kickPads[lastStepDrawn].className.replace(" current", "");
snarePads[lastStepDrawn].className = snarePads[lastStepDrawn].className.replace(" current", "");
hhPads[lastStepDrawn].className = hhPads[lastStepDrawn].className.replace(" current", "");
}
lastStepDrawn = thisStep;
}
// set up to draw again
requestAnimFrame(draw);
} //end draw function
function getPatternList() {
var opt;
for (i=0;i<patterns.length;i++) {
opt = document.createElement("option");
opt.innerHTML = patterns[i].title; // whatever property it has
// then append it to the select element
patternLoader.appendChild(opt);
}
}
function getPatternSnapshot() {
return (JSON.stringify({
"title" : "pattern snapshot",
"numSteps" : numSteps,
"bpm" : tempo,
"globalSwing" : swingControl.value,
"melodyNotes" : readMelodyPads("melody", "melodyNotes"),
"playNotes" : readMelodyPads("melody", "playNotes"),
"melodyAccents" : readMelodyPads("melody", "melodyAccents"),
"melodyLevel" : melodyMixLevel.value,
"melodyAccentLevel" : melodyAccent.value,
"playSnare" : readPercPads("snare"),
"snareAccents" : readPercPads("snare", true),
"snareLevel" : snareMixLevel.value,
"snareAccentLevel" : snareAccent.value,
"playHh" : readPercPads("hihat"),
"hhAccents" : readPercPads("hihat", true),
"hhLevel" : hhMixLevel.value,
"hhAccentLevel" : hhAccent.value,
"playKick" : readPercPads("kick"),
"kickAccents" : readPercPads("kick", true),
"kickLevel" : kickMixLevel.value,
"kickAccentLevel" : kickAccent.value
}));
} //getPatternSnapshot()
/*
function igniteIOS() { //get audio working on iOS
var osc = audioContext.createOscillator();
//osc.gain.value = 1;
osc.start();
osc.stop(audioContext.currentTime + 2);
osc.connect( audioContext.destination );
}
*/
function initBeatbox(){
//populate pattern select box
getPatternList();
//create AudioContext
var AudioContextCreator = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContextCreator();
// load audio files, etc., here
initHiHat();
initSnareNoise();
loadPattern();
requestAnimFrame(draw); // start the drawing loop.
timerWorker = new Worker(window.URL.createObjectURL(metronomeWorkerBlob));
timerWorker.onmessage = function(e) {
if (e.data == "tick") {
// console.log("tick!");
scheduler();
}
else
console.log("message: " + e.data);
};
timerWorker.postMessage({"interval":lookahead});
}
function initHiHat() {
var channels = 2;
// Create an empty 1/80 second stereo buffer at the sample rate of the AudioContext
var frameCount = audioContext.sampleRate / 80;
hiHatBuffer = audioContext.createBuffer(channels, frameCount, audioContext.sampleRate);
// Fill the buffer with white noise; just random values between -1.0 and 1.0
for (var channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
var nowBuffering = hiHatBuffer.getChannelData(channel);
for (var i = 0; i < frameCount; i++) {
// Math.random() is in [0; 1.0]; audio needs to be in [-1.0; 1.0]
nowBuffering[i] = Math.random() * 2 - 1;
}
}
}
function initSnareNoise() {
var channels = 2;
var frameCount = audioContext.sampleRate / 3;
snareBuffer = audioContext.createBuffer(channels, frameCount, audioContext.sampleRate);
// Fill the buffer with white noise; just random values between -1.0 and 1.0
for (var channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
var nowBuffering = snareBuffer.getChannelData(channel);
for (var i = 0; i < frameCount; i++) {
nowBuffering[i] = Math.random() * 2 - 1;
}
}
}
function loadPattern(patternIndex) {
if (patternIndex===undefined) { //set default pattern to load
patternIndex = 0;
}
setPattern(patterns[patternIndex]);
patternLoader.selectedIndex = patternIndex;
}
function logPattern() {
var logButton = document.getElementById("save-pattern"), oldLogButtText = logButton.innerText;
console.log(getPatternSnapshot());
logButton.innerText = "check console";
setTimeout(function(){
logButton.innerText = oldLogButtText;
}, 2000);
}
function nextStep() {
// Advance current step and time: Chris
var swingAmount = swing.value;
var secondsPerBeat = 60.0 / tempo; // picks up CURRENT tempo value to calculate beat length
//nextStepTime += 0.25 * secondsPerBeat; // Add beat length to last beat time (0.25 because convert from 1/4 to 1/16)
nextStepTime += 0.25 * secondsPerBeat * (currentStep%2?2-swingAmount:swingAmount); // Add beat length to last beat time (0.25 because convert from 1/4 to 1/16)
currentStep++; // Advance the beat number, wrap to zero
if (currentStep == numSteps) {
currentStep = 0;
}
}
function noteName(midiNoteNumber) {
var noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
return noteNames[(midiNoteNumber % 12)].concat(parseInt(midiNoteNumber / 12));
}
function playBass(beatNumber, time) {
var noteLength = 60/(tempo*4), trimEnds = 0.001; //(in seconds)
var envelope = audioContext.createGain();
var bassLevel = audioContext.createGain();
var saws = [], panners = [];
for (var i = 0;i<4;i++) {
saws.push(); //looks odd but pushing a new OscillatorNode() here confused Android
saws[i] = audioContext.createOscillator();
saws[i].type = "sawtooth";
saws[i].detune = 10-(i*3);
saws[i].frequency.value = (midiNoteFreqs[melodyPads[beatNumber].dataset.midiNote])/4;
saws[i].start( time );
saws[i].stop( time + noteLength );
/*
panners.push();
panners[i] = audioContext.createStereoPanner(); //try 3D panner to avoid crashing iOS
panners[i].pan.value = (1.5 - i)/2;
saws[i].connect(panners[i]);
panners[i].connect(envelope);
*/
saws[i].connect(envelope);
}
envelope.gain.linearRampToValueAtTime(1, time+trimEnds);
envelope.gain.linearRampToValueAtTime(0, time+noteLength-trimEnds);
envelope.connect( bassLevel );
bassLevel.gain.value = (1+((melodyPads[beatNumber].className.indexOf("accent")===-1)?0:Number(melodyAccent.value))) * Number(melodyMixLevel.value);
bassLevel.connect( audioContext.destination );
} //playBass()
function playHiHat(beatNumber, time) {
var hihat = audioContext.createBufferSource(), hhGain = audioContext.createGain();
hihat.buffer = hiHatBuffer;
hihat.connect(hhGain);
hhGain.gain.value = ((beatNumber%4)?1:(1+Number(hhAccent.value)))*Number(hhMixLevel.value);
hhGain.connect(audioContext.destination);
hihat.start( time );
hhGain.gain.value = (1+((hhPads[beatNumber].className.indexOf("accent")===-1)?0:Number(hhAccent.value))) * Number(hhMixLevel.value);
} //playHiHat()
function playKick(beatNumber, time) {
var noteLength = 0.05, trimEnds = 0.001; //(in seconds)
var osc = audioContext.createOscillator(), envelope = audioContext.createGain(), kickLevel = audioContext.createGain(), squealFilter = audioContext.createBiquadFilter();
var startFreq = 820, endFreq = 55; //Hz
osc.type = 'sine';
osc.frequency.value = startFreq;
osc.frequency.exponentialRampToValueAtTime(endFreq, time + (noteLength/2));
osc.start(time);
osc.stop(time + noteLength);
osc.connect(envelope);
envelope.gain.value = 0;
envelope.gain.linearRampToValueAtTime(1, time+trimEnds);
envelope.gain.linearRampToValueAtTime(0, time+noteLength-trimEnds);
envelope.connect(squealFilter);
squealFilter.type = "lowpass";
squealFilter.frequency.value = 1000;
squealFilter.Q.value = 15;
squealFilter.connect(kickLevel);
kickLevel.gain.value = (1+((kickPads[beatNumber].className.indexOf("accent")===-1)?0:Number(kickAccent.value))) * Number(kickMixLevel.value);
kickLevel.connect(audioContext.destination);
} //playKick()
function playOrgan(beatNumber, time) {
var noteLength = 60/(tempo*4), trimEnds = 0.001; //(in seconds)
var envelope = audioContext.createGain();
var bassLevel = audioContext.createGain();
var overtone = function (interval) {
var a = Math.pow(2, 1/12); // as per http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html
return Math.pow(a, interval);
}
var oscs = [], playFreqs = [];
//var tonewheels = [-12, 0, 7, 12, 19, 24, 28, 31, 36]; //the original hammond tonewheels
var tonewheels = [-12, 0, 7, 12, 19, 24, 31, 36]; //nuked the major third so can play less discordant chords
var notes = [0, 7]; //chord
for (var j = 0; j < notes.length; j++) {
for (var k = 0; k < tonewheels.length; k++) {
playFreqs.push([overtone((tonewheels[k]+notes[j])), 1]);
}
}
for (var i = 0;i<playFreqs.length;i++) {
oscs.push(); //looks odd but pushing a new OscillatorNode() here confused Android
oscs[i] = audioContext.createOscillator();
oscs[i].type = "sine";
//oscs[i].detune = 10-(i*3);
oscs[i].frequency.value = (midiNoteFreqs[melodyPads[beatNumber].dataset.midiNote])*playFreqs[i][0];
oscs[i].start( time );
oscs[i].stop( time + noteLength );
oscs[i].connect(envelope);
}
envelope.gain.linearRampToValueAtTime(0.4, time+trimEnds);
envelope.gain.linearRampToValueAtTime(0, time+noteLength-trimEnds);
envelope.connect( bassLevel );
bassLevel.gain.value = (1+((melodyPads[beatNumber].className.indexOf("accent")===-1)?0:Number(melodyAccent.value))) * Number(melodyMixLevel.value);
bassLevel.connect( audioContext.destination );
} //playOrgan()
function oldPlayOrgan(beatNumber, time) { //uncalled at the moment
var noteLength = 0.05; // length of "beep" (in seconds)
var osc, gainNode;
//if pad is on, play it
// create an oscillator
osc = audioContext.createOscillator();
gainNode = audioContext.createGain();
//osc.oscillatorType = 'sawtooth';
osc.connect( gainNode );
gainNode.connect( audioContext.destination );
osc.frequency.value = midiNoteFreqs[melodyPads[beatNumber].dataset.midiNote];
osc.start( time );
osc.stop( time + noteLength );
gainNode.gain.value = (1+((melodyPads[beatNumber].className.indexOf("accent")===-1)?0:Number(melodyAccent.value))) * Number(melodyMixLevel.value);
//the 2nd oscillator makes an organ-like sound
var octaver = audioContext.createOscillator(), octaveGainNode = audioContext.createGain();
octaver.connect( octaveGainNode );
octaveGainNode.connect( audioContext.destination );
octaver.frequency.value = midiNoteFreqs[melodyPads[beatNumber].dataset.midiNote-12]; //why does this crash if I add sth to midiNote rather than subtract?
octaver.start( time );
octaver.stop( time + noteLength );
octaveGainNode.gain.value = gainNode.gain.value * 0.5;
} //playOrgan()
function playSnare(beatNumber, time) {
//for main waveform
var snareHead = audioContext.createOscillator(), headEnv = audioContext.createGain();
var headNoteLength = 0.1, headGainAttack = 0.005, headGainSustain = 0.03, headGainDecay = 0.08; //in seconds
var startFreq = 3000, quickDropFreq = 400, finalFreq = 140; //Hz
var quickDropTime = 0.005, finalFreqTime = 0.02;
//for noise
var snareNoise = audioContext.createBufferSource(), noiseEnv = audioContext.createGain(), noiseHpf = audioContext.createBiquadFilter();
var noiseGainAttack = 0.01, noiseGainPeakAttack = 0.02, noiseGainQuickDecay = 0.06, noiseGainFinalDecay = 0.1;
var noiseHpfFreq = 3600;
//for setting mix output level
var snareLevel = audioContext.createGain();
//make and put gain envelope on triangle oscillator for main sound
snareHead.type = 'triangle';
snareLevel.connect(audioContext.destination);
snareHead.frequency.value = startFreq;
snareHead.frequency.exponentialRampToValueAtTime(quickDropFreq, time + quickDropTime);
snareHead.frequency.exponentialRampToValueAtTime(finalFreq, time + quickDropTime + finalFreqTime);
snareHead.start(time);
snareHead.stop(time + headNoteLength);
snareHead.connect(headEnv);
headEnv.gain.linearRampToValueAtTime(1.0, time + headGainAttack);
headEnv.gain.linearRampToValueAtTime(1.0, time + headGainAttack + headGainSustain);
headEnv.gain.linearRampToValueAtTime(0, time + headGainAttack + headGainSustain + headGainDecay);
headEnv.connect(snareLevel);
//add and apply envelope and hpf to noise
snareNoise.buffer = snareBuffer;
snareNoise.connect(noiseEnv);
noiseEnv.gain.value = 0;
noiseEnv.gain.linearRampToValueAtTime(0.5, time + noiseGainAttack);
noiseEnv.gain.linearRampToValueAtTime(0.7, time + noiseGainAttack + noiseGainPeakAttack);
noiseEnv.gain.exponentialRampToValueAtTime(0.2, time + noiseGainAttack + noiseGainPeakAttack + noiseGainQuickDecay);
noiseEnv.gain.linearRampToValueAtTime(0, time + noiseGainAttack + noiseGainPeakAttack + noiseGainQuickDecay + noiseGainFinalDecay);
snareNoise.start( time );
noiseEnv.connect(noiseHpf);
noiseHpf.type = "highpass";
noiseHpf.frequency.value = noiseHpfFreq;
noiseHpf.connect(snareLevel);
//apply mixer and accent levels
snareLevel.gain.value = (1+((snarePads[beatNumber].className.indexOf("accent")===-1)?0:Number(snareAccent.value))) * Number(snareMixLevel.value);
} //playSnare()
function readMelodyPads(instrument, readParam) { //for outputting user-changed pattern to console
var ret = [];
var padArray = document.getElementById(instrument).children;
for (var i=1; i< padArray.length; i++) { //ignore padArray[0] which is h2 element
switch (readParam) {
case "melodyNotes":
ret.push(padArray[i].firstElementChild.nextElementSibling.dataset.midiNote);
break;
case "playNotes":
ret.push(padArray[i].firstElementChild.nextElementSibling.className.indexOf("off")===-1);
break;
case "melodyAccents":
ret.push(padArray[i].firstElementChild.nextElementSibling.className.indexOf("accent")!==-1);
break;
}
}
return (ret);
}
function readPercPads(instrument, accent) { //for outputting user-changed pattern to console
var ret = [];
var padArray = document.getElementById(instrument).children;
for (var i=1; i< padArray.length; i++) { //ignore padArray[0] which is h2 element
if (accent) {
ret.push(padArray[i].className.indexOf("accent")!==-1);
} else {
ret.push(padArray[i].className.indexOf("off")===-1);
}
}
return (ret);
}
function scheduleHiHat(beatNumber, time) {
//if pad is on, play it
if (hhPads[beatNumber].className.indexOf("off")===-1) {
playHiHat(beatNumber, time);
}
}
function scheduleKick(beatNumber, time) {
//if pad is on, play it
if (kickPads[beatNumber].className.indexOf("off")===-1) {
playKick(beatNumber, time);
}
}
function scheduleMelody(beatNumber, time) {
//if pad is on, play it
if (melodyPads[beatNumber].className.indexOf("off")===-1) {
playOrgan(beatNumber, time);
}
}
function scheduleSnare(beatNumber, time) {
//if pad is on, play it
if (snarePads[beatNumber].className.indexOf("off")===-1) {
playSnare(beatNumber, time);
}
}
function scheduler() {
// while there are steps that will need to play before the next interval,
// schedule them and advance the pointer.
// Chris W
while (nextStepTime < audioContext.currentTime + scheduleAheadTime ) {
scheduleStep( currentStep, nextStepTime );
nextStep();
}
}
function scheduleStep( beatNumber, time ) {
// push the step on the queue, even if we're not playing.
// Chris
stepsInQueue.push( { step: beatNumber, time: time } );
scheduleMelody(beatNumber, time);
scheduleKick(beatNumber, time);
scheduleSnare(beatNumber, time);
scheduleHiHat(beatNumber, time);
}
function setPattern(pattern) {
setStepCount(pattern.numSteps);
setTempo(pattern.bpm);
swing.value = pattern.globalSwing;
setMelody(pattern.melodyNotes, pattern.playNotes, pattern.melodyAccents);
melodyMixLevel.value = pattern.melodyLevel;
melodyAccent.value = pattern.melodyAccentLevel;
setPercPattern(kickPads, pattern.playKick, pattern.kickAccents);
kickMixLevel.value = pattern.kickLevel;
kickAccent.value = pattern.kickAccentLevel;
setPercPattern(snarePads, pattern.playSnare, pattern.snareAccents);
snareMixLevel.value = pattern.snareLevel;
snareAccent.value = pattern.snareAccentLevel;
setPercPattern(hhPads, pattern.playHh, pattern.hhAccents);
//setHiHat(pattern.hiHatBackbeat, pattern.hiHatResolution);
hhMixLevel.value = pattern.hhLevel;
hhAccent.value = pattern.hhAccentLevel;
}
function setPercPattern(percPads, playPad, accents) {
for (var i = 0; i < percPads.length; i++) {
percPads[i].className = (playPad[i]===true?(accents[i]===true?"perc-pad accent":"perc-pad on"):"perc-pad off");
}
}
function setMelody(notes, playNote, accents) {
for (var i = 0; i < melodyPads.length; i++) {
melodyPads[i].innerHTML = noteName(notes[i]);
melodyPads[i].setAttribute('data-midi-note', notes[i]);
//melodyPads[i].className = (playNote[i]===true?"seq-pad on":"seq-pad off");
melodyPads[i].className = (playNote[i]===true?(accents[i]===true?"seq-pad accent":"seq-pad on"):"seq-pad off");
melodyPads[i].previousElementSibling.className = (playNote[i]===true?(accents[i]===true?"pitch-adjust up accent":"pitch-adjust up on"):"pitch-adjust up off");
melodyPads[i].nextElementSibling.className = (playNote[i]===true?(accents[i]===true?"pitch-adjust down accent":"pitch-adjust down on"):"pitch-adjust down off");
}
}
function setStepCount(newStepCount) {
if (newStepCount === numSteps) { //exit if no need to change number of steps
return;
} else {
var j, melody = document.getElementById("melody"), kick = document.getElementById("kick"), snare = document.getElementById("snare"), hihat = document.getElementById("hihat");
if (newStepCount > numSteps) { //add steps
var padTemplate, kickTemplate, snareTemplate, hhTemplate;
for (j = 0; j < newStepCount-numSteps; j++) {
padTemplate = melody.firstElementChild.nextElementSibling.cloneNode(true);
melody.appendChild(padTemplate);
kickTemplate = kick.firstElementChild.nextElementSibling.cloneNode(true);
kick.appendChild(kickTemplate);
snareTemplate = snare.firstElementChild.nextElementSibling.cloneNode(true);
snare.appendChild(snareTemplate);
hhTemplate = hihat.firstElementChild.nextElementSibling.cloneNode(true);
hihat.appendChild(hhTemplate);
}
} else if (newStepCount < numSteps) { //delete steps
if (currentStep > newStepCount) {//prevent attempt to play step that doesn't exist anymore when shorter pattern chosen
//attempting bug fix here
//beatNumber = 0;
currentStep = 0;
lastStepDrawn = -1;
stepsInQueue = [];
//forceRestart = true;
//draw(0);
}
for (j = 0; j < numSteps-newStepCount; j++) {
melody.lastElementChild.remove();
kick.lastElementChild.remove();
snare.lastElementChild.remove();
hihat.lastElementChild.remove();
}
}
melodyPads = melody.getElementsByClassName("seq-pad"); //bind for later use in turning pads on/off
kickPads = kick.getElementsByClassName("perc-pad"); //bind for later use in turning pads on/off
snarePads = snare.getElementsByClassName("perc-pad"); //bind for later use in turning pads on/off
hhPads = hihat.getElementsByClassName("perc-pad"); //bind for later use in turning pads on/off
numSteps = newStepCount;
}
}
function setTempo(newTempo) {
tempo = newTempo;
tempoSlider.value = newTempo;
document.getElementById('showTempo').innerText=tempo;
}
/*
function toggle_more_pads(pad){
pad.className = ((pad.className==="seq-pad off")?"seq-pad on":"seq-pad off");
//console.log (pad.dataset.midiNote);
}
*/
function togglePad(pad, padType){
//cycles through 3 (off, on, and accent) states
switch (pad.className) {
case padType + " accent current": //deliberate fall-through
case padType + " accent": //advance to next pad status
pad.className = padType + " on";
if (padType === "seq-pad") {
pad.previousElementSibling.className = "pitch-adjust up on";
pad.nextElementSibling.className = "pitch-adjust down on";
}
break;
case padType + " off current": //deliberate fall-through
case padType + " off": //advance to next pad status
pad.className = padType + " accent";
if (padType === "seq-pad") {
pad.previousElementSibling.className = "pitch-adjust up accent";
pad.nextElementSibling.className = "pitch-adjust down accent";
}
break;
case padType + " on current": //deliberate fall-through
case padType + " on": //advance to next pad status
pad.className = padType + " off";
if (padType === "seq-pad") {
pad.previousElementSibling.className = "pitch-adjust up off";
pad.nextElementSibling.className = "pitch-adjust down off";
}
break;
default:
console.log("unexpected pad class name: "+pad.className);
}
} //end togglePad()
function oldTogglePad(pad){
//cycles through 3 (off, on, and accent) states
switch (pad.className) {
case "seq-pad accent current": //deliberate fall-through
case "seq-pad accent": //advance to next pad status
pad.className = "seq-pad on";
//pad.previousElementSibling.className = "pitch-adjust on";
//pad.nextElementSibling.className = "pitch-adjust on";
break;
case "seq-pad off current": //deliberate fall-through
case "seq-pad off": //advance to next pad status
pad.className = "seq-pad accent";
//pad.previousElementSibling.className = "pitch-adjust accent";
//pad.nextElementSibling.className = "pitch-adjust accent";
break;
case "seq-pad on current": //deliberate fall-through
case "seq-pad on": //advance to next pad status
pad.className = "seq-pad off";
//pad.previousElementSibling.className = "pitch-adjust off";
//pad.nextElementSibling.className = "pitch-adjust off";
break;
default:
console.log("unexpected seq-pad class name: "+pad.className);
}
} //end togglePad()
function transport() { //toggle play/stop
if (!unlocked) {
// play silent buffer to unlock the audio
var buffer = audioContext.createBuffer(1, 1, 22050);
var node = audioContext.createBufferSource();
node.buffer = buffer;
node.start(0);
unlocked = true;
}
var transportText = document.getElementById("play");
if (!isPlaying) { // start playing
currentStep = 0;
nextStepTime = audioContext.currentTime;
timerWorker.postMessage("start");
transportText.innerText = "stop";
} else {
timerWorker.postMessage("stop");
transportText.innerText = "restart";
}
isPlaying = !isPlaying;
}
}());
@import url('https://fonts.googleapis.com/css?family=VT323');
//colors
$coolGrey: #444444;
$brownish: #664433;
$almostWhite: #dddddd;
$blueButton: #3090ff;
$accent: #ff3300;
$on: #ef8d00;
$off: #555555;
//layout
$ctrlGrpSpacing: 4px;
$padWidth: 28px;
$padBorderRadius: 6px;
$padSpacing: 2px;
$lowWidth: "(max-width: 499px)";
$midWidth: "(min-width: 500px) and (max-width: 799px)";
$hiWidth: "(min-width: 800px)";
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
@mixin two-sharp-corners($radius) {
-webkit-border-radius: $radius 0 $radius 0;
-moz-border-radius: $radius 0 $radius 0;
-ms-border-radius: $radius 0 $radius 0;
border-radius: $radius 0 $radius 0;
}
@mixin rounded-top($radius) {
-webkit-border-radius: $radius $radius 0 0;
-moz-border-radius: $radius $radius 0 0;
-ms-border-radius: $radius $radius 0 0;
border-radius: $radius $radius 0 0;
}
@mixin rounded-bot($radius) {
-webkit-border-radius: 0 0 $radius $radius;
-moz-border-radius: 0 0 $radius $radius;
-ms-border-radius: 0 0 $radius $radius;
border-radius: 0 0 $radius $radius;
}
@mixin key-gradient($baseColor) {
background: $baseColor;
background: -moz-linear-gradient(-45deg, $baseColor*1.5 1%, $baseColor*.66 100%);
background: -webkit-linear-gradient(-45deg, $baseColor*1.5 1%, $baseColor*.66 100%);
background: linear-gradient(135deg, $baseColor*1.5 1%, $baseColor*.66 100%);
}
@mixin standalone-pad($radius) {
margin:$padSpacing;
@include border-radius($radius);
}
@mixin key-common($baseColor) {
overflow:hidden;
vertical-align:middle;
cursor:pointer;
z-index:3;
color:$baseColor*3;
text-align:center;
border:2px solid;
border-color:$baseColor;
@include key-gradient($baseColor);
&:hover {
border-color:$baseColor*1.5;
@include key-gradient($baseColor*2);
}
&.current {
border-color:$baseColor*1.5;
@include key-gradient($baseColor*2);
}
}
* {
-webkit-user-select: none;
}
body, html {
margin: 0;
height: 100%;
position: relative;
}
body {
font-family: sans-serif;
//font-weight:100;
overflow: auto;
background-color:$coolGrey/2;
color:$almostWhite;
}
select, option { //abstract some of this to SCSS
font-family: 'VT323',sans-serif;
background-color:$coolGrey*.5;
color:#ffffff;
}
select {
@include two-sharp-corners(6px);
height:24px;
}
input[type=range]{
-webkit-appearance: none;
margin:12px 0 12px 10px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 300px;
height: 5px;
background: #ddd;
border: none;
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: goldenrod;
margin-top: -4px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
.seq-group {
padding:2px 0;
}
.seq-group > * {
float:left;
clear:left;
}
.mix-channel {
height:240px;
width:70px;
float:left;
//border:1px solid goldenrod;
padding:0 3px;
}
.channel-label {
position:absolute;
top:232px;
width:70px;
text-align:center;
}
.vert-sliders {
width:200px;
height:60px;
-webkit-transform:rotate(-90deg);
background-color:$coolGrey*0.9;
-webkit-transform-origin:left top;
padding:3px;
position:absolute;
top:228px;
}
@media #{$lowWidth} {
.seq-group:nth-child(8n+2), .perc-pad:nth-child(8n+2) {
float:left;
clear:left;
}
}
@media #{$midWidth} {
.seq-group:nth-child(8n+2), .perc-pad:nth-child(8n+2) {
float:left;
clear:left;
}
}
@media #{$hiWidth} {
.seq-group:nth-child(16n+2), .perc-pad:nth-child(16n+2) {
float:left;
clear:left;
}
}
.seq-pad, .perc-pad {
height:$padWidth;
width:$padWidth;
line-height:$padWidth;
@include key-common($off);
font-size:1.4em;
}
.seq-pad {
margin-left: $padSpacing;
margin-right: $padSpacing;
}
.perc-pad {
@include standalone-pad($padBorderRadius);
}
.pitch-adjust {
height:12px;
width:$padWidth;
@include key-common($off);
line-height:12px;
margin-left: $padSpacing;
margin-right: $padSpacing;
}
#boom-box {
font-family: 'VT323',sans-serif;
@media #{$midWidth}, #{$hiWidth} {
background-color:$coolGrey*.9;
border: 6px $coolGrey*1.3 solid;
@include border-radius(14px);
padding: $ctrlGrpSpacing*1.5;
position:absolute;
left:50%;
margin-top:1em;
}
@media #{$midWidth} {
width:336px;
margin-left:-168px;
}
@media #{$hiWidth} {
width:640px;
margin-left:-320px;
}
}
#boom-box * {
display:inline-block;
}
.control-group {
background-color:$coolGrey*.7;
margin: $ctrlGrpSpacing;
padding: 10px;
border: 2px solid $brownish*2;
@include two-sharp-corners(12px);
position:relative;
//min-width:304px;
}
.control-group h2 {
position:absolute;
right:0.5em;
top:-0.6em;
font-size:0.8em;
color:$brownish*2.2;
z-index:2;
}
#patches {
& select {
width:140px;
}
& label {
margin-left:6px;
}
}
.control-button {
@include key-common($blueButton);
@include border-radius(6px);
width:100px;
}
.on {
@include key-common($on);
}
.accent {
@include key-common($accent);
}
.up {
@include rounded-top($padBorderRadius);
}
.down {
@include rounded-bot($padBorderRadius);
}
.triangle-up, .triangle-down {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.triangle-down {
border-top: 10px solid $almostWhite;
}
.triangle-up {
border-bottom: 10px solid $almostWhite;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment