def limiting_notes(block): r""" Given a block, generate all the limiting notes and the index of the chord they belong to. Note that, for the sake of predictability, notes within the same chord are generated based on their string order: limiting notes on a high string are yielded before limiting notes on a low string within the same chord. >>> limiting_notes(['E|-0', 'B|-0', 'G|-0', 'D|-0', 'A|-0', 'E|-0']).next() (('E', 2), 0) """ chord_index = 0 for chord in chords(block): for string_num in sorted(chord.keys()): note = fret2note(chord[string_num], string_num) if is_limiting(note): yield (note, chord_index) chord_index += 1
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_over_one_octave(self): expected = ('A', 4) self.assertEqual(notemappings.fret2note(14, 2), expected)
def test_one_octave(self): expected = ('E', 3) self.assertEqual(notemappings.fret2note(12, 5), expected)
def test_open_string(self): expected = ('E', 2) self.assertEqual(notemappings.fret2note(0, 5), expected)
def chord_contexts(block): r""" Generate all the successive chord "contexts" that occur in the piece. There is *one context for each chord*. A context consists of: #. an ordered sequence of up to `CHORDS_BEFORE_CURRENT` preceding chords #. a current chord #. up to `CHORDS_AFTER_CURRENT` successor chords #. a mapping from notes to (fret, string) pairs indicating the notes' most recent occurrence #. the instance index of the chord context; this is useful to calculate the weight of a possible limiting note that follows #. the first limiting note that follows the current chord, if any The preceding, current and successor chords are given in physical form. **Note that in actual use, the successor chords can only be theoretical.** Note that the tuple returned consists of copies of the data, so it can safely be modified by client code. """ chord = 0 chord_deque = [] generator = chords(block) limit_generator = limiting_notes(block) # first set up the chords that come after the first "current" chord while chord <= CHORDS_AFTER_CURRENT: try: generated = generator.next() chord_deque.append(generated) chord += 1 except StopIteration: break # now act like a shift register, but start with first "current" chord chord = 0 # the index in the deque - not the instance yielded yielded = 0 # the instance yielded, so monotonically increasing recent_notes = {} limiting_note = None while chord < len(chord_deque): current_chord = chord_deque[chord] LOG.debug("Yielding current: {current_chord}".format(**locals())) while True: try: limiting_note = limit_generator.next() if limiting_note[1] > yielded: break except StopIteration: limiting_note = None break yield ( chord_deque[0:chord], dict(current_chord), chord_deque[chord + 1 :], dict(recent_notes), yielded, limiting_note, ) yielded += 1 for string_num in current_chord: note = fret2note(current_chord[string_num], string_num) LOG.debug("Memorizing note/fret for {note}".format(**locals())) recent_notes[note] = (current_chord[string_num], string_num) if chord < CHORDS_BEFORE_CURRENT: chord += 1 else: del (chord_deque[0]) try: generated = generator.next() chord_deque.append(generated) except StopIteration: pass LOG.debug("Returning from context generator.")
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