gogins / csound-extended

Extensions for Csound including algorithmic composition, Android app, and WebAssembly.
GNU Lesser General Public License v2.1
40 stars 1 forks source link

Fix bugs in ChordSpace code for porting HarmonyIFS and ChordSpace to CsoundAC in C++ and WebAssembly #142

Closed gogins closed 4 years ago

gogins commented 4 years ago

I have discovered that my concepts and code for the OPTI and OPTTI symmetries of chord space were wrong, and therefore wrongly implemented. This is to fix that in both JavaScript and C++ and thus make the ChordSpaceGroup class usable as intended.

gogins commented 4 years ago

Differences are appearing in results between JavaScript and C++ that must be fixed.

The test Hutchinson operator in JavaScript:

transformation 0:
[[     0.3333,          0,          0,          0,          0,          0,          0,          0],
 [          0,       1e-3,          0,          0,          0,          0,          0,      41.99],
 [   -0.03333,          0,       1e-3,          0,          0,          0,          0,     0.9997],
 [    0.06667,          0,          0,       1e-3,          0,          0,          0,          0],
 [          0,          0,          0,          0,       0.85,          0,          0,         -1],
 [          0,          0,          0,          0,          0,          1,          0,          0],
 [          0,          0,          0,          0,          0,          0,          1,          0],
 [          0,          0,          0,          0,          0,          0,          0,          1]]
HarmonyIFS.js:347 transformation 1:
[[     0.3333,          0,          0,          0,          0,          0,          0,         10],
 [       -1.1,       1e-3,          0,          0,          0,          0,          0,      41.97],
 [    0.03333,          0,       1e-3,          0,          0,          0,          0,  -6.667e-4],
 [    -0.2333,          0,          0,       1e-3,          0,          0,          0,          2],
 [          0,          0,          0,          0,       0.85,          0,          0,          1],
 [          0,          0,          0,          0,          0,          1,          0,          0],
 [          0,          0,          0,          0,          0,          0,          1,          0],
 [          0,          0,          0,          0,          0,          0,          0,          1]]
HarmonyIFS.js:347 transformation 2:
[[     0.3333,          0,          0,          0,          0,          0,          0,         20],
 [        1.1,       1e-3,          0,          0,          0,          0,          0,      8.958],
 [          0,          0,       1e-3,          0,          0,          0,          0,      0.999],
 [     0.1667,          0,          0,       1e-3,          0,          0,          0,         -5],
 [          0,          0,          0,          0,       0.85,          0,          0,         -1],
 [          0,          0,          0,          0,          0,          1,          0,          0],
 [          0,          0,          0,          0,          0,          0,          1,          0],
 [          0,          0,          0,          0,          0,          0,          0,          1]]
HarmonyIFS.js:347 transformation 3:
[[          1,          0,          0,          0,          0,          0,          0,          0],
 [          0,          1,          0,          0,          0,          0,          0,          0],
 [          0,          0,          1,          0,          0,          0,          0,          0],
 [          0,          0,          0,          1,          0,          0,          0,          0],
 [          0,          0,          0,          0,     0.7071,    -0.7071,          0,          0],
 [          0,          0,          0,          0,     0.7071,     0.7071,          0,          0],
 [          0,          0,          0,          0,          0,          0,          1,          0],
 [          0,          0,          0,          0,          0,          0,          0,          1]]
HarmonyIFS.js:347 transformation 4:
[[          1,          0,          0,          0,          0,          0,          0,          0],
 [          0,          1,          0,          0,          0,          0,          0,          0],
 [          0,          0,          1,          0,          0,          0,          0,          0],
 [          0,          0,          0,          1,          0,          0,          0,          0],
 [          0,          0,          0,          0,     0.7071,          0,    -0.7071,          0],
 [          0,          0,          0,          0,          0,          1,          0,          0],
 [          0,          0,          0,          0,     0.7071,          0,     0.7071,          0],
 [          0,          0,          0,          0,          0,          0,          0,          1]]

C++:

  0.333333          0          0          0          0          0          0          0
         0      0.001          0          0          0          0          0    24.9917
-0.0333333          0      0.001          0          0          0          0   0.999667
 0.0666667          0          0      0.001          0          0          0          0
         0          0          0          0       0.85          0          0         -1
         0          0          0          0          0          1          0          0
         0          0          0          0          0          0          1          0
         0          0          0          0          0          0          0          1
    0.333333            0            0            0            0            0            0           10
   -0.666667        0.001            0            0            0            0            0      24.9833
   0.0333333            0        0.001            0            0            0            0 -0.000666667
   -0.233333            0            0        0.001            0            0            0            2
           0            0            0            0         0.85            0            0            1
           0            0            0            0            0            1            0            0
           0            0            0            0            0            0            1            0
           0            0            0            0            0            0            0            1
0.333333        0        0        0        0        0        0       20
0.666667    0.001        0        0        0        0        0    4.975
       0        0    0.001        0        0        0        0    0.999
0.166667        0        0    0.001        0        0        0       -5
       0        0        0        0     0.85        0        0       -1
       0        0        0        0        0        1        0        0
       0        0        0        0        0        0        1        0
       0        0        0        0        0        0        0        1
        1         0         0         0         0         0         0         0
        0         1         0         0         0         0         0         0
        0         0         1         0         0         0         0         0
        0         0         0         1         0         0         0         0
        0         0         0         0  0.707107 -0.707107         0         0
        0         0         0         0  0.707107  0.707107         0         0
        0         0         0         0         0         0         1         0
        0         0         0         0         0         0         0         1
        1         0         0         0         0         0         0         0
        0         1         0         0         0         0         0         0
        0         0         1         0         0         0         0         0
        0         0         0         1         0         0         0         0
        0         0         0         0  0.707107         0 -0.707107         0
        0         0         0         0         0         1         0         0
        0         0         0         0  0.707107         0  0.707107         0
        0         0         0         0         0         0         0         1
gogins commented 4 years ago

So the problem is in the OPTTI of the ChordSpaceGroup. In JavaScript:

ChordSpaceGroup.range :       60
ChordSpaceGroup.g     :        1.0000
ChordSpaceGroup.countP:      112
ChordSpaceGroup.countI:        2
ChordSpaceGroup.countT:       12
ChordSpaceGroup.countV:     1295
ChordSpaceGroup.countA:        1
ChordSpaceGroup.countL:      256
ChordSpaceGroup.countD:      625
index:     0  opti: [  -5.0000000   -1.0000000    3.0000000    3.0000000 ]  index from opti: G-2 B-2 D#-1 D#-1 0
index:     1  opti: [  -4.0000000   -2.0000000    0.0000000    6.0000000 ]  index from opti: G#-2 A#-2 C-1 F#-1 1
index:     2  opti: [  -4.0000000   -2.0000000    1.0000000    5.0000000 ]  index from opti: Bbm7 2
index:     3  opti: [  -4.0000000   -2.0000000    2.0000000    4.0000000 ]  index from opti: Bb7b5 3
index:     4  opti: [  -4.0000000   -2.0000000    3.0000000    3.0000000 ]  index from opti: G#-2 A#-2 D#-1 D#-1 4
index:     5  opti: [  -4.0000000   -1.0000000    0.0000000    5.0000000 ]  index from opti: G#-2 B-2 C-1 F-1 5
index:     6  opti: [  -4.0000000   -1.0000000    1.0000000    4.0000000 ]  index from opti: E6 6
index:     7  opti: [  -4.0000000   -1.0000000    1.0000000    5.0000000 ]  index from opti: Db7 7
index:     8  opti: [  -4.0000000   -1.0000000    2.0000000    3.0000000 ]  index from opti: G#-2 B-2 D-1 D#-1 8
index:     9  opti: [  -4.0000000   -1.0000000    2.0000000    4.0000000 ]  index from opti: E7 9
index:    10  opti: [  -4.0000000   -1.0000000    2.0000000    5.0000000 ]  index from opti: Bo7 10
index:    11  opti: [  -4.0000000   -1.0000000    3.0000000    3.0000000 ]  index from opti: G#-2 B-2 D#-1 D#-1 11
index:    12  opti: [  -4.0000000   -1.0000000    3.0000000    4.0000000 ]  index from opti: EM7 12
index:    13  opti: [  -4.0000000    0.0000000    0.0000000    4.0000000 ]  index from opti: G#-2 C-1 C-1 E-1 13
index:    14  opti: [  -4.0000000    0.0000000    1.0000000    3.0000000 ]  index from opti: G#-2 C-1 C#-1 D#-1 14
index:    15  opti: [  -4.0000000    0.0000000    1.0000000    4.0000000 ]  index from opti: Dbm#7 15
index:    16  opti: [  -4.0000000    0.0000000    2.0000000    2.0000000 ]  index from opti: G#-2 C-1 D-1 D-1 16
index:    17  opti: [  -4.0000000    0.0000000    2.0000000    3.0000000 ]  index from opti: G#-2 C-1 D-1 D#-1 17
index:    18  opti: [  -4.0000000    0.0000000    2.0000000    4.0000000 ]  index from opti: E7#5 18
index:    19  opti: [  -4.0000000    0.0000000    3.0000000    3.0000000 ]  index from opti: G#-2 C-1 D#-1 D#-1 19
index:    20  opti: [  -4.0000000    1.0000000    1.0000000    2.0000000 ]  index from opti: G#-2 C#-1 C#-1 D-1 20
index:    21  opti: [  -4.0000000    1.0000000    1.0000000    3.0000000 ]  index from opti: G#-2 C#-1 C#-1 D#-1 21
index:    22  opti: [  -4.0000000    1.0000000    2.0000000    2.0000000 ]  index from opti: G#-2 C#-1 D-1 D-1 22
index:    23  opti: [  -4.0000000    1.0000000    2.0000000    3.0000000 ]  index from opti: G#-2 C#-1 D-1 D#-1 23
index:    24  opti: [  -4.0000000    2.0000000    2.0000000    2.0000000 ]  index from opti: G#-2 D-1 D-1 D-1 24
index:    25  opti: [  -3.0000000   -3.0000000   -3.0000000    9.0000000 ]  index from opti: A-2 A-2 A-2 A-1 25
index:    26  opti: [  -3.0000000   -3.0000000   -2.0000000    8.0000000 ]  index from opti: A-2 A-2 A#-2 G#-1 26
index:    27  opti: [  -3.0000000   -3.0000000   -1.0000000    7.0000000 ]  index from opti: A-2 A-2 B-2 G-1 27
index:    28  opti: [  -3.0000000   -3.0000000    0.0000000    6.0000000 ]  index from opti: A-2 A-2 C-1 F#-1 28
index:    29  opti: [  -3.0000000   -3.0000000    3.0000000    3.0000000 ]  index from opti: A-2 A-2 D#-1 D#-1 29
index:    30  opti: [  -3.0000000   -2.0000000   -2.0000000    7.0000000 ]  index from opti: A-2 A#-2 A#-2 G-1 30
index:    31  opti: [  -3.0000000   -2.0000000   -2.0000000    8.0000000 ]  index from opti: A-2 A#-2 A#-2 G#-1 31
index:    32  opti: [  -3.0000000   -2.0000000   -1.0000000    6.0000000 ]  index from opti: A-2 A#-2 B-2 F#-1 32
index:    33  opti: [  -3.0000000   -2.0000000   -1.0000000    7.0000000 ]  index from opti: A-2 A#-2 B-2 G-1 33
index:    34  opti: [  -3.0000000   -2.0000000   -1.0000000    8.0000000 ]  index from opti: A-2 A#-2 B-2 G#-1 34
index:    35  opti: [  -3.0000000   -2.0000000    0.0000000    5.0000000 ]  index from opti: A-2 A#-2 C-1 F-1 35
index:    36  opti: [  -3.0000000   -2.0000000    0.0000000    6.0000000 ]  index from opti: A-2 A#-2 C-1 F#-1 36
index:    37  opti: [  -3.0000000   -2.0000000    0.0000000    7.0000000 ]  index from opti: A-2 A#-2 C-1 G-1 37
index:    38  opti: [  -3.0000000   -2.0000000    1.0000000    5.0000000 ]  index from opti: Bbm#7 38
index:    39  opti: [  -3.0000000   -2.0000000    1.0000000    6.0000000 ]  index from opti: A-2 A#-2 C#-1 F#-1 39
index:    40  opti: [  -3.0000000   -2.0000000    2.0000000    3.0000000 ]  index from opti: A-2 A#-2 D-1 D#-1 40
index:    41  opti: [  -3.0000000   -2.0000000    2.0000000    4.0000000 ]  index from opti: A-2 A#-2 D-1 E-1 41
index:    42  opti: [  -3.0000000   -2.0000000    2.0000000    5.0000000 ]  index from opti: BbM7 42
index:    43  opti: [  -3.0000000   -2.0000000    3.0000000    3.0000000 ]  index from opti: A-2 A#-2 D#-1 D#-1 43
index:    44  opti: [  -3.0000000   -2.0000000    3.0000000    4.0000000 ]  index from opti: A-2 A#-2 D#-1 E-1 44
index:    45  opti: [  -3.0000000   -1.0000000   -1.0000000    5.0000000 ]  index from opti: A-2 B-2 B-2 F-1 45
index:    46  opti: [  -3.0000000   -1.0000000   -1.0000000    6.0000000 ]  index from opti: A-2 B-2 B-2 F#-1 46
index:    47  opti: [  -3.0000000   -1.0000000   -1.0000000    7.0000000 ]  index from opti: A-2 B-2 B-2 G-1 47
index:    48  opti: [  -3.0000000   -1.0000000    0.0000000    5.0000000 ]  index from opti: A-2 B-2 C-1 F-1 48
index:    49  opti: [  -3.0000000   -1.0000000    0.0000000    6.0000000 ]  index from opti: A-2 B-2 C-1 F#-1 49
index:    50  opti: [  -3.0000000   -1.0000000    0.0000000    7.0000000 ]  index from opti: A-2 B-2 C-1 G-1 50
index:    51  opti: [  -3.0000000   -1.0000000    1.0000000    3.0000000 ]  index from opti: A-2 B-2 C#-1 D#-1 51
index:    52  opti: [  -3.0000000   -1.0000000    1.0000000    4.0000000 ]  index from opti: E6sus 52
index:    53  opti: [  -3.0000000   -1.0000000    1.0000000    5.0000000 ]  index from opti: Db7#5 53
index:    54  opti: [  -3.0000000   -1.0000000    2.0000000    2.0000000 ]  index from opti: A-2 B-2 D-1 D-1 54
index:    55  opti: [  -3.0000000   -1.0000000    2.0000000    3.0000000 ]  index from opti: A-2 B-2 D-1 D#-1 55
index:    56  opti: [  -3.0000000   -1.0000000    2.0000000    4.0000000 ]  index from opti: E7sus 56
index:    57  opti: [  -3.0000000   -1.0000000    3.0000000    3.0000000 ]  index from opti: A-2 B-2 D#-1 D#-1 57
index:    58  opti: [  -3.0000000    0.0000000    0.0000000    3.0000000 ]  index from opti: A-2 C-1 C-1 D#-1 58
index:    59  opti: [  -3.0000000    0.0000000    0.0000000    4.0000000 ]  index from opti: A-2 C-1 C-1 E-1 59
index:    60  opti: [  -3.0000000    0.0000000    0.0000000    5.0000000 ]  index from opti: A-2 C-1 C-1 F-1 60
index:    61  opti: [  -3.0000000    0.0000000    0.0000000    6.0000000 ]  index from opti: A-2 C-1 C-1 F#-1 61
index:    62  opti: [  -3.0000000    0.0000000    1.0000000    2.0000000 ]  index from opti: A-2 C-1 C#-1 D-1 62
index:    63  opti: [  -3.0000000    0.0000000    1.0000000    3.0000000 ]  index from opti: A-2 C-1 C#-1 D#-1 63
index:    64  opti: [  -3.0000000    0.0000000    2.0000000    2.0000000 ]  index from opti: A-2 C-1 D-1 D-1 64
index:    65  opti: [  -3.0000000    0.0000000    2.0000000    3.0000000 ]  index from opti: A-2 C-1 D-1 D#-1 65
index:    66  opti: [  -3.0000000    0.0000000    3.0000000    3.0000000 ]  index from opti: A-2 C-1 D#-1 D#-1 66
index:    67  opti: [  -3.0000000    1.0000000    1.0000000    1.0000000 ]  index from opti: A-2 C#-1 C#-1 C#-1 67
index:    68  opti: [  -3.0000000    1.0000000    1.0000000    2.0000000 ]  index from opti: A-2 C#-1 C#-1 D-1 68
index:    69  opti: [  -3.0000000    1.0000000    1.0000000    3.0000000 ]  index from opti: A-2 C#-1 C#-1 D#-1 69
index:    70  opti: [  -3.0000000    1.0000000    2.0000000    2.0000000 ]  index from opti: A-2 C#-1 D-1 D-1 70
index:    71  opti: [  -3.0000000    1.0000000    2.0000000    3.0000000 ]  index from opti: A-2 C#-1 D-1 D#-1 71
index:    72  opti: [  -3.0000000    2.0000000    2.0000000    2.0000000 ]  index from opti: A-2 D-1 D-1 D-1 72
index:    73  opti: [  -2.0000000   -2.0000000   -2.0000000    6.0000000 ]  index from opti: A#-2 A#-2 A#-2 F#-1 73
index:    74  opti: [  -2.0000000   -2.0000000   -2.0000000    7.0000000 ]  index from opti: A#-2 A#-2 A#-2 G-1 74
index:    75  opti: [  -2.0000000   -2.0000000   -2.0000000    8.0000000 ]  index from opti: A#-2 A#-2 A#-2 G#-1 75
index:    76  opti: [  -2.0000000   -2.0000000   -2.0000000    9.0000000 ]  index from opti: A#-2 A#-2 A#-2 A-1 76
index:    77  opti: [  -2.0000000   -2.0000000   -1.0000000    5.0000000 ]  index from opti: A#-2 A#-2 B-2 F-1 77
index:    78  opti: [  -2.0000000   -2.0000000   -1.0000000    6.0000000 ]  index from opti: A#-2 A#-2 B-2 F#-1 78
index:    79  opti: [  -2.0000000   -2.0000000   -1.0000000    7.0000000 ]  index from opti: A#-2 A#-2 B-2 G-1 79
index:    80  opti: [  -2.0000000   -2.0000000   -1.0000000    8.0000000 ]  index from opti: A#-2 A#-2 B-2 G#-1 80
index:    81  opti: [  -2.0000000   -2.0000000    0.0000000    5.0000000 ]  index from opti: A#-2 A#-2 C-1 F-1 81
index:    82  opti: [  -2.0000000   -2.0000000    0.0000000    6.0000000 ]  index from opti: A#-2 A#-2 C-1 F#-1 82
index:    83  opti: [  -2.0000000   -2.0000000    0.0000000    7.0000000 ]  index from opti: A#-2 A#-2 C-1 G-1 83
index:    84  opti: [  -2.0000000   -2.0000000    2.0000000    2.0000000 ]  index from opti: A#-2 A#-2 D-1 D-1 84
index:    85  opti: [  -2.0000000   -2.0000000    2.0000000    3.0000000 ]  index from opti: A#-2 A#-2 D-1 D#-1 85
index:    86  opti: [  -2.0000000   -2.0000000    3.0000000    3.0000000 ]  index from opti: A#-2 A#-2 D#-1 D#-1 86
index:    87  opti: [  -2.0000000   -1.0000000   -1.0000000    5.0000000 ]  index from opti: A#-2 B-2 B-2 F-1 87
index:    88  opti: [  -2.0000000   -1.0000000   -1.0000000    6.0000000 ]  index from opti: A#-2 B-2 B-2 F#-1 88
index:    89  opti: [  -2.0000000   -1.0000000   -1.0000000    7.0000000 ]  index from opti: A#-2 B-2 B-2 G-1 89
index:    90  opti: [  -2.0000000   -1.0000000    0.0000000    4.0000000 ]  index from opti: A#-2 B-2 C-1 E-1 90
index:    91  opti: [  -2.0000000   -1.0000000    0.0000000    5.0000000 ]  index from opti: A#-2 B-2 C-1 F-1 91
index:    92  opti: [  -2.0000000   -1.0000000    1.0000000    2.0000000 ]  index from opti: A#-2 B-2 C#-1 D-1 92
index:    93  opti: [  -2.0000000   -1.0000000    2.0000000    2.0000000 ]  index from opti: A#-2 B-2 D-1 D-1 93
index:    94  opti: [  -2.0000000   -1.0000000    2.0000000    3.0000000 ]  index from opti: A#-2 B-2 D-1 D#-1 94
index:    95  opti: [  -2.0000000    0.0000000    0.0000000    2.0000000 ]  index from opti: A#-2 C-1 C-1 D-1 95
index:    96  opti: [  -2.0000000    0.0000000    1.0000000    1.0000000 ]  index from opti: A#-2 C-1 C#-1 C#-1 96
index:    97  opti: [  -2.0000000    0.0000000    1.0000000    2.0000000 ]  index from opti: A#-2 C-1 C#-1 D-1 97
index:    98  opti: [  -2.0000000    0.0000000    2.0000000    2.0000000 ]  index from opti: A#-2 C-1 D-1 D-1 98
index:    99  opti: [  -2.0000000    1.0000000    1.0000000    1.0000000 ]  index from opti: A#-2 C#-1 C#-1 C#-1 99
index:   100  opti: [  -2.0000000    1.0000000    1.0000000    2.0000000 ]  index from opti: A#-2 C#-1 C#-1 D-1 100
index:   101  opti: [  -2.0000000    1.0000000    2.0000000    2.0000000 ]  index from opti: A#-2 C#-1 D-1 D-1 101
index:   102  opti: [  -1.0000000   -1.0000000   -1.0000000    4.0000000 ]  index from opti: B-2 B-2 B-2 E-1 102
index:   103  opti: [  -1.0000000   -1.0000000   -1.0000000    5.0000000 ]  index from opti: B-2 B-2 B-2 F-1 103
index:   104  opti: [  -1.0000000   -1.0000000    1.0000000    1.0000000 ]  index from opti: B-2 B-2 C#-1 C#-1 104
index:   105  opti: [  -1.0000000   -1.0000000    1.0000000    2.0000000 ]  index from opti: B-2 B-2 C#-1 D-1 105
index:   106  opti: [  -1.0000000   -1.0000000    2.0000000    2.0000000 ]  index from opti: B-2 B-2 D-1 D-1 106
index:   107  opti: [  -1.0000000    0.0000000    0.0000000    1.0000000 ]  index from opti: B-2 C-1 C-1 C#-1 107
index:   108  opti: [  -1.0000000    0.0000000    1.0000000    1.0000000 ]  index from opti: B-2 C-1 C#-1 C#-1 108
index:   109  opti: [  -1.0000000    1.0000000    1.0000000    1.0000000 ]  index from opti: B-2 C#-1 C#-1 C#-1 109
index:   110  opti: [   0.0000000    0.0000000    0.0000000    0.0000000 ]  index from opti: C-1 C-1 C-1 C-1 110
index:   111  opti: [   0.0000000    0.0000000    1.0000000    1.0000000 ]  index from opti: C-1 C-1 C#-1 C#-1 111

Major P:         42.0000
Dominant P:       9.0000
A:    0.0000
gogins commented 4 years ago

In C++:

ChordSpaceGroup.voices:        4
ChordSpaceGroup.range :       60.0000
ChordSpaceGroup.g     :        1.0000
ChordSpaceGroup.countP:       79
ChordSpaceGroup.countI:        2
ChordSpaceGroup.countT:       12
ChordSpaceGroup.countV:     1295
index:     0  optti:   -5.0000000   -1.0000000    3.0000000    3.0000000  index from optti:     0  
index:     1  optti:   -4.0000000   -2.0000000    2.0000000    4.0000000  index from optti:     1  A#7b5
index:     2  optti:   -4.0000000   -2.0000000    3.0000000    3.0000000  index from optti:     2  
index:     3  optti:   -4.0000000   -1.0000000    1.0000000    4.0000000  index from optti:     3  C#m7
index:     4  optti:   -4.0000000   -1.0000000    2.0000000    3.0000000  index from optti:     4  
index:     5  optti:   -4.0000000   -1.0000000    2.0000000    4.0000000  index from optti:     5  E7
index:     6  optti:   -4.0000000   -1.0000000    2.0000000    5.0000000  index from optti:     6  Ab 3 semitone
index:     7  optti:   -4.0000000   -1.0000000    3.0000000    3.0000000  index from optti:     7  
index:     8  optti:   -4.0000000   -1.0000000    3.0000000    4.0000000  index from optti:     8  EM7
index:     9  optti:   -4.0000000    0.0000000    0.0000000    4.0000000  index from optti:     9  
index:    10  optti:   -4.0000000    0.0000000    1.0000000    3.0000000  index from optti:    10  
index:    11  optti:   -4.0000000    0.0000000    1.0000000    4.0000000  index from optti:    11  C#m#7
index:    12  optti:   -4.0000000    0.0000000    2.0000000    2.0000000  index from optti:    12  
index:    13  optti:   -4.0000000    0.0000000    2.0000000    3.0000000  index from optti:    13  
index:    14  optti:   -4.0000000    0.0000000    2.0000000    4.0000000  index from optti:    14  E7#5
index:    15  optti:   -4.0000000    0.0000000    3.0000000    3.0000000  index from optti:    15  
index:    16  optti:   -4.0000000    1.0000000    1.0000000    2.0000000  index from optti:    16  
index:    17  optti:   -4.0000000    1.0000000    1.0000000    3.0000000  index from optti:    17  
index:    18  optti:   -4.0000000    1.0000000    2.0000000    2.0000000  index from optti:    18  
index:    19  optti:   -4.0000000    1.0000000    2.0000000    3.0000000  index from optti:    19  
index:    20  optti:   -4.0000000    2.0000000    2.0000000    2.0000000  index from optti:    20  
index:    21  optti:   -3.0000000   -3.0000000    3.0000000    3.0000000  index from optti:    21  
index:    22  optti:   -3.0000000   -2.0000000    1.0000000    5.0000000  index from optti:    22  A#m#7
index:    23  optti:   -3.0000000   -2.0000000    2.0000000    3.0000000  index from optti:    23  
index:    24  optti:   -3.0000000   -2.0000000    2.0000000    4.0000000  index from optti:    24  
index:    25  optti:   -3.0000000   -2.0000000    2.0000000    5.0000000  index from optti:    25  A#M7
index:    26  optti:   -3.0000000   -2.0000000    3.0000000    3.0000000  index from optti:    26  
index:    27  optti:   -3.0000000   -2.0000000    3.0000000    4.0000000  index from optti:    27  
index:    28  optti:   -3.0000000   -1.0000000    1.0000000    3.0000000  index from optti:    28  
index:    29  optti:   -3.0000000   -1.0000000    1.0000000    4.0000000  index from optti:    29  E6sus
index:    30  optti:   -3.0000000   -1.0000000    1.0000000    5.0000000  index from optti:    30  C#7#5
index:    31  optti:   -3.0000000   -1.0000000    2.0000000    2.0000000  index from optti:    31  
index:    32  optti:   -3.0000000   -1.0000000    2.0000000    3.0000000  index from optti:    32  
index:    33  optti:   -3.0000000   -1.0000000    2.0000000    4.0000000  index from optti:    33  E7sus
index:    34  optti:   -3.0000000   -1.0000000    3.0000000    3.0000000  index from optti:    34  
index:    35  optti:   -3.0000000    0.0000000    0.0000000    3.0000000  index from optti:    35  
index:    36  optti:   -3.0000000    0.0000000    0.0000000    4.0000000  index from optti:    36  
index:    37  optti:   -3.0000000    0.0000000    1.0000000    2.0000000  index from optti:    37  
index:    38  optti:   -3.0000000    0.0000000    1.0000000    3.0000000  index from optti:    38  
index:    39  optti:   -3.0000000    0.0000000    1.0000000    4.0000000  index from optti:    39  
index:    40  optti:   -3.0000000    0.0000000    2.0000000    2.0000000  index from optti:    40  
index:    41  optti:   -3.0000000    0.0000000    2.0000000    3.0000000  index from optti:    41  
index:    42  optti:   -3.0000000    0.0000000    3.0000000    3.0000000  index from optti:    42  
index:    43  optti:   -3.0000000    1.0000000    1.0000000    1.0000000  index from optti:    43  
index:    44  optti:   -3.0000000    1.0000000    1.0000000    2.0000000  index from optti:    44  
index:    45  optti:   -3.0000000    1.0000000    1.0000000    3.0000000  index from optti:    45  
index:    46  optti:   -3.0000000    1.0000000    2.0000000    2.0000000  index from optti:    46  
index:    47  optti:   -3.0000000    1.0000000    2.0000000    3.0000000  index from optti:    47  
index:    48  optti:   -3.0000000    2.0000000    2.0000000    2.0000000  index from optti:    48  
index:    49  optti:   -2.0000000   -2.0000000    0.0000000    5.0000000  index from optti:    49  
index:    50  optti:   -2.0000000   -2.0000000    2.0000000    2.0000000  index from optti:    50  
index:    51  optti:   -2.0000000   -2.0000000    2.0000000    3.0000000  index from optti:    51  
index:    52  optti:   -2.0000000   -2.0000000    3.0000000    3.0000000  index from optti:    52  
index:    53  optti:   -2.0000000   -1.0000000    0.0000000    5.0000000  index from optti:    53  
index:    54  optti:   -2.0000000   -1.0000000    1.0000000    2.0000000  index from optti:    54  
index:    55  optti:   -2.0000000   -1.0000000    1.0000000    3.0000000  index from optti:    55  
index:    56  optti:   -2.0000000   -1.0000000    2.0000000    2.0000000  index from optti:    56  
index:    57  optti:   -2.0000000   -1.0000000    2.0000000    3.0000000  index from optti:    57  
index:    58  optti:   -2.0000000    0.0000000    0.0000000    2.0000000  index from optti:    58  
index:    59  optti:   -2.0000000    0.0000000    0.0000000    3.0000000  index from optti:    59  
index:    60  optti:   -2.0000000    0.0000000    1.0000000    1.0000000  index from optti:    60  
index:    61  optti:   -2.0000000    0.0000000    1.0000000    2.0000000  index from optti:    61  
index:    62  optti:   -2.0000000    0.0000000    1.0000000    3.0000000  index from optti:    62  
index:    63  optti:   -2.0000000    0.0000000    2.0000000    2.0000000  index from optti:    63  
index:    64  optti:   -2.0000000    1.0000000    1.0000000    1.0000000  index from optti:    64  
index:    65  optti:   -2.0000000    1.0000000    1.0000000    2.0000000  index from optti:    65  
index:    66  optti:   -2.0000000    1.0000000    2.0000000    2.0000000  index from optti:    66  
index:    67  optti:   -1.0000000   -1.0000000   -1.0000000    5.0000000  index from optti:    67  
index:    68  optti:   -1.0000000   -1.0000000    1.0000000    1.0000000  index from optti:    68  
index:    69  optti:   -1.0000000   -1.0000000    1.0000000    2.0000000  index from optti:    69  
index:    70  optti:   -1.0000000   -1.0000000    2.0000000    2.0000000  index from optti:    70  
index:    71  optti:   -1.0000000    0.0000000    0.0000000    1.0000000  index from optti:    71  
index:    72  optti:   -1.0000000    0.0000000    0.0000000    2.0000000  index from optti:    72  
index:    73  optti:   -1.0000000    0.0000000    1.0000000    1.0000000  index from optti:    73  
index:    74  optti:   -1.0000000    0.0000000    1.0000000    2.0000000  index from optti:    74  
index:    75  optti:   -1.0000000    1.0000000    1.0000000    1.0000000  index from optti:    75  
index:    76  optti:    0.0000000    0.0000000    0.0000000    0.0000000  index from optti:    76  
index:    77  optti:    0.0000000    0.0000000    0.0000000    1.0000000  index from optti:    77  
index:    78  optti:    0.0000000    0.0000000    1.0000000    1.0000000  index from optti:    78  
gogins commented 4 years ago

I fixed some questionable logic in both JavaScript and C++, but the number of OPTTIs still differs between them. This could be tedious to debug. I believe the JavaScript code is currently more correct, but only because the test piece sounds more like I think it should. I need to compare iseOPTTI in both quite closely.

gogins commented 4 years ago
 Chord.prototype.iseRPTTI = function(range) {
        if (this.iseRPTT(range) === false) {
            return false;
        }
        var inverse = this.I();
        var normalRPTT = inverse.eRPTT(range);
        if (this.le_epsilon(normalRPTT) === true) {
            return true;
        }
        return false;
    };

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_RPTgI>(const Chord &chord, double range, double g) {
    if (isNormal<EQUIVALENCE_RELATION_RPTg>(chord, range, g) == false) {
        return false;
    }
    Chord inverse = chord.I();
    Chord normalRPTg = normalize<EQUIVALENCE_RELATION_RPTg>(inverse, range, g);
    if (chord <= normalRPTg) {
        return true;
    }
    return false;
}
gogins commented 4 years ago
Chord.prototype.iseRPTT = function(range) {
    if (this.iseR(range) === false) {
        return false;
    }
    if (this.iseP() === false) {
        return false;
    }
    if (this.iseTT() === false) {
        return false;
    }
    if (this.iseV() === false) {
        return false;
    }
    return true;
};

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_RPTg>(const Chord &chord, double range, double g) {
    if (!isNormal<EQUIVALENCE_RELATION_R>(chord, range, g)) {
        return false;
    }
    if (!isNormal<EQUIVALENCE_RELATION_P>(chord, range, g)) {
        return false;
    }
    if (!isNormal<EQUIVALENCE_RELATION_Tg>(chord, range, g)) {
        return false;
    }
    // TODO: Should this be here?
    if (!isNormal<EQUIVALENCE_RELATION_V>(chord, range, g)) {
        return false;
    }
    return true;
}
gogins commented 4 years ago

Finally a real difference! But I'm not sure if it is significant, or if so which is correct. I'll change the JavaScript to match. No obvious change in behavior.

Chord.prototype.iseR = function(range) {
    var max_ = this.max()[0];
    var min_ = this.min()[0];
    if (ChordSpace.le_epsilon(max_, (min_ + range)) === false) {
        return false;
    }
    var layer_ = this.layer();
    if (ChordSpace.le_epsilon(0, layer_) === false) {
        return false;
    }
    if (ChordSpace.le_epsilon(layer_, range) === false) {
        return false;
    }
    return true;
};

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_R>(const Chord &chord, double range, double g) {
    double max = chord.max()[0];
    double min = chord.min()[0];
    if (le_epsilon(max, (min + range)) == false) {
        return false;
    }
    double layer = chord.layer();
    if (le_epsilon(0.0, layer) == false) {
        return false;
    }
    if (lt_epsilon(layer, range) == false) {
        return false;
    }
    return true;
}
gogins commented 4 years ago

Another difference, again not sure, again change JavaScript. No obvious change in behavior.

Chord.prototype.iseP = function() {
    for (var voice = 1; voice < this.size(); voice++) {
        if (ChordSpace.le_epsilon(this.voices[voice - 1], this.voices[voice]) === false) {
            return false;
        }
    }
    return true;
};

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_P>(const Chord &chord, double range, double g) {
    for (size_t voice = 1; voice < chord.voices(); voice++) {
        if (gt_epsilon(chord.getPitch(voice - 1), chord.getPitch(voice))) {
            return false;
        }
    }
    return true;
}
gogins commented 4 years ago
Chord.prototype.iseTT = function(g) {
    g = typeof g !== 'undefined' ? g : 1;
    var ep = this.eP();
    if (ep.eq_epsilon(ep.eTT(g)) === false) {
        return false;
    }
    return true;
};

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_Tg>(const Chord &chord, double range, double g) {
    Chord normalP = normalize<EQUIVALENCE_RELATION_P>(chord, range, g);
    Chord normalPTg = normalize<EQUIVALENCE_RELATION_Tg>(normalP, range, g);
    if (normalP == normalPTg) {
        return true;
    } else {
        return false;
    }
}
gogins commented 4 years ago
Chord.prototype.eTT = function(g) {
    g = typeof g !== 'undefined' ? g : 1;
    var normal = this.eT();
    var indg = Math.ceil(normal.voices[0] / g);
    var transposition = (indg * g) - normal.voices[0];
    normal = normal.T(transposition);
    return normal;
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_Tg>(const Chord &chord, double range, double g) {
    Chord normal = normalize<EQUIVALENCE_RELATION_T>(chord, range, g);
    // Make it work for any g not just 1: double transposition = std::ceil(normal.getPitch(0)) - normal.getPitch(0);
    double ng = std::ceil(normal.getPitch(0) / g);
    double transposition = (ng * g) - normal.getPitch(0);
    normal = normal.T(transposition);
    return normal;
}
gogins commented 4 years ago
Chord.prototype.eT = function() {
    var layer_ = this.layer();
    var sumPerVoice = layer_ / this.size();
    return this.T(-sumPerVoice);
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_T>(const Chord &chord, double range, double g) {
    Chord normal = chord;
    double layer_ = normal.layer();
    double sumPerVoice = layer_ / double(normal.voices());
    normal = normal.T(-sumPerVoice);
    return normal;
}
gogins commented 4 years ago

These differ, but I think both are correct.

Chord.prototype.eP = function() {
    clone_ = this.clone();
    clone_.voices.sort(ChordSpace.compare_epsilon);
    return clone_;
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_P>(const Chord &chord, double range, double g) {
    Chord normal = chord;
    bool sorted = false;
    while (!sorted) {
        sorted = true;
        for (int voice = 1; voice < normal.voices(); voice++) {
            if (gt_epsilon(normal.getPitch(voice - 1), normal.getPitch(voice))) {
                sorted = false;
                normal.row(voice - 1).swap(normal.row(voice));
            }
        }
    }
    return normal;
}
gogins commented 4 years ago

Again a small difference, again not sure if makes a difference, again changing JavaScript to match C++. This change made a significant different, the number of P is now 79 just as in C++. Which is correct?

Chord.prototype.iseV = function(range) {
    range = typeof range !== 'undefined' ? range : ChordSpace.OCTAVE;
    var outer = this.voices[0] + range - this.voices[this.size() - 1];
    var inner;
    var voice;
    for (voice = 0; voice < this.size() - 2; voice++) {
        inner = this.voices[voice + 1] - this.voices[voice];
        if (ChordSpace.ge_epsilon(outer, inner) === false) {
            return false;
        }
    }
    return true;
};

template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_V>(const Chord &chord, double range, double g) {
    double outer = chord.getPitch(0) + range - chord.getPitch(chord.voices() - 1);
    bool isNormal = true;
    for (size_t voice = 0; voice < chord.voices() - 1; voice++) {
        double inner = chord.getPitch(voice + 1) - chord.getPitch(voice);
        if (!(ge_epsilon(outer, inner))) {
            isNormal = false;
        }
    }
    return isNormal;
}
gogins commented 4 years ago

Neither was correct. I reviewed my emails with Dmitri Tymoczko, and also "Generalized Voice-Leading Spaces," and corrected my code for what I call "voicing equivalence," which I think is really about which simplex of the "cyclical region" OPT is in, for both the JavaScript and the C++. I will have to run my unit tests again.

gogins commented 4 years ago

Chord.eV is blowing up in C++:

Chord.prototype.eV = function(range) {
    range = typeof range !== 'undefined' ? range : ChordSpace.OCTAVE;
    var permutations = this.permutations();
    for (var i = 0; i < this.size(); i++) {
        var permutation = permutations[i];
        if (permutation.iseV(range)) {
            return permutation;
        }
    }
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_V>(const Chord &chord, double range, double g) {
    const std::vector<Chord> permutations = chord.permutations();
    for (size_t i = 0; i < permutations.size(); i++) {
        const Chord &permutation = permutations[i];
        if (isNormal<EQUIVALENCE_RELATION_V>(permutation, range, g)) {
            return permutation;
        }
    }
    throw "Shouldn't come here.";
}
gogins commented 4 years ago
template<> inline SILENCE_PUBLIC bool isNormal<EQUIVALENCE_RELATION_V>(const Chord &chord, double range, double g) {
    double outer_interval = chord.getPitch(0) + range - chord.getPitch(chord.voices() - 1);
    for (size_t voice = 0; voice < chord.voices() - 2; voice++) {
        double inner_interval = chord.getPitch(voice + 1) - chord.getPitch(voice);
        if (le_epsilon(outer_interval, inner_interval) == false) {
            return false;
        }
    }
    return true;
}

Chord.prototype.iseV = function(range) {
    range = typeof range !== 'undefined' ? range : ChordSpace.OCTAVE;
    var outer_interval = this.voices[0] + range - this.voices[this.size() - 1];
    var inner_interval;
    for (let voice = 0; voice < this.size() - 2; voice++) {
        inner_interval = this.voices[voice + 1] - this.voices[voice];
        if (ChordSpace.le_epsilon(outer_interval, inner_interval) === false) {
            return false;
        }
    }
    return true;
};
gogins commented 4 years ago

Still a difference in ChordSpace.countP!

gogins commented 4 years ago
Chord.prototype.eRPTTI = function(range) {
    var normalRPTT = this.eRPTT(range);
    var inverse = normalRPTT.I();
    var inverseNormalRPTT = inverse.eRPTT(range);
    if (normalRPTT.le_epsilon(inverseNormalRPTT) === true) {
        return normalRPTT;
    }
    return inverseNormalRPTT;
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_RPTgI>(const Chord &chord, double range, double g) {
    Chord normalRPTg = normalize<EQUIVALENCE_RELATION_RPTg>(chord, range, g);
    Chord inverse = normalRPTg.I();
    Chord inverseNormalRPTg = normalize<EQUIVALENCE_RELATION_RPTg>(inverse, range, g);
    if (normalRPTg <= inverseNormalRPTg) {
        return normalRPTg;
    }
    return inverseNormalRPTg;
}
gogins commented 4 years ago

Code is different. I will change the C++ to be like the JavaScript. Makes no obvious difference.

Chord.prototype.eRPTT = function(range) {
    var erp = this.eRP(range);
    var voicings_ = erp.voicings();
    for (var i = 0; i < voicings_.length; i++) {
        var voicing = voicings_[i].eTT();
        if (voicing.iseV()) {
            return voicing;
        }
    }
    console.info('ERROR: chord.eRPTT() should not come here: ' + this);
};

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_RPTg>(const Chord &chord, double range, double g) {
    Chord normalRP = normalize<EQUIVALENCE_RELATION_RP>(chord, range, g);
    std::vector<Chord> voicings_ = normalRP.voicings();
    for (size_t voice = 0; voice < normalRP.voices(); voice++) {
        const Chord &voicing = voicings_[voice];
        Chord normalTg = normalize<EQUIVALENCE_RELATION_Tg>(voicing, range, g);
        if (isNormal<EQUIVALENCE_RELATION_V>(normalTg, range, g) == true) {
            return normalTg;
        }
    }
    throw "Shouldn't come here.";
}
gogins commented 4 years ago
template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_RP>(const Chord &chord, double range, double g) {
    Chord normal = normalize<EQUIVALENCE_RELATION_R>(chord, range, g);
    normal = normalize<EQUIVALENCE_RELATION_P>(normal, range, g);
    return normal;
}

Chord.prototype.eRP = function(range) {
    return this.eR(range).eP();
};
gogins commented 4 years ago
template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_R>(const Chord &chord, double range, double g) {
    Chord normal = normalize<EQUIVALENCE_RELATION_r>(chord, range, g);
    //Chord normal = chord;
    //while (le_epsilon(0.0, normal.layer()) == false) {
    //  std::vector<double> minimum = normal.min();
    //  normal.setPitch(minimum[1], minimum[0] + range);
    //}
    while (lt_epsilon(normal.layer(), range) == false) {
        std::vector<double> maximum = normal.max();
        normal.setPitch(maximum[1], maximum[0] - range);
    }
    return normal;
}

Chord.prototype.eR = function(range) {
    // The clue here is that at least one voice must be >= 0,
    // but no voice can be > range.
    // First, move all pitches inside the interval [0,  range),
    // which is not the same as the fundamental domain.
    var normal = this.er(range);
    // Then, reflect voices that are outside of the fundamental domain
    // back into it, which will revoice the chord, i.e.
    // the sum of pitches will then be in [0,  range].
    while (ChordSpace.lt_epsilon(normal.layer(), range) === false) {
        var max_ = normal.max();
        var maximumPitch = max_[0];
        var maximumVoice = max_[1];
        // Because no voice is above the range,
        // any voices that need to be revoiced will now be negative.
        normal.voices[maximumVoice] = maximumPitch - range;
    }
    return normal;
};
gogins commented 4 years ago
template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_r>(const Chord &chord, double range, double g) {
    Chord normal = chord;
    for (int voice = 0; voice < chord.voices(); ++voice) {
        double pitch = chord.getPitch(voice);
        pitch = modulo(pitch, range);
        normal.setPitch(voice, pitch);
    }
    return normal;
}

inline SILENCE_PUBLIC double modulo(double dividend, double divisor) {
    double quotient = 0.0;
    if (divisor < 0.0) {
        quotient = std::ceil(dividend / divisor);
    }
    if (divisor > 0.0) {
        quotient = std::floor(dividend / divisor);
    }
    double remainder = dividend - (quotient * divisor);
    return remainder;
}

Chord.prototype.er = function(range) {
    var chord = this.clone();
    for (var voice = 0; voice < this.size(); voice++) {
        chord.voices[voice] = ChordSpace.modulo(chord.voices[voice], range);
    }
    return chord;
};

ChordSpace.modulo = function(dividend, divisor) {
    var quotient = 0.0;
    if (divisor < 0.0) {
        quotient = Math.ceil(dividend / divisor);
    }
    if (divisor > 0.0) {
        quotient = Math.floor(dividend / divisor);
    }
    var remainder = dividend - (quotient * divisor);
    return remainder;
};
gogins commented 4 years ago
virtual double layer() const {
    double sum = 0.0;
    for (size_t voice = 0; voice < voices(); ++voice) {
        sum += getPitch(voice);
    }
    return sum;
}

Chord.prototype.layer = function() {
    var s = 0;
    for (var voice = 0; voice < this.size(); voice++) {
        s = s + this.voices[voice];
    }
    return s;
};
gogins commented 4 years ago

Chord.prototype.voicings = function() {
    var chord = this.clone();
    var voicings = [];
    voicings.push(chord);
    for (var i = 1; i < chord.size(); i++) {
        chord = chord.v();
        voicings.push(chord);
    }
    return voicings;
};

    Chord.prototype.v = function(direction) {
        direction = typeof direction !== 'undefined' ? direction : 1;
        var chord = this.clone();
        while (direction > 0) {
            chord.voices[0] = chord.voices[0] + ChordSpace.OCTAVE;
            chord = chord.cycle(1);
            direction = direction - 1;
        }
        var n = chord.size() - 1;
        while (direction < 0) {
            chord.voices[n] = chord.voices[n] - ChordSpace.OCTAVE;
            chord = chord.cycle(-1);
            direction = direction + 1;
        }
        return chord;
    };

    virtual std::vector<Chord> voicings() const {
        Chord chord = *this;
        std::vector<Chord> voicings;
        voicings.push_back(chord);
        for (size_t voicing = 1; voicing < voices(); voicing++) {
            chord = chord.v();
            voicings.push_back(chord);
        }
        return voicings;
    }

    virtual Chord v(int direction = 1) const {
        Chord chord = *this;
        int head = voices() - 1;
        while (direction > 0) {
            chord = chord.cycle(1);
            chord.setPitch(head, chord.getPitch(head) + OCTAVE());
            direction--;
        }
        while (direction < 0) {
            chord = chord.cycle(-1);
            chord.setPitch(0, chord.getPitch(0) + OCTAVE());
            direction++;
        }
        return chord;
    }
gogins commented 4 years ago
template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_Tg>(const Chord &chord, double range, double g) {
    Chord normal = normalize<EQUIVALENCE_RELATION_T>(chord, range, g);
    // Make it work for any g not just 1: double transposition = std::ceil(normal.getPitch(0)) - normal.getPitch(0);
    double ng = std::ceil(normal.getPitch(0) / g);
    double transposition = (ng * g) - normal.getPitch(0);
    normal = normal.T(transposition);
    return normal;
}

    Chord.prototype.eTT = function(g) {
        g = typeof g !== 'undefined' ? g : 1;
        var normal = this.eT();
        var indg = Math.ceil(normal.voices[0] / g);
        var transposition = (indg * g) - normal.voices[0];
        normal = normal.T(transposition);
        return normal;
    };
gogins commented 4 years ago
    Chord.prototype.eT = function() {
        var layer_ = this.layer();
        var sumPerVoice = layer_ / this.size();
        return this.T(-sumPerVoice);
    };

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_T>(const Chord &chord, double range, double g) {
    Chord normal = chord;
    double layer_ = normal.layer();
    double sumPerVoice = layer_ / double(normal.voices());
    normal = normal.T(-sumPerVoice);
    return normal;
}
gogins commented 4 years ago

I found where the problem is happening. The C++ code comes up with different indexes for chords. It misses some. The actual number of chords in the map of chords to indexes is the same, but some of the indexes are skipped.

    virtual void initialize(int N_, double range_, double g_ = 1.0) {
        System::inform("ChordSpaceGroup.initialize...\n");
        preinitialize(N_, range_, g_);
        std::set<Chord> representative_opttis = fundamentalDomainByNormalize<EQUIVALENCE_RELATION_RPTgI>(N, OCTAVE(), g);
        System::inform("ChordSpaceGroup.initialize: representative_opttis: %6d\n", representative_opttis.size());
        std::set<Chord> equivalent_opttis = fundamentalDomainByIsNormal<EQUIVALENCE_RELATION_RPTgI>(N, OCTAVE(), g);
        System::inform("ChordSpaceGroup.initialize: equivalent_opttis:     %6d\n", equivalent_opttis.size());
        for (auto it = representative_opttis.begin(); it != representative_opttis.end(); ++it) {
            opttisForIndexes.push_back(*it);
        }
        countP = opttisForIndexes.size();
        for (auto equivalent_it = equivalent_opttis.begin(); equivalent_it != equivalent_opttis.end(); ++equivalent_it) {
            const Chord &representative = equivalent_it->eOPTTI();
            auto representative_it = std::find(opttisForIndexes.begin(), opttisForIndexes.end(), representative);
            if (representative_it == opttisForIndexes.end()) {
                System::error("ChordSpaceGroup::initialize: error: representative OPTTI missing: %s\n", representative.information().c_str());
            } else {
                auto index = std::distance(opttisForIndexes.begin(), representative_it);
                indexesForOpttis[*equivalent_it] = index;
            }
        }
        System::inform("ChordSpaceGroup.initialize: indexesForOpttis:      %6d\n", indexesForOpttis.size());
        System::inform("ChordSpaceGroup.initialize.\n");
    }

    ChordSpaceGroup.prototype.initialize = function(voices, range, instruments, dynamics, durations, g) {
        var began = performance.now();
        console.info("ChordSpaceGroup.prototype.initialize...");
        this.voices = typeof voices !== 'undefined' ? voices : 3;
        this.range = typeof range !== 'undefined' ? range : 60;
        this.instruments = typeof instruments !== 'undefined' ? instruments : [1];
        this.g = typeof g !== 'undefined' ? g : 1;
        if (typeof dynamics !== 'undefined') {
            this.dynamics = dynamics;
        } else {
            this.dynamics = [0, 1, 2, 3];
        };
        if (typeof durations !== 'undefined') {
            this.durations = durations;
        } else {
            // These are sixteenth notes (subdivisions of the nominal beat).
            this.durations = [0, 1/4, 2/4, 3/4, 1];
        };
        this.countP = 0;
        this.countI = 2;
        this.countT = ChordSpace.OCTAVE / this.g;
        var chord = new ChordSpace.Chord();
        chord.resize(voices);
        this.countV = ChordSpace.octavewiseRevoicings(chord, this.range);
        this.countA = Math.pow(this.instruments.length, this.voices);
        this.countL = Math.pow(this.dynamics.length, this.voices);
        this.countD = Math.pow(this.durations.length, this.voices);
        var result = ChordSpace.allOfEquivalenceClass(voices, 'OPTTI');
        this.optisForIndexes = result[0];
        this.indexesForOptis = result[1];
        this.countP = this.optisForIndexes.length;
        var ended = performance.now();
        var elapsed = (ended - began) / 1000.;
        console.info("ChordSpaceGroup.prototype.initialize: " + elapsed);
    };

    ChordSpace.allOfEquivalenceClass = function(voices, equivalence, g) {
        g = typeof g !== 'undefined' ? g : 1;
        var is_equivalent = null;
        var make_equivalent = null;
        if (equivalence == 'OP') {
            is_equivalent = Chord.prototype.iseOP;
            make_equivalent = Chord.prototype.eOP;
        }
        if (equivalence == 'OPT') {
            is_equivalent = Chord.prototype.iseOPT;
            make_equivalent = Chord.prototype.eOPT;
        }
        if (equivalence == 'OPTT') {
            is_equivalent = Chord.prototype.iseOPTT;
            make_equivalent = Chord.prototype.eOPTT;
        }
        if (equivalence == 'OPI') {
            is_equivalent = Chord.prototype.iseOPI;
            make_equivalent = Chord.prototype.eOPI;
        }
        if (equivalence == 'OPTI') {
            is_equivalent = Chord.prototype.iseOPTI;
            make_equivalent = Chord.prototype.eOPTI;
        }
        if (equivalence == 'OPTTI') {
            is_equivalent = Chord.prototype.iseOPTTI;
            make_equivalent = Chord.prototype.eOPTTI;
        }
        var upperI = 2 * (ChordSpace.OCTAVE + 1)    ;
        var lowerI = - (ChordSpace.OCTAVE + 1);
        var iterator = ChordSpace.iterator(voices, lowerI);
        var origin = iterator.clone();
        // Construct two maps to correctly map back and forth between chords 
        // in the fundamental domain and their indexes, taking singularities 
        // into account:
        // (1) From the index ->  the _representative_ chord in the 
        //     fundamental domain (one-to-one).
        var chords_for_indexes = new Array();
        // (2) From _any_ chord in the fundamental domain -> the index of the 
        //     corresponding _representative_ chord (many-to-one). This must 
        //     use a _value key_.
        var indexes_for_chords = new Map();
        while (ChordSpace.next(iterator, origin, upperI, g) === true) {
             ///if (iterator.iseP() === true) {
                if (is_equivalent.apply(iterator) == true) {
                    var equivalent = iterator.clone();
                    var representative = make_equivalent.apply(equivalent);
                    // If the representative chord is not already in 
                    // chords_for_indexes, add it. In either case, obtain its 
                    // index.
                    var finder = function(element) {
                        if (element.eq_epsilon(representative)) {
                            return true;
                        } else {
                            return false;
                        }
                    };
                    var index = chords_for_indexes.findIndex(finder);
                    if (index == -1) {
                        chords_for_indexes.push(representative);
                        index = chords_for_indexes.length - 1;
                    }
                    // Map each equivalent to the index of the representative 
                    // chord. They key must be a value not a reference.
                    indexes_for_chords.set(equivalent.toString(), index);
                } 
            ///}
        }
        return [chords_for_indexes, indexes_for_chords];
    };
gogins commented 4 years ago

This iterator produces an error, see what happens in both JavaScript and C++: -13.0000000 -13.0000000 -12.0000000 -3.0000000. The JavaScript seems OK.

gogins commented 4 years ago

Didn't look at Chord.I.

    virtual Chord I(double center = 0.0) const {
        Chord inverse = *this;
        for (size_t voice = 0; voice < voices(); voice++) {
            inverse.setPitch(voice, csound::I(getPitch(voice), center));
        }
        return inverse;
    }

inline SILENCE_PUBLIC double I(double pitch, double center = 0.0) {
    return center - pitch;
}

    Chord.prototype.I = function(center) {
        center = typeof center !== 'undefined' ? center : 0;
        var inverse = this.clone();
        for (var voice = 0; voice < this.size(); voice++) {
            inverse.voices[voice] = ChordSpace.I(this.voices[voice], center);
        }
        return inverse;
    };

    ChordSpace.I = function(pitch, center) {
        center = typeof center !== 'undefined' ? center : 0;
        return center - pitch;
    };
gogins commented 4 years ago

Tymoczko's predicate for OPTI (1-based):

gogins commented 4 years ago

There is a much more elegant way of implementing this stuff. And that is to have perfectly correct predicates for each equivalence class, then iterate over all the chords in the space and collect all the equivalents, including any duplicates at the gluings. There would be a map from each chord in the space to each of its equivalents. These collections could become quite large and therefore might take some time to compute. But once these maps were generated, the code would be blindingly fast.

But this would be a ghastly amount of work, so I will try to fix what I have first.

gogins commented 4 years ago

I have found and fixed a real bug. Chord::eV and Chord::iseV were not correct. They wrongly used Chord::permutations but now have been corrected to use Chord::voicings so that Tymoczko's shortcut interval arithmetic, which assumes that voices are in order of increasing pitch (not pitch-class), will work.

gogins commented 4 years ago

The C++ unit tests now fail only for RPTgI (2 out of 64 tests with 3 and 4 voices). RPTI appears to be correct. And I suspect that the JavaScript code is correct. That implies eRPTgI returns a false result in a few cases. Back to this, which now simply looks wrong:

template<> inline SILENCE_PUBLIC Chord normalize<EQUIVALENCE_RELATION_RPTgI>(const Chord &chord, double range, double g) {
    Chord normalRPTg = normalize<EQUIVALENCE_RELATION_RPTg>(chord, range, g);
    Chord inverse = normalRPTg.I();
    Chord inverseNormalRPTg = normalize<EQUIVALENCE_RELATION_RPTg>(inverse, range, g);
    if (normalRPTg <= inverseNormalRPTg) {
        return normalRPTg;
    }
    return inverseNormalRPTg;
}
gogins commented 4 years ago

I used Tymoczko's predicate and analogous logic and all unit tests now pass. But now there are 184 OPTTIs for 7th chords instead of 96. I will change the JavaScript to match and see if the test score changes. I'm not sure I understand the logic of creating the equivalent in this case.

The numbers are wrong in any case. In 12TET there are 19 OPTTI trichords and 83 OPTTI tetrachords. This is from both the Science article.

gogins commented 4 years ago

Some principles:

gogins commented 4 years ago

Transpositional equivalence was not precisely enough defined in the Science article. However, what was intended is to remain within 12TET, so of all the transpositions of a chord within OP space (the sum of pitches is within [0, 12]), take the one with the smallest sum of pitches as the equivalent. I will make my code do this in a clear way. The minor triad (pitch class set (-3, 0, 4)) is OPTI. Looking at its transpositions:

 1  4 8 sums to 13 - outside OP.
 0  3 7 sums to 10 - inside OP.
-1  2 6 sums to  7 - inside OP.
-2  1 5 sums to  4 - inside OP.
-3  0 4 sums to  1 - inside OP.
-4 -1 3 sums to -2 - outside OP.
gogins commented 4 years ago

Oh boy. I got tired of beating my head against the wall and made a spreadsheet with all the details for minor triads. I see my stupid mistake. Summing to 1 (not 0) is good, i.e. OPT and OPTI are definitely in the lowest layer in OP, not literally in layer 0. But there are 3 minor triads in that lowest layer. My predicate only looks at 1 of these. I have to transpose my chord to the lowest layer, and rotate it, and compare each rotation with the original chord. Similarly for N voice chords, there will be 4, 5, etc. and each rotation must be tested. The OPTI triad is actually (-4, 0, 5).

gogins commented 4 years ago

Another false assumption of mine: that I knew how to print the chord type for any chord of the same type. I did not. This also requires having the correct octavewise inversion.

gogins commented 4 years ago

I have renamed this issue to better reflect what it is really about.

gogins commented 4 years ago

Dmitri Tymoczko makes his ChordGeometries software, which appears to do correctly all that I need to do, available as Max "source code." This is binary code actually. I have downloaded this code and would run it to see if I can read the Max patches to find what I'm doing wrong, but there is no Max for Linux. I can try running it in Wine. Max does run in Wine.

gogins commented 4 years ago

I have discovered another one of my many hasty false assumptions. Voicing equivalence is not root position, it is first inversion. I need a way to identify root position in order to derive chord type so I can check my list for OPTI triads against Science. I will try just shifting the test.

gogins commented 4 years ago

OPTI for three voices now reports the exact same chords as the Science article.

gogins commented 4 years ago
gogins commented 4 years ago

OPTTI for 4 voices now gives the same chord types as the Science article and a count of 84 (which is one duplicate more than the correct 83). So my "is" code is now essentially correct. The "e" code for making chords into their representative equivalence classes may need work.

I could use a reliable method of selecting, from duplicate equivalents, the representative equivalent, e.g. from (-6 -6 6 6) (chord type 0 0 0 12) and (-3 -3 -3 9) (chord type 0 0 0 0). Perhaps once I have obtained the chord type, obtain eopcs of the chord type to obtain the real chord type, then that is the representative.

gogins commented 4 years ago

Re-reading Tymoczko's emails, it seems there might possibly be problems with eI and iseI always reflecting in the origin. But I don't see it in my tests, which reproduce his results. I had this all wrong....

Also, he advises checking opposing faces of the orbifolds to remove duplicates, but I'm not sure how to do that, though I am sure I have some of these duplicates.

gogins commented 4 years ago

I will enforce unique OPTTI only in the ChordSpaceGroup class, which is the only place I really need that. I will permit ChordSpace.allOfEquivalenceClass to return collections with some duplicates, but I will write a test to ensure that every eX is iseX.

gogins commented 4 years ago

Here is an example duplicate in OPTTI: (-3 -3 -3 9) and (-6 -6 6 6).

gogins commented 4 years ago

Another big fat hasty oversight. For inversional equivalence here, assuming the definition of OPT from the Science article, is neither reflection in the origin nor reflection in the unison axis as I wrongly assumed, but rather reflection in the inversion flat defined by the axis of even spaced chords (the "center") and the point midway between the kinks in the OPT hyperprism. It may that simply reflecting in the center will produce inversional equivalence. It appears to do that for OPT, anyway.

In a bit more detail, reflecting in the origin does produce the required chord type, but in a chord of N voices, there are N hypercones of the OT polygon, and I moves a chord outside of its original hypercone in all but one of these. And that hypercone, it seems, is not the one used by Tymoczko to define the fundamental domain of OPT. Therefore, reflecting an OPT chord in the origin to obtain OPTI moves the chord out of the fundamental domain of OPT (and so OPTI).

Reflecting in the inversion flat certainly, and in the evens axis I believe, moves the chord only within the OPT fundamental domain.

In my original code, I did not use Tymoczko's predicate for OPT, and the fundamental domain of OPTI was therefore a dart, not half an isoceles hypercone. In that case, reflecting in the origin might have preserved the OPTI dart (though I would have to delve into the nasty details to prove it).

I will try reflecting in the lowest chord on the evens axis, i.e. the center. For trichords in OP, that chord is (-8, 0, 8).

gogins commented 4 years ago

I know that reflecting in the inversion flat is correct, but I'm not clear about how to construct the reflection matrix for that. Try to minimize linear algebra by reflecting any chord in the center of OPTT as follows:

to_origin := - center
chord1 := chord + to_origin
chord1_inverse := origin - chord1
chord_inverse := chord1_inverse - to_origin
chord_inverse_optt := chord_inverse.eOPTT()

Seems to work in a spreadsheet.

gogins commented 4 years ago

Sorry, that only works if the sum is a fraction below 0. If the sum is an integer below zero it ceiling does not work.

Reflection in the center -- ignore the above, it is just what it says: reflect in the center.