def _best_sane_chord(preceding, current, following, recent_notes, chord_index, limiting, net): r""" Given a current theoretical chord, find the best plausible form for it. * `preceding` is a chronological list of preceding chords in physical form. * `current` is the theoretical chord to be instantiated. * `following` is a chronological list of following chords. * `recent_notes` is a mapping from theoretical notes to physical notes; it indicates what the most recent instance of a particular note was. * `chord_index` is the chord number of `current`, e.g. 0 if `current` is the first chord. * `limiting` is the first "limiting" note part of a chord with an index strictly greater than `chord_index`. * `net` is the trained neural net that makes the final decision. """ min_error = None best_config = None for sane in sane_chords(current): # TODO move "transformation" to sane_chords where it belongs transformed_sane = {} for sane_entry in sane: string_index = STANDARD_STRINGS.index(sane_entry[1]) transformed_sane[string_index] = sane_entry[0] LOG.debug('Transformed: {transformed_sane}'.format(**locals())) error = _sane_chord_error(preceding, transformed_sane, following, recent_notes, chord_index, limiting, net) if min_error is None or error < min_error: min_error = error best_config = transformed_sane for elem in best_config: if elem > 24: LOG.warning('Outside of fret range!') return best_config
def test_sane_chords_with_capo(self): notes = set([('G', 5), ('F#', 4)]) expected = set([ frozenset([(24, ('G', 3)), (21, ('A', 2))]), frozenset([(24, ('G', 3)), (2, ('E', 4))]), frozenset([(20, ('B', 3)), (2, ('E', 4))]), frozenset([(20, ('B', 3)), (21, ('A', 2))]), frozenset([(20, ('B', 3)), (16, ('D', 3))]), frozenset([(15, ('E', 4)), (11, ('G', 3))]), frozenset([(15, ('E', 4)), (16, ('D', 3))]) ]) generated = set(notemappings.sane_chords(notes, capo=2)) self.assertEqual(generated, expected)
def test_sane_chords_a3_fifth(self): notes = set([('A', 3), ('E', 4)]) expected = set([ frozenset([(17, ('E', 2)), (19, ('A', 2))]), frozenset([(17, ('E', 2)), (0, ('E', 4))]), frozenset([(17, ('E', 2)), (14, ('D', 3))]), frozenset([(12, ('A', 2)), (14, ('D', 3))]), frozenset([(12, ('A', 2)), (0, ('E', 4))]), frozenset([(12, ('A', 2)), (9, ('G', 3))]), frozenset([(7, ('D', 3)), (9, ('G', 3))]), frozenset([(7, ('D', 3)), (5, ('B', 3))]), frozenset([(7, ('D', 3)), (0, ('E', 4))]), frozenset([(2, ('G', 3)), (5, ('B', 3))]), frozenset([(2, ('G', 3)), (0, ('E', 4))]) ]) generated = set(notemappings.sane_chords(notes)) self.assertEqual(generated, expected)
def annotate(midi_path, net): r""" Given a path to a generated midi, yield the best configuration for each chord. """ preceding = [] recent_notes = {} for (current, following, current_index, limiting) in midi_contexts(midi_path): # sane_chords actually uses note lists instead of canonical chords # this is okay for single-note melodies, though # TODO adjust called code so that this whole hack is not necessary LOG.debug('Following: {f}'.format(f=following)) following_notelists = [] for theor_following in following: theor_notelist = [] for key in theor_following.keys(): theor_notelist.extend([key] * theor_following[key]) following_notelists.append(theor_notelist) LOG.debug('Following notelists: {f}'.format(f=following_notelists)) phys_following = [next(sane_chords(successor)) for successor in following_notelists] LOG.debug('Phys following: {p}'.format(p=phys_following)) phys_transformed = [] for frozen in phys_following: el = next(iter(frozen)) # only one, anyway phys_transformed.append({STANDARD_STRINGS.index(el[1]): el[0]}) LOG.debug('Phys transformed: {p}'.format(p=phys_transformed)) best_config = _best_sane_chord(preceding, current, phys_transformed, recent_notes, current_index, limiting, net) yield best_config if len(preceding) >= CHORDS_BEFORE_CURRENT: del(preceding[0]) preceding.append(best_config) for string_num in best_config: fret = best_config[string_num] recent_notes[fret2note(fret, string_num)] = (string_num, fret)
def test_sane_chords_single_unique_note(self): notes = set([('E', 2)]) # each config is a frozenset, and there is a set of configs expected = set([frozenset([(0, ('E', 2))])]) generated = set(notemappings.sane_chords(notes)) self.assertEqual(generated, expected)