The Wheels Of Steel: An Ode To Turntables (in HTML)
TL;DR version: I made some shiny. Turntables in your browser. wheelsofsteel.net. Safari 5 or Chrome 12 on OS X is best.
The turntable is more than just a hi-fi music player for the home. To the DJ, it's also a creative tool for remixing music in real-time. The turntable gave birth to a culture of disc jockeys and is an important element of hip-hop; it also plays a role, quite literally, in the evolution of electronic music.
Turntables have had many nicknames given to them by DJs including 1200s, the 1s and 2s, and T-turns among others. The famous 1981 DJ set, The Adventures of Grandmaster Flash on the Wheels Of Steel, highlights the significance of turntables in DJing and hip-hop. Flash's work is considered to be an early example of Turntablism, the use of turntables as an instrument.
Technics SL-1200: 1972 - 2010 (The End Of An Era?)
In late 2010, Panasonic Corp. (formerly Matsushita) announced that it was ceasing production of the world-famous Technics 1200 series turntable. DJs worldwide mourned the news as it spread around online, perhaps with good reason: the SL-1200 is the standard for equipment in clubs and bars, thanks in part to its tank-like resilience. In the eyes of turntablist and "skratch" DJs, the 1200 is also a musical instrument in its own right.
Despite the speed at which technology has evolved - particularly in audio and video - it's impressive that the 1200 lasted for 38 years as a "music player", staying relevant throughout both the 8-track and cassette tape eras. Even with the advances of digital audio bringing us CDs, MP3s and "internet radio", the turntable and vinyl records are still seen as a modern, albeit fading, source of music. New albums continue to be released in 12" and LP formats, though the market is leaning towards specialty and limited-release production.
As a hobbyist DJ, owner of two SL-1200 M3Ds and a professional Front-end Engineer / "HTML Custodian", it seemed appropriate that I finally try building a web-based prototype that simulates the real thing.
Virtual Turntables: Digital Analogs
There were a few notable online flash-based "virtual turntables" sites in 1999/2000 ( turntables.de is the one I remember), but they may have been limited by the Flash version 3 + 4 technology available at the time. Animations were simplistic, and there was no real scratching aside from pre-recorded loops and samples that could be triggered. Without pitch bending, mixing tracks was not really possible. Nonetheless, it was a pretty fun experience combining animation and layering of sound within Flash at the time.
Software has come a long way since the early 2000s. Desktop DJ software packages have grown to be popular, and offer plenty of bells and whistles. Numerous desktop programs (and more recently, touch-screen apps on the iPad) include virtual turntables in their designs and have solved most digital issues including latency and synchronization.
Many professional DJs use computer-plus-turntable hybrids such as Serato's "Scratch Live" software in live performances. However, desktop and mobile DJ programs remain somewhat of a specialty/niche interest - and despite the social effects of music, these applications have yet to really tie into the contexts of the internet and social networking.
http://wheelsofsteel.net: Turntables in your browser.
HTML (and CSS and JS and sometimes Flash, Oh My!)
General disclaimer, May 2011: This is an experimental, cutting-edge prototype and has requirements similar to some of the "Chrome Experiments" site demos. Safari 5 or Chrome 12 are recommended, and OS X has the lowest audio latency. If your browser doesn't have hardware acceleration support, the UI will be really slow and your CPU will likely catch on fire, among other things. Consider yourself warned. ;)
I have been interested in the idea of building a turntable-based UI in HTML for years; however, the past presented a number of technical hurdles. Setting dreams of browser-based remixing aside, simply recreating the core design elements of a turntable was practically infeasible until the advent of CSS3. The features most notably missing from browsers involved drawing circles, rotation of elements and low-level control of audio. As of 2011, it's a pleasure to say that these features can be implemented almost entirely using HTML, CSS and JavaScript alone.
While some will mistakenly assume this prototype is "made using HTML5", it is a technically inaccurate label. The design is largely thanks to CSS3 and while a few HTML5 elements are used by the prototype, none of them are visible in the UI. The mixer uses some hidden <input type="range">
elements for tracking the values of the crossfader, upfader and rotary knobs behind the scenes; the same approach applies to the pitch slider on the turntables. In the event Flash is not present, HTML5 Audio()
may be used by SoundManager 2 for playback of MP3s. Aside from these two elements, the markup is run-of-the-mill (and hopefully, somewhat-semantic) HTML4.
The majority of the "shiny" in the UI is thanks to a mix of standard and developing CSS3 features including border-radius
, transform:rotate()
and transitions. Since a turntable is a combination of circular elements that rotate inside a static rectangle, CSS is well-suited to the design.
In terms of JavaScript, the HTML5 Audio()
object, querySelector
-based methods, localStorage
and JSON
objects are the only "current-generation" features used. Aside from these elements, the turntable logic should be entirely functional in older browsers. Flash is currently used for the fancier audio features including pitch bending, scratching and EQ, but at the time of writing (June 2011) there are low-level JavaScript audio APIs being offered both by Mozilla and Google which may be able to replace the work Flash is doing. The prototype doesn't currently take advantage of these APIs, but they are very shiny and I expect they will be implemented at some point.
OK - here's where I attempt to break down the thinking behind the implementation. Fair warning, Danger, Will Robinson, here there be dragons etc.
The Markup: HTML
Deciding how to represent a real object in virtual markup is always a challenge. My take is that the controls should be presented as links, form elements or buttons where possible, and that the remainder of the markup is structural and forms the design and layout of the object.
<rant>
As all good HTML documents should be information-rich, the UI is marked up in "meaningful" HTML as appropriate. Because of this, the page gracefully degrades and the core UI + content renders nicely even with JavaScript turned off (though since the turntables have no "power", the power light will also be off, you can't play music etc.) It should be obvious, but blank or illegible pages shown to users without JS is something that many so-called "HTML5" websites suffer from these days; it reflects poorly on the site developers, and I consider them as examples of how not to do it.
</rant>
A turntable has controls for power and start/stop, 33/45 RPM speed buttons and a pitch slider. These can be marked up as links, inputs and buttons. The remainder of the turntable is design and layout; the tonearm, platter and record can be interacted with, but only in the sense of "drag-and-drop" actions which ultimately change the pitch and/or position of the sound.
Many thanks to the creative energy of graphic illustrator Kyle Kesterson from Giant Thinkwell, who made the decks 100% sexier.
From the rendering, we have a number of alpha-transparent PNG assets to work with:
By combining the images with some elements rendered in pure HTML + CSS (eg., the pitch slider) and giving the turntable body a background color of #30313b
, we get a UI like this:
The turntable markup ultimately takes a structure like this (labels indicate class names/roles):
Turntable UI (HTML + CSS), "debug" (outline) mode
turntable
platter
ring
record
record-ui <-- graphics + rotation box -->
slipmat <-- eg., "techniques" mat -->
grooves <-- record groove overlay -->
loader <-- "loading progress" shade -->
label <-- white paper circle -->
spindle <-- metal nub -->
shiny <-- light reflections -->
cover <-- captures mouse events -->
powerlight
tonearm
pitch
<input id="pitch-slider" />
<ul class="legend">
<li>(-8% ... 8%)</li>
</ul>
powerdial
<a href="#powerdial"></a>
<a href="#startstop"></a>
<a href="#rpm-33"></a>
<a href="#rpm-45"></a>
In the turntable case, there is a lot of structural markup to provide hooks for the positioning and independent rotation of elements. The platter contains a "record", which can have a few items stacked on top of it - ie., the slipmat when empty, or a record with album art, grooves and so on. The spindle (nested inside the record element) is always shown, but the label element in the middle is hidden when no record is present, and instead the slipmat is shown. The element swap is acheived simply by making a class name change at the top of the structure.
The remaining elements include the tonearm, power light, power dial and pitch control. The latter is the only one with any real structure including an unordered list and a hidden input; the former are all links which are picked up by JavaScript and call methods on the relevant turntable object.
The mixer is more rich in the semantic sense, since the layout is more columnar and modular; each "panel" is a single horizontal slice, containing one or two columns. Within each list item is a hidden input that tracks the value of the pot (or fader), as well as a non-semantic element for the control itself.
<form id="mixer">
panel
channel 1
<ul class="pots">
<li><input id="tt-1-gain" /></li>
</ul>
channel 2
<ul class="pots">
<li><input id="tt-2-gain" /></li>
</ul>
panel
channel 1
<ul class="pots">
<li><input id="tt-1-eq-1" /></li>
<li><input id="tt-1-eq-2" /></li>
<li><input id="tt-1-eq-3" /></li>
</ul>
channel 2
<ul class="pots">
<li><input id="tt-2-eq-1" /></li>
<li><input id="tt-2-eq-2" /></li>
<li><input id="tt-2-eq-3" /></li>
</ul>
panel
channel 1
upfader
<input id="tt-1-upfader" />
channel 2
upfader
<input id="tt-2-upfader" />
panel
crossfader
<input id="control-xfader" />
</form>
As each of these inputs is a type="range"
, they could be (and in fact, were) used functionally "as-is" during earlier prototyping.
Given the design, it didn't make sense to try to re-style the inputs, but they remain intact in the markup and are used to set and track the values of the controls. (A fun aside: The vertical fader elements have transform:rotate(-90deg)
applied.) You can see the underlying input elements if you enable debug mode in the UI.
The rotary, potentiometer-style controls are block-level elements made circular via border-radius
. JavaScript hooks into these elements' relevant mouse events in order to handle click and drag actions, meanwhile updating the values of the hidden input elements beneath.
CSS
The styling of the decks and mixer is fairly straightforward in terms of structure, including liberal use of border-radius
and rotation of a few key elements - namely the platter, record and tonearm containers.
An amount of strategic element nesting and absolute + relative positioning trickery goes into making the UI functional with the interactions between tonearm, platter, slipmat, record and album art that come into play. Where logical, elements to be rotated are nested together; the platter "ring" and record UI elements are the two circular items that rotate independently of each other.
.turntable .platter .record .record-ui {
/*
* circular things that sit on the platter
* including .slipmat + .cover + .label
*/
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background: transparent no-repeat 50% 50%;
/* large circle size */
background-size: 384px 384px;
border-radius: 384px;
}
.turntable .record .cuepoint .tape {
/*
* "pieces of tape" that mark cue points
* on the record (bookmarking time)
*/
position: absolute;
left: 50%;
top: 50%;
margin: 0px; /* reset */
/* width: how far out (set via JS)... */
/* how wide.. */
border-right: 8px solid rgba(255,255,255,0.65);
height: 16px; /* and how tall. */
}
The turntable tonearm, platter, record and elements inside the record are then rotated using transform:rotate()
with the angle set via JavaScript, which is where most of the rendering work is done. (More on this later, regarding hardware acceleration and CPU use.) While they appear stacked, the record and underlying platter can rotate relative to one another, allowing for things like record "slipperiness", back-cueing and scratching.
Look Ma, no images!
Certain elements of the UI were created without the use of <img />
(or external background images) and instead use text, CSS borders, background colors, gradients and shading to fill in the details. In the case of the pitch slider control, the HTML is an unordered list of numbers ranging from -8 to +8, and using the ■ character for the little "box" that shows up next to each number. A few positioning and line-height tricks made the text line up nicely with the desired target height of the box, matching the numbers to the actual pitch being applied.
Creating the slider knob was an interesting challenge in testing and tweaking of alpha-transparent CSS gradients, spacing and borders; I'm still not sure I got it quite right, but I think it's close enough that the shadows correctly suggest the "sloped" style of the knob with the flat-ish top, and a notch in the middle. Sometimes it looks correct, other times I think my brain fools itself into thinking it just looks like a flat, albeit warped button. The mixer's up-fader controls use a similar gradient, but they are more wide and round and don't seem to have this visual discrepancy.
The current mixer design is entirely CSS-based; the only "external" image is a base64-encoded data:
URI-based "line" on the rotary knobs. The rest is boxes, borders and native CSS gradients.
Record details: Grooves, texture, highlights
A virtual turntable UI simply isn't convincing if it misses some of the stuff a real one has; a record should have a slightly-glossy look, the appearance of concentric rings (or at least, a pattern of fine circles), and upon inspection, some sort of texture to the actual vinyl itself. As it turns out, image editing programs' "polar coordinates", gaussian noise filters and blur tools come in quite handy for these things.
There's nothing terribly special here, but there are a few layers in effect. Underlying everything is the slipmat, which is a single graphic showing the "Techniques" text. Substituting this for other slipmat images is easy, and fun; I even downloaded a few product shots of real slipmats and threw them on during development, and they worked wonderfully.
In making your standard 12" record, the base layer is simply gaussian noise reduced to greyscale with some extra contrast applied. Sitting atop the noise is the grooves layer, concentric 4x4-pixel circles rendered as a circle via "polar coordinates" filter. The highlights sit on top of everything else and are mirrored white triangles rotated 45 degrees, stretched and blurred with alpha transparency at the edges.
The detail is lost in the record thumbnail as shown here since it's a lossy .JPG, but the full-size thing has grooves visible when zoomed in.
JavaScript
function Turntable(ttID) {
// pseudo-turntable structure
this.dom = {
// references to key HTML elements
o: document.getElementById(ttID),
tonearm: null,
platter: null,
power: null
};
this.data = {
// internal state, DOM cache etc.
tonearm: {
offsetLeft: 0,
offsetTop: 0,
offsetWidth: 0,
offsetHeight: 0
},
platter: {
angle: 0,
locked: false,
rpm: 0,
velocity: 0,
...
},
record: {
angle: 0,
cuePoints: [],
dragging: false,
velocity: 0
},
sound: {
gain: 1,
soundObject: null
},
tonearm: {
angle: 0,
angleToRecord: 16,
angleMax: 28,
dragging: false
},
};
this.start = function() {};
this.stop = function() {};
this.setPitch = function() {};
this.init = function() {};
}
At the core, you have a fancy UI on top of a pretty standard sound player. Load, stop, play, pause, seek and load/play progress are the basics for offering a "usable" turntable implementation. Adding the mixer into the, er - mix, volume (and optionally, panning) control are also needed. All of this is covered by the SoundManager 2 API, a project that I also develop and maintain.
Where things really get interesting is when pitch control, scratching and EQ are added to the list; scratching and related record tricks DJs perform are done by manipulating pitch over time, so pitch provides the base functionality for all sorts of interesting effects in combination with panning and volume. In effect, all turntablism is a sub-function of pitch control combined with the volume-cutting capabilities of a mixer.
Taking the literal object approach, there are Turntable()
and Mixer()
constructors which reference the three distinct items marked up in HTML - #tt-1
, #tt-2
and #mixer
.
The turntable and mixer objects have logical structures for data/state and DOM references. At init or first-use, the data structure reads and caches static layout information from the DOM eg. offsetHeight
, position
and others. A caching approach avoids expensive browser reflow/layout that would otherwise be caused by repeated accesses to properties like offsetHeight
. Properties like this are important when dealing with mouse events for grabbing and scratching the record, moving the various sliders and so forth.
Handling mouse events
Both turntables and mixer have a number of draggable interactions associated with them. The record and platter watch mouse events for handling cueing (i.e., "seeking" for the beginning of a sound) and scratching via pitch control, as well as the pitch control knob itself which can be dragged. On the mixer side, rotary controls, horizontal and vertical faders can be clicked and dragged to slide or rotate. The standard mousedown()
-> mousemove()
-> mouseup()
events apply, with event delegation being used in some cases to allow a single handler to target multiple controls.
Pitch Bending: Dynamic sound processing
General disclaimer, here there be dragons etc.: My knowledge and understanding of practical math is not great. This is what I learned from attempting to build this feature, in addition to looking at others' partial implementations and example code.
Pitch bending + RPM Button Demo (YouTube)
Sample source: SonReal - Haunted (ft. Ali Milner.) Used with permission.
Screencast recorded on a slow 2008-era Mac Mini, so pardon the occasional graphics glitch.
As mentioned, scratching, platter and record "velocity" can be affected by the mouse and pitch controls. This ultimately results in a function call to alter the pitch of the sound, which is currently implemented within a special Flash 10/AS3-based build of the SoundManager 2 .SWF. The function call effectively boils down to setPitch(n)
where n
is say, ±10%. By dynamically adjusting this value over varying periods of time, scratching and pitch bending effects are both possible.
When creating or resampling sound data on-the-fly, Flash's sampleData
event is used (or, Mozilla or Google's related audio data API methods.) The Flash sampleData
event fires often during the regular playback of a sound object, and provides a buffer of 32-bit floating-point ByteArray
samples which can be modified within a loop and fed into an output buffer. This data is then ultimately sent out to the sound card, and the sound is finally played.
In the case of speeding up sound - let's say 200% normal speed, in the simplest case - the buffer provided to the sampleData
event is ignored and twice the samples needed are extracted from the source sound object, beginning at the sound's current position. If playing at a position 5 seconds into the sound and with a buffer of 1 second's worth of audio, we'd extract 200% of the buffer's amount of sample data (i.e., from 5 to 7 seconds) for our source. A loop then goes through the two seconds of source data and writes every other sample to the output buffer; thus, we end up with two seconds of audio data cut in half, and the sound is heard playing back at 200% normal speed.
200% faster: source [0,1,2,3,4,5,6,7] -> output [0,2,4,6]
Where things get interesting and funky is playing between 0% and 100% speed, where source sound data must be "stretched", literally inserted into the output buffer more than once, in order to make the sound playback rate slower.
200% slower: source [0,1,2,3] -> output [0,0,1,1,2,2,3,3]
This self-imposed "DSP 101" crash course was frustrating at times, but sometimes trial-and-error is a good learning tool. Analogies can work, too: if the concept is a bit fuzzy, perhaps a fuzzy analogy will help.
Think of what happens when you zoom in on an image; at 200%, each pixel is blown up to twice its original size. In order to prevent the image from looking blocky and pixelated, resampling (often bicubic or linear) is used to smooth out the image data after resizing. Instead of chunky pixels, you get a slightly-blurry double-size image that is more acceptable than the pixelated, "raw" version. On the audio side, a similar thing happens with interpolation and resampling as the "gaps" of space created by stretching are filled in with identical data.
Again, my practical knowledge of DSP is pretty non-existent, so this implementation is probably inefficient (and inaccurate) in many ways. I suspect the interpolation is overly-simplistic, so scratching can audibly "break down" especially on high-frequency sounds. Bass drums and lower-frequency sounds are OK, though, and pitch bending is pretty safe down until a certain point.
Big thank-yous go out to both Kelvin Luck and the flash "AudioTool" animal himself, Andre Michelle, for posting their own code, findings and notes which helped me to get this far on the Flash/AS3 audio processing side. As it turns out, debugging "one-off" and buffer underrun errors with 2048-iteration size loops through 44.1 KHz, 16-bit stereo waveform data samples inside Flash is a pain.
function sampleData(event: SampleDataEvent): void {
/*
* Flash 10/AS3 pseudo-code for pitch bending, fast/slow playback
* actual implementation is much more ugly + hackish. :D
*/
// 32-bit floats
var source:ByteArray = new ByteArray();
// number of samples to process (buffer size)
var buffer_size:int = 2048;
// how much source data we need to fetch (eg. 2 seconds)
var bytesNeeded:int = buffer_size*rate;
// get source data, store in ByteArray
sound.extract(source, bytesNeeded, soundPosition);
// main processing loop
for (var i = 0; i < buffer_size; i++) {
// where to extract from
sound.position = i*rate;
/*
* source.readFloat() calls x2 to fetch sound bytes
* interpolate ByteArray data
* output.writeFloat() with interpolated values
*/
}
}
Simulating power-down and electronic brake effects
Since we now have pitch control, it's relevant to consider how switching the turntable power on and off (or pressing the start/stop button) affects the motor, controlling the platter and thus the record speed. In the case of a turntable, the start/stop button applies an electronic "braking" effect when stopping, whereas turning off the power allows the platter to freely spin down naturally.
Start/Stop vs. Power brake effects (YouTube)
Sample source: ill.Gates - Scratchdisc. Used with permission.
For both braking and powering down, the turntable starts a single interval timer where the velocity is cut by either 2% or 10% for each "frame" of animation.
turntable.applyBrakeEffect = function(hasPower) {
var timer,
recordRate = self.data.record.velocity,
/*
* how fast the platter should spin down, based on
* whether power knob or stop/start button were used.
*/
brakeMultiplier = (hasPower ? 0.98 : 0.9);
function endEffect() {
// callback: when the platter has fully stopped
window.clearInterval(timer);
timer = null;
self.data.platter.locked = false;
}
function brakeEffect() {
// reduce platter velocity by % until 0.
if (!self.power.motor) {
recordRate *= brakeMultiplier;
self.setVelocity(recordRate);
if (Math.abs(recordRate) < 0.01) {
self.setVelocity(0);
self.applyVelocity(0);
endEffect();
}
} else {
endEffect();
}
}
// keep platter + record velocities in sync
self.data.platter.locked = true;
// begin braking
timer = window.setInterval(brakeEffect, 20);
};
Playing the record: Angle and velocity
In terms of the UI, the turntable spins when the power light is on, and the "motor" is on (via the start/stop button.) When powering up, the motor begins acceleration at a given rate and applies this velocity to the platter. The record trails the platter velocity and allows for a certain amount of slip or lag, in the spirit of the real thing. When throwing or backspinning the record, it may also take some time to lose its own velocity and eventually get up to speed with the underlying platter. The same logic applies if the platter is not spinning, or if the motor is off.
Record "physics": Slipperiness, inertia, velocity vs. platter
As the UI update "thread" runs, the record velocity is continually compared to the platter's and is adjusted as needed. In a special-case scenario, the record has less friction if backspin has been applied (i.e., the record has been thrown in reverse direction of the platter.)
// check record vs. platter velocity
if (self.data.record.velocity !== self.data.platter.velocity) {
var diff = self.data.record.velocity - self.data.platter.velocity,
// adjust speed by 25% of delta each time
absDiffScaled = Math.abs(diff) * 0.25;
if (diff < 0) {
// record needs to speed up
// if moving backwards, compensate for forward record movement by scaling down diff.
self.data.record.velocity += absDiffScaled * (self.data.record.velocity < 0 && self.data.platter.velocity !== 0 ? 0.5 : 1);
} else {
// record needs to slow down
self.data.record.velocity -= absDiffScaled;
}
}
Tracking: The tonearm, angle and "drift"
As the record plays through, the tonearm will track through a virtual spiral groove until it reaches the end. In MP3 land this is calculated simply and in a linear fashion, where the tonearm angle is set as tonearm.startAngle + (tonearm.maxAngle * (sound.position / sound.duration))
. From start to finish of a "track", the tonearm will move between 15 and 55 degrees over the record.
In reality, very few records are truly circular in shape; many have slight deviations, and this can be seen in the tonearm movement while the record is spinning. To this end and for a bit of fun, the whole tonearm assembly can "wobble" side-to-side slightly while on the record simply by applying Math.sin()
, keying it off of the rotation angle of the record and scaling up the motion slightly.
self.data.tonearm.drift = Math.sin(self.data.record.angle) * self.data.tonearm.driftMax;
The "End Of Record" case
Turntable owners will recognize the sound of a record that has run its course. Perhaps you were distracted, busy or in the other room when the last song ended; once the needle hits the end of the groove, it sits in an endless loop at the edge of the paper label and makes a distinctive sound unique to the record being played.
Turntable Loop (YouTube)
Recreating the "end of record" bump and/or click soundtrack associated with this is fairly trivial in JavaScript. The prototype uses the sound taken from this video, in fact.
turntable.sound.onfinish = function() {
this.setPosition(0); // reset current "record"
turntable.endOfRecordSound.play({
onfinish: turntable.sound.onfinish // endless loop
});
}
Performance & Support Considerations, caveats etc.
Hardware Acceleration (or alternately, "CPU fires")
The shiny UI and layout of this prototype comes at a cost, of course. As web designs "expand" to take advantage of CSS for rich animations, transitions, alpha-transparent elements and so forth, drawing operations consequently become more expensive. In addition to effects, more designs can be rendered in pure CSS instead of using external images. Thus, "accelerated drawing" - having rendering and/or layout work done by the GPU or via another efficient processor - is becoming increasingly important.
Theoretically, CSS can provide for greater optimization opportunities than inline images because the structure is implied. Realistically, could a CSS drop shadow be rendered faster by a GPU as opposed to a tiled alpha-transparent PNG? Is it just bytes, or is there a difference at this point? (This is beyond my limited knowledge of rendering engines, so I'm left to theorize.) In either event, the turntable prototype does use CSS for many effects, and even a few key UI elements; the turntable pitch control and the mixer are drawn entirely using CSS, and that may provide for some optimizations over the use of static images.
Acceleration as a browser selling point
Without acceleration, the turntable UI really chokes as it falls back to leaning on the CPU to do the heavy lifting, animating layers using CSS' transform:rotate()
, gradients, alpha opacity and so on. Supporting accelerated drawing (or "GPU-accelerated compositing") may turn out to be a big competitive edge in the near future for making popular browser-based "HTML5" games shine.
Browser acceleration is presently not a majority feature. For acceleration to work support must exist not only in the browser, but also in the video drivers (sometimes down to specific versions) and the operating system on top of the hardware itself. (One might argue this is true for any feature, but acceleration in browsers is a relatively-new and specific thing.)
Microsoft boasts of acceleration as a new and important feature for Internet Explorer 9 - which is great - but the browser is available only on Vista SP2+ and Windows 7. This differs from Opera, Mozilla and even Apple, who are all aiming for acceleration on at least two third-party operating systems. Surprisingly, Safari 5 on my 2006-era Fujitsu Lifebook laptop, running WinXP SP3 at 1.2 Ghz with an Intel GMA 915 chipset video card, manages to be accelerated one way or another; the turntable demo gets 20 frames per second on Safari 5, beating Firefox 4 which gets only 4 fps by comparison (and pegged CPU use) - obviously not accelerated.
(Maybe it's my video card and/or driver that is the problem here; Firefox is my favourite browser, for the record, and I'd love to see it be fast on this hardware as well. It shows 15 fps on my GMA 950-based, 2008-era Mac Mini, but appears not to be accelerated and still pegs the CPU. Maybe I'm just doing it wrong?)
Credit is due to Apple here, who appear to have done quite a nice job of getting the most out of legacy hardware where others haven't yet (or in the case of Microsoft, won't support a legacy OS), and on a non-native OS at that. It's worth mentioning that Apple's mobile devices do a pretty good job at acceleration, also; an iPhone 3GS can render the UI and draw the turntable playing a record surprisingly smoothly. However, iOS can currently only play one sound at a time - so despite the nice UI, you won't be DJing there via JavaScript just yet.
GPU and related drawing acceleration is still relatively new to browsers, so it's assumed that things will improve. At time of writing, Google's Chrome browser has acceleration enabled by default as of version 12, but like Mozilla, blacklists acceleration for certain known "bad" or buggy hardware and/or software configurations. Similar rules apply for Firefox, last I checked (using about:support
for acceleration status.)
Latency and "performance"
Delays between "pushing the button" and "hearing the sound" are the bane of digital DJs and musicians everywhere. In a perfect world, digital would match analog's minimal or zero-latency performance. My understanding is that in exchange for the digital conversion, compression and routing of binary bits representing analog sound being passed around in systems with additional hardware and software stacks below and underneath the sound hardware itself (including the operating system, drivers and so on), we'll simply have to accept a certain amount of latency. In spite of complexity, however, computers can still provide very low-latency audio in the right situations.
Interestingly, Flash under Safari on OS X has reasonably-low latency considering its history and position in the software stack. From within the sampleData
event in Flash 10, latency is reported as either 23 or 46 milliseconds. This would be considered as being pretty poor in live DJ hardware or console-based cases, but it's less of an issue when you're DJing, say, via mouse and keyboard controls in a browser on your laptop at a small house party. Even if your performance lags a bit, at <50 msec, members of the appropriate sex will probably still find you attractive.
Flash 10 under Firefox and Safari on my Windows XP laptop has far worse latency, reporting 278 and 371 milliseconds, respectively. This makes scratching effectively useless, as the technique is near-impossible to pull off if the mouse and record movement are not seen and heard at effectively the same time. Provided you can resist the urge to wikki-wikki up a scratch-fest a la Kid Koala, Mix Master Mike or Q-Bert, your house party guests probably won't notice the 0.3-second difference. Mixing and beat-matching tracks will still prove to be challenging, though, and you'll have to account for the delay when cueueing music.
Scratching, mouse resolution and timing
It is unclear whether JavaScript mousemove()
events are throttled on a per-browser basis, and if so, whether throttling is based on a time interval or something else. In any case, it has been a challenge to capture mouse movements and translate them into motion vectors that are anywhere near real-world physics from within JavaScript.
Given the display size of the UI and mouse event frequency, it is difficult to smoothly scratch the record when the mouse is being moved closer to the center as there are fewer data points to work with. More events are fired when the mouse is moving along the outside of the record, but the points are still limited within the record's 326-pixel radius. The smoothest record movement is obtained when the mouse drags outside of the record area, and can move with increased precision.
A third wrinkle to consider is that mouse events do not fire when the mouse stops moving; rather, the mousemove()
event simply ceases to fire. Left unchecked, the record would continue to play at its last applied velocity - an undesirable behaviour. When the mouse stops, the record should lose its velocity and quickly revert to "zero." Since no events fire when the mouse stops, a timer is constantly being set, canceled and re-set during mousemove()
that checks for continued movement - and if none is found, then the record can instantly drop its velocity to zero. Implementing this behaviour allows for scratching to work at various speeds, and to reasonable effect considering the lack of event granularity.
/*
* (within mousemove() handler)
* try to cancel/stop motion within a reasonable delay
* after the last mouse move - otherwise, the record
* will follow the last "known" speed.
* the next cancel check is scheduled based
* on the time since the last mousemove() event.
*/
if (drag_timer) {
window.clearTimeout(drag_timer);
}
drag_timer = window.setTimeout(function() {
// draging, or scratching with power off
if (self.data.record.dragging || !self.power.motor) {
self.data.record.velocity = 0;
self.applyVelocity(self.data.record.velocity);
drag_timer = null;
}
// next stop between 20 and 150 msec, based on mouse timing
}, Math.min(150, Math.max(20, self.data.record.lastMouseMove ? (
(now - self.data.record.lastMouseMove) * 2 : 0 ) ) );
// update the "last fired" value
self.data.record.lastMouseMove = new Date();
This video shows some of the velocity-killing code at work, as well as the effects of moving the mouse both at different speeds and distances from the center of the record. (Again, granularity increases with distance.) There is room for improvement on both the Flash and JavaScript sides in terms of handling scratching and sound quality, but timers in JS aren't exactly precise.
Scratching, timing and mouse movements (YouTube)
Sample used: Common - The Light.
Support vs. features: HTML5 vs. Flash
At some point, the palpable elephant in the room has to be acknowledged. Due to the state of HTML5 audio, Flash is still a valid option for providing audio capabilities to browsers that lack HTML5 support entirely, or don't support non-free audio formats like MP3 which are still popular with both DJs and the web at large.
For the interests of the turntable demo, Flash 10 also provides the ability to do dynamic sound processing - which means that things like scratching, pitch bending and EQ, things not officially in the HTML5 spec (at time of writing) are possible while also using MP3 formats on a wider range of browsers. If you're on a device that doesn't support Flash (like iOS), then you will get the non-Flash experience - which will currently mean that "scratch mode", pitch bending, scratching and EQ features are not available, but the basics will still work.
As mentioned, Mozilla's Audio Data API and Google's Web Audio API both provide similar low-level access to audio data from JavaScript, which is a wonderful thing. Google specifically is aiming for a low-level, low-latency API which will provide conveniences to developers hoping to write applications that need responsive sound. The demo does not currently use either APIs, but both are in development and should be functional enough to supplant most or all of the work done by Flash at some point. (Mozilla's API does not support arbitrary byte extraction at this time, which is required to make scratching-in-reverse work in particular.)
Miscellany
Waveforms
The waveforms of Cypress Hill's "Boom Bitty Bye Bye" (top), and Westside Connection's "Bow Down", wrapped for display purposes.
In order to show a waveform and track the approximate position of the sound, it made sense to generate and cache a waveform image which could be loaded as a background and positioned based on the image width and the sound's progress.
To generate the image, a PHP script (adapted from code by Andrew Freiday) runs LAME, downsampling the MP3 and outputting a temporary 8 Khz .WAV file. PHP then reads the file, skipping the RIFF header and storing the following bytes in an array. The array is then looped over, and PHP's GD image library creates a .PNG by drawing the values as lines scaled to the desired image dimensions. Finally, optiPNG runs and optimizes the file, storing it in a cache directory for future requests.
A 500-pixel-scaled version of the waveform is also generated for the hover state, which shows the entire track within the turntable UI. Perhaps for historical reasons, Firefox 4 will not currently display any image with a side larger than 65535 pixels (AKA, a 16-bit unsigned integer?) To work around this, a third image is created which is <= 65535 pixels wide.
Cue points
Once waveforms are being shown, it's interesting to think about being able to "bookmark" the start of a beat, loop or a sample at a certain point in a track for later reference. If a marker is placed where a bass drum kicks off a loop, that position can easily be recalled and the loop can be seamlessly re-started (subject to the user's timing and accuracy, of course.)
In the real world, hip-hop DJs would (and still do) use pieces of tape to physically mark a spot on the record, either as a reference point or as the actual part of a record where a desired loop or sound sample begins. With a small border a bit of opacity, placing a piece of "tape" on the record is fairly straightforward.
In the prototype, the keys 1-5
and 6-0
set cue points on the left and right decks, respectively, while a record is playing. Subsequent key presses will recall a cue point; using the shift
key will overwrite or update a cue point marker, and ctrl
will remove it.
Cue Points (Sampling) Demo (YouTube)
Sample source: Beck - Loser, which itself samples the drums from Johnny Jenkins' "I Walk On Guilded Splinters."
Putting It All Together
Here's a quick example showing the turntables in CSS "debug" / layout mode, setting of cue points, cueing, backspin, pitch bending, beat matching of two tracks, cross-fading and cutting using the mixer.
This demo uses samples of favourites from my personal music collection: Lyrics Born's I Changed My Mind (iTunes album: Same !@#$ Different Day), and The Cardigans' Erase / Rewind (iTunes album: Gran Turismo.)
This is a pretty experimental hack with lots of caveats. Is it actually usable?
The prototype is an experimental bit of web audio fun, stamped with a number of warning labels and is not intended for pro or "skratch" DJs as frankly, it will quickly disappoint - and what DJ really wants to scratch records with a mouse, anyway. ;)
CPU and audio latency are the primary issues with the prototype. Firstly, the shiny UI requires a fair bit of processing power to draw and animate. On top of this, the Flash 10/AS3-based audio processing portion needs an amount of CPU time to do its work. If you don't have a GPU (i.e., "hardware acceleration") taking care of the layout, that work falls on the CPU and can max it out, causing the Flash-based "scratch mode" audio to become completely unusable. In the non-GPU case, you can use non-scratch mode and while the UI may be slow (and your CPU may still be pegged), audio plays normally and the demo is at least somewhat functional.
DJ Schill @ Bayjax by Derek Gathright, on Flickr (an earlier prototype version, Yahoo!-themed)
If you are using a modern browser and a software + hardware stack that gets you GPU acceleration in said browser, you should be in good standing. Bonus points if you're on OS X, where flash audio latency is also less severe; thus, Safari 5, Webkit nightlies or Chrome 12 builds on a MacBook Pro make for a pretty usable experience. Audio stuttering can still occur under load, so it's helpful to close other programs that might eat up system resources.
In theory, an accelerated MacBook Pro + Safari setup should suffice for DJing a small gathering, house party, or perhaps the pre-show of a "Bayjax" talk at Yahoo! in Sunnyvale, California. (In my case, I was giving a talk on HTML5 audio at an internal YUI conference and was invited to play some "pre-show" music at the Bayjax event coincidentally being hosted downstairs. Thankfully, the demo worked.)
If you're using Windows XP, the 300+ millisecond latency that Flash audio has on XP will make scratching and mixing frustrating and basically impossible. I'm not sure how Vista and Windows 7 are in terms of latency, so your mileage may vary.
As previously mentioned, newer JavaScript audio APIs are being developed by Mozilla and Google that have the potential for higher-performing, lower-latency audio manipulation - so the current flash implementation may be less relevant over time.
What was the point of all of this, again?
Given the almost 8,000 words in this article (and congratulations and thanks for making it this far) I wouldn't blame you for having forgotten, either.
Development history: Snapshots
Periodic photos of the UI in development.
Day #1: border-radius
, transform:rotate
and a pretty terrible attempt at drawing a tonearm. ;)
Week #1: Larger size, tonearm line-art via Flickr user fumes1200. The image was a tattoo design he made for a friend(!)
Week #2: A hacked-together platter ring and functioning HTML5 <input type="range" />
element for the cross-fader.
Week #4: The hi-fi goes hi-fi, thanks to graphics from a graphic designer friend, Kyle Kesterson.
Week #6: Waveforms and cue points have been added to the UI. The first take at pitch bending has recently been implemented.
Week #8: Pitch controls are under each deck, and a mixer UI is in the works; a Yahoo!-themed CSS skin is made for a YUI conference demo.
I've wanted to push the capabilities of the web browser and build a functional, web-based turntable UI in HTML + CSS + JS for years; native web capabilities are just now getting to the point where it's actually feasible, with Flash providing audio support in some cases.
The "Wheels Of Steel" prototype started on January 31st, 2011 as an experimental side project, and took twelve weeks of personal time split between evenings and weekends to get to "feature complete." An additional month (to date) has been spent tweaking layout and features. At the end of day #1, I had a box with a border-radius
-based circle for a record along with a cheaply-drawn tonearm tracking across it. While simplistic, this was encouraging and motivated me to improve the design and add features.
A few more graphic elements were added as work continued. I searched on Flickr for reference images, and found a piece of tonearm line art that a DJ and illustrator had made for a friend who was getting a tattoo. An email and a response granting permission later, the tonearm was worked into the prototype. Meanwhile, I was working on "scrubbing" the record on the JavaScript side, tracking position as the record rotates.
By week two, basic drag and tracking of the record was working; the first stab at a cross-fader was in there, too. It was now possible to cue the beginning of a track, and do some basic beat juggling between two turntables. It was still early, but the progress was promising.
A month in, an email titled Graduation day! arrived in my inbox; it was a surprise gift from graphic illustrator friend Kyle Kesterson, who provided some amazing graphics and took the turntable UI from "eh" to "whoah" overnight. Kyle said he was inspired to do a turntable rendering after playing with the demo for over an hour. I can't thank him enough!
After the new graphic design, I started adding elements like Technics-"inspired" slipmats. Work was being done on the Flash 10/AS3 pitch controls and handling behind the scenes, so that scratch and pitch bending would be possible. The pitch stuff eventually proved to be the most challenging to implement. Some time around here the cue point stuff began, an experiment that turned into a fun feature.
Record "physics" were also being developed around this point - namely, the interactions between the turntable "motor", platter and record - with an eye toward pitch bending and scratching in the near future. If you throw the record, it should backspin and eventually catch up with the forward-moving platter, and the record's pitch should also follow suit.
By week six, I had cue points (represented as pieces of tape on the record, like DJs use) up and working nicely; this made looping and sample cutting possible, similar to what an MPC might do, but with the bonus of having a scratch-able record that could be grabbed and moved immediately after recalling a sample. Pitch slider controls were now visible in the UI, and PHP-generated waveforms started tracking the progress of the sound as it played.
The pitch controls moved onto the turntable UI within the next two weeks, and were rendered via HTML and CSS, using no static images. Surprisingly, the labels looked tight when rendered as text, even at sub-10-pixel sizes.
Three months in, the UI and core features had more or less been finished. Lots of time had been spent testing and playing music. The whole thing is still quite experimental and can eat up CPU in the best of cases, but I'm quite surprised with how far the design and features have run; much further than I ever thought.
Feedback from co-workers and friends who have seen and played with the Wheels Of Steel prototype have been highly positive. It's also been demoed at meet-ups, a conference at Yahoo! and a few external venues, so it's fun to finally get the release out for a larger audience to play with.
Fin
We now have digital toys and things that allow music to be manipulated in ways never imagined before - and yet, there remains something special and meaningful about the old, scratchy, dusty sound of analog mediums and the hands-on, real physical experience of mixing music that digital technology - this HTML experiment included - still aims to match.
--- ( END OF RECORD ) ---
Since you made it all the way to the end, thank you for reading!
And now, for something completely different...
Nyan Cat Overload! (YouTube)