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 _fret_string_combos(note): r""" Given a (theoretical) `note`, return all supported fret/string combos, along with flags indicating whether the returned values are usable. This assumes that a note can occur in no more than six locations. To ease testing, entries go from low strings to high strings. >>> _fret_string_combos(('E', 3)) (12, 5, 1, 7, 4, 1, 2, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) """ result_builder = [] for location in frets(note): result_builder.append(location[0]) result_builder.append(STANDARD_STRINGS.index(location[1])) result_builder.append(1) missing_values = 18 - len(result_builder) result_builder.extend([0] * missing_values) return tuple(result_builder)
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 _sample_data(preceding, current_chord, following, recent_notes, current_chord_index, limiting_note): r""" Transform a given chord context into an input-output pair for an ANN. Given a dataset and a chord context, this function yields pairs of input and output values *for each note in the current chord*. `recent_notes` is a dict-like object which, for each (theoretical) note, specifies the most recent fret and string values (bundled in an indexed sequence). `limiting_note`, if any value, is a tuple whose elements are: #. the next (theoretical) limiting note, i.e. the first note that comes after `current_chord` and which imposes strict constraints on where it can be played #. the chord index of that note Chords belonging to the chord context are specified in their physical form. This includes the current chord and successor chords. """ LOG.debug('Current chord: {current}'.format(current=current_chord)) highest_note_chord = _highest_note_chord(current_chord) lowest_note_chord = _lowest_note_chord(current_chord) avg_frets = _avg_frets(preceding) preceding_notes = _pad_physical_notes(_adjacent_notes(preceding, 4), 4) following_notes = _pad_physical_notes(_adjacent_notes(following, 6, fwd=True), 6) LOG.debug('Following notes: {f}'.format(f=following_notes)) following_notes = [fret2note(phys[1], phys[0]) for phys in list(following_notes)] if limiting_note: to_limiting_note = limiting_note[1] - current_chord_index minimal_fret_limiting_note = None for fret in frets(limiting_note[0]): if minimal_fret_limiting_note is None or fret[0] < minimal_fret_limiting_note: minimal_fret_limiting_note = fret[0] minimal_string_limiting_note = STANDARD_STRINGS.index(fret[1]) limiting_note_flag = 1 else: to_limiting_note = 1000 # just use something so big it is irrelevant minimal_fret_limiting_note = 0 minimal_string_limiting_note = 0 limiting_note_flag = 0 if preceding: num_notes_prev_chord = len(preceding[-1]) else: num_notes_prev_chord = 0 if len(preceding) > 1: num_notes_prev_chord_2 = len(preceding[-2]) else: num_notes_prev_chord_2 = 0 if following: num_notes_next_chord = len(following[0]) else: num_notes_next_chord = 0 if len(following) > 1: num_notes_next_chord_2 = len(following[1]) else: num_notes_next_chord_2 = 0 for string_num in sorted(current_chord.keys()): note = fret2note(current_chord[string_num], string_num) recent_note = recent_notes.get(note, (0, 0)) fret_string_combos = _fret_string_combos(note) bits = [0] * SUPPORTED_FRETS bits[current_chord[string_num]] = 1 # fret alone is fine inputs = (# 1: notes in current chord len(current_chord.keys()), # 2-3: current note octave and value int(note[1]), NOTE_SEQUENCE.index(note[0]), # 4-5: highest note in chord's octave and value highest_note_chord[1], NOTE_SEQUENCE.index(highest_note_chord[0]), # 6-7: lowest note in chord's octave and value lowest_note_chord[1], NOTE_SEQUENCE.index(lowest_note_chord[0]), # 8-10: average fret for three most recent chords avg_frets[0], avg_frets[1], avg_frets[2], # 11-12: fret and string for most recent occurrence note recent_note[0], recent_note[1], # 13-20: string and fret for four most recent notes preceding_notes[0][0], preceding_notes[0][1], preceding_notes[1][0], preceding_notes[1][1], preceding_notes[2][0], preceding_notes[2][1], preceding_notes[3][0], preceding_notes[3][1], # 21: number of chords played since last four notes _chords_for_notes(4, preceding, fwd=False), # 22-23: number of notes in previous two chords num_notes_prev_chord, num_notes_prev_chord_2, # 24-29: booleans indicating whether string are in use int(0 in current_chord), int(1 in current_chord), int(2 in current_chord), int(3 in current_chord), int(4 in current_chord), int(5 in current_chord), # 30-47: all string/fret combinations for the current note # **ADDED** each third entry indicates whether combo is real # **ADDED** supports up to 24 real frets, so 6 locations fret_string_combos[0], fret_string_combos[1], fret_string_combos[2], fret_string_combos[3], fret_string_combos[4], fret_string_combos[5], fret_string_combos[6], fret_string_combos[7], fret_string_combos[8], fret_string_combos[9], fret_string_combos[10], fret_string_combos[11], fret_string_combos[12], fret_string_combos[13], fret_string_combos[14], fret_string_combos[15], fret_string_combos[16], fret_string_combos[17], # 48-49: number of notes in next two chords num_notes_next_chord, num_notes_next_chord_2, # 50-61: octave and note for next six notes NOTE_SEQUENCE.index(following_notes[0][0]), following_notes[0][1], NOTE_SEQUENCE.index(following_notes[1][0]), following_notes[1][1], NOTE_SEQUENCE.index(following_notes[2][0]), following_notes[2][1], NOTE_SEQUENCE.index(following_notes[3][0]), following_notes[3][1], NOTE_SEQUENCE.index(following_notes[4][0]), following_notes[4][1], NOTE_SEQUENCE.index(following_notes[5][0]), following_notes[5][1], # 62: number of chords between current note and next six notes _chords_for_notes(6, following, fwd=True), # 63-65: fret/string furthest from body for limiting note minimal_fret_limiting_note, minimal_string_limiting_note, limiting_note_flag, # 66: number of chords until next limiting note to_limiting_note, ) sample = (inputs, tuple(bits)) LOG.debug('Yielding sample: {sample}'.format(sample=sample)) yield sample