首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Web的音频训练器/测试器

使用Web的音频训练器/测试器
EN

Code Review用户
提问于 2018-09-13 13:34:22
回答 1查看 146关注 0票数 4

我正在做一个音频训练器作为一个学习练习。它可能是有用的音频技术员谁需要识别音频频率(例如。当在现场音乐会或配乐时敲响反馈时)。

几天前我贴出了卑微的开端。我得到了非常有用的反馈,并更进一步。下面是JSFiddle上的一个工作示例 & 以下是GitHub上的完整代码.如果有人喜欢在自己的浏览器中使用devtools,这里有一个联机工作示例

在我继续增加更多功能之前,我想知道到目前为止你们对此的看法。欢迎任何反馈!

代码语言:javascript
复制
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;
}
代码语言:javascript
复制
<!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>
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-09-18 15:52:50

通用反馈

这看起来是个整洁的小程序。我承认在阅读你的第一篇文章之前我还没有探索过音频API,作为一名音乐家,我喜欢看到技术连接音频和视觉元素。频率所包含的颜色是一个很好的数组。干得好!下面提到了一些可以帮助优化内存和编码风格的事情。反馈既来自我的经验,也来自于阅读就像这个文章。

建议

事件委托

使用事件委托,而不是手动设置onclick处理程序。

例如,与其将回调函数分配给每个频率按钮的onclick属性,不如对click事件使用EventTarget.addEventListener(),并检查event.target是否有类frequency-container

代码语言:javascript
复制
frequencyGrid.addEventListener('click', function(event) {
    const target = event.target;
    if (target.classList.contains('frequency-container')) {
        let frequencyChosen = target.getAttribute('data-frequency');

它不仅允许在容器上设置一次单击处理程序,而且还可以避免内存泄漏(因为可以从DOM中删除频率)。有关更多信息,请参见多姆:为什么这是内存泄漏?的答案。

严格模式

javascript部分中的许多函数在第一行都有'use strict';,而页面上的IIFE在开始时就有这个指令。为什么不让Javascript部分更像生活呢?

使用const作为默认值,而不是let

对于任何不需要重新分配的变量,最好使用const而不是let。然后,当您确定重新分配是必要的,使用let。这样,覆盖价值的可能性就会更小。

在同一主题上,在分配实际值之前不久分配null值几乎没有意义--例如,startFrequencyTrainer()中有以下行:

设频率=空;让频率=空;//创建对象toneContext =新的(window.AudioContext欧元/ window.webkitAudioContext)();toneAmplifier = toneContext.createGain();//挑选频率= getFrequencies(difficultyMode);频率=getNewFrequency(频率,previousFrequency);

相反,frequenciesfrequency在分配时可以声明为const

代码语言:javascript
复制
// Create objects
toneContext = new(window.AudioContext || window.webkitAudioContext)();
toneAmplifier = toneContext.createGain();

// Pick a frequency
const frequencies = getFrequencies(difficultyMode);
const frequency = getNewFrequency(frequencies, previousFrequency);

newFrequencygetNewFrequency()中的情况也是如此:

设newFrequency =空;newFrequency =频率Math.floor(Math.random()*frequencies.length);

这可以简化为单行,因为在赋值之前,值是null是没有意义的:

代码语言:javascript
复制
let newFrequency = frequencies[Math.floor(Math.random() * frequencies.length)];

使用扩展运算符

扩展算子用于将HTMLElement集合放入数组中而不是使用Array.prototype.forEach.call

像这样的线条:

设difficultyButtons =difficultyButtons函数(difficultyButton) {

可以更改为:

代码语言:javascript
复制
let difficultyButtons = document.getElementsByClassName('difficulty-button');
[...difficultyButtons].forEach( function (difficultyButton) {

基于难度

频率选择

函数getFrequencies()没有任何问题,但是可以通过声明函数外部的映射来缩短它:

代码语言:javascript
复制
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"]
};

然后,该函数可以简单地从映射中查找频率:

代码语言:javascript
复制
function getFrequencies(difficultyMode) {
    if (difficultyMode in frequenciesByDifficulty) {
      return frequenciesByDifficulty[difficultyMode];
    }
    //fallback
    return null;
}

这样,就可以改变频率,而不必修改功能。

格式化程序函数

这可能只是个人偏好--函数formatterFunction()可以通过删除frequencyFormatted来简化,并在它们准备好后立即返回:

代码语言:javascript
复制
function frequencyFormatter(frequency) {
    if (frequency > 999) {
        return frequency / 1000 + ' k';
    } 
    return frequency + ' ';    
}

如果您愿意,可以使用箭头函数(与所有函数一样)对其进行简化:

代码语言:javascript
复制
const frequencyFormatter = frequency => frequency > 999 ? frequency / 1000 + ' k' : frequency + ' ';

对卷更改回调

使用部分函数

使用Function.bind()oninput回调创建一个部分应用函数

代码语言:javascript
复制
volumeControl.oninput = changeVolume.bind(null, volumeControl);

更新代码

见下面的片段。我没有把所有函数都更改为箭头函数,但是可以.

代码语言:javascript
复制
'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);
}());
代码语言:javascript
复制
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;
}
代码语言:javascript
复制
<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>

或者使用Arrow函数:

代码语言:javascript
复制
'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);
}());
代码语言:javascript
复制
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;
}
代码语言:javascript
复制
<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>
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/203680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档