I'm making an audio frequency trainer as a learning exercise. It could be useful for audio technicians who need to recognize audio frequencies (eg. when ringing out feedback during a live concert or soundcheck).
A few days ago I posted the humble beginnings. I got very useful feedback and went further. Here is a working example on JSFiddle & here is the full code on GitHub. In case anyone prefers the devtools in their own browser, here is a working example online.
Before I continue adding more functions, I would like to know what you guys think of this so far. Any feedback is welcome!
let toneContext = null;
let toneGenerator = null;
let toneAmplifier = null;
function startFrequencyTrainer(difficultyMode, previousFrequency) {
let frequencies = null;
let frequency = null;
// Create objects
toneContext = new(window.AudioContext || window.webkitAudioContext)();
toneAmplifier = toneContext.createGain();
// Pick a frequency
frequencies = getFrequencies(difficultyMode);
frequency = getNewFrequency(frequencies, previousFrequency);
return {
frequencies,
frequency
};
}
function stopFrequencyTrainer() {
toneContext.close();
}
function startToneGenerator(frequency, volumeControl, startTimer, stopTimer) {
// Create and configure the oscillator
toneGenerator = toneContext.createOscillator();
toneGenerator.type = 'sine'; // could be sine, square, sawtooth or triangle
toneGenerator.frequency.value = frequency;
// Connect toneGenerator -> toneAmplifier -> output
toneGenerator.connect(toneAmplifier);
toneAmplifier.connect(toneContext.destination);
// Set the gain volume
toneAmplifier.gain.value = volumeControl.value / 100;
// Fire up the toneGenerator
toneGenerator.start(toneContext.currentTime + startTimer);
toneGenerator.stop(toneContext.currentTime + startTimer + stopTimer);
}
function stopToneGenerator() {
if (toneGenerator) {
toneGenerator.disconnect();
}
}
function changeVolume(volumeControl) {
toneAmplifier.gain.value = volumeControl.value / 100;
}
function getFrequencies(difficultyMode) {
let frequencies = null;
if (difficultyMode === 'easy') {
frequencies = ["250", "800", "2500", "8000"];
} else if (difficultyMode === 'normal') {
frequencies = ["100", "200", "400", "800", "1600", "3150", "6300", "12500"];
} else if (difficultyMode === 'hard') {
frequencies = ["80", "125", "160", "250", "315", "500", "630", "1000", "1250", "2000", "2500", "4000", "5000", "8000", "10000", "16000"];
} else if (difficultyMode === 'pro') {
frequencies = ["20", "25", "31.5", "40", "50", "63", "80", "100", "125", "160", "200", "250", "315", "400", "500", "630", "800", "1000", "1250", "1600", "2000", "2500", "3150", "4000", "5000", "6300", "8000", "10000", "12500", "16000", "20000"];
}
return frequencies;
}
function getNewFrequency(frequencies, previousFrequency) {
let newFrequency = null;
newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
// Avoid getting the same frequency twice in a row
while (newFrequency === previousFrequency) {
newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
}
return newFrequency;
}
function frequencyFormatter(frequency) {
let frequencyFormatted = null;
if (frequency > 999) {
frequencyFormatted = frequency / 1000 + ' k';
} else {
frequencyFormatted = frequency + ' ';
}
return frequencyFormatted;
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:900" />
</head>
<body>
<div class="body">
<div class="title">
<h1>Frequency Trainer</h1>
</div>
<div class="controls">
<br />
<button type="button" id="start-button" class="control-button">Start</button>
<button type="button" id="stop-button" class="control-button">Stop</button>
<button type="button" id="next-button" class="control-button">Next</button><br />
<br />
Volume:<br />
<input type="range" id="volume-control" class="volume-control" min="0" max="20" value="2" step="0.1" /><br />
<br />
<button type="button" id="difficulty-easy" class="difficulty-button" data-difficulty="easy">Easy</button>
<button type="button" id="difficulty-normal" class="difficulty-button" data-difficulty="normal">Normal</button>
<button type="button" id="difficulty-hard" class="difficulty-button" data-difficulty="hard">Hard</button>
<button type="button" id="difficulty-pro" class="difficulty-button" data-difficulty="pro">Pro</button><br />
<br />
</div>
<div class="grid">
</div>
</div>
<script>
(function () {
let difficultyMode = 'easy'; // default difficulty mode
let frequencyTrainer = startFrequencyTrainer(difficultyMode, null);
let frequency = frequencyTrainer.frequency;
let frequencyContainers = null;
// Control buttons
let startButton = document.getElementById('start-button');
startButton.onclick = function () {
stopToneGenerator();
startToneGenerator(frequency, volumeControl, 0, 3);
};
let stopButton = document.getElementById('stop-button');
stopButton.onclick = function () {
stopToneGenerator();
};
let nextButton = document.getElementById('next-button');
nextButton.onclick = function () {
stopToneGenerator();
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
};
let volumeControl = document.getElementById('volume-control');
volumeControl.oninput = function () {
changeVolume(volumeControl);
};
function fillFrequencyGrid(frequencies) {
let frequencyFormatted = null;
let frequencyGrid = document.getElementsByClassName('grid')[0];
frequencyGrid.innerHTML = '';
frequencies.forEach(function (frequency) {
frequencyFormatted = frequencyFormatter(frequency);
frequencyGrid.insertAdjacentHTML('beforeend', '<div class="frequency-container" data-frequency="' + frequency + '">' + frequencyFormatted + 'Hz</div>');
});
}
function makeFrequencyGridInteractive() {
frequencyContainers = document.getElementsByClassName('frequency-container');
Array.prototype.forEach.call(frequencyContainers, function (frequencyContainer) {
frequencyContainer.onclick = function () {
let frequencyChosen = frequencyContainer.getAttribute('data-frequency');
let frequencyChosenFormatted = frequencyFormatter(frequencyChosen);
stopToneGenerator();
if (frequencyChosen === frequency) {
if (window.confirm(frequencyChosenFormatted + 'Hz is correct!\nLet\'s try another one!')) {
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
} else {
window.alert(frequencyChosenFormatted + 'Hz is not correct.\nPlease try again.');
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
};
});
}
// Generate frequency grid
fillFrequencyGrid(frequencyTrainer.frequencies);
makeFrequencyGridInteractive();
// Difficulty buttons
let difficultyButtons = document.getElementsByClassName('difficulty-button');
Array.prototype.forEach.call(difficultyButtons, function (difficultyButton) {
difficultyButton.onclick = function () {
stopToneGenerator();
stopFrequencyTrainer();
difficultyMode = difficultyButton.getAttribute('data-difficulty');
frequencyTrainer = startFrequencyTrainer(difficultyMode, frequency);
frequency = frequencyTrainer.frequency;
fillFrequencyGrid(frequencyTrainer.frequencies);
makeFrequencyGridInteractive();
};
});
}());
</script>
</body>
</html>
1 Answer 1
General Feedback
This looks like a neat little program. I admit I hadn't explored the audio APIs before reading your first post and as a musician I enjoy seeing technology connect audio and visual elements. The colors on the frequency contains is a nice array. Good work! There are a few things mentioned below that can help optimize memory as well as the coding style. The feedback comes both from my experience as well as reading articles like this one.
Suggestions
Event delegation
Use event delegation instead of manually setting the onclick
handlers.
For example, instead of assigning a callback function to the onclick
attribute of each frequency button, use EventTarget.addEventListener() for the click
event, and inspect event.target
to see if it has the class frequency-container
:
frequencyGrid.addEventListener('click', function(event) {
const target = event.target;
if (target.classList.contains('frequency-container')) {
let frequencyChosen = target.getAttribute('data-frequency');
Not only does it allow the click handler to be set once on a container but also it can avoid memory leaks (since the frequencies can be removed from the DOM). See answers to DOM: why is this a memory leak? for more information.
Strict Mode
Many of the functions in the javascript section have 'use strict';
at the first line, while the IIFE on the page has that directive at the start. Why not make the Javascript section more like the IIFE?
Use const
as default instead of let
It is advisable to use const
instead of let
for any variable that doesn't need to be re-assigned. Then when you determine re-assignment is necessary, use let
. That way there will be less chance of overwriting a value.
On that same topic, there is little point in assigning a value of null
shortly before assigning an actual value - e.g. the following lines come in startFrequencyTrainer()
:
let frequencies = null; let frequency = null; // Create objects toneContext = new(window.AudioContext || window.webkitAudioContext)(); toneAmplifier = toneContext.createGain(); // Pick a frequency frequencies = getFrequencies(difficultyMode); frequency = getNewFrequency(frequencies, previousFrequency);
Instead frequencies
and frequency
can be declared as a const
when assigned.
// Create objects
toneContext = new(window.AudioContext || window.webkitAudioContext)();
toneAmplifier = toneContext.createGain();
// Pick a frequency
const frequencies = getFrequencies(difficultyMode);
const frequency = getNewFrequency(frequencies, previousFrequency);
The same is true for newFrequency
in getNewFrequency()
:
let newFrequency = null; newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
This can be simplified to a single line, since there is no point in the value being null
before it is assigned:
let newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
Use spread operator
Use the spread operator for things like putting the HTMLElement collections into an array instead of using Array.prototype.forEach.call
.
So lines like these:
let difficultyButtons = document.getElementsByClassName('difficulty-button'); Array.prototype.forEach.call(difficultyButtons, function (difficultyButton) {
Can be changed to this:
let difficultyButtons = document.getElementsByClassName('difficulty-button');
[...difficultyButtons].forEach( function (difficultyButton) {
Selecting frequencies by difficulty
There isn't anything wrong with the function getFrequencies()
but it could be shortened by declaring a mapping outside the function:
const frequenciesByDifficulty = {
'easy': ["250", "800", "2500", "8000"],
'normal': ["100", "200", "400", "800", "1600", "3150", "6300", "12500"],
'hard': ["80", "125", "160", "250", "315", "500", "630", "1000", "1250", "2000", "2500", "4000", "5000", "8000", "10000", "16000"],
'pro': ["20", "25", "31.5", "40", "50", "63", "80", "100", "125", "160", "200", "250", "315", "400", "500", "630", "800", "1000", "1250", "1600", "2000", "2500", "3150", "4000", "5000", "6300", "8000", "10000", "12500", "16000", "20000"]
};
Then that function can simply look up frequencies from that mapping:
function getFrequencies(difficultyMode) {
if (difficultyMode in frequenciesByDifficulty) {
return frequenciesByDifficulty[difficultyMode];
}
//fallback
return null;
}
That way the frequencies can be altered without having to modify the function.
formatter function
This is probably just a personal preference - the function formatterFunction()
could be simplified by removing frequencyFormatted
and simply return the values as soon as they are ready:
function frequencyFormatter(frequency) {
if (frequency > 999) {
return frequency / 1000 + ' k';
}
return frequency + ' ';
}
And if you wanted, that could be simplifed using an arrow function (as could all functions):
const frequencyFormatter = frequency => frequency > 999 ? frequency / 1000 + ' k' : frequency + ' ';
Use a function partial for the volume change callback
Use Function.bind()
to create a partially applied function for the oninput
callback:
volumeControl.oninput = changeVolume.bind(null, volumeControl);
Updated code
See snippet below. I didn't change all the functions to arrow functions but could...
'use strict';
let toneContext = null;
let toneGenerator = null;
let toneAmplifier = null;
function startFrequencyTrainer(difficultyMode, previousFrequency) {
// Create objects
toneContext = new(window.AudioContext || window.webkitAudioContext)();
toneAmplifier = toneContext.createGain();
// Pick a frequency
const frequencies = getFrequencies(difficultyMode);
const frequency = getNewFrequency(frequencies, previousFrequency);
return {
frequencies,
frequency
};
}
function stopFrequencyTrainer() {
toneContext.close();
}
function startToneGenerator(frequency, volumeControl, startTimer, stopTimer) {
// Create and configure the oscillator
toneGenerator = toneContext.createOscillator();
toneGenerator.type = 'sine'; // could be sine, square, sawtooth or triangle
toneGenerator.frequency.value = frequency;
// Connect toneGenerator -> toneAmplifier -> output
toneGenerator.connect(toneAmplifier);
toneAmplifier.connect(toneContext.destination);
// Set the gain volume
toneAmplifier.gain.value = volumeControl.value / 100;
// Fire up the toneGenerator
toneGenerator.start(toneContext.currentTime + startTimer);
toneGenerator.stop(toneContext.currentTime + startTimer + stopTimer);
}
function stopToneGenerator() {
if (toneGenerator) {
toneGenerator.disconnect();
}
}
function changeVolume(volumeControl) {
toneAmplifier.gain.value = volumeControl.value / 100;
}
const frequenciesByDifficulty = {
'easy': ["250", "800", "2500", "8000"],
'normal': ["100", "200", "400", "800", "1600", "3150", "6300", "12500"],
'hard': ["80", "125", "160", "250", "315", "500", "630", "1000", "1250", "2000", "2500", "4000", "5000", "8000", "10000", "16000"],
'pro': ["20", "25", "31.5", "40", "50", "63", "80", "100", "125", "160", "200", "250", "315", "400", "500", "630", "800", "1000", "1250", "1600", "2000", "2500", "3150", "4000", "5000", "6300", "8000", "10000", "12500", "16000", "20000"]
};
function getFrequencies(difficultyMode) {
if (difficultyMode in frequenciesByDifficulty) {
return frequenciesByDifficulty[difficultyMode];
}
//fallback
return null;
}
function getNewFrequency(frequencies, previousFrequency) {
let newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
// Avoid getting the same frequency twice in a row
while (newFrequency === previousFrequency) {
newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
}
return newFrequency;
}
function frequencyFormatter(frequency) {
if (frequency > 999) {
return frequency / 1000 + ' k';
}
return frequency + ' ';
}
(function() {
'use strict';
let difficultyMode = 'easy'; // default difficulty mode
let frequencyTrainer = startFrequencyTrainer(difficultyMode);
let frequency = frequencyTrainer.frequency;
let frequencyContainers = null;
const frequencyGrid = document.getElementsByClassName('grid')[0];
const controls = document.getElementsByClassName('controls')[0];
// Control buttons
const startButton = document.getElementById('start-button');
startButton.onclick = function() {
stopToneGenerator();
startToneGenerator(frequency, volumeControl, 0, 3);
};
const stopButton = document.getElementById('stop-button');
stopButton.onclick = function() {
stopToneGenerator();
};
const nextButton = document.getElementById('next-button');
nextButton.onclick = function() {
stopToneGenerator();
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
};
let volumeControl = document.getElementById('volume-control');
volumeControl.oninput = changeVolume.bind(null, volumeControl);
function fillFrequencyGrid(frequencies) {
let frequencyFormatted = null;
frequencyGrid.innerHTML = '';
frequencies.forEach(function(frequency) {
frequencyFormatted = frequencyFormatter(frequency);
frequencyGrid.insertAdjacentHTML('beforeend', '<div class="frequency-container" data-frequency="' + frequency + '">' + frequencyFormatted + 'Hz</div>');
});
}
frequencyGrid.addEventListener('click', function(event) {
const target = event.target;
if (target.classList.contains('frequency-container')) {
let frequencyChosen = target.getAttribute('data-frequency');
let frequencyChosenFormatted = frequencyFormatter(frequencyChosen);
stopToneGenerator();
if (frequencyChosen === frequency) {
if (window.confirm(frequencyChosenFormatted + 'Hz is correct!\nLet\'s try another one!')) {
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
} else {
window.alert(frequencyChosenFormatted + 'Hz is not correct.\nPlease try again.');
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
}
})
// Generate frequency grid
fillFrequencyGrid(frequencyTrainer.frequencies);
// Difficulty buttons
controls.addEventListener('click', event => {
if (event.target.classList.contains('difficulty-button')) {
event.stopPropagation();
stopToneGenerator();
stopFrequencyTrainer();
difficultyMode = event.target.getAttribute('data-difficulty');
frequencyTrainer = startFrequencyTrainer(difficultyMode, frequency);
frequency = frequencyTrainer.frequency;
fillFrequencyGrid(frequencyTrainer.frequencies);
}
}, false);
}());
body {
font-family: 'Montserrat', sans-serif;
text-align: center;
padding-top: 10px;
}
h1 {
margin: 0 auto;
font-size: 30px;
text-decoration: underline;
}
h2 {
margin: 0;
font-size: 25px;
}
a {
color: #0000BB;
}
a:hover {
color: #000000;
}
button {
font-family: 'Montserrat', sans-serif;
text-align: center;
font-size: calc(10px + 1vw);
}
.body {
max-width: 1500px;
border: 1px solid black;
width: 95%;
margin: 0 auto;
}
.title {
padding: 10px 0 0 0;
margin: 0 auto;
width: 95%;
}
.content {
padding: 30px 0 0 0;
margin: 0 auto;
width: 95%;
}
.controls {
padding: 0;
margin: 0 auto;
width: 95%;
}
.volume-control {
padding: 0;
margin: 0 auto;
min-width: 200px;
width: 80%;
}
.footer {
padding: 20px 0 10px 0;
margin: 0 auto;
width: 95%;
}
.grid {
margin: 0 auto;
width: 95%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
}
.frequency-container {
margin: 2px;
border: 1px solid black;
padding: 0;
min-width: 80px;
min-height: 80px;
max-width: 300px;
max-height: 300px;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(30px + 0.2vw);
text-shadow: 0 0 25px white;
}
.frequency-container:before {
content: '';
padding-top: 100%;
float: left;
}
[data-frequency="20"] {
background: #CC2828;
}
[data-frequency="25"] {
background: #CC3028;
}
[data-frequency="31.5"] {
background: #CC3928;
}
[data-frequency="40"] {
background: #CC4128;
}
[data-frequency="50"] {
background: #CC4928;
}
[data-frequency="63"] {
background: #CC5128;
}
[data-frequency="80"] {
background: #CC5928;
}
[data-frequency="100"] {
background: #CC6128;
}
[data-frequency="125"] {
background: #CC6A28;
}
[data-frequency="160"] {
background: #CC7228;
}
[data-frequency="200"] {
background: #CC7A28;
}
[data-frequency="250"] {
background: #CC8228;
}
[data-frequency="315"] {
background: #CC8A28;
}
[data-frequency="400"] {
background: #CC9228;
}
[data-frequency="500"] {
background: #CC9B28;
}
[data-frequency="630"] {
background: #CCAB28;
}
[data-frequency="800"] {
background: #CCBB28;
}
[data-frequency="1000"] {
background: #CCCC28;
}
[data-frequency="1250"] {
background: #BBCC28;
}
[data-frequency="1600"] {
background: #ABCC28;
}
[data-frequency="2000"] {
background: #9BCC28;
}
[data-frequency="2500"] {
background: #8ACC28;
}
[data-frequency="3150"] {
background: #7ACC28;
}
[data-frequency="4000"] {
background: #6ACC28;
}
[data-frequency="5000"] {
background: #59CC28;
}
[data-frequency="6300"] {
background: #49CC28;
}
[data-frequency="8000"] {
background: #39CC28;
}
[data-frequency="10000"] {
background: #28CC28;
}
[data-frequency="12500"] {
background: #28CC39;
}
[data-frequency="16000"] {
background: #28CC49;
}
[data-frequency="20000"] {
background: #28CC59;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:900" />
<div class="body">
<div class="title">
<h1>Frequency Trainer</h1>
</div>
<div class="controls">
<br />
<button type="button" id="start-button" class="control-button">Start</button>
<button type="button" id="stop-button" class="control-button">Stop</button>
<button type="button" id="next-button" class="control-button">Next</button><br />
<br /> Volume:
<br />
<input type="range" id="volume-control" class="volume-control" min="0" max="20" value="2" step="0.1" /><br />
<br />
<button type="button" id="difficulty-easy" class="difficulty-button" data-difficulty="easy">Easy</button>
<button type="button" id="difficulty-normal" class="difficulty-button" data-difficulty="normal">Normal</button>
<button type="button" id="difficulty-hard" class="difficulty-button" data-difficulty="hard">Hard</button>
<button type="button" id="difficulty-pro" class="difficulty-button" data-difficulty="pro">Pro</button><br />
<br />
</div>
<div class="grid">
</div>
<div class="footer">
<a href="https://github.com/MaxVMH/frequency-trainer/tree/v.0.0.4-alpha">v.0.0.4</a>
</div>
</div>
Or with Arrow functions:
'use strict';
let toneContext = null;
let toneGenerator = null;
let toneAmplifier = null;
const startFrequencyTrainer = (difficultyMode, previousFrequency) => {
// Create objects
toneContext = new(window.AudioContext || window.webkitAudioContext)();
toneAmplifier = toneContext.createGain();
// Pick a frequency
const frequencies = getFrequencies(difficultyMode);
const frequency = getNewFrequency(frequencies, previousFrequency);
return {
frequencies,
frequency
};
}
const stopFrequencyTrainer = _ => toneContext.close();
const startToneGenerator = (frequency, volumeControl, startTimer, stopTimer) => {
// Create and configure the oscillator
toneGenerator = toneContext.createOscillator();
toneGenerator.type = 'sine'; // could be sine, square, sawtooth or triangle
toneGenerator.frequency.value = frequency;
// Connect toneGenerator -> toneAmplifier -> output
toneGenerator.connect(toneAmplifier);
toneAmplifier.connect(toneContext.destination);
// Set the gain volume
toneAmplifier.gain.value = volumeControl.value / 100;
// Fire up the toneGenerator
toneGenerator.start(toneContext.currentTime + startTimer);
toneGenerator.stop(toneContext.currentTime + startTimer + stopTimer);
}
const stopToneGenerator = _ => {
if (toneGenerator) {
toneGenerator.disconnect();
}
}
const changeVolume = volumeControl => toneAmplifier.gain.value = volumeControl.value / 100;
const frequenciesByDifficulty = {
'easy': ["250", "800", "2500", "8000"],
'normal': ["100", "200", "400", "800", "1600", "3150", "6300", "12500"],
'hard': ["80", "125", "160", "250", "315", "500", "630", "1000", "1250", "2000", "2500", "4000", "5000", "8000", "10000", "16000"],
'pro': ["20", "25", "31.5", "40", "50", "63", "80", "100", "125", "160", "200", "250", "315", "400", "500", "630", "800", "1000", "1250", "1600", "2000", "2500", "3150", "4000", "5000", "6300", "8000", "10000", "12500", "16000", "20000"]
};
const getFrequencies = difficultyMode => {
if (difficultyMode in frequenciesByDifficulty) {
return frequenciesByDifficulty[difficultyMode];
}
//fallback
return null;
}
const getNewFrequency = (frequencies, previousFrequency) => {
let newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
// Avoid getting the same frequency twice in a row
while (newFrequency === previousFrequency) {
newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];
}
return newFrequency;
}
const frequencyFormatter = frequency => {
if (frequency > 999) {
return frequency / 1000 + ' k';
}
return frequency + ' ';
}
(function() {
'use strict';
let difficultyMode = 'easy'; // default difficulty mode
let frequencyTrainer = startFrequencyTrainer(difficultyMode);
let frequency = frequencyTrainer.frequency;
let frequencyContainers = null;
const frequencyGrid = document.getElementsByClassName('grid')[0];
const controls = document.getElementsByClassName('controls')[0];
// Control buttons
const startButton = document.getElementById('start-button');
startButton.onclick = function() {
stopToneGenerator();
startToneGenerator(frequency, volumeControl, 0, 3);
};
const stopButton = document.getElementById('stop-button');
stopButton.onclick = function() {
stopToneGenerator();
};
const nextButton = document.getElementById('next-button');
nextButton.onclick = function() {
stopToneGenerator();
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
};
let volumeControl = document.getElementById('volume-control');
volumeControl.oninput = changeVolume.bind(null, volumeControl);
const fillFrequencyGrid = frequencies => {
frequencyGrid.innerHTML = '';
frequencies.forEach(function(frequency) {
const frequencyFormatted = frequencyFormatter(frequency);
frequencyGrid.insertAdjacentHTML('beforeend', '<div class="frequency-container" data-frequency="' + frequency + '">' + frequencyFormatted + 'Hz</div>');
});
};
frequencyGrid.addEventListener('click', event => {
const target = event.target;
if (target.classList.contains('frequency-container')) {
event.stopPropagation();
let frequencyChosen = target.getAttribute('data-frequency');
let frequencyChosenFormatted = frequencyFormatter(frequencyChosen);
stopToneGenerator();
if (frequencyChosen === frequency) {
if (window.confirm(frequencyChosenFormatted + 'Hz is correct!\nLet\'s try another one!')) {
stopFrequencyTrainer();
frequency = startFrequencyTrainer(difficultyMode, frequency).frequency;
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
} else {
window.alert(frequencyChosenFormatted + 'Hz is not correct.\nPlease try again.');
startToneGenerator(frequency, volumeControl, 0.05, 3);
}
}
}, true);
// Generate frequency grid
fillFrequencyGrid(frequencyTrainer.frequencies);
controls.addEventListener('click', event => {
if (event.target.classList.contains('difficulty-button')) {
event.stopPropagation();
stopToneGenerator();
stopFrequencyTrainer();
difficultyMode = event.target.getAttribute('data-difficulty');
frequencyTrainer = startFrequencyTrainer(difficultyMode, frequency);
frequency = frequencyTrainer.frequency;
fillFrequencyGrid(frequencyTrainer.frequencies);
}
}, false);
}());
body {
font-family: 'Montserrat', sans-serif;
text-align: center;
padding-top: 10px;
}
h1 {
margin: 0 auto;
font-size: 30px;
text-decoration: underline;
}
h2 {
margin: 0;
font-size: 25px;
}
a {
color: #0000BB;
}
a:hover {
color: #000000;
}
button {
font-family: 'Montserrat', sans-serif;
text-align: center;
font-size: calc(10px + 1vw);
}
.body {
max-width: 1500px;
border: 1px solid black;
width: 95%;
margin: 0 auto;
}
.title {
padding: 10px 0 0 0;
margin: 0 auto;
width: 95%;
}
.content {
padding: 30px 0 0 0;
margin: 0 auto;
width: 95%;
}
.controls {
padding: 0;
margin: 0 auto;
width: 95%;
}
.volume-control {
padding: 0;
margin: 0 auto;
min-width: 200px;
width: 80%;
}
.footer {
padding: 20px 0 10px 0;
margin: 0 auto;
width: 95%;
}
.grid {
margin: 0 auto;
width: 95%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
}
.frequency-container {
margin: 2px;
border: 1px solid black;
padding: 0;
min-width: 80px;
min-height: 80px;
max-width: 300px;
max-height: 300px;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(30px + 0.2vw);
text-shadow: 0 0 25px white;
}
.frequency-container:before {
content: '';
padding-top: 100%;
float: left;
}
[data-frequency="20"] {
background: #CC2828;
}
[data-frequency="25"] {
background: #CC3028;
}
[data-frequency="31.5"] {
background: #CC3928;
}
[data-frequency="40"] {
background: #CC4128;
}
[data-frequency="50"] {
background: #CC4928;
}
[data-frequency="63"] {
background: #CC5128;
}
[data-frequency="80"] {
background: #CC5928;
}
[data-frequency="100"] {
background: #CC6128;
}
[data-frequency="125"] {
background: #CC6A28;
}
[data-frequency="160"] {
background: #CC7228;
}
[data-frequency="200"] {
background: #CC7A28;
}
[data-frequency="250"] {
background: #CC8228;
}
[data-frequency="315"] {
background: #CC8A28;
}
[data-frequency="400"] {
background: #CC9228;
}
[data-frequency="500"] {
background: #CC9B28;
}
[data-frequency="630"] {
background: #CCAB28;
}
[data-frequency="800"] {
background: #CCBB28;
}
[data-frequency="1000"] {
background: #CCCC28;
}
[data-frequency="1250"] {
background: #BBCC28;
}
[data-frequency="1600"] {
background: #ABCC28;
}
[data-frequency="2000"] {
background: #9BCC28;
}
[data-frequency="2500"] {
background: #8ACC28;
}
[data-frequency="3150"] {
background: #7ACC28;
}
[data-frequency="4000"] {
background: #6ACC28;
}
[data-frequency="5000"] {
background: #59CC28;
}
[data-frequency="6300"] {
background: #49CC28;
}
[data-frequency="8000"] {
background: #39CC28;
}
[data-frequency="10000"] {
background: #28CC28;
}
[data-frequency="12500"] {
background: #28CC39;
}
[data-frequency="16000"] {
background: #28CC49;
}
[data-frequency="20000"] {
background: #28CC59;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:900" />
<div class="body">
<div class="title">
<h1>Frequency Trainer</h1>
</div>
<div class="controls">
<br />
<button type="button" id="start-button" class="control-button">Start</button>
<button type="button" id="stop-button" class="control-button">Stop</button>
<button type="button" id="next-button" class="control-button">Next</button><br />
<br /> Volume:
<br />
<input type="range" id="volume-control" class="volume-control" min="0" max="20" value="2" step="0.1" /><br />
<br />
<button type="button" id="difficulty-easy" class="difficulty-button" data-difficulty="easy">Easy</button>
<button type="button" id="difficulty-normal" class="difficulty-button" data-difficulty="normal">Normal</button>
<button type="button" id="difficulty-hard" class="difficulty-button" data-difficulty="hard">Hard</button>
<button type="button" id="difficulty-pro" class="difficulty-button" data-difficulty="pro">Pro</button><br />
<br />
</div>
<div class="grid">
</div>
<div class="footer">
<a href="https://github.com/MaxVMH/frequency-trainer/tree/v.0.0.4-alpha">v.0.0.4</a>
</div>
</div>
-
\$\begingroup\$ Thanks for the extensive feedback, I’ll have more time after the weekend to look at your review more closely so I might have some questions then but for now everything looks pretty straight-forward :) \$\endgroup\$Max– Max2018年09月20日 06:16:07 +00:00Commented Sep 20, 2018 at 6:16
-
1\$\begingroup\$ Alrighty- I expanded the section on
let
andconst
variables \$\endgroup\$2018年09月21日 17:33:05 +00:00Commented Sep 21, 2018 at 17:33
Explore related questions
See similar questions with these tags.