Dear Developers, today I'll guiding you to creating an MP3 Player with HTML programming step-by-step. Let's go!
What is HTML5 Audio Player? It is a subject of the HTML5 specification, incorporating audio input, playback, and synthesis, as well as speech to text, in the browser, given rise to some new and exciting possibilities, especially when it comes to music related web applications... etc.
We hope to introduce you to some of these possibilities by walking you through how I created this jam station. This project originally began as an experiment, but over time evolved into an open ended practice and teaching tool for guitar players.
A solid understanding of Javascript fundamentals is necessary for following this tutorial.
If you aren’t particularly interested in recreating this tutorial exactly, that’s all right. Much of what I cover in this tutorial is applicable to different types of musical applications.
A basic grasp on music theory will help you in following this tutorial. An understanding of time signatures, measures and beats are a must.
Things You Will Need
The audio track you choose will need to be recorded to a click track or metronome. In other words, the length of each beat will need to be uniform across the entire track.There are a few things you will need to know about your audio track in order to find the current beat and measure.
- Beats Per Minute (BPM or Tempo)
- Time Signature
- Time the Track Starts
- How many measures are used to count in (not always applicable)
Here’s the relevant data for that track.
- BMP = 117
- Time Signature = 4/4
- Start time of the first beat = 0.2s
- Measures used for count in = 1
Markup and CSS
Before getting into the fun stuff, here’s HTML and CSS we’ll be using. <div class="wrapper">
<div id="chord-diagram"></div>
<audio id="jam-track" src="https://myguitarpal.com/wp-content/uploads/2014/09/12-Bar-Blues-in-A-Version-1.mp3" controls></audio>
<br>
<label>Beat: </label>
<div class="data" id="beat"></div>
<label>Measure: </label>
<div class="data" id="measure"></div>
<label>Section: </label>
<div class="data" id="section"></div>
<label>Chord: </label>
<div class="data" id="chord"></div>
<div id="chord-progression"></div>
</div>
.wrapper {
max-width: 400px;
}
#chord-progression {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid lightgray;
}
.section {
margin-bottom: 20px;
display: none;
}
.measure {
width: 25%;
display: inline-block;
}
.m-line {
float: right;
width: 10px;
}
audio {
width: 100%;
}
.data {
display: inline;
margin-right: 10px;
}
label {
font-weight: bold;
}
Set up the Variables
First we’ll take all of the track data we discussed and set up some variables. // beats per minute
var BPM = 117;
// beats per second
var BPS = 60 / BPM;
// measures used for count in
var measuresCount = 1;
// time the track starts
var offsetSeconds = 0.2;
// time signature
var timeSigTop = 4;
var timeSigBottom = 4;
Chord Progression
I’ve kept the chord progression data model pretty simple for the purposes of this tutorial and used a multidimensional array.The top level array is the chord progression sections. These could be verses, choruses, bridges, etc.
The nested arrays contain the chords for each measure in their respective sections.
var sectionOne = ['A7', 'A7', 'A7', 'A7', 'D7', 'D7', 'A7', 'A7', 'E7', 'D7', 'A7', 'E7'];
var sectionTwo = ['A7', 'A7', 'A7', 'A7', 'D7', 'D7', 'A7', 'A7', 'E7', 'D7', 'A7', 'A7', 'A7', 'A7', 'A7', 'A7', 'A7', 'A7'];
var chordProgression = [
sectionOne,
sectionOne,
sectionOne,
sectionTwo
];
Music often repeats certain sections like the chorus, so in order to
keep things DRY, store each section like shown above in a variable, then
include the sections in the chordProgression
array.Running the Jam Station
This jam station uses the timeupdate event which is similar to using a game loop. Each time thetimeupdate
event fires (several times per second while the track is playing) we’re
going to run a function and update some data such as the current beat,
measure and chord. The only time the data will not update is when the
track is paused.When the
timeupdate
event fires we’ll run the jamStation
function. The function will be called several times per second while the audio is playing. var audio = document.getElementById('jam-track');
audio.ontimeupdate = function() {
jamStation();
};
Fun Part
This function should only deal with data, not presentation. We’ll use another function later (renderJamStation
) to deal with presentation.To find the current beat we will use the expression
(audio.currentTime - offsetSeconds + BPS) / BPS
, and we’ll store this value in the variable beat
.We can then find the current measure with the expression
(beat - 1) / timeSigTop
, which we’ll store in the variable measure
. function jamStation() {
var beat = (audio.currentTime - offsetSeconds + BPS) / BPS;
var measure = (beat - 1) / timeSigTop;
}
The beat
and measure
variables should now
be rounded down. This will give us easier numbers to work with when we
are doing comparisons. I wouldn’t suggest doing this however if you were
to do a more complex app where you needed to use fractions of a beat.We’ll store these whole numbers in
cleanBeat
and cleanMeasure
.If you want to display the current beat in a measure you can use the following expression:
((cleanBeat - 1) % timeSigTop) + 1
. Let’s say we’re on beat 13. The expressions values would then be: ((13 - 1) % 4) + 1
. We add 1 because there is no such thing as beat 0.Now instead of having the beat increment infinitely it will only increment as high as the number in the top of the time signature.
measureBeat
will contain the value of the beat within the measure. So, cleanMeasure
will count 1,2,3,4,5,6,7,8, etc. whereas measureBeat
will count 1,2,3,4,1,2,3,4 etc. function jamStation() {
var beat = (audio.currentTime - offsetSeconds + BPS) / BPS;
var measure = (beat - 1) / timeSigTop;
// round down for beat and measure
var cleanBeat = Math.floor(beat);
cleanMeasure = Math.floor(measure);
// find the current beat within the measure
measureBeat = ((cleanBeat - 1) % timeSigTop) + 1;
}
Don’t put var
in front of cleanMeasure
as we’ve already declared this variable outside of the function (see the CodePen) as we want to access this value from outside of jamStation()
.At this point we actually have the most important data we’ll need. The tricky part though is going to be dealing with the chord progression.
The next part of the
jamStation
function is for determining two things: the currentSection
and the currentChord
.First we need to use an if…else statement to tests if we are past the first measure. If we are, we will find the value of the current section and current chord. If we aren’t,
currentSection
and currentChord
will be set to null.To find the current section and current chord we loop through the sections, then run a nested loop that loops through the measures in each section.
The variable
count
is set and incremented by 1 every time we loop through a measure of each section. Now, if cleanMeasure
equals count
,
we know we’ve found the section and measure we’re on. Because we’ve
found the correct section and measure the audio track is currently at,
we need store those values and we need to break both loops so the
correct data doesn’t get overwritten the next time the loop runs.Don’t use
var
in front of currentSection
or currentChord
, as again, these variables have been declared outside of jamStation()
so we can share them between functions. function jamStation() {
var beat = (audio.currentTime - offsetSeconds + BPS) / BPS;
var measure = (beat - 1) / timeSigTop;
// round down for beat and measure
var cleanBeat = Math.floor(beat);
cleanMeasure = Math.floor(measure);
// find the current beat within the measure
measureBeat = ((cleanBeat - 1) % timeSigTop) + 1;
if (cleanMeasure > 0) {
// find the currentSection and currentChord
var count = 0;
var br = false;
for (var s = 0; s < chordProgression.length; s++) {
for (var m = 0; m < chordProgression[s].length; m++) {
count++;
if (cleanMeasure == count) {
currentSection = s + 1;
currentChord = chordProgression[s][m];
br = true;
break;
}
}
if (br === true) {
break;
}
}
} else {
currentSection = null;
currentChord = null;
}
// display the jam station and its data
renderJamStation();
}
Now we have all of the data we need and it’s accessible globally.At the end of the
jamStation
function you can see that renderJamStation()
is run. This function will be used for presentation purposes only and we will cover it in a moment.Rendering the Chord Progression
We need to display the chord progression. Let’s wrap it in a function calledrenderChordProgression
to keep things tidy. This function will only be run once as the chord progression data is never updated.First we loop through the sections in the chord progression. We create a div with the class of “section” and id of “section-[number]” each time the loop loops. Each section has a unique id so we can display that specific section when it is being played.
// take the chordProgression array and render the HTML
function renderChordProgression() {
var progression = document.getElementById('chord-progression');
// make the sections
for (var s = 0; s < chordProgression.length; s++) {
progression.innerHTML += '<div class="section" id="section-' + (s + 1) + '"></div>';
}
}
Next we loop through the sections again so we can then loop through
all the measures in a nested loop. Each measure will then be included in
it’s respective section.You probably noticed the count variable was set and incremented. This is done in order to increment through every measure in every section.
The complete function looks like this:
// take the chordProgression array and render the HTML
function renderChordProgression(){
var progression = document.getElementById('chord-progression');
// make the sections
for (var s = 0; s < chordProgression.length; s++){
progression.innerHTML += '<div class="section" id="section-' + (s + 1) + '"></div>';
}
var count = 0;
for (var s = 0; s < chordProgression.length; s++) {
for (var m = 0; m < chordProgression[s].length; m++) {
count++;
var section = document.getElementById('section-' + (s + 1));
section.innerHTML += '<div class="measure" id="measure-' + count + '">'
+ chordProgression[s][m]
+ '<div class="m-line">|</div></div>';
}
}
}
You will probably want to style the chord progression. You can reference the CSS in the CodePen at the top of this post.Presentation now
The functionrenderJamStation
is called from inside the function jamStation
. function renderJamStation() {
// show the beat within the measure, not overall
document.getElementById('beat').innerHTML = measureBeat;
// show the current measure, but only if the jam track is past the count in measures
var measureElem = document.getElementById('measure');
// only show the current measure if it's > 0
if (cleanMeasure > 0) {
measureElem.innerHTML = cleanMeasure;
} else {
measureElem.innerHTML = '';
}
// show the section number
document.getElementById('section').innerHTML = currentSection;
// show the current chord name
document.getElementById('chord').innerHTML = currentChord;
// hide all sections before displaying only the section we want to see
var allSections = document.getElementsByClassName('section');
for (var i = 0; i < allSections.length; i++) {
allSections[i].style.display = 'none';
}
// show the currently playing section
if (currentSection != null) {
document.getElementById('section-' + currentSection).style.display = 'block';
} else {
allSections[0].style.display = 'block';
}
// style the current chord in the chord progression
if (cleanMeasure > 0) {
// style all measures black
var measures = document.getElementsByClassName('measure');
for (var i = 0; i < measures.length; i++) {
measures[i].style.color = 'black';
}
// style current measure red
document.getElementById('measure-' + cleanMeasure).style.color = 'red';
}
}
I won’t cover every part of this function as most of it should be
self-explanatory if you’re familiar with JavaScript. For the most part,
it takes some data such as the current chord and current measure and
displays it.The main bit of this function you should look closely at is how it displays the right section.
// hide all sections before displaying only the section we want to see
var allSections = document.getElementsByClassName('section');
for (var i = 0; i < allSections.length; i++) {
allSections[i].style.display = 'none';
}
// show the currently playing section
if (currentSection != null) {
document.getElementById('section-' + currentSection).style.display = 'block';
} else {
allSections[0].style.display = 'block';
}
You can see every section is first hidden. If this isn’t done, each
section will appear when it’s time is ready, but the sections which
aren’t playing will still be visible.To display the current section being played we select it by its ID and set its display property back to block.
If
currentSection
is null, we show the first section. If
we don’t do this, the first section will not be visible before playing
the track and arriving at a point in time where the section should be
visible.Rendering the Initial Display
Finally, we run two functions.renderChordProgression()
needs to be run once in order to render the chord progression and jamStation()
should be run once as well. Yes, the function jamStation()
will be run whenever timeupdate
fires, but it should also be run once on its own, otherwise the chord progression will not render at first. renderChordProgression();
jamStation();
Further Thoughts and Ideas
If you are interested in displaying chord diagrams for an instrument, you have a lot of the data you need, most importantly the chord name and type.Let’s say you wanted to display chord diagram images when the chord progression was on the appropriate chord. We already have the current chord stored in the variable
currentChord
.You could create an array of chord objects.
var chords = [
{
name: 'A',
type: '7',
src: '/images/a7.jpg',
},
{
name: 'D',
type: '7',
src: '/images/d7.jpg',
},
{
name: 'E',
type: '7',
src: '/images/e7.jpg',
}
];
Then, run this bit of logic inside the jamStation
function which will display the right chord at the right time. for (var i = 0; i < chords.length; i++) {
if (chords[i].name + chords[i].type == currentChord) {
document.getElementById('chord-diagram').innerHTML = '<img src="' + chords[i].src + '"/>';
}
}
Conclusion
Maybe you don’t want to build exactly what I’ve covered today and that’s OK. I can think of so many ways you could apply what I’ve covered in this article to all different types of projects. Here are a few ideas:- A new take on galleries or sliders. Transition images on certain beats or measures.
- Animate images to a beat. Tapping feet? Poses of someone dancing?
- Audio visualizations.
- Annotations or diagrams used for teaching purposes which are synced to a piece of music.
- There are all sorts of things you could do with this amazing, open source JavaScript music notation API