2

I am writing a PHP application that represents the six strings of the guitar as a series of ranges. In this way, I can add, or subtract to a set of numbers to systematically change an archetypical structure I define.

The ranges are:

//E: 1-15, A: 26-40, D: 51-65, G: 76-90, b: 101-115, e: 126-140 

I am having trouble with the code below.

The function

//the key of the key value pair represents its position on the guitar, and the value 
//represents a theoretical function. If the inversion is "0", do nothing. If the 
//inversion is "1", all notes that have the value of "r" should have their key 
//incremented by 4 and their value changed to '3'. 

//example: 1=>"r",28=>"5",52=>"7",77=>"3"
function invChange($pattern, $inversion) {  
    if ($inversion == 1) {
        foreach ($pattern as $position => $function) {
            if ($function == 'r' ) { $position += 4; $junction = '3'; }
            if ($function == '3' ) { $position += 3; $junction = '5'; }
            if ($function == '5' ) { $position += 4; $junction = '7'; }
            if ($function == '7' ) { $position += 1; $junction = 'r'; }
            $modScale[$position] = $junction; 
        }
    }
    if ($inversion == 2) {
        foreach ($pattern as $position => $function) {
            if ($function == 'r' ) { $position += 7; $junction = '5';}
            if ($function == '3' ) { $position += 7; $junction = '7';}
            if ($function == '5' ) { $position += 5; $junction = 'r';}
            if ($function == '7' ) { $position += 5; $junction = '3';}
            $modScale[$position] = $junction; 
        }
    }
    if ($inversion == 3) {
        foreach ($pattern as $position => $function) {
            if ($function == 'r' ) { $position += 11; $junction = '7';}
            if ($function == '3' ) { $position += 8; $junction = 'r';}
            if ($function == '5' ) { $position += 9; $junction = '3';}
            if ($function == '7' ) { $position += 8; $junction = '5';}
            $modScale[$position] = $junction; 
        }
    }
    return $modScale;
}

As you can see, this is quite repetitive. Just looking at this code makes me think there has got to be a better way. I think what I need is to use an array in an infinite linear fashion:

array("root" => 4, "third" => 3, "fifth" => 4, "seventh" => 1); 

Now I need to take any of a predefined $note => $offset pair and jump them across this array by 1, 2, or 3 jumps. For instance, if it starts as a root, and it makes one jump, I need to add 4 to turn it into a "third", and change its value to 'third'. But if it starts as a root and makes two jumps, I need to add 4, add 3, and then change its value to "fifth".

double-beep
  • 5,031
  • 17
  • 33
  • 41

1 Answers1

1

TL;DR;

Well.. you can do it with math. Even more, with restrictions you've provided, it can be done with, actually, one function. Quadratic function.

How it's done

All you need to do - is somehow create a math function, which will take your inversion as an argument, and return a certain position by a given "function" (how it's called inside your PHP function). Since you have three values for "inversion", you can done this with quadratic function:

f(x) = ax2 + bx + c

here, you'll need to find coefficients a, b, and c by given x values ($inversion for your case) and function values (values for "position" in each of your "inversion" if blocks).

However, you have four different position switches for each of your "function" (that's inside foreach block) - that's why, actually, you'll have to deal with four quadratic functions.

Now, about junctions. I'm not sure what it is in logical sense, but, certainly, the dependence is obvious: given the array of all possible junctions, value for certain "function" is just junction at position "current" + "inversion". So, if we have junctions array ['r', '3', '5', '7'], then, if inversion is 1 and current is 3, then result would be 5. If inversion is 2, then result would be 7 e t.c.

And the code

Here we go:

function invChangeN($pattern, $inversion)
{
   $junc = ['r', '3', '5', '7'];
   $pos  = [
      'r' => [ 1, 7, 14],
      '3' => [-3, 5, 14],
      '5' => [ 3, 5, 10],
      '7' => [-1, 7, 10]
   ];
   $inversion -= 2;
   $modScale   = [];
   foreach ($pattern as $p => $f)
   {
      $p += ($pos[$f][0]*pow($inversion,2) + $pos[$f][1]*$inversion + $pos[$f][2])/2;
      $j  = $junc[(array_search($f, $junc)+$inversion+2)%count($junc)];
      $modScale[$p] = $j; 
   }
   return $modScale;
}

Little about, why are we doing $inversion -= 2. It's just about - to transpose three points from 1, 2 and 3 (original inversion values) to -1, 0 and 1 - with that it's much easier to calculate our a, b and c.

$pos contains arrays of coefficients for quadratic function for each "function" (that is $f inside loop). Due to resolving linear equations system, they all are "half-integers" (so, have a view W/2, so denominator of fraction is 2). That's why I've just multiplied them all to 2 and added division by 2 to position calculation.

Tests

I've done tests of your function and this new "math" analog - with successful results, which are, of course for inversion as 1, 2 and 3, like:

//derived from old function:
array(4) {
  [8]=>
  string(1) "5"
  [33]=>
  string(1) "r"
  [57]=>
  string(1) "3"
  [84]=>
  string(1) "7"
}
//derived from new function:
array(4) {
  [8]=>
  string(1) "5"
  [33]=>
  string(1) "r"
  [57]=>
  string(1) "3"
  [84]=>
  string(1) "7"
}

Results above are for pattern [1=>"r",28=>"5",52=>"7",77=>"3"] and inversion 2

Alma Do
  • 37,009
  • 9
  • 76
  • 105
  • 2
    After reading only the first paragraph I just knew it was you ;-) – Ja͢ck Jun 24 '14 at 10:48
  • @Jack who else would calculate those coefs four times to get four functions? :p – Alma Do Jun 24 '14 at 10:55
  • Well, your code absolutely works, as-is, no question about it. Hats off to you. I had hoped the answer would be one step beyond my comprehension level but I think it's a few more than that. ;) I am going through it bit by bit, trying to understand it. My next goal is to add a check, to test if ($pos[$f][0]*pow($inversion,2) + $pos[$f][1]*$inversion + $pos[$f][2])/2 + $newthirdarguement > 12, which should help me decide if I should subtract 12 from P or not. Thanks for the great answer. – AardvarkApostrophe Jun 24 '14 at 10:55
  • I wanted to add a follow up question, especially in case someone else finds this because it would be useful to anyone programming chords. In some cases, the set only has 1, 3, and 5 and so they each need to skip over 7. To skip over 7, the value would be incremented by 1. Would that be a minor change to the coefficients calculated above, or a major change? – AardvarkApostrophe Jun 26 '14 at 06:28
  • Not sure. What is "1, 3 and 5" ? We have "r", "3", "5" and "7" (if you're about them). So are you telling about - input $pattern may consist from three items - and there will be no "7" and also "1" instead of "r" ? – Alma Do Jun 26 '14 at 06:30
  • Sorry about that, I mean "r", "3", and "5" – AardvarkApostrophe Jun 26 '14 at 06:37
  • So you're telling that "junction" should be calculated in some other way? (because "position" calculation will use incremental calculation in this case normally - it does not matters if we don't have "7" part, it just won't be used since it's absent in $pattern) – Alma Do Jun 26 '14 at 06:40
  • In my test using input set "r", "3" and "5", the first inversion will turn "5" into "7". But in a set with "r", "3" and "5", the "5" should be turned into "r" again, so it would need to be incremented by 5 instead of 4. – AardvarkApostrophe Jun 26 '14 at 06:41
  • Oh, I got it. It's just because I've hardcoded transitions in `$junc`. As you can see, it's `$junc = ['r', '3', '5', '7'];` now. If I've got all correctly, replace it to `$junc = array_values($pattern);` and then you'll get proper transition from "5" to "r" e t.c. – Alma Do Jun 26 '14 at 06:44
  • The math is different for 'r', '3', '5', because now there's no '7' so instead of '5' going to '7' it be incremented by 1 more so it can reach 'r' again: if ($inversion == 1) { foreach ($pattern as $p => $f) { if ($f == 'r' ) { $p += 4; $j = '3'; } if ($f == '3' ) { $p += 3; $j = '5'; } if ($f == '5' ) { $p += 5; $j = 'r'; } $modScale[$p] = $j; if ($inversion == 2) { foreach ($pattern as $p => $f) { if ($f == 'r' ) { $p += 7; $j = '5';} if ($f == '3' ) { $p += 8; $j = '7';} if ($f == '5' ) { $p += 5; $j = 'r';} $modScale[$p] = $j; } – AardvarkApostrophe Jun 26 '14 at 07:10
  • I'm lost. The math part will be different for junction calculation (`$j` in loop) - yes, but that's easily can be fixed as I've already stated. But I'm not sure what's about "position" (`$p` in loop). Why should it be changed? You have no `7` in pattern, so in `foreach` it just won't appear and won't be calculated. Where's a mistake? – Alma Do Jun 26 '14 at 07:13
  • That's true, but even though there's no '7' in the pattern, the purpose of the function is to modify 'r', '3', and '5' so they will have the value of '7'. So with the current math using $inversion = 1: 'r'+4 turns it into '3'. But '5'+ 4 assumes that '5' should go to '7'. However, if there *is* no 7, it should be '5' + 5 to turn it into the 'r' again. Does that make sense? – AardvarkApostrophe Jun 26 '14 at 07:27
  • So may be it's not what I've posted before. Check [this fiddle](http://3v4l.org/MqPtG) - this is how I've got your point for now – Alma Do Jun 26 '14 at 07:39