6

我想知道如何在 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 value 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 转调音乐和弦?

4

3 回答 3

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);

Ami, A#7, Bsus7, C#mi, Dsus4, D#mi, EA/E, Bmi/F#, D#sus7/G

于 2016-06-11T07:54:47.833 回答
1

快速解决方案:

<?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

代码有很大的改进空间,我只是试图将其编码为易于理解。

于 2016-06-06T11:31:52.637 回答
0

好的,所以您需要处理一些事情。

首先,您希望能够在数组中循环。这很简单:使用模数运算符,在 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;
}
于 2016-06-06T11:12:20.610 回答