import mido

silent_night = mido.MidiFile('Music/SongCollection/for_CD/SilentNt.MID')

silent_night

silent_night.ticks_per_beat

silent_metas = [m for m in silent_night.tracks[0] if m.is_meta]

silent_metas

"""
Explanation: What are the Chord Messages?
The DGX-505 supports chords, which are present in the recorded tracks. These chords affect the accompaniment, harmony and performance assist, and are also listed in the lyrics screen.
There is a list of the recognised chords in the manual (p.62), and most of them can also be viewed in the chord dictionary function (although some aren't). Also mentioned are the power chord accompaniment, for example C1+5 and C1+8.
There are some that the screen can display that are not listed in the manual. These are weird combinations that are indicated with a star $*$, which seems to indicate some several different unrecognised chords (the accompaniment works with it), for example trying the accompaniment with the keys C-D♭-E♭ or C-D♭-F♯.
There are also what appears to be inversions/hybrid chords/upper structure thing (I'm not a music theory expert), where a lower note is notated separately after a slash, like "C/G", which you can get 
A weird thing to note is that when playing the accompaniment notes/keys are represented as C, D♭, D, E♭, E, F, F♯, G, G♯, A, B♭, B. This means the key of A♭ major is represented as G♯ major and so on. This is not a limitation for the chords data in general, as you can see these A♭ and C♯ chords in the songs.
The chords are present in the supplied MID files on the CD, which gives an opportunity to examine them.
Chords for selected songs
These were already transferred to the DGX-505 flash memory. The following songs' chords looked interesting on the lyrics screen:
America The Beautiful (America.MID)
C A♭7 C/G A7♭9 Dm7 G7 C C♯dim7 G7/D C C♯dim7 G/D G G/B G C C♯dim7 G7 C C♯dim7 G/D G G/D D7 G7 Gaug C Csus4 C G7/D G7 C Dm C7/E C7 F C/G F G7 C Csus4 C G7/D G7 C Dm C7/E C7 F C/E F G7 C F6 Dm C
Turkey In The Straw (Turkey.MID)
C C/B F/A Fm/A♭ C/G G7 C C G7 C C/B F/A Fm/A♭ G7 C F C/E G/B  C C/B F/A Fm/A♭ C/G G7 C G7 C G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C
Silent Night (SilentNt.MID)
C G G♯dim7 Am E7♭9/G♯ Am7/G F♯m7♭5  C/G G7sus4 G7 Csus4 C C G7 C G7/D C7/E F C F C G7 G♯dim7 Am F♯m7♭5 C/G G7 C A♭/C D♭/C C
Examination
Let's open up Silent Night in mido, to see the meta messages.
End of explanation
"""


silent_seq_spec = [m.data for m in silent_metas if m.type == 'sequencer_specific']

for md in silent_seq_spec:
    print(" ".join(format(b, "02X") for b in md))

"""
Explanation: It seems like most of the meta messages are tempo changes, but there are two sequencer specific meta messages of interest.
End of explanation
"""


silent_sysex = [m for m in silent_night.tracks[0] if m.type == 'sysex']

silent_sysex

from commons.messages import controlstate

s = controlstate.MidiControlState()
silent_wrapped = [s.feed(m) for m in silent_sysex]
for w in silent_wrapped:
    print(w)

"""
Explanation: The chord information must not be meta messages, then. Maybe they're system exclusive...
End of explanation
"""


silent_us = silent_wrapped[5:]

len(silent_us)

silent_chords = "C G G♯dim7 Am E7♭9/G♯ Am7/G F♯m7♭5  C/G G7sus4 G7 Csus4 C C G7 C G7/D C7/E F C F C G7 G♯dim7 Am F♯m7♭5 C/G G7 C A♭/C D♭/C C".split()

len(silent_chords)

for w, c in zip(silent_us, silent_chords):
    print(w, c)

"""
Explanation: AHA, so they do set the MIDI master volume to something else... Anyway, there are a whole bunch of unknown messages here.
End of explanation
"""


america = mido.MidiFile('America.MID')

turkey = mido.MidiFile('Turkey.MID')

for x in [america, turkey]:
    print(x, x.ticks_per_beat)
    for m in x.tracks[0]:
        if m.is_meta and m.type != 'set_tempo':
            print(m)

america_wrapped = [s.feed(m) for m in america.tracks[0] if m.type=='sysex']

turkey_wrapped = [s.feed(m) for m in turkey.tracks[0] if m.type == 'sysex']

america_us = [w for w in america_wrapped if w.message.data[:3] == (0x43, 0x7E, 0x02)]
turkey_us = [w for w in turkey_wrapped if w.message.data[:3] == (0x43, 0x7E, 0x02)]

america_chords = "C A♭7 C/G A7♭9 Dm7 G7 C C♯dim7 G7/D C C♯dim7 G/D G G/B G C C♯dim7 G7 C C♯dim7 G/D G G/D D7 G7 Gaug C Csus4 C G7/D G7 C Dm C7/E C7 F C/G F G7 C Csus4 C G7/D G7 C Dm C7/E C7 F C/E F G7 C F6 Dm C".split()
turkey_chords = "C C/B F/A Fm/A♭ C/G G7 C C G7 C C/B F/A Fm/A♭ G7 C F C/E G/B  C C/B F/A Fm/A♭ C/G G7 C G7 C G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C".split()

len(america_us) == len(america_chords) 

len(turkey_us) == len(turkey_chords)

for w, c in zip(america_us, america_chords):
    print(w, c)

for w, c in zip(turkey_us, turkey_chords):
    print(w, c)

def chord_assign(chord_mapping, wraps, chords, rev_mapping):    
    for w, c in zip(wraps, chords):
        code = w.message.data[-4:]
        if c in chord_mapping:
            assert chord_mapping[c] == code
        else:
            chord_mapping[c] = code
        if code in rev_mapping:
            assert rev_mapping[code] == c
        else:
            rev_mapping[code] = c

cmap = {}
rcmap = {}
chord_assign(cmap, silent_us, silent_chords, rcmap)
chord_assign(cmap, america_us, america_chords, rcmap)
chord_assign(cmap, turkey_us, turkey_chords, rcmap)



def hexspace(x):
    return " ".join(format(b, "02X") for b in x)

for chord, code in sorted(cmap.items()):
    print(hexspace(code), chord)

"""
Explanation: Let's try the other two.
End of explanation
"""


for chord, code in sorted(cmap.items(), key=lambda x:(x[1][1], x[1][0], x[1][2], x[1][3])):
    print(hexspace(code), chord)

"""
Explanation: It looks like the codes are of the format A B A B, where A is the root and B is the chord type. If both sets of A and B are the same, it's a straight up chord; if the second is different that means there's some sort of inversion thing happening, and the last ends in 1E.
| note | ♭  | ♮       | ♯  |
|------|----|---------|----|
| C    | ?  |00,31|41|
| D    |22|02     |?   |
| E    |?   |04     |?   |
| F    |?   |05     |06|
| G    |?   |07     |45|
| A    |26|09     |?   |
| B    |?   |0B     |?   |
If I were to hazard a guess at the pattern, I think there's two types going on here.
First, there's the basic sequence:
|code| note |
 |----|------|
 |00| C
 |01| C♯/D♭
 |02| D
 |03| D♯/E♭
 |04| E
 |05| F
 |06| F♯/G♭
 |07| G
 |08| G♯/A♭
 |09| A
 |0A| A♯/B♭
 |0B| B
This is used for the base keys, and also F♯, but also not for the lower note if it is C.
This doesn't distinguish between enharmonic notes, so we also have:
|lo |hi2|3|4|
 |---|----|----|----|
 |1|C♭  | C  | C♯ |
 |2|D♭  | D  | D♯ |
 |3|E♭  | E  | E♯ |
 |4|F♭  | F  | F♯ |
 |5|G♭  | G  | G♯ |
 |6|A♭  | A  | A♯ |
 |7|B♭  | B  | B♯ |
Hmm, how could we test this out.
End of explanation
"""


import mido

import glob
import os.path

midtracks = [(os.path.basename(fn), mido.MidiFile(fn).tracks[0]) for fn in 
             glob.glob(os.path.expanduser('~/Music/SongCollection/for_CD/*.MID'))]

def hexspace(x):
    return " ".join(format(b, '02X') for b in x)

sysex_occurences = {}
for song, track in midtracks:
    song_name = song.partition('.')[0]
    for m in track:
        if m.type == 'sysex':
            sysex_occurences.setdefault(m.data, set()).add(song_name)

def ocprint(ocs, key=None):
    for d in sorted(ocs, key=key):
        print(hexspace(d), ','.join(sorted(ocs[d]))[:75], sep=": ")

seqspec_occurences = {}
for song, track in midtracks:
    song_name = song.partition('.')[0]
    for m in track:
        if m.type == 'sequencer_specific':
            seqspec_occurences.setdefault(m.data, set()).add(song_name)

ocprint(sysex_occurences)

ocprint(seqspec_occurences)

ocprint({k:v for k, v in sysex_occurences.items() if k[-1] == 0x1E}, key=lambda k:k[-2])

ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and k[4] >= 0x1E}, key=lambda k:k[4])

"""
Explanation: 00: Major (C)
01: Sixth (C6)
07: Augmented (Caug)
08: Minor (Cm)
0A: Minor seventh (Cm7)
0B: Minor seventh flatted fifth (Cm7♭5)
12: Diminished seventh (Cdim7)
13: Seventh (C7)
14: Seventh suspended fourth (C7sus4)
19: Seventh flatted ninth (C7♭9)
20: Suspended fourth (Csus4)

This is the same as the PSR-225, except that these are part of SysEx messages. This would imply that you can't have the same with an open octave 1+8, which makes sense.
Return
After Investigationing V: User Songs, I've returned to continue investigating the supplied Songs for chords.
End of explanation
"""


ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and k[4] != k[6] and k[6] != 0x1E}, key=lambda k:k[4])

"""
Explanation: No odd chords.
End of explanation
"""


ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and 
         not((0x20 <= (k[3] & 0xF0) <= 0x40) and ( 0x00 <= (k[3] & 0xF) <= 0x07))},
        key=lambda k:k[3])

ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and 
         not (0x00 <= k[3] <= 0x0B)},
        key=lambda k:k[3])

control_occurences = {}
for song, track in midtracks:
    song_name = song.partition('.')[0]
    for m in track:
        if m.type == 'control_change':
            control_occurences.setdefault(m.control, set()).add(song_name)

control_occurences

from commons.messages import wrappers

def cwrap(c):
    try:
        return wrappers.Control(c)
    except ValueError:
        return c

[cwrap(c) for c in control_occurences]

"""
Explanation: Hmmm, it looks like there are weird things in D_Weasel and D_Mondshin
End of explanation
"""


m_occurences = {}
for song, track in midtracks:
    song_name = song.partition('.')[0]
    for m in track:
        if m.type not in {
            'note_on', 'note_off', 'pitchwheel', 'control_change', 'sysex', 'program_change', 'set_tempo',
            'end_of_track', 'key_signature', 'time_signature', 'copyright', 'sequencer_specific',
        }:
            m_occurences.setdefault(mido.frozen.freeze_message(m.copy(time=0)), set()).add(song_name)

m_occurences

"""
Explanation: We have controls 99, 98, 94 not recognised.
End of explanation
"""


