Давайте напишем приложение для визуализации фортепианных аккордов — учебник по ванильному JS для начинающих — часть II.
В части I мы изучили основные правила создания аккорда и создали нашу фортепианную клавиатуру HTML/CSS. В этой части мы закончим шаблон HTML/CSS и создадим основные функции JavaScript.
Содержание:
- Минимальное объяснение теории музыки для лучшего понимания темы
- Настраивать
- [HTML/CSS] Модель клавиатуры фортепиано.
3.1 Полный код - [HTML/CSS] Корень аккорда и выбор типа
4.1 Полный код - [JS] Генерация аккордов
5.1 Полный код - [HTML/JS] Собираем все вместе — WIP (скоро в части III)
[HTML/CSS] Корень аккорда и выбор типа
Чтобы завершить нашу HTML/CSS часть приложения, мы должны добавить форму выбора аккорда. Чтобы не пролистывать множество вариантов заметок, я решил сделать выделение в виде группы кнопок для каждой части.
HTML/CSS результат выбора аккорда
Я разделил выбор основной ноты аккорда и выбор типа, так как мы будем менять их независимо.
ПРИМЕЧАНИЕ: Аккорд основная нота дает имя аккорду и устанавливает связь между нотами в аккорде.
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<label for="root-notes-group" class="form-label"><i class="bi bi-music-note"></i> Root note</label>
<div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
<div id="root-notes-group" class="btn-group me-2" role="group" aria-label="Root notes group">
<button type="button" class="btn btn-outline-light">C</button>
<button type="button" class="btn btn-outline-light">C#</button>
<button type="button" class="btn btn-outline-light">D</button>
<button type="button" class="btn btn-outline-light">D#</button>
<button type="button" class="btn btn-outline-light">E</button>
<button type="button" class="btn btn-outline-light">F</button>
<button type="button" class="btn btn-outline-light">F#</button>
<button type="button" class="btn btn-outline-light">G</button>
<button type="button" class="btn btn-outline-light">G#</button>
<button type="button" class="btn btn-outline-light">A</button>
<button type="button" class="btn btn-outline-light">A#</button>
<button type="button" class="btn btn-outline-light">B</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label for="chord-types-group" class="form-label"><i class="bi bi-music-note-beamed"></i> Chord type</label>
<div class="btn-toolbar mb-3" role="toolbar2" aria-label="Toolbar with button groups">
<div id="chord-types-group" class="btn-group me-2" role="group2" aria-label="Chord types group">
<button type="button" class="btn btn-outline-light">Major</button>
<button type="button" class="btn btn-outline-light">Minor</button>
<button type="button" class="btn btn-outline-light">Diminished</button>
</div>
</div>
</div>
</div>
</div>
Структура HTML проста. В первой группе в <div id="root-notes-group" class="btn-group me-2" role="group" aria-label="Root notes group">
каждая кнопка представляет одну основную ноту. Во второй группе <div id="chord-types-group" class="btn-group me-2" role="group2" aria-label="Chord types group">
каждая кнопка представляет один тип аккорда.
Все стили предоставляются фреймворком Bootstrap. Вы можете найти детали стиля группы кнопок с помощью Bootstrap здесь.
Наше приложение будет обрабатывать отображение трех типов аккордов: мажорный, минорный и уменьшенный.
Сейчас в index.htmlзаменить деталь <!-- chord root and type selection -->
с выбором типа аккорда.
index.html должен представить следующий результат.
Полный HTML/CSS для выбора аккорда
[HTML/CSS] Выбор корня и типа аккорда — полный код
В этом разделе мы отредактировали только index.html файл.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<title>Piano chord visualizer</title>
<link rel="stylesheet" href=" />
<link href=" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous" />
<link rel="stylesheet" href="piano-keyboard.css" />
</head>
<body class="d-flex flex-column h-100 min-vh-100">
<main class="bg-secondary text-white flex-grow-1">
<div class="container">
<div class="row mt-3">
<div class="col-md-12">
<h1>Piano chord visualizer</h1>
</div>
</div>
<div class="row mt-5">
<div class="col-md-6">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<label for="root-notes-group" class="form-label"><i class="bi bi-music-note"></i> Root note</label>
<div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
<div id="root-notes-group" class="btn-group me-2" role="group" aria-label="Root notes group">
<button type="button" class="btn btn-outline-light">C</button>
<button type="button" class="btn btn-outline-light">C#</button>
<button type="button" class="btn btn-outline-light">D</button>
<button type="button" class="btn btn-outline-light">D#</button>
<button type="button" class="btn btn-outline-light">E</button>
<button type="button" class="btn btn-outline-light">F</button>
<button type="button" class="btn btn-outline-light">F#</button>
<button type="button" class="btn btn-outline-light">G</button>
<button type="button" class="btn btn-outline-light">G#</button>
<button type="button" class="btn btn-outline-light">A</button>
<button type="button" class="btn btn-outline-light">A#</button>
<button type="button" class="btn btn-outline-light">B</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label for="chord-types-group" class="form-label"><i class="bi bi-music-note-beamed"></i> Chord type</label>
<div class="btn-toolbar mb-3" role="toolbar2" aria-label="Toolbar with button groups">
<div id="chord-types-group" class="btn-group me-2" role="group2" aria-label="Chord types group">
<button type="button" class="btn btn-outline-light">Major</button>
<button type="button" class="btn btn-outline-light">Minor</button>
<button type="button" class="btn btn-outline-light">Diminished</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 d-flex justify-content-center flex-wrap">
<div id="piano-keyboard">
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-sharp"></div>
<div class="key key-natural"></div>
<div class="key key-natural"></div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
База знаний раздела
Здесь вы найдете ссылки на полезные статьи по теме, которую я не полностью освещаю в этом разделе.
Стиль группы кнопок в Bootstrap от getbootstrap.com — Документация группы кнопок Bootstrap
[JS] Генерация аккордов
В этом разделе мы начинаем писать JavaScript-часть приложения. Этот раздел фокусируется только на создании данных, которые нам позже понадобятся для рендеринга изменений. Код, отвечающий за управление выбором и визуализацию выбранных опций, вы найдете в следующем разделе.
Весь JS-контент, который мы размещаем в index.js файл.
const notes = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"];
Начнем с определения константы, хранящей все заметки. Это будет наша точка входа для функций, генерирующих аккордовые ноты.
const chordScheme = {
maj: [0, 4, 7],
min: [0, 3, 7],
dim: [0, 3, 6],
};
Мы храним наши типы аккордов в объекте, где ключи объекта — это типы аккордов, а значения — это ноты, принадлежащие аккорду. Цифры представляют место заметки в массиве заметок.
Быстрое напоминание, расстояние между нотами аккорда «до мажор» составляет 4 и 3 полутона. Примечание. «C» — это первая строка в
notes
массив, поэтому его индекс равен 0. Затем мы увеличиваем его на 4, чтобы получить следующую ноту, и, наконец, мы увеличиваем его на 3, чтобы получить последнюю ноту аккорда.0, 4 [0 + 4], 7 [0 + 4 + 3]
Таким образом, мы можем указать универсальную схему для хранения наших типов аккордов. Тогда вы можете начать задаваться вопросом,
«Это работает для аккордов на основе C, но как насчет A, B, G и других аккордов?»
Мы заставим это работать, сдвинув notes
множество; таким образом, желаемая основная нота будет под индексом 0. Для этого мы создадим функцию, которая:
- найти индекс новой корневой заметки в массиве заметок,
- разделить массив заметок на два массива, где точкой разделения является наш найденный индекс,
- вернуть новый массив заметок, объединив разделенные массивы с замененной позицией
Визуализация логики функций
Для этого мы создаем функцию с именем getNotesFromRoot
который принимает rootNote
аргумент.
const getNotesFromRoot = (rootNote) => {
};
Во-первых, мы находим индекс новой корневой ноты, используя indexOf
функция. notes.indexOf(rootNote.toLowerCase());
возвращается -1
или целочисленное значение с позицией rootNote.toLowerCase()
. Мы используем toLowerCase()
способ сделать поиск нечувствительным к регистру. Это исключает ситуацию, когда, например, мы передали строку «G#», которая является правильным названием ноты, но не существует в массиве notes. Мы храним результаты в slicePivot
постоянный. Если slicePivot
равно -1
мы возвращаем пустой массив.
arrayTypeVar.indexOf (искомое значение) — метод, возвращающий позицию
searchedValue
в массиве, для которого была вызвана функция
stringTypeVar.toLowerCase() — метод, возвращающий строчное значение строковой переменной, по которой была вызвана функция
const getNotesFromRoot = (rootNote) => {
let slicePivot = notes.indexOf(rootNote.toLowerCase());
if (slicePivot === -1) {
return [];
}
};
Затем мы разделяем массив заметок на два меньших массива. Для этого мы используем slice()
метод. Первую часть мы называем fromPivot
содержит ноты от позиции новой корневой ноты до конца массива. Вторая часть toPivot
и включает ноты от начала до первой перед новой корневой нотой. Наконец, мы объединяем две части в одну, возвращая новый массив скопированных значений массивов. Для этого мы используем оператор расширения массива.
В качестве альтернативы мы можем использовать concat()
метод. Если тогда, оператор возврата будет return fromPivot.concat(toPivot);
.
Наша функция, возвращающая смещенный массив заметок, готова.
arrayTypeVar.slice(startIndex, endIndex) — метод, возвращающий неглубокую копию массива, содержащего элементы из диапазона
[…[1, 2, 3], …[4, 5, 6]] вернется [1, 2, 3, 4, 5, 6]
;
arrayTypeVar.concat (массив1, массив2, …, массивN) — метод, возвращающий новый массив путем конкатенации массива с массивами, указанными в аргументах. Бывший.:
[1, 2, 3].concat([4, 5, 6])
вернется[1, 2, 3, 4, 5, 6]
;
const getNotesFromRoot = (rootNote) => {
let slicePivot = notes.indexOf(rootNote.toLowerCase());
if (slicePivot === -1) {
return [];
}
const fromPivot = notes.slice(slicePivot, notes.length);
const toPivot = notes.slice(0, slicePivot);
return [...fromPivot, ...toPivot];
};
Наконец, мы создадим функцию, возвращающую ноты аккордов. Назовем эту функцию getChord
и заставить его принимать аргументы rootNote
а также chordType
. Логика работы следующая:
- Подготавливаем массив с нотами, начинающимися с основной ноты аккорда — для этого используем ранее созданный
getNotesFromRoot.
- Мы проверяем, если
notesFromRoot
не пусто. - Мы проверяем, если
chordScheme[chordType]
вернул значение. Допустимые свойстваchordScheme
объекта являются «maj», «min» и «dim», что соответствует «major», «minor» и «уменьшению».
Мы возвращаемся на картуchordScheme[chordType]
если проверки прошли. Мы возвращаем новый массив при замене индексов заметок на сами заметки.
Если проверки не пройдены, мы возвращаем пустой массив.
arrayTypeVar.map((element) => {/* ... */})
— метод, возвращающий новый массив с измененными элементами, как указано в переданной функции сопоставления в качестве аргумента. Бывший.:[1, 2, 3].map((element) => {return element + 5;})
вернется[6, 7, 8]
. Каждый элемент будет преобразован функцией сопоставления.
const getChord = (rootNote, chordType) => {
const notesFromRoot = getNotesFromRoot(rootNote);
if (notesFromRoot.length > 0 && chordScheme[chordType]) {
return chordScheme[chordType].map((noteIndex) => notesFromRoot[noteIndex]);
}
return [];
};
[JS] Генерация аккордов — полный код
В этом разделе мы отредактировали только index.js файл.
index.js
const notes = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"];
const chordScheme = {
maj: [0, 4, 7],
min: [0, 3, 7],
dim: [0, 3, 6],
};
const getNotesFromRoot = (rootNote) => {
let slicePivot = notes.indexOf(rootNote.toLowerCase());
if (slicePivot === -1) {
return [];
}
const fromPivot = notes.slice(slicePivot, notes.length);
const toPivot = notes.slice(0, slicePivot);
return [...fromPivot, ...toPivot];
};
const getChord = (rootNote, chordType) => {
const notesFromRoot = getNotesFromRoot(rootNote);
if (notesFromRoot.length > 0 && chordScheme[chordType]) {
return chordScheme[chordType].map((noteIndex) => notesFromRoot[noteIndex]);
}
return [];
};
Мы можем проверить то, что мы закодировали до сих пор, запустив файл index.html в браузере. Затем в консоли инструментов разработчика браузера мы можем напрямую вызвать функцию getChord
.
Выполнение функции getChord() в консоли Chrome Developer Tool
База знаний раздела
Здесь вы найдете ссылки на полезные статьи по теме, которую я не полностью освещаю в этом разделе.
Array.prototype.slice() от developer.mozilla.org — Метод массива slice()
Синтаксис распространения от developer.mozilla.org — Оператор спреда
Array.prototype.map() от developer.mozilla.org — Метод карты массива ()
В части III мы добавим интерактивность в наш HTML-шаблон, чтобы помечать клавиши пианино в зависимости от выбранного аккорда.