освещенная последовательность
Когда народный мальчик держал в руках гитару и напевал «Love Has to Be Done», он правой рукой провел по струнам у звукового отверстия, а левой прижал струны к грифу. Не глядя на его лицо или его певческие способности, какой принцип придает этому младшему брату такое кокетливое очарование?
Это гитарный аккомпанемент.
А еще он только начинающий гитарист, и не может подобрать аккомпанемент к песне, поэтому ему приходится искать в интернете гитарную партитуру и играть ее по логотипу внутри. Он нашел следующие ноты:
Вот как должна выглядеть типичная гитарная партитура, которую можно разделить на четыре части:
-
和弦指法
: Используется для обозначения названия аккорда и соответствующей аппликатуры в такте. -
六线谱
: Это партитура специально для гитары. Шесть горизонтальных линий сверху вниз соответствуют первой и шестой струнам гитары соответственно. Различные символы добавляются на горизонтальную линию, чтобы обозначить способ игры правой рукой. -
简谱
: Вот цифровая запись с различными символами для описания мелодии и ритма песни. -
歌词
: Ну, это лирика.
Для начинающих ключ к началу игры на гитаре кроется в аппликатуре левой руки.В то время я записал схемы аппликатуры большинства аккордов, и мозоли на кончике левого пальца так же бледнели и бледнели. музыкальный отморозок, я ваще один день, я запутался, и вопросительные знаки повторились трижды:
1. 这个和弦为什么叫这个名字?
2. 这个和弦为什么是这个指法?
3. 同一和弦在吉他上到底有多少种不同的指法?
Основываясь на базовых знаниях теории музыки, в этой статье будет использоваться вывод кода для расчета ответов на приведенные выше вопросы и визуализации результатов.
1. Начните с одного тона
心虚的声明:外行人基于自己的理解强行解释乐理,望专业人士轻喷
Звук создается вибрацией предмета, и каждый звук разной частоты (т. е. разной высоты) можно назвать одним тоном, но человеческое ухо имеет ограниченную способность распознавания звука.Минимальный интервал называется半音
(Это деление находится не только в пределах восприятия человеческого уха, но и соответствует циклу интервалов);
Отношение частот двух тонов, разделенных полутоном, равно2的12次方根
.
Почему это значение, это должно быть упомянуто十二平均律
.
После долгих прослушиваний ветераны музыкальной индустрии обнаружили, что, например,do
прибыть高音do
Этот интервал гармоничнее всего звучит как луп, и это高音do
иdo
Отношение частот точно2
, в случае обеспечения того, чтобы промежутки между отдельными тонами были гармоничны и могли быть четко различимы, интервал разбивается на12等份
, которая имеет взаимоотношение с китайской пентатоникой (Gongshangjiao Zhengyu) и западной гептатоникой, как показано на следующем рисунке (здесь я временно помечаю каждую ноту на двенадцати хорошо темперированном интервале цифрами):
do
и高音do
Соотношение между гептатоническими шкалами называется八度
;
Другими словами, промежуток между нотой и соответствующей ей нотой более высокой октавы является интервалом, а отношение их частот равно1:2
.
1(do)
и2(re)
между全音
Промежуток и3(mi)
и4(fa)
,7(si)与1.(高音do)
между半音
диапазон, полный тоновый диапазон эквивалентен двум полутоновым диапазонам, можно видеть, что1(do)
и2(re)
Между ними также есть тон, который мы называем#1(升do)
Илиb2(降re)
.
Как только вы это поймете, вы сможете реализовать монофонический класс в коде:
1. Сначала определитесь с однотонной формой письма
Вы можете позаимствовать способ разметки нотной записи, цифры1、2、3、4、5、6、7
, соответственно представляющие перекличкуdo、re、mi、fa、sol、la、si
;
когда этот звук升半调
, добавить перед номером#
,Например#1(升do)
,降半调
, добавить перед номеромb
,Напримерb1(降do)
;
Отмечая верхнюю октаву ноты, добавьте «точку» справа от числа, например.1.(高音do)
,#2.(高音升re)
(Поскольку у строк не может быть точки в верхней части числа, как в записи), при маркировке нижней октавы ноты добавьте «точку» слева от числа, например..1(低音do)
,.b2(低音降re)
;
2. Создайте монофонический класс
// 检测数据类型的公用方法
function is(data) {
return function(type) {
return Object.prototype.toString.call(data) === `[object ${type}]`;
}
}
// 单音类,用于音的映射查询与音高的改变,同时可标记记录其在吉他上的位置
class Tone {
constructor(toneString = '1', string, fret) {
// 所有唱名数组
this.syllableMap = ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'];
// 音程
this.keyMap = ['1', ['#1', 'b2'], '2', ['#2', 'b3'], '3', '4', ['#4', 'b5'], '5', ['#5', 'b6'], '6', ['#6', 'b7'], '7'];
//所有调名
this.intervalMap = ['C', ['#C', 'bD'], 'D', ['#D', 'bE'], 'E', 'F', ['#F', 'bG'], 'G', ['#G', 'bA'], 'A', ['#A', 'bB'], 'B'];
// 单音的字符串表示
this.toneString = toneString;
// 单音的字符串表示(去除八度标记)
this.toneNormal = toneString.replace(/\./g, '');
// 数字音
this.key = toneString.replace(/\.|b|#/g, '');
// 唱名
this.syllableName = this.syllableMap[+this.key - 1];
// 降半调标记
this.flat = toneString.match('b') ? 'b' : '';
// 升半调标记
this.sharp = toneString.match('#') ? '#' : '';
let octave_arr = toneString.split(this.key);
let octave_flat = octave_arr[0].toString().match(/\./g);
let octave_sharp = octave_arr[1].toString().match(/\./g);
// 八度度数
this.octave = (octave_sharp ? octave_sharp.length : 0) - (octave_flat ? octave_flat.length : 0);
// 吉他按弦位置
this.position = {
// 第几弦
string: string,
// 第几品格
fret: fret
};
}
// 获取某个音在音程上的位置
findKeyIndex(keyString) {
return this.keyMap.findIndex((item) => {
if (is(item)('Array')) {
return item.includes(keyString);
} else if (item === keyString) {
return true;
} else {
return false;
}
});
}
// 音高增减,num为增或减的半音数量
step(num) {
let keyString = this.flat + this.sharp + this.key;
let len = this.keyMap.length;
let index = this.findKeyIndex(keyString);
if (index > -1) {
num = +num;
// 计算改变音高后的音在音程上的位置
let nextIndex = parseInt(index + num, 0);
let octave = this.octave;
if (nextIndex >= len) {
let index_gap = nextIndex - len;
octave += Math.floor(index_gap / len) + 1;
nextIndex = index_gap % len;
} else if (nextIndex < 0) {
let index_gap = nextIndex;
octave += Math.floor(index_gap / len);
nextIndex = index_gap % len + len;
}
let nextKey = this.keyMap[nextIndex];
// 计算并添加高低八度的记号
let octaveString = new Array(Math.abs(octave)).fill('.').join('');
let toneString = '';
if (!is(nextKey)('Array')) {
toneString = (octave < 0 ? octaveString : '') + nextKey + (octave > 0 ? octaveString : '');
return new this.constructor(toneString, this.position.string, this.position.fret + num);
} else {
// 可能得到两个音高一样但标记方式不一样的音
return nextKey.map((key) => {
return new this.constructor((octave < 0 ? octaveString : '') + key + (octave > 0 ? octaveString : ''), this.position.string, this.position.fret + num);
});
}
} else {
return null;
}
}
}
Когда у вас есть этот монофонический класс, вы можете использовать его позже, чтобы легко сравнить диапазон между двумя тонами, и вы можете построить начальный тон каждой струны гитары с помощьюstep
Этот метод позволяет получить высоту тона любой другой позиции на гитаре.
Пример выполнения:
Создавать1(do)
однотонный экземпляр
однотонный1(do)
, к высокому5个半音
, получить однотонный4(fa)
;6个半音
, получить два тона#4(升fa)
иb5(降sol)
, эти два тона одной высоты, суть одна, но способ обозначения разный.
Во-вторых, происхождение названия аккорда
1. Что такое аккорды
Первая запись Baidu:
Из этого просторечия выделяют три элемента аккордов:
(1)由三个或三个以上的音构成;
(2)音之间有跨度关系(三度或非三度);
(3)音之间要从低到高排列。
Из этого я нарисовал картину:
на интервале12个音
можно упорядочить, как тиканье часов,顺时针
представитель направления音的从低到高
; Затем возимся с «часовой стрелкой», «минутной стрелкой» и «секундной стрелкой» по желанию, не перекрывая друг друга и на определенном расстоянии друг от друга, и соединяем звуки, на которые они указывают, по часовой стрелке, затем可能
представляет собой трехцветный和弦
(Точно так же аккорд, состоящий из большего количества нот, эквивалентен добавлению к нему указателя).
Глядя на это таким образом, вы можете увидеть, что это больше похоже на排列组合
Вопрос, возьмем для примера комбинацию из трех тонов, выберем любые 3 тона из 12 тонов (не отсортированных), будет220
Ситуация, но это не все和弦
; аккорды и аккорды, как следует из названия, звучат гармонично и не неприятно, что поначалу больше похоже на субъективное суждение людей, но по мере зрелости системы музыкальных знаний аккорды также будут иметь набор признанных эталонов, которые станут как математические формулы Следы, чтобы следовать.
Подумайте хорошенько, хорошо ли звучит аккорд или нет, и какую эмоциональную окраску он имеет, зависит от взаимного контрастного отношения между составляющими звуками, то есть взаимного интервала высоты тона между звуками. один, этот умеренный三度
;
三度
разделен на大三度
и小三度
大三度
:два全音
Продолжительность, то есть4
Кусок半音
охватывать.
小三度
:Один全音
добавить одну半音
размах, то есть3
Кусок半音
охватывать.
в тональности CC和弦
Составные звуки следующие:
1(do)
и3(mi)
зажатый между#1/b2
,2
,#2/b3
Три тона по 4 полутона;
3(mi)
и5(sol)
зажатый между4
,#4/b5
Эти 2 тона имеют общий диапазон 3 полутона;
Тогда такой аккорд становится大三和弦
.
2. Общие правила маркировки аккордов
тип аккорда | сочинение | отметка |
---|---|---|
大三和弦 |
Большая терция + второстепенная терция | |
小三和弦 |
малая терция + большая терция | m |
增三和弦 |
большая терция + большая терция | aug |
减三和弦 |
второстепенная терция + второстепенная терция | dim |
大小七和弦(属七和弦) |
大三和弦 + второстепенная терция |
7 илиMm7
|
大大七和弦(大七和弦) |
大三和弦 + большая треть |
maj7 илиM7
|
小小七和弦(小七和弦) |
小三和弦 + второстепенная терция |
m7 илиmm7
|
小大七和弦 |
小三和弦 + большая треть |
mM7 |
减七和弦 |
减三和弦 + второстепенная терция |
dim7 |
半减七和弦 |
减三和弦 + большая треть |
m7-5 |
增属七和弦 |
增三和弦 + минус третий |
7#5 илиM7+5
|
增大七和弦 |
增三和弦 + второстепенная терция |
aug7 илиMaj7#5
|
加音和弦
и指定和弦根音
Это относительно сложно и пока не будет обсуждаться.
3. Основной тон аккорда
Первая нота аккорда - это первая нота аккорда根音
,Также известен как基础音
, по текущим调式
с аккордом根音
определить начальное название аккорда, например вC调
Вниз,根音
и和弦名
Соотношение сравнения выглядит следующим образом:
Ⅰ | II | Ⅲ | Ⅳ | Ⅴ | VI | VII |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
C | D | E | F | G | A | B |
С точки зрения непрофессионала, это эквивалентно某调
вниз, основной тон аккорда находится в этой тональности1(do)
, то это называется某和弦
(Дополнительные баллы добавляются по третьей степени отношения между звуками), например:
C调下
:
основная нота1(do)
Образовавшиеся хорды называютсяC
;
основная нота2(re)
Образовавшиеся хорды называютсяD
;
D调下
:
основная нота1(do)
Образовавшиеся хорды называютсяD
;
основная нота1(do)
Образовавшиеся хорды называютсяE
;
B调下
:
основная нота1(do)
Образовавшиеся хорды называютсяB
;
основная нота2(do)
Образовавшиеся хорды называютсяC
;
4. Расчет полного имени аккорда
Основываясь на приведенных выше правилах теории музыки, могут быть реализованы следующие классы для получения названий аккордов:
// 和弦名称推导
class ChordName {
constructor(chordTone) {
// 实例化一个单音类做工具,用来计算音与各种标记的映射关系
this.toneUtil = new Tone();
}
// 获取两个音的间隔跨度
getToneSpace(tonePre, toneNext) {
let toneSpace = this.toneUtil.findKeyIndex(toneNext) - this.toneUtil.findKeyIndex(tonePre);
return toneSpace = toneSpace < 0 ? toneSpace + 12 : toneSpace;
}
// 大三度
isMajorThird(tonePre, toneNext) {
return this.getToneSpace(tonePre, toneNext) === 4;
}
// 小三度
isMinorThird(tonePre, toneNext) {
return this.getToneSpace(tonePre, toneNext) === 3;
}
// 增三度
isMajorMajorThird(tonePre, toneNext) {
return this.getToneSpace(tonePre, toneNext) === 5;
}
// 减三度
isMinorMinorThird(tonePre, toneNext) {
return this.getToneSpace(tonePre, toneNext) === 2;
}
// 大三和弦
isMajorChord(chordTone) {
return this.isMajorThird(chordTone[0], chordTone[1]) && this.isMinorThird(chordTone[1], chordTone[2]);
}
// 小三和弦 m
isMinorChord(chordTone) {
return this.isMinorThird(chordTone[0], chordTone[1]) && this.isMajorThird(chordTone[1], chordTone[2]);
}
// 增三和弦 aug
isAugmentedChord(chordTone) {
return this.isMajorThird(chordTone[0], chordTone[1]) && this.isMajorThird(chordTone[1], chordTone[2]);
}
// 减三和弦 dim
isDiminishedChord(chordTone) {
return this.isMinorThird(chordTone[0], chordTone[1]) && this.isMinorThird(chordTone[1], chordTone[2]);
}
// 挂四和弦
isSus4(chordTone) {
return this.isMajorMajorThird(chordTone[0], chordTone[1]) && this.isMinorMinorThird(chordTone[1], chordTone[2]);
}
// 大小七和弦/属七和弦 7 / Mm7
isMajorMinorSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isMajorChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
}
// 小大七和弦 mM7
isMinorMajorSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isMinorChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
}
// 大七和弦 maj7 / M7
isMajorMajorSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isMajorChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
}
// 小七和弦 m7 / mm7
isMinorMinorSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isMinorChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
}
// 减七和弦 dim7
isDiminishedSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isDiminishedChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
}
// 半减七和弦 m7-5
isHalfDiminishedSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isDiminishedChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
}
// 增属七和弦 7#5 / M7+5
isHalfAugmentedSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isAugmentedChord(chordTone) && this.isMinorMinorThird(chordTone[2], chordTone[3]);
}
// 增大七和弦 aug7 / Maj7#5
isAugmentedSeventhChord(chordTone) {
if (chordTone.length < 4) return false;
return this.isAugmentedChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
}
// 获取音对应的根音和弦名
getKeyName(key) {
let keyName = this.toneUtil.intervalMap[this.toneUtil.findKeyIndex(key)];
if (is(keyName)('Array')) {
keyName = /b/.test(key) ? keyName[1] : keyName[0];
};
return keyName;
}
// 计算和弦名
getChordName(chordTone) {
let rootKey = chordTone[0];
// 和弦的字母名
let chordRootName = this.getKeyName(rootKey);
// 和弦字母后面的具体修饰名
let suffix = '...';
let suffixArr = [];
// 三音和弦的遍历方法及对应修饰名
let chord3SuffixMap = [{
fn: this.isMajorChord,
suffix: ''
}, {
fn: this.isMinorChord,
suffix: 'm'
}, {
fn: this.isAugmentedChord,
suffix: 'aug'
}, {
fn: this.isDiminishedChord,
suffix: 'dim'
}, {
fn: this.isSus4,
suffix: 'sus4'
}];
// 四音和弦的遍历方法及对应修饰名
let chord4SuffixMap = [{
fn: this.isMajorMinorSeventhChord,
suffix: '7'
}, {
fn: this.isMinorMajorSeventhChord,
suffix: 'mM7'
}, {
fn: this.isMajorMajorSeventhChord,
suffix: 'maj7'
}, {
fn: this.isMinorMinorSeventhChord,
suffix: 'm7'
}, {
fn: this.isDiminishedSeventhChord,
suffix: 'dim7'
}, {
fn: this.isHalfDiminishedSeventhChord,
suffix: 'm7-5'
}, {
fn: this.isHalfAugmentedSeventhChord,
suffix: '7#5'
}, {
fn: this.isAugmentedSeventhChord,
suffix: 'aug7'
}];
// 三音和弦
if (chordTone.length === 3) {
suffixArr = chord3SuffixMap.filter((item) => {
return item.fn.bind(this, chordTone)();
});
suffix = suffixArr.length > 0 ? suffixArr[0].suffix : suffix;
} else {
// 四音和弦
suffixArr = chord4SuffixMap.filter((item) => {
return item.fn.bind(this, chordTone)();
});
suffix = suffixArr.length > 0 ? suffixArr[0].suffix : suffix;
}
// 拼接起来得到完整的和弦名
return chordRootName + suffix;
}
}
Запустите пример:
Три, аккордовая аппликатура
1. Таблица аппликатуры
Пример полной гитарной аккордовой аппликации находится следующим образом, с реальной гитарой справа:
Объясните значение следующих существительных:品位
: Настоящий гриф гитары разделен на множество сеток.Когда пальцы нажимают на разные сетки, длина струны, соответствующая вибрации струн, изменяется, а затем соответственно меняется и ее произношение.Эти нажатия могут изменить звук.высокий. сетки называются品位
,или品格
(надо сказать, что слово "качество" действительно хорошее);
品位标记
: В левой части аппликатуры указано число, указывающее количество строк на диаграмме.品位
на самом деле на гитаре品位
Что это такое (т.н. относительная система координат и абсолютная система координат);
空弦音
:который第0品
, левой руке не нужно нажимать на струну, соответствующую отметке, а правая рука непосредственно ее защипывает;
和弦外音
: Когда звук открытой струны определенной струны и звук, который она может издавать в диапазоне аппликатурной диаграммы, не принадлежат составному звуку аккорда, то струну следует запретить играть, поэтому пометьте струну на струне .“叉号”
;
按弦手指标记
: Отметьте положение, в котором ваши пальцы нажимают на каждую струну, черной точкой, и в наиболее полной аппликатурной таблице также будут добавлены цифры.1
,2
,3
,4
, Представляя食指
,中指
,无名指
,小拇指
, что эквивалентно указанию, куда следует поместить палец.
2. Распределение нот по гитарным струнам
Эту картинку-раскраску с историческим колоритом я подобрал из интернета:
Можно заметить, что один и тот же тон может иметь много позиций на гитарных струнах, а состав простых аккордов всего три-четыре, поэтому, если вы хотите найти все возможные аккорды из этих вертикальных и горизонтальных сеток сразу, аппликатируйте, а также принимая во внимание различные ограничения фактической аппликатуры:Например, ваша левая рука может использовать не более 5 пальцев и 6 струн, но указательный палец может использовать большой горизонтальный пресс для нажатия на несколько струн, а большой горизонтальный пресс можно нажимать только на самый нижний лад аппликатуры; Также надо учитывать, что после нажатия аппликатуры включаются все ноты в аккорде, и ноты двух соседних струн не могут быть одинаковыми...
Такие вещи, пытаясь просчитать все возможные результаты сразу, боюсь, это будет неловко для меня.
Однако это очень подходит для решения рекурсивного алгоритма.
3. Аппликатура
Класс специально построен для этой цели, а монофонический класс, написанный ранее, используется во время инициализации для расчета нот всех позиций на гитарах строки. затем черезthis.toneMap[tring][fret]
Звук этой позиции непосредственно, напримерthis.toneMap[1][3]
Получать1弦3品
звук.
// 吉他和弦指法推导类
class GuitarChord {
constructor() {
// 暂定的吉他的最大品格数
this.fretLength = 15;
// 构建1到6弦的初始音
this.initialTone = [
new Tone('3.', 1, 0),
new Tone('7', 2, 0),
new Tone('5', 3, 0),
new Tone('2', 4, 0),
new Tone('.6', 5, 0),
new Tone('.3', 6, 0)
];
// 用于吉他上所有位置对应的音
this.toneMap = [];
// 从1到6弦,从品位数的低到高,依次计算每个位置的音
for (let string = 1; string <= this.initialTone.length; string++) {
this.toneMap[string] = [];
for (let fret = 0; fret <= this.fretLength; fret++) {
this.toneMap[string].push(this.initialTone[string - 1].step(fret));
}
}
}
}
Добавьте к нему обычный монофонический метод поиска позиции:
// 在指定的品格数范围内,查找某个音在某根弦的音域下所有的品格位置
/*
* @param key 搜寻的音(字符串形式)
* @param toneArray 音域数组,即某根弦上所有单音类按顺序组成的数组
* @param fretStart 搜寻的最低品格数
* @param fretEnd 搜寻的最高品格数
*/
findFret(key, toneArray, fretStart, fretEnd) {
key = key.replace(/\./g, '');
let fretArray = [];
fretStart = fretStart ? fretStart : 0;
fretEnd = fretEnd ? (fretEnd + 1) : toneArray.length;
for (let i = fretStart; i < fretEnd; i++) {
if (is(toneArray[i])('Array')) {
let toneStringArray = toneArray[i].map((item) => {
return item.toneNormal;
});
if (toneStringArray.includes(key)) {
fretArray.push(i);
}
} else {
if (toneArray[i].toneString.replace(/\./g, '') === key) {
fretArray.push(i);
}
}
}
return fretArray;
}
Далее следует алгоритм рекурсии основного цикла, сначала представьте общий рекурсивный процесс:
(1) Укажите с 1-й строки, чтобы начать рекурсию. (рекурсивный вход)
(2) После указания струны прокрутите ноты, составляющие аккорд, и подсчитайте, попадают ли какие-либо ноты в указанный диапазон ладов струны.Если нет, вернитеfalse
; если да, перейти к шагу (3).
(3) первый保存
Звук и его положение на ладу, текущая позиция окончательно зависит от,当且仅当
Все строки после него также являются допустимыми решениями для поиска положения лада, если строка является 6-й строкой, возвратtrue
, рекурсия заканчивается (рекурсивный выход), иначе перейти к шагу (4);
(4)当前结果最终的有效性
= 当前临时结果有效性(true)
&& 下一根弦是否存在有效解(此时已转至步骤(3))
. Если текущий результат окончательно действителен, вернитеtrue
; если неверно,回退pop
вывести результат, ранее сохраненный в этой строке.
В окончательной реализации также необходимо учитывать, что две соседние строки не могут быть одинаковыми.Кроме того, чтобы облегчить ретроспективу общего результата, при сохранении единственного результата добавляется указатель на предыдущий результат.pre
.
// 递归遍历范围内的指定和弦的所有位置组合
/*
* @param stringIndex 当前遍历到的弦的序号
* @param toneIndex 上一根弦使用的音的序号(用于相邻的两根弦的音不重复)
* @param fretStart 遍历的最低品格数
* @param fretEnd 遍历的最高品格数
* @param preResult 上一根弦确定的音的结果
* @param positionSave 保存该轮递归的结果
*/
calc(stringIndex, toneIndex, fretStart, fretEnd, preResult, positionSave) {
let toneArray = this.toneMap[stringIndex];
let result = false;
// 从和弦音的数组里逐个选出音进行试探(this.chordTone在后面提到的函数中赋值)
for (let i = 0; i < this.chordTone.length; i++) {
// 相邻的上一根弦已使用的音不做本次计算
if (i !== toneIndex) {
let resultNext = false;
let toneKey = this.chordTone[i];
// 在品格范围内查找当前音的位置
let fret = this.findFret(toneKey, toneArray, fretStart, fretEnd);
// 品格范围内存在该音
if (fret.length > 0) {
// 记录该音的位置,几弦几品与音的数字描述
let resultNow = {
string: stringIndex,
fret: fret[0],
key: toneKey
}
// 在本次记录上保存上一根弦的结果,方便回溯
resultNow.pre = preResult ? preResult : null;
// 保存本次结果
positionSave.push(resultNow);
// 设置该弦上的结果标记
resultNext = true;
// 没有遍历完所有6根弦,则继续往下一根弦计算,附带上本次的结果记录
if (stringIndex < this.initialTone.length) {
let nextStringIndex = stringIndex + 1;
// 该弦上的结果的有效标记,取决上它后面的弦的结果均有效
resultNext = resultNext && this.calc(nextStringIndex, i, fretStart, fretEnd, resultNow, positionSave);
} else {
// 所有弦均遍历成功,代表递归结果有效
resultNext = true;
}
// 在该弦的计算结果无效,吐出之前保存的该弦结果
if (!resultNext) {
positionSave.pop();
}
} else {
// 品格范围内不存在该音
resultNext = false;
}
// 任意一个和弦里的音,能在该弦取得有效结果,则该弦上的结果有效
result = result || resultNext;
}
};
return result;
}
Используя этот рекурсивный метод, с1
,3
,5
Введите для образования аккордов, и вы получите что-то вроде этого:
filter
функция:
// 和弦指法过滤器
filter(positionSave) {
// 从6弦开始回溯记录的和弦指法结果,拆解出所有指法组合
let allResult = positionSave.filter((item) => {
return item.string === this.initialTone.length
}).map((item) => {
let resultItem = [{
string: item.string,
fret: item.fret,
key: item.key
}];
while (item.pre) {
item = item.pre;
resultItem.unshift({
string: item.string,
fret: item.fret,
key: item.key
});
}
return resultItem;
});
if (allResult.length > 0) {
// 依次调用各个过滤器
return this.integrityFilter(this.fingerFilter(this.rootToneFilter(allResult)));
} else {
return [];
}
}
Можно видеть, что после того, как возврат вычислит форму идеального результата, конец также называется多个过滤器
, потому что все комбинации аппликатуры, которые соответствуют составляющим звукам, рассчитанным кодом, могут не соответствовать реальной ситуации нажатия на струну, и требуется множественная фильтрация.
4. Фильтр аппликатуры
根音条件过滤
Например, с1
,3
,5
В качестве ноты аккорда основная нота1
, и первоначальные результаты могут быть следующими:
6弦0品
из3
, либо в6弦3品
из5
, не соответствует требованиям, а5弦3品
Это корень аккорда, поэтому он должен быть禁用第6弦
(Здесь отключен лад струныfret
отметить какnull
)
// 根音条件过滤
rootToneFilter(preResult) {
let nextResult = new Set();
preResult.forEach((item) => {
// 允许发声的弦的总数,初始为6
let realStringLength = 6;
// 从低音弦到高音弦遍历,不符合根音条件则禁止其发声
for (var i = item.length - 1; i >= 0; i--) {
if (item[i].key !== this.rootTone) {
item[i].fret = null;
item[i].key = null;
realStringLength--;
} else {
break;
}
}
if (realStringLength >= 4) {
// 去重复
nextResult.add(JSON.stringify(item));
}
});
// 去重后的Set解析成对应数组返回
return [...nextResult].map(item => JSON.parse(item));
}
按弦手指数量过滤
При нажатии на струны левой рукой вы можете использовать максимум 4 пальца (большой используется редко), а результат, рассчитанный рекурсивным методом, может содержать различные странные методы нажатия, такие как следующие:
Кажется, что он содержит все составляющие ноты аккорда, но даже если 6-я струна была отключена после предыдущего раунда фильтрации, каждая非0
Оценки5
пальцы, поэтому результаты, подобные этому, должны быть отфильтрованы дважды:
// 按弦手指数量过滤
fingerFilter(preResult) {
return preResult.filter((chordItem) => {
// 按弦的最小品位
let minFret = Math.min.apply(null, chordItem.map(item => item.fret).filter(fret => (fret != null)));
// 记录需要的手指数量
let fingerNum = minFret > 0 ? 1 : 0;
chordItem.forEach((item) => {
if (item.fret != null && item.fret > minFret) {
fingerNum++;
}
});
return fingerNum <= 4;
});
}
和弦组成音完整性过滤
При рекурсивном вычислении всех возможных комбинаций аппликатуры хотя и гарантируется, что две соседние ноты не повторяются, но не гарантируется, что используются все ноты, составляющие аккорд, и звук некоторых струн может быть отключен во время предыдущего раунда фильтрации основных нот. , что может привести к потере только одного из составляющих звуков, поэтому, наконец, требуется раунд фильтрации целостности для удаления дефектных продуктов:
// 和弦组成音完整性过滤
integrityFilter(preResult) {
return preResult.filter((chordItem) => {
let keyCount = [...new Set(chordItem.map(item => item.key).filter(key => key != null))].length;
return keyCount === this.chordTone.length;
});
}
5. Запись расчета аппликатуры
Введите сюда составляющие ноты аккорда, рассчитайте все возможные ладовые позиции этих нот, а затем рассчитайте аккордовые аппликатуры в диапазоне 4 или 5 ладов по очереди от низкой к высокой, и получите правильную аппликатуру всех позиций аккорда после интеграции и фильтрации.
注意,这里的输入音是在C调的基础下,故计算出的对应的和弦名和指法图也是C调下的。
// 和弦指法计算入口
chord() {
let chordTone;
if (is(arguments[0])('Array')) {
chordTone = arguments[0];
} else {
chordTone = Array.prototype.slice.apply(arguments).map((item) => {
let tone = new Tone(item.toString());
return tone.flat + tone.sharp + tone.key;
});
}
// 和弦组成音
this.chordTone = chordTone;
// 根音
this.rootTone = chordTone[0];
this.chordResult = [];
let fretArray = [];
// 查找和弦里的音可能存在的品格位置,保存至fretArray
chordTone.forEach((item) => {
for (let i = 1; i < this.toneMap.length; i++) {
fretArray = fretArray.concat(this.findFret(item, this.toneMap[i]));
}
});
fretArray = [...new Set(fretArray)];
// 品格位置从小到大排序
fretArray.sort((a, b) => {
return a - b;
});
// 从低把位到高把位,计算范围内的所有该和弦指法
for (let i = 0; i < fretArray.length; i++) {
let fretStart = fretArray[i];
// 在不需要使用大横按时,即在最低的把位计算时,可把计算的品格范围扩大一格
let fretEnd = fretStart > 0 ? (fretStart + 4) : (fretStart + 5);
// 最高范围不能超过吉他的最高品格数
if (fretEnd <= this.fretLength) {
let positionSave = [];
// 从1弦开始启动递归计算
if (this.calc(1, null, fretStart, fretEnd, null, positionSave)) {
// 单次结果过滤并保存
this.chordResult.push(...this.filter(positionSave));
}
}
}
// 结果去重
let result = [...new Set(this.chordResult.map(item => JSON.stringify(item)))].map(item => JSON.parse(item));
return result;
}
Запустите пример:
3. Визуализация результатов аппликатуры аккордов
Я сознательно выбрал svg для рисования, потому что раньше не знал его, поэтому воспользовался этой возможностью, чтобы изучить его.
Относительно полная диаграмма аппликатуры аккордов, пример кода svg выглядит следующим образом (вставьте это в свой собственный html и откройте его, чтобы интуитивно увидеть результат):
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svg" width="200" height="220" viewBox="0 0 150 150" preserveAspectRatio="xMidYMin meet">
<defs>
<g id="forbidden">
<path d="M-5 -5 L5 5 M-5 5 L5 -5" stroke="#666" stroke-width="1" fill="none"/>
</g>
<g id="blank_circle">
<circle cx="0" cy="0" r="6" stroke="#666" stroke-width="1" fill="none"/>
</g>
<g id="block_circle">
<circle cx="0" cy="0" r="8" fill="#333"/>
</g>
</defs>
<rect x="25" y=45 rx="5" ry="5" width="100" height="100" style="fill:none;stroke:#666;stroke-width:2"/>
<path d="M25 65 L125 65 M25 85 L125 85 M25 105 L125 105 M25 125 L125 125 M45 45 L45 145 M65 45 L65 145 M85 45 L85 145 M105 45 L105 145 M25 40 L125 40" stroke="#666" stroke-width="2" fill="none"/>
<use xlink:href="#forbidden" x="25" y="30" />
<use xlink:href="#blank_circle" x="125" y="30" />
<use xlink:href="#blank_circle" x="85" y="30" />
<use xlink:href="#block_circle" x="105" y="55" />
<use xlink:href="#block_circle" x="65" y="75" />
<use xlink:href="#block_circle" x="45" y="95" />
<text x="67" y="20" fill="#333" font-size="20" font-weight="700">C</text>
<text x="41.5" y="160" fill="#333" font-size="10" font-weight="700">C</text>
<text x="61.5" y="160" fill="#333" font-size="10" font-weight="700">E</text>
<text x="81.5" y="160" fill="#333" font-size="10" font-weight="700">G</text>
<text x="101.5" y="160" fill="#333" font-size="10" font-weight="700">C</text>
<text x="121.5" y="160" fill="#333" font-size="10" font-weight="700">E</text>
<text x="8" y="60" font-size="14" font-weight="700" fill="#333">1</text>
</svg>
Эффект отображения следующий:
Конечно, нам нужно разработать схему, которая может нарисовать любую карту аппликатуры svg.Проще говоря, это разделение карты аппликатуры на несколько подэлементов, и некоторые из них рисуют网格
, некоторые картины按弦位置
, некоторые картины空弦符号
, и так далее, а затем динамически создавать эти подэлементы и добавлять их в svg в соответствии с поступающими результатами аппликатуры; однако особое внимание следует уделить положению, в котором каждый элемент может динамически изменяться, и для大横按
обработка чертежей.
Весь код выкладываю сюда, с более подробными комментариями, поэтому не буду вдаваться в подробности (устал печатать...)
// 和弦svg绘图
class ChordSvg {
constructor() {
this.SVG_NS = "http://www.w3.org/2000/svg";
this.XLINK_NS = "http://www.w3.org/1999/xlink";
this.ATTR_MAP = {
"className": "class",
"svgHref": "href"
};
this.NS_MAP = {
"svgHref": this.XLINK_NS
};
this.initChordSvg();
this.minFret = 0;
}
// 创建svg相关元素
createSVG(tag, attributes) {
let elem = document.createElementNS(this.SVG_NS, tag);
for (let attribute in attributes) {
let name = (attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute);
let value = attributes[attribute];
if (attribute in this.NS_MAP) {
elem.setAttributeNS(this.NS_MAP[attribute], name, value);
} else {
elem.setAttribute(name, value);
}
}
return elem;
}
// 创建use标签
createUse(href, x, y) {
return this.createSVG('use', {
svgHref: href,
x: x,
y: y
});
}
// 设置禁止弹奏的叉号位置,位于几弦
setForbidden(svg, string = 6) {
svg.appendChild(this.createUse('#forbidden', 25 + 20 * (6 - string), 30));
}
// 设置空弦弹奏的空心圈位置,位于几弦
setOpen(svg, string = 6) {
svg.appendChild(this.createUse('#blank_circle', 25 + 20 * (6 - string), 30));
}
// 设置指法按弦位置,几弦几品
setFinger(svg, string = 6, fret = 0) {
if (+fret > 0 && +fret <= 5) {
svg.appendChild(this.createUse('#block_circle', 25 + 20 * (6 - string), 35 + 20 * fret));
}
}
// 设置大横按位置
setBarre(svg, stringTo, fret, barreFret) {
if (fret > 0 && fret <= 5) {
svg.appendChild(this.createSVG('rect', {
className: 'chord-barre',
width: stringTo * 20,
x: 15 + 20 * (6 - stringTo),
y: 27 + 20 * fret,
rx: 8,
ry: 8
}));
}
}
// 设置把位偏移的数字提示
setFretOffset(svg, fret, fretOffset, isBarreCover) {
if (fret > 0) {
let text = this.createSVG('text', {
className: 'chord-barre-fret',
x: isBarreCover ? 1 : 8,
y: 40 + fret * 20
});
text.innerHTML = fretOffset;
svg.appendChild(text);
}
}
// 设置每根弦在按住和弦后的发音名
setStringKey(svg, string, keyName) {
let xFixed = keyName.length === 2 ? -4 : 0;
let text = this.createSVG('text', {
className: 'chord-string-key',
x: 21.5 + 20 * (6 - string) + xFixed,
y: 160
});
text.innerHTML = keyName;
svg.appendChild(text);
}
// 设置和弦名称
setChordName(svg, name = '') {
let xFixed = /\.\.\./.test(name) ? 10 : 0;
let text = this.createSVG('text', {
className: 'chord-name',
x: 75 - name.toString().length * 7 + xFixed,
y: 20
});
text.innerHTML = name;
svg.appendChild(text);
}
// 初始化svg
initChordSvg() {
// svg元素
this.svg = this.createSVG('svg', {
className: 'chord-svg',
viewBox: '0 0 150 150',
preserveAspectRatio: 'xMidYMin meet'
});
// 和弦图方块
this.chordRect = this.createSVG('rect', {
className: 'chord-rect',
x: 25,
y: 45,
rx: 5,
ry: 5
});
// 和弦网格,代表弦和品
this.chordGird = this.createSVG('path', {
className: 'chord-gird',
d: 'M25 65 L125 65 M25 85 L125 85 M25 105 L125 105 M25 125 L125 125 M45 45 L45 145 M65 45 L65 145 M85 45 L85 145 M105 45 L105 145 M25 40 L125 40'
});
// 用于放置可复用的svg元素
this.defs = this.createSVG('defs');
// 禁止按弦的叉号标志
this.g_forbidden = this.createSVG('g', {
id: 'forbidden'
});
this.g_forbidden.appendChild(this.createSVG('path', {
className: 'chord-forbidden',
d: 'M-5 -5 L5 5 M-5 5 L5 -5'
}));
// 空弦弹奏的空心圈标志
this.g_blank_circle = this.createSVG('g', {
id: 'blank_circle',
});
this.g_blank_circle.appendChild(this.createSVG('circle', {
className: 'chord-blank-circle',
cx: 0,
cy: 0,
r: 6
}));
// 表示按弦位置的实心圈标志
this.g_block_circle = this.createSVG('g', {
id: 'block_circle'
});
this.g_block_circle.appendChild(this.createSVG('circle', {
className: 'chord-block-circle',
cx: 0,
cy: 0,
r: 8
}));
// 可复用元素加入
this.defs.appendChild(this.g_forbidden);
this.defs.appendChild(this.g_blank_circle);
this.defs.appendChild(this.g_block_circle);
// svg子元素加入
this.svg.appendChild(this.chordRect);
this.svg.appendChild(this.chordGird);
this.svg.appendChild(this.defs);
}
// 绘制和弦svg图案
/*
* @param chordTone 和弦组成音数组
* @param chord 和弦指法结果
* @param target svg指法图dom容器
*/
drawChord(chordTone, chord, target) {
let svg = this.svg.cloneNode(true);
let fretArr = chord.map(item => item.fret).filter(fret => (fret != null));
// 和弦指法中出现的最高品格位置
let maxFret = Math.max.apply(null, fretArr);
// 和弦指法中出现的最低品位位置
let minFret = Math.min.apply(null, fretArr);
// svg指法图案的起始品格位置相对于吉他上0品位置的偏移量
let fretOffset = maxFret <= 5 ? 0 : minFret;
// 记录指法最低品位可能需要大横按的按弦数
let barreCount = 0;
// 大横按初始只横跨1弦到1弦(相当于没横按)
let barreStringTo = 1;
// 实例化用于计算和弦名称的类
let chordName = new ChordName();
// 遍历和弦指法数组
chord.forEach((item) => {
if (item.fret == null) {
// 某根弦没标记品格位置时禁止该弦弹奏
this.setForbidden(svg, item.string);
} else if (item.fret === 0) {
// 某根弦没标记的品格位置为0品时标记空弦弹奏
this.setOpen(svg, item.string);
} else {
// 剩下的指法绘制其对应的按法位置
this.setFinger(svg, item.string, fretOffset > 0 ? item.fret - fretOffset + 1 : item.fret);
}
// 当按在该和弦的最低品格位置的指法反复出现时
if (item.fret === minFret) {
// 计算大横按的跨度
barreStringTo = item.string > barreStringTo ? item.string : barreStringTo;
// 计算大横按实际按弦的数量
barreCount++;
}
// 在允许弹奏的弦的下方标记其对应的音名
if (item.fret != null) {
this.setStringKey(svg, item.string, chordName.getKeyName(item.key));
}
});
// 将真实的按弦品格位置转换为相对于svg图案上的品格位置
let relativeFret = fretOffset > 0 ? minFret - fretOffset + 1 : minFret;
if (barreCount > 1) {
// 横按数大于1才需要使用大横按
this.setBarre(svg, barreStringTo, relativeFret, minFret);
}
// 在图案左侧绘制品格位置偏移标记
this.setFretOffset(svg, relativeFret, minFret, barreStringTo === 6);
// 在图案上侧绘制和弦名称
this.setChordName(svg, chordName.getChordName(chordTone));
// 将生成号的svg图案塞到指定结构中
target ? target.appendChild(svg) : document.body.appendChild(svg);
}
}
SVG также может использовать css для изменения некоторых свойств, и необходимо добавить общедоступный стиль:
.chord-svg{
width: 200px;
height: 220px;
}
.chord-rect{
width: 100px;
height: 100px;
fill:none;
stroke:#666;
stroke-width:2;
}
.chord-gird{
stroke: #666;
stroke-width: 2;
fill: none;
}
.chord-forbidden,.chord-blank-circle{
stroke: #666;
stroke-width: 1;
fill: none;
}
.chord-block-circle{
fill: #333;
}
.chord-barre{
height: 16px;
fill:#333;
}
.chord-barre-fret{
fill:#333;
font-size: 14px;
font-weight: 700;
}
.chord-string-key{
fill:#333;
font-size: 10px;
font-weight: 700;
}
.chord-name{
fill:#333;
font-size: 20px;
font-weight: 700;
}
Dangdang работающий пример:
Конечно, как я мог остановиться на этом.
На основе кода, который был реализован выше, я выбросил веб-инструмент, перетаскивая число влево и вправо, чтобы изменить состав аккорда, чтобы время от времени вычислять диаграмму аппликатуры аккорда:
Если вы играли нестандартно и давали составляющие ноты со странным интервалом, это могло выглядеть так (поскольку полное название аккорда нельзя было рассчитать, вместо него использовались многоточия):
Конечно, если вы возитесь с этим, большинство вещей будет выглядеть так:
ароматный хвост
При поиске базовой музыкальной теории, при заполнении безграничного пробела в знаниях, это можно рассматривать как получение этой вещи, и это включает только верхушку айсберга музыкальных основ, таких как многие более сложные аккорды, состоящие из большего количества нот, и более экзотические странные названия аккордов и ограниченные возможности, поэтому я не буду их здесь рассматривать.
Должен сказать, что я был здесь, чтобы писать код, но по незнанию дал себе урок музыки.
Некоторые мотивы делать что-то просто потрясающие.
Если судье все же покажется это интересным, он выиграет, но в мире бессчетное количество людей.
адрес проекта:GitHub.com/Young’s RO/So…
Играйте во вторичный портал онлайн
-------- Следующие объявления можно игнорировать --------
Прошлые статьи:
Эффекты частиц Three.js, рендеринг шейдеров
Путь к пробуждению консоли, как насчет печати анимации?
узел гусеничный фонд, самостоятельный и самостоятельный, чтобы понять?