HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div class="page-wrap">
<h1>The Repeater Orchestra</h1>
<div class="orchestra">
<div class="label -conductor">The Conductor (You)</div>
<div class="label -repeaters">Repeaters</div>
</div>
</div>
<div class="controls">
<h2>Controls</h2>
<button class="gate-open">Turn Mic On/Off</button>
<div class="slider-wrap">
<div class="slider micGain"></div>
<div class="rot -left">Mic</div>
</div>
<div class="slider-wrap">
<div class="slider masterGain"></div>
<div class="rot -right">Orch</div>
</div>
<button class="clear-delays">Restart Repeaters</button>
</div>
<a href="https://codepen.io/poopsplat/post/codepen-chicago-june-2016" target="_blank" class="example">Video Example <span class="fake-link">here</span></a>
<div class="loading"><h1>The Repeater Orchestra (1.0)</h1></div>
// Box-sizing
*, *::before, *::after {
box-sizing: inherit;
}
html {
box-sizing: border-box;
font-size: 16px; //rem
}
// Fonts
@import url(https://fonts.googleapis.com/css?family=Share+Tech+Mono);
// Colors
$blue: rgb(18, 104, 207);
$black: rgb(20,20,20);
$gray: rgb(150,150,150);
$lite-gray: rgb(180,180,180);
$white: rgb(255,255,255);
// Base
body {
background: $blue;
text-align: center;
text-transform: uppercase;
@extend %font;
}
%font {
color: $white;
font-family: 'Share Tech Mono';
letter-spacing: -0.05em;
}
%stroke {
text-shadow:
-2px -2px 0 $black,
2px -2px 0 $black,
-2px 2px 0 $black,
2px 2px 0 $black;
}
h1, h2 {
@extend %stroke;
margin-top: 0;
}
.page-wrap {
padding-top: 1rem;
width: 40rem;
margin: auto;
}
// Orchestra
.orchestra {
position: relative;
margin: auto;
width: 100%;
height: 20rem;
background: rgba($black,.2);
margin: 8rem 0 10rem;
.label {
position: absolute;
&.-repeaters{
top: 1rem;
left: 41rem;
}
&.-conductor{
top: -4rem;
left: 26rem;
}
}
}
canvas {
background: $blue;
transform: translate(-50%,0); // Thus, our bottom/left refers to the center
position: absolute;
border: 2px solid $white;
border-radius: 100%;
@extend %stroke;
}
#conductor{
top: 0;
left: 50%;
transform: translate(-50%,-50%);
}
// UI
.controls {
position: fixed;
padding: 1em;
left: 0;
bottom: 0;
background: $blue;
border-top: 2px solid $white;
border-right: 2px solid $white;
opacity: 0.5;
&:hover { opacity: 1; }
}
// Buttons
%button-hover {
&:hover{
cursor: pointer;
background: $lite-gray;
}
}
button{
position: relative;
border: $white 2px solid;
background-color: $black;
color: $white;
font-family: 'Share Tech Mono';
letter-spacing: -0.1em;
text-align: center;
text-transform: uppercase;
@extend %button-hover;
display: block;
margin: auto;
padding: .4rem;
&.on{
background-color: $white;
color: black;
border-color: $black;
}
}
// Sliders
.slider-wrap {
display: inline-block;
margin: 1rem .5rem;
}
.slider {
border: $black 2px solid;
border-radius: 0;
background-color: $black;
background-image: none;
display: inline-block;
height: 6rem;
&:hover{
cursor: pointer;
}
.ui-slider-range {
border-radius: 0;
background-color: $white;
background-image: none;
}
.ui-slider-handle {
border: $black 2px solid;
border-radius: 0;
background-color: $white;
background-image: none;
// transition: transform 0.2s;
&:hover{
cursor: pointer;
}
}
}
// Alert
.alert {
position: fixed;
top: 0;
right: 0;
left: 0;
height: 100%;
background: rgba($white, 0.8);
.content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 500px;
background: $blue;
border: 2px solid $black;
padding: 2em;
z-index: 100;
a {
color: $white;
}
.message {
// float: left;
text-align: left;
display: inline-block;
width: 300px;
}
.icon {
float: left;
width: 100px;
height: auto;
margin-right: 2em;
}
button {
clear: both;
display: block;
margin: 2em auto 0;
}
}
}
// Example Performance
.example {
position: fixed;
bottom: 0;
left: 100%;
padding: 1rem;
width: 200px;
background: $blue;
display: inline-block;
color: white;
text-decoration: none;
.fake-link {
text-decoration: underline;
}
border-top: 2px $white solid;
border-right: 2px $white solid;
transform: rotate(270deg);
transform-origin: 0% 100%;
opacity: 0.5;
&:hover{ opacity: 1; }
}
//Loading
.loading{
position: fixed;
z-index: 100;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
background-color: $blue;
transition: opacity 0.2s, visibility 0s 0.2s;
h1 {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%,-50%);
text-shadow: none;
color: $black;
}
}
// TODO ---------------------------------------------------------
// [ ] Make all options changeable via UI: n-repeaters, tempo, latency
// [ ] Latency reduction should be additive not multiplicative, right?
// [ ] Add links for how to record yourself!
// GLOBALS ---------------------------------------------------------
// Options
_nDelays = 50; // Number of Repeaters
_tempo = 80; // Global tempo (bpm)
_maxEighths = 60; // Max eigth notes a repeater can be delayed.
_minGain = 0.2; // Min gain multiple for a repeater.
_maxGain = 2; // Max gain multiple for a repeater.
_gateOpen = false; // Is the mic "on"?
_micGainStart = 0.5; // Starting mic gain
_masterGainStart = 0.5; // Starting gain of Orchestra
_monitorGainStart = 4; // How much louder should the mic feed be than the repeaters? (Monitor)
_latencyTune = 0; // A slight crunching of our delay times to help account for latency
// Calculations
var eighthTime = (60 / _tempo / 2)*(1-_latencyTune/1000.0); // This _latencyTune thing is HACKY
var gainRange = _maxGain - _minGain;
// CUSTOM USER ALERTS ---------------------------------------------------------
function customAlert(msg, btn, svg) {
var btn = btn || 'Word';
var svg = svg || 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/385326/achtung.svg';
if ($('.alert').length) { // If there is already an alert, we're gonna try again with this message in a short while.
setTimeout(function() {
customAlert(msg, btn);
}, 100);
} else {
$('<div class="alert"><div class="content"><img src="' + svg + '" class="icon"><div class="message">' + msg + '</div><button>' + btn + '</button></div></div>').appendTo('body');
$('.alert button').click(function(e) {
e.preventDefault();
$('.alert').remove();
})
}
}
// REQUEST USER AUDIO INPUT STREAM ---------------------------------------------------------
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
navigator.getUserMedia({
audio: {
latency: 0, //Does this do anything?
sampleSize: 128 //Does this do anything? Probably not....
}
}, gotStream, streamErr);
// If error getting input...
function streamErr() {
// Clear loading screen
$('.loading').remove();
// Clear the stage, because nothing will work.
$('body *:not(h1)').remove();
// Give the user some advice
customAlert('Couldn\'t get the mic. Likely, you need to give this site permission to use your mic. You may have been prompted. <a href="https://support.google.com/chrome/answer/2693767?hl=en-GB">This article</a> might help. Then refresh the page.', 'OK. I\'ll figure it out and refresh!');
}
// If success...
function gotStream(stream) {
// Clear loading screen
$('.loading').remove();
// Alert user to wear headphones!!!
customAlert('If your mic is near your speakers (like on most computers), this thing is gonna make gnarly feedback. Unless you have a fancy schmancy mic/speaker setup, you should put on headphones to enjoy this.', 'Word. I put on headphones!', 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/385326/headphones.svg');
// AUDIO CONTEXT ---------------------------------------------------------
// Build the audio context. This is from where all the magic of web audio stems.
window.AudioContext = window.AudioContext || window.webkitAudioContext;
_cxt = new AudioContext();
// PRIMARY INPUT/OUTPUT CHAIN ---------------------------------------------------------
// _mic -> _micGain -> _monitorGain -> _masterGain -> _compressor -> destination
// Create + connect _mic
_mic = _cxt.createMediaStreamSource(stream);
// Create + connect _micGain -- This controls mic volume (it will also be how we "turn the mic off")
_micGain = _cxt.createGain(); // Create
_micGain.gain.value = _micGainStart * _gateOpen;
_mic.connect(_micGain); // Chain _mic to _micGain
// Create + connect _monitorGain -- Makes the mic chain louder than the delay chains so you can better monitor.
_monitorGain = _cxt.createGain();
_monitorGain.gain.value = _monitorGainStart;
_micGain.connect(_monitorGain); // Chain
// Create + connect _masterGain -- Volume of entire orchestra
_masterGain = _cxt.createGain();
_masterGain.gain.value = _masterGainStart;
_monitorGain.connect(_masterGain); // Chain
// Create + connect _compressor -- Compress me because I hate clipping
_compressor = _cxt.createDynamicsCompressor(); // The defaults for this are good.
_masterGain.connect(_compressor); // Chain
// Finally, connect us to the destination (where the audio is outputted)
_compressor.connect(_cxt.destination);
// DELAY CHAINS ---------------------------------------------------------
// We need to another chain for each of our _delays:
// _mic -> _micGain -> _delays[i] -> _gains[i] -> _panners[i] -> _masterGain -> _compressor -> destination
// Make some empty arrays to fill
_delays = new Array();
_delayTimes = new Array();
_gains = new Array();
_panners = new Array();
for (i = 0; i < _nDelays; i++) {
// Create delay object
_delays[i] = _cxt.createDelay(_maxEighths * eighthTime * 1.1);
// Asign a delayTime of some random integer (< _maxEighths) number of eighth notes
var nEighths = Math.ceil(Math.random() * _maxEighths); // integer num of eights up to max Eighths
_delayTimes[i] = (nEighths) * eighthTime; // Calculate time for this delay and store it
_delays[i].delayTime.value = _delayTimes[i]; // Apply delay time.
// Chain (_mic is already connected _micGain)
_micGain.connect(_delays[i]);
// Create gain node
_gains[i] = _cxt.createGain();
// Set random gain up to within gain r
_gains[i].gain.value = Math.random() * gainRange + _minGain;
// Chain
_delays[i].connect(_gains[i]);
// Create pan node
_panners[i] = _cxt.createStereoPanner();
// Set random pan
_panners[i].pan.value = (i % 10) / 5 - 1;
// Chain
_gains[i].connect(_panners[i]);
// Connect _masterGain (and therefore out to _compressor and destination)
_panners[i].connect(_masterGain);
}
// INIT VISUALIZER ---------------------------------------------------------
// With more than a little help from: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API
// Objects / Object Arrays
var analysers = new Array();
var canvases = new Array();
orchW = $('.orchestra').width();
orchH = $('.orchestra').height();
numInRow = 10;
repW = (orchW / numInRow);
// Initiate and place canvases for all repeaters
for (i = 0; i < _nDelays; i++) {
// Use the awesome power of arithmetic to put things in places
$('.orchestra').append('<canvas id="repeater-' + i + '" width="' + (repW - 10) + '" height="' + (repW - 10) + '">');
var $canvas = $('#repeater-' + i);
var x = (((1 / numInRow) / 2) + (i % numInRow) * (1 / numInRow)) * orchW + (Math.floor(Math.random() * 8) - 4);
var y = (-Math.sin(x * Math.PI / orchW) + 1) * (orchH / 2) - ((Math.floor(i / numInRow) - 2) * repW); // Who remembers high school trig? This guy remembers high school trig.
$canvas.css('left', x);
$canvas.css('bottom', y); // Todo -- don't do this with JS, SCSS can probably handle this. And then everyone will be happier.
canvases[i] = $canvas[0].getContext('2d');
// Create analyzer and connect to _micGain
analysers[i] = _cxt.createAnalyser(); // Create
_gains[i].connect(analysers[i]); // Chain
// Set up an array to house data collected from analyser.
analysers[i].fftSize = 1024;
}
// Do same for conductor (but he's bigger, and we'll just place him in CSS)
$('.orchestra').append('<canvas id="conductor" width="' + (repW * 3) + '" height="' + (repW * 3) + '">');
conductorCanvas = $('#conductor')[0].getContext('2d');
conductorAnalyser = _cxt.createAnalyser();
_micGain.connect(conductorAnalyser);
// Build array to house analysis data
var bufferLength = analysers[0].frequencyBinCount;
var dataArray = new Float32Array(bufferLength);
// ANIMATE OSCILLOSCOPES
function draw() { //Our drawing function to be called every frame
drawVisual = requestAnimationFrame(draw); // Keep calling this from now on.
for (j = 0; j < _nDelays + 1; j++) {
if (j < _nDelays) {
analysers[j].getFloatTimeDomainData(dataArray); // Get analyser data
var canvasToDraw = canvases[j];
var canvasWidth = (repW - 10); // Get width and height
var canvasHeight = (repW - 10);
} else {
conductorAnalyser.getFloatTimeDomainData(dataArray); // Get analyser data
var canvasToDraw = conductorCanvas;
var canvasWidth = (repW * 3); // Get width and height
var canvasHeight = (repW * 3);
}
canvasToDraw.clearRect(0, 0, canvasWidth, canvasHeight); // Clear canvas
canvasToDraw.strokeStyle = 'rgb(255,255,255)'; // Style the line
canvasToDraw.lineWidth = 2;
canvasToDraw.beginPath(); // Draw the line
var sliceWidth = canvasWidth * 1.0 / bufferLength;
var x = 0;
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] * (canvasHeight / 2) * 40;
var y = v + canvasHeight / 2;
if (i === 0) { //Either start or continue line
canvasToDraw.moveTo(x, y);
} else {
canvasToDraw.lineTo(x, y);
}
x += sliceWidth;
}
canvasToDraw.lineTo(canvasWidth, canvasHeight / 2); // Finish in the right spot
canvasToDraw.stroke(); // Stroke it (T.W.S.S.)
}
}
draw(); // Start her up! Now we're drawing!
// UI ---------------------------------------------------------
// Make sliders
// For DRY sake, these are the base options:
baseOptions = {
min: 0,
max: 1,
range: 'min',
step: 0.01,
orientation: 'vertical'
}
// Make slider to control _masterGain's level (for options, duplicate and extend baseOptions object to include slide callback)
$('.slider.masterGain').slider($.extend(true, {}, baseOptions, {
value: _masterGain.gain.value,
change: function(event, ui) {
_masterGain.gain.linearRampToValueAtTime(ui.value, _cxt.currentTime + 0.2); //We want to set this with a ramp, to make the change gradual and remove potential pops
}
}));
// Ditto for _micGain
$('.slider.micGain').slider($.extend(true, {}, baseOptions, {
value: _micGainStart,
change: function(event, ui) {
_micGain.gain.linearRampToValueAtTime(ui.value * _gateOpen, _cxt.currentTime + 0.2);
}
}));
// Gate Open Button
_gateOpenToggle = function() {
if (_gateOpen) {
$('.gate-open').removeClass('on');
_gateOpen = false;
} else {
$('.gate-open').addClass('on');
_gateOpen = true;
}
setTimeout(function() {
_micGain.gain.linearRampToValueAtTime($('.slider.micGain').slider('value') * _gateOpen, _cxt.currentTime + 0.2);
}, 500);
}
$('.gate-open').click(function(e) {
e.preventDefault();
_gateOpenToggle();
});
$(window).keyup(function(e) {
e.preventDefault();
if (e.which === 32) _gateOpenToggle();
});
// Clear Delays
$('.clear-delays').click(function(e) {
// Force Mute. This will help us avoid pops from delays coming in and out of being
_micGain.gain.linearRampToValueAtTime(0, _cxt.currentTime + 0.05);
setTimeout(function() { // A bit after we mute
for (i = 0; i < _nDelays; i++) {
// Close mic gate
_gateOpen = false;
$('.gate-open').removeClass('on');
// Disconnect old gain object
_delays[i].disconnect();
_delays[i] = null; // I'm doing this in the hopes that it helps JS collect my garbage. I don't know if it's working. I worry these delay objects are just piling up somewhere.
// Create new delay object to replace it.
_delays[i] = _cxt.createDelay(_maxEighths * eighthTime * 1.1);
// Give it its old delay time
_delays[i].delayTime.value = _delayTimes[i];
// Rechain
_micGain.connect(_delays[i]);
_delays[i].connect(_gains[i]);
}
}, 200);
// Schedule unmuting (but gate stays closed)
setTimeout(function() {
_micGain.gain.linearRampToValueAtTime($('.slider.micGain').slider('value') * _gateOpen * 1, _cxt.currentTime + 1);
}, 500);
});
}
Also see: Tab Triggers