regex — Как транспонировать музыкальные аккорды с помощью PHP?

Мне было интересно, как можно создать функцию в PHP, которая используется для транспонирования некоторых музыкальных аккордов.

Я попытаюсь объяснить, как это работает в теории музыки. Я надеюсь, что я ничего не забуду. Если есть ошибки, пожалуйста, помогите мне исправить это.


1. Простые аккорды.

Простые аккорды почти так же просты, как алфавит, и это выглядит так:

C, C #, D, D #, E, F, F #, G, G #, A, A # B

От B он снова возвращается к C. Поэтому, если исходный аккорд E и мы хотим транспонировать +1, результирующий аккорд F, Если мы транспонируем +4, результирующий аккорд G#,

2. Расширенные аккорды.

Они работают почти как простые аккорды, но содержат еще несколько символов, которые можно смело игнорировать при транспонировании. Например:

Cmi, C # 7, Dsus7, Emi, Fsus4, F # mi, G …

Опять же, как с простыми аккордами, если мы транспонируем Dsus7 + 3 = Fsus7

3. Некорневой басовый тон.

Проблема возникает, когда бас играет тональный сигнал, отличный от основного тона аккордов. Это отмечено косой чертой после аккорда и также должно быть транспонировано. Примеры:

C / G, Dmi / A, F # sus7 / A #

Как и в примерах 1 и 2, все то же самое, но часть после косой черты тоже нуждается в транспонировании:

C/G + 5 = F/C

F#sus7/A# + 1 = Gsus7/B


Итак, представьте, что у вас есть переменная PHP с именем chord и значение транспонирования transpose, Какой код будет транспонировать аккорд?

Примеры:

var chord = 'F#sus7/C#';
var transpose = 3; // remember this value also may be negative, like "-4"... code here ...
var result; // expected result = 'Asus7/E';

Я нашел существующий вопрос о StackOverflow, здесь. Они говорят об алгоритме аккордов-прогрессий.


Как я могу транспонировать музыкальные аккорды с помощью PHP, увеличивая или уменьшая полутона?

5

Решение

Быстрое решение:

<?php

// produces the expected result
echo transpose("F#sus7/C#",3);

function transpose($chord,$transpose)
{
// the chords
$chords = array("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B");

$result = "";

// get root tone
$root_arr = explode("/",$chord);
$root = strtoupper($root_arr[0]);

// the chord is the first character and a # if there is one
$root = $root[0].((strpos($root, "#") !== false)?"#":"");

// get any extra info
$root_extra_info = str_replace("#","",substr($root_arr[0],1)); // assuming that extra info does not have any #

// find the index on chords array
$root_index = array_search($root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$root_transpose_index = floor(($root_index + $transpose) % 12);

if ($root_transpose_index < 0)
{
$root_transpose_index += 12;
}

$result.= $chords[$root_transpose_index].$root_extra_info;

if(count($root_arr)>1)
{
// get the non root tone
$non_root = $root_arr[1];
// the chord is the first character and a # if there is one
$non_root = strtoupper($non_root[0]).((strpos($non_root, "#") !== false)?"#":"");
// get any extra info
$non_root_extra_info = str_replace("#","",substr($root_arr[1],1)); // assuming that extra info does not have any #

// find the index on chords array
$non_root_index = array_search($non_root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$non_root_transpose_index = floor(($non_root_index + $transpose) % 12);

if ($non_root_transpose_index < 0)
{
$non_root_transpose_index += 12;
}

$result.= "/".$chords[$non_root_transpose_index].$non_root_extra_info;
}

return $result;
}

https://3v4l.org/Cd9Pg

много места для улучшения кода, я просто попытался написать код, чтобы его было легко понять.

1

Другие решения

Здесь моя идея регулярных выражений с preg_replace_callback (использование анонимная функция требует PHP 5.3).

function transpose($str, $t=0)
{
// the chords
$chords = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];

// set transpose, return if none
$t = (int)$t % 12 + 12; if($t % 12 == 0) return $str;

// regex with callback
return preg_replace_callback('~[A-G]#?~', function($m) use (&$chords, &$t) {
return $chords[(array_search($m[0], $chords) + $t) % 12];
}, $str);
}

Демо на eval.in   (для тестирования шаблон регулярных выражений [A-G]#? см. regex101)

echo transpose("Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G C/G, Dmi/A, F#sus7/A#", -3);

Ами, A # 7, Bsus7, C # mi, Dsus4, D # mi, E A / E, Bmi / F #, D # sus7 / G

1

Итак, есть несколько вещей, с которыми вы хотите справиться.

Во-первых, вы хотите иметь возможность зацикливаться в массиве. Это легко: используйте модуль оператор, который в php %,

function transpose($chord, $increment) {
$map = array('A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#');

// Get the index of the given chord
$index = array_search($chord, $map);
if($index === false)
return false;

// Get the transposed index and chord
$transpose_index = ($index + $increment) % count($map);
if($transpose_index < 0)
$transpose_index += count($map);
return $map[$transpose_index];
}

Во-вторых, вы хотите быть в состоянии обрезать фактические аккорды, которые важны для вас. Вы можете сделать это с помощью регулярное выражение (RegEx):

function transposeFull($chords, $increment) {

// This RegEx looks for one character (optionally followed by a sharp).
//     .\#?
// This RegEx looks for an optional series of characters which are not /
//     [^\/]*
// Put them together to get a RegEx that looks for an expanded chord
//     (.\#?)([^\/]*)
// Then, do it again, but add a / first, and make it optional.
//     (\/(.\#?)([^\/]*))?
$regex = '%(.\#?)([^\/]*)(\/(.\#?)([^\/]*))?%';

// Note that the () allow us to pull out the matches.
// $matches[0] is always the full thing.
// $matches[i] is the ith match
//   (so $matches[3] is the whole optional second chord; which is not useful)
$matches = array();
preg_match($regex, $chords, $matches);

// Then, we get any parts that were matched and transpose them.
$chord1 = (count($matches) >= 2) ? transpose($matches[1], $increment) : false;
$expanded1 = (count($matches) >= 2) ? $matches[2] : '';
$chord2 = (count($matches) >= 5) ? transpose($matches[4], $increment) : false;
$expanded2 = (count($matches) >= 6) ? $matches[5] : '';

// Finally, put it back together.
$chords = '';
if($chord1 !== false)
$chords .= $chord1.$expanded1;
if($chord2 !== false)
$chords .= '/'.$chord2.$expanded2;

return $chords;
}
0
По вопросам рекламы [email protected]