def __init__(self, melody_pattern_data, continuous_bar_data): self.input_data = [] # 输入model的数据 self.output_data = [] # 从model输出的数据 # 1.从数据集中读取鼓点, raw_drum_data = get_raw_song_data_from_dataset('drum', None) for song_it in range(TRAIN_FILE_NUMBERS): if raw_drum_data[song_it] != dict(): raw_drum_data[song_it] = flat_array(raw_drum_data[song_it]) else: raw_drum_data[song_it] = [] # 对于没有鼓点的歌曲,将格式转化为list格式 # 2.获取最常见的鼓点组合 common_pattern_cls = CommonMusicPatterns(COMMON_DRUM_PAT_NUM) common_pattern_cls.train(raw_drum_data, 0.125, 2) common_pattern_cls.store('drum') self.common_drum_pats = common_pattern_cls.common_pattern_list # 3.生成输入输出数据 for song_it in range(TRAIN_FILE_NUMBERS): if raw_drum_data[song_it] and melody_pattern_data[song_it]: # 3.1.将一首歌的鼓点编码为常见的鼓点组合。如果该鼓点组合不常见,则记为common_drum_patterns+1 drum_pat_data = DrumPatternEncode(self.common_drum_pats, raw_drum_data[song_it], 0.125, 2).music_pattern_list # 3.2.生成训练数据 输入内容是当前时间的编码 最近两小节+一个时间步长的主旋律和前两小节的鼓点 输出内容是这个时间步长的鼓点 self.get_model_io_data(drum_pat_data, melody_pattern_data[song_it], continuous_bar_data[song_it]) DiaryLog.warn('Generation of drum train data has finished!')
def root_chord_encode(chord_out, all_rc_pats, base_rootnote): """ 根据和弦的输出编码为根音-和弦组合 :param chord_out: 和弦的输出 :param all_rc_pats: 根音-和弦组合的列表 :param base_rootnote: 基准根音音高 :return: 根音数据和根音-和弦组合 """ root_data = [] rc_pat_list = [] for chord_it in range(len(chord_out)): if chord_it == 0: root_data.append( get_chord_root_pitch(chord_out[0], 0, base_rootnote)) else: root_data.append( get_chord_root_pitch(chord_out[chord_it], root_data[chord_it - 1], base_rootnote)) # 2.将和弦和根音组合进行编码 for chord_it in range(len(chord_out)): try: rc_pat_list.append( all_rc_pats.index([ root_data[chord_it], chord_out[chord_it] ])) # 由于前面获取状态转移矩阵和输出矩阵时将所有的和弦根音组合和PG组合都减了1 因此在这里要减一 except ValueError: # 根音-和弦对照表中没有这个根音-和弦组合 DiaryLog.warn('chord_output中出现了根音-和弦对照表中找不到和根音-和弦组合, 是' + repr([root_data[chord_it], chord_out[chord_it]])) rc_pat_list.append(0) return root_data, rc_pat_list
def keypress_encode(melody_output, keypress_pats): """ 根据主旋律的输出编码为按键组合 :param melody_output: 主旋律的输出 :param keypress_pats: 按键组合的列表 :return: 编码为按键组合后的结果 """ keypress_pat_list = [] for melody_step_it in range(0, len(melody_output), 16): keypress_list = [ 1 if t != 0 else 0 for t in melody_output[melody_step_it:melody_step_it + 16] ] try: keypress_dx = keypress_pats.index(keypress_list) if keypress_dx == -1: keypress_dx = keypress_pat_list[-1] keypress_pat_list.append(keypress_dx) except ValueError: DiaryLog.warn('melody_output中出现了中keypress_pat_list中不存在的旋律组合, 是' + repr(keypress_list)) keypress_pat_list.append(keypress_pat_list[-1]) except IndexError: DiaryLog.warn('melody_output中出现了中keypress_pat_list中不存在的旋律组合, 是' + repr(keypress_list)) keypress_pat_list.append(keypress_pat_list[-1]) return keypress_pat_list
def __init__(self): # 1.从sqlite中读取common_melody_pattern common_pattern_cls = CommonMusicPatterns( COMMON_MELODY_PAT_NUM) # 这个类可以获取常见的主旋律组合 common_pattern_cls.restore('melody') # 直接从sqlite文件中读取 self.common_melody_pats = common_pattern_cls.common_pattern_list # 常见的旋律组合列表 self.melody_pats_num_list = common_pattern_cls.pattern_number_list # 这些旋律组合出现的次数列表 # 2.从sqlite中读取所有按键组合数据 keyperss_pattern_cls = BaseMusicPatterns() keyperss_pattern_cls.restore('keypress') self.all_keypress_pats = keyperss_pattern_cls.common_pattern_list # 常见的旋律组合列表 self.keypress_pat_count = keyperss_pattern_cls.pattern_number_list # 这些旋律组合出现的次数列表 # 3.从sqlite中读取常见的骨干音组合数据 core_note_pattern_cls = CommonMusicPatterns( COMMON_CORE_NOTE_PAT_NUM) # 这个类可以获取常见的骨干音组合 core_note_pattern_cls.restore('core_note') # 直接从sqlite文件中读取 self.common_corenote_pats = core_note_pattern_cls.common_pattern_list # 常见的旋律组合列表 # 4.从sqlite中读取每首歌的相邻两小节的音高变化情况,和每个段落的前半部分与后半部分的差异的合理的阈值 self.ShiftConfidence = ShiftConfidenceCheck() # 计算训练集中所有歌曲相邻两小节的音高变化情况 self.DiffNoteConfidence = DiffNoteConfidenceCheck() self.ShiftConfidence.restore('melody_shift') self.DiffNoteConfidence.restore('melody_diffnote') # 5.获取主旋律模型的输入数据,用于和生成结果进行比对 self.input_data = np.load( os.path.join(PATH_PATTERNLOG, 'MelodyInputData.npy')).tolist() DiaryLog.warn('Restoring of melody associated data has finished!')
def generate(self, session, melody_out_notes, melody_out_pats, common_corenote_pats, core_note_pat_list, melody_beat_num, end_check_beats, tone_restrict=DEF_TONE_MAJOR): self.generate_init(session, melody_out_notes, melody_out_pats, common_corenote_pats, core_note_pat_list, melody_beat_num, end_check_beats, tone_restrict) while True: self.generate_by_step(session) self.check_1step(session) if self.rollback_times >= MAX_GEN_CHORD_FAIL_TIME: DiaryLog.warn('和弦被打回次数超过%d次,重新生成。\n\n\n' % self.rollback_times) raise RuntimeError if self.beat_dx == self.melody_beat_num: assert len(self.chord_out) == self.melody_beat_num break DiaryLog.warn('和弦的输出: ' + repr(self.chord_out) + '\n\n\n') return self.chord_out
def cal_centers(self, session): # 计算中心点 while True: centerpoints_legal = True # 这里判断计算出的中心点有没有NaN # 1.初始化 vector_indices = list(range(self.input_size)) random.shuffle(vector_indices) session.run(self.center_assigns, feed_dict={self.center_placeholder: [self.input_values[vector_indices[t]] for t in range(self.cluster_number)]}) session.run(self.attachment_assigns, feed_dict={self.attachment_placeholder: [0 for t in range(self.input_size)]}) # 2.计算中心点 centerpoint_array, result = self.calculate(session=session, operate_times=self.iterate_times) # 3.判断计算出的中心点是否有NaN,如果有 则重新计算 for centerpoint in centerpoint_array: if np.isnan(centerpoint): centerpoints_legal = False break if not centerpoints_legal: # print(centerpoint_array) DiaryLog.warn('音符中心点的计算结果中出现了nan值,需要重新生成。') # 4.如果数组符合要求,则对其进行排序后返回 if centerpoints_legal: centerpoint_array.sort() # 由小到大排序 # attachment_vector = [] # 存储分类情况 # for input_iterator in range(self.input_size): # attachment_vector.append(sess.run(self.cluster_assignment, feed_dict={self.input_value_placeholder: [self.input_values[input_iterator] for t in range(self.cluster_number)], self.cluster_placeholder: centerpoint_array})) return centerpoint_array
def __init__(self): # 1.从sqlite中读取音高变化的情况 self.ShiftConfidence = IntroShiftConfidenceCheck() self.ShiftConfidence.restore('intro_shift') # 2.获取前奏模型的输入数据,用于和生成结果进行比对 self.input_data = np.load( os.path.join(PATH_PATTERNLOG, 'IntroInputData.npy')).tolist( ) # 在generate的时候要比较生成数据和训练集是否雷同,因此这个也要存储 DiaryLog.warn( 'Restoring of intro and interlude associated data has finished!')
def get_cluster_center_points(self, session, train=True): """ 使用K-Means算法获取10个中心点 :param session: tf.session :param train: 是否是训练模式 :return: 十个中心点 """ if train is True: # 训练状态 获取平均音高 self.cluster_center_points = self.kmeans_model.cal_centers(session) else: # 非训练模式 从文件中获取平均音高 self.cluster_center_points = self.kmeans_model.restore_centers( session) DiaryLog.warn('平均音高的十个中心点分别为: ' + repr(self.cluster_center_points))
def generate_by_step(self, session): # 1.如果输入数据为空 随机生成一个起始pattern作为乐曲的开始 if self.pat_step_dx == 0: start_pattern = get_first_melody_pat(self.train_data.melody_pats_num_list, 1, COMMON_MELODY_PAT_NUM) # 随机选取开始的pattern 注意 开始的第一个pattern不能为空 self.melody_out_pats.append(start_pattern) self.melody_out_notes.extend(music_pattern_decode(self.train_data.common_melody_pats, [start_pattern], 0.125, 1)) # 将第一个旋律组合解码 得到最初的8个音符的列表 DiaryLog.warn('第%d次生成开始: 主旋律开始的组合编码是%d, 组合为%s' % (self.rollback_times + 1, start_pattern, repr(self.melody_out_notes))) self.pat_step_dx = 1 # 2.使用LstmModel生成一个步长的音符 # 2.1.判断这个步长能否为空拍。判断依据为 # a.当过去两小节有八个(含)以上音符时,下一小节的第一个步长不能为空。 # b.不能连续两个小节的第一个步长为空。 # c.一个乐段的第一个步长不能为空。 # d.不能连续四拍为空 flag_allow_empty = True if self.pat_step_dx % 4 == 0: if self.pat_step_dx > 8 and self.melody_out_notes[-64:].count(0) <= 56: flag_allow_empty = False # 当过去两小节有八个(含)以上音符时,下一小节的第一个步长不能为空 if self.melody_out_pats[self.pat_step_dx - 4] == 0: flag_allow_empty = False # 不能连续两个小节的第一个步长为空 if self.pat_step_dx >= 3 and self.melody_out_pats[-3:] == [0, 0, 0]: flag_allow_empty = False # 不能连续四拍为空 # 2.2.逐时间步长生成test model输入数据 time_add = self.pat_step_dx % 8 melody_input = [[time_add]] # 当前时间的编码 # 对于不是一个乐段的第一小节,训练数据只从这个乐段的第一拍开始 if len(self.melody_out_pats) >= 16: # melody_out_pat_list足够长的情况 melody_input[0].extend(self.melody_out_pats[-16:]) # 最近4小节的旋律组合 else: # melody_out_pat_list不够长的情况 melody_input[0].extend([0] * (16 - len(self.melody_out_pats)) + self.melody_out_pats) # 2.3.生成输出的音符 melody_predict = self.predict(session, melody_input) # LSTM预测 得到二维数组predict if self.pat_step_dx <= 3: # 还没到五拍,不可能出现“连续五拍与某个常见组合雷同”的情况 out_pat_dx = music_pattern_prediction(melody_predict, int(not flag_allow_empty), COMMON_MELODY_PAT_NUM) else: try: # 人为排除与训练集中数据雷同的主旋律组合 out_pat_dx = melody_pattern_prediction_unique(melody_predict, int(not flag_allow_empty), COMMON_MELODY_PAT_NUM, self.melody_out_pats, self.train_data.input_data) except RuntimeError: DiaryLog.warn('在第%d个步长, 主旋律第%02d次打回,难以选出不雷同的组合, 最后四拍音符为%s' % (self.pat_step_dx, self.rollback_times, repr(self.melody_out_notes[-32:]))) self.rollback(4) self.rollback_times += 1 # 被打回重新生成了一次 return self.melody_out_pats.append(out_pat_dx) self.melody_out_notes.extend(music_pattern_decode(self.train_data.common_melody_pats, [out_pat_dx], 0.125, 1)) self.pat_step_dx += 1
def generate(self, session, melody_out_notes, keypress_out, chord_out, melody_beat_num, end_check_beats): self.generate_init(session, melody_out_notes, keypress_out, chord_out, melody_beat_num, end_check_beats) while True: self.generate_by_step(session) self.check_1step(session) if self.rollback_times >= MAX_GEN_PG_FAIL_TIME: DiaryLog.warn('piano_guitar被打回次数超过%d次,重新生成。\n\n\n' % self.rollback_times) raise RuntimeError if self.beat_dx == self.melody_beat_num: assert self.beat_dx == len(self.pg_out_notes) // 4 break DiaryLog.warn('piano_guitar的输出: ' + repr(self.pg_out_notes)) return self.pg_out_notes
def generate(self, session): self.generate_init() while True: self.generate_by_step(session) # 生成一个步长 self.check_1step(session) # 对这个步长生成的数据进行校验 # 退出的条件有二:一是生成失败的次数过多 打回重新生成,二是生成完成 if self.rollback_times >= MAX_GEN_MELODY_FAIL_TIME: DiaryLog.warn('主旋律被打回次数超过%d次,重新生成。\n\n\n' % self.rollback_times) raise RuntimeError if self.flag_end: assert self.pat_step_dx == len(self.melody_out_notes) // 8 break self.section_data = [(0, "main"), (self.pat_step_dx // 4, "empty")] # 生成只有一个段落的section数据 core_note_pat_list = CoreNotePatternEncode(self.train_data.common_corenote_pats, self.melody_out_notes, 0.125, 2).music_pattern_list keypress_pat_list = keypress_encode(self.melody_out_notes, self.train_data.all_keypress_pats) DiaryLog.warn('主旋律的输出:' + repr(self.melody_out_notes) + '\n\n\n') return self.melody_out_notes, self.melody_out_pats, core_note_pat_list, keypress_pat_list
def __init__(self): # 1.从sqlite中读取common_string_pattern common_pattern_cls = CommonMusicPatterns(COMMON_STRING_PAT_NUM) # 这个类可以获取常见的string组合 common_pattern_cls.restore('String') # 直接从sqlite文件中读取 self.common_string_pats = common_pattern_cls.common_pattern_list # 常见的string组合列表 # 2.获取和弦的根音组合 self.string_avr_root = get_nearest_number_multiple(STRING_AVR_NOTE, 12) rc_pattern_cls = BaseMusicPatterns() rc_pattern_cls.restore('StringRC') self.all_rc_pats = rc_pattern_cls.common_pattern_list self.rc_pat_num = len(self.all_rc_pats) # 3.从sqlite中读取每首歌的string的前后段差异,以及string与同时期和弦的差异的合理的阈值 string_confidence_config = StringConfidenceCheckConfig() self.StringConfidence = AccompanyConfidenceCheck(string_confidence_config) self.StringConfidence.restore('string') DiaryLog.warn('Restoring of string associated data has finished!')
def __init__(self): # 1.获取和弦的根音组合 cc_pattern_cls = BaseMusicPatterns() cc_pattern_cls.restore('ChordChord') self.all_cc_pats = cc_pattern_cls.common_pattern_list self.cc_pat_num = len(self.all_cc_pats) # 2.获取主旋律与同时期和弦的状态转移矩阵 self.transfer_count = np.load( os.path.join(PATH_PATTERNLOG, 'ChordTransferCount.npy')) self.real_transfer_count = np.load( os.path.join(PATH_PATTERNLOG, 'ChordTransferCountReal.npy')) assert self.transfer_count.shape[0] == COMMON_CORE_NOTE_PAT_NUM * 2 + 2 assert self.transfer_count.shape[1] == len(CHORD_LIST) + 1 assert self.real_transfer_count.shape[ 0] == COMMON_CORE_NOTE_PAT_NUM * 2 + 2 assert self.real_transfer_count.shape[1] == len(CHORD_LIST) + 1 DiaryLog.warn('Restoring of chord associated data has finished!')
def generate(self, session): self.generate_init() while True: self.generate_by_step(session) # 生成一个步长 if self.pat_step_dx % 4 == 0 and self.section_data[self.sec_dx + 1][0] == self.pat_step_dx // 4: self.calc_profile(session) # 生成过渡段的最后一拍后,计算这一个段落的melody profile self.check_1step(session) # 对这个步长生成的数据进行校验 # 退出的条件有二:一是生成失败的次数过多 打回重新生成,二是生成完成 if self.rollback_times >= MAX_GEN_MELODY_FAIL_TIME: DiaryLog.warn('主旋律被打回次数超过%d次,重新生成。\n\n\n' % self.rollback_times) raise RuntimeError if self.pat_step_dx // 4 == self.section_data[-1][0]: assert self.pat_step_dx == len(self.melody_out_notes) // 8 break core_note_pat_list = CoreNotePatternEncode(self.train_data.common_corenote_pats, self.melody_out_notes, 0.125, 2).music_pattern_list keypress_pat_list = keypress_encode(self.melody_out_notes, self.train_data.all_keypress_pats) DiaryLog.warn('主旋律的输出:' + repr(self.melody_out_notes) + '\n\n\n') return self.melody_out_notes, self.melody_out_pats, core_note_pat_list, keypress_pat_list
def check_1step(self, session): # 1.检查string与同时期的和弦差异是否过大,在每小节的末尾进行检验 if self.beat_dx >= 8 and self.beat_dx % 4 == 0 and not string_chord_check( self.string_out_notes[-32:], self.chord_out[ (self.beat_dx - 8):self.beat_dx]): # string与同时期的和弦差异过大 DiaryLog.warn('在第%d拍, string第%02d次打回,与同时期和弦差异太大, 最后八拍音符为%s' % (self.beat_dx, self.rollback_times, repr(self.string_out_notes[-32:]))) self.rollback(4) self.rollback_times += 1 return # 2.连续八拍伴奏的偏离程度(包括按键/音高差异/和同时期和弦的差异综合评定)。每生成了奇数小节之后进行校验 if self.beat_dx >= 12 and self.beat_dx % 8 == 4: # 每生成了奇数小节之后进行校验 total_diff_score = self.train_data.StringConfidence.evaluate( note_out=self.string_out_notes[-48:], chord_out=self.chord_out[( self.beat_dx - 8):self.beat_dx]) # 根据训练集90%bass差异分判断的校验法 if not self.train_data.StringConfidence.compare(total_diff_score): bar_dx = self.beat_dx // 4 - 1 # 当前小节 减一 self.confidence_back_times[bar_dx] += 1 DiaryLog.warn( '第%d拍, string的误差分数为%.4f, 高于临界值%.4f' % (self.beat_dx, total_diff_score, self.train_data.StringConfidence.confidence_level)) if total_diff_score <= self.diff_score_bak[bar_dx]: self.string_abs_note_bak[bar_dx] = self.string_out_notes[ -32:] self.string_choose_bak[bar_dx] = self.string_out_pats[-4:] self.diff_score_bak[bar_dx] = total_diff_score self.rollback(4) if self.confidence_back_times[bar_dx] >= 10: DiaryLog.warn( '第%d拍, string使用备选方案, 误差函数值为%.4f, 这八拍的string为%s' % (self.beat_dx, self.diff_score_bak[bar_dx], repr(self.string_out_notes[bar_dx]))) self.string_out_notes.extend( self.string_abs_note_bak[bar_dx]) self.string_out_pats.extend(self.string_choose_bak[bar_dx]) self.beat_dx += 8 else: return # 3.string结束阶段的检验: 必须全部在有1级大和弦或6级小和弦内。在最后一个步长执行检验 if self.beat_dx in self.end_check_beats and not string_end_check( self.string_out_notes): DiaryLog.warn('在%d拍, string第%02d次打回,最后一个音是弦外音, 最后八拍音符为%s' % (self.beat_dx, self.rollback_times, repr(self.string_out_notes[-32:]))) self.rollback(4) self.rollback_times += 1
def __init__(self, all_keypress_pats): # 1.从sqlite中读取common_bass_pattern common_pattern_cls = CommonMusicPatterns( COMMON_BASS_PAT_NUM) # 这个类可以获取常见的bass组合 common_pattern_cls.restore('Bass') # 直接从sqlite文件中读取 self.common_bass_pats = common_pattern_cls.common_pattern_list # 常见的bass组合列表 # 2.获取和弦的根音组合 self.bass_avr_root = get_nearest_number_multiple(BASS_AVR_NOTE, 12) rc_pattern_cls = BaseMusicPatterns() rc_pattern_cls.restore('BassRC') self.all_rc_pats = rc_pattern_cls.common_pattern_list self.rc_pat_num = len(self.all_rc_pats) self.keypress_pat_num = len(all_keypress_pats) # 一共有多少种按键组合数据(步长为2拍) # 3.从sqlite中读取每首歌的bass的前后段差异,以及bass与同时期和弦的差异的合理的阈值 bass_confidence_config = BassConfidenceCheckConfig() self.BassConfidence = AccompanyConfidenceCheck(bass_confidence_config) self.BassConfidence.restore('bass') DiaryLog.warn('Restoring of bass associated data has finished!')
def generate(self, session, melody_out_notes, melody_out_pats): self.generate_init(session, melody_out_notes, melody_out_pats) while True: self.generate_by_step(session) self.check_1step(session) if self.rollback_times >= MAX_GEN_INTRO_FAIL_TIME: DiaryLog.warn('前奏被打回次数超过%d次,重新生成。\n\n\n' % self.rollback_times) raise RuntimeError if self.beat_dx == self.intro_bar_num * 4: assert self.beat_dx == len(self.intro_out_notes) // 8 break core_note_pat_list = CoreNotePatternEncode( self.melody_pipe_cls.train_data.common_corenote_pats, self.intro_out_notes, 0.125, 2).music_pattern_list keypress_pat_list = keypress_encode( self.intro_out_notes, self.melody_pipe_cls.train_data.all_keypress_pats) DiaryLog.warn('前奏的输出:' + repr(self.intro_out_notes) + '\n\n\n') return self.intro_out_notes, self.intro_out_pats, core_note_pat_list, keypress_pat_list
def check_1step(self, session): # 1.检查音符按键位置是否符合要求。在整小节处检验 if self.pat_step_dx % 4 == 0: if not keypress_check(self.melody_out_notes[-32:]): # 检查这一小节的音乐是否符合要求 如果不符合要求 则返工 重新生成这一小节的音乐 DiaryLog.warn('在第%d个pattern, 主旋律第%02d次打回,按键位置有异常, 最后四拍音符为%s' % (self.pat_step_dx, self.rollback_times, self.melody_out_notes[-32:])) self.rollback(4) self.rollback_times += 1 return # 2.检查两小节内的音高变化幅度是否过大。在整小节处检验 if self.pat_step_dx % 4 == 0 and self.pat_step_dx >= 8: shift_score = self.train_data.ShiftConfidence.evaluate(melody_note_list=self.melody_out_notes[-64:]) if not self.train_data.ShiftConfidence.compare(shift_score): # 这两小节的音高变化是否在一定限制幅度内 DiaryLog.warn('在第%d个pattern, 主旋律第%02d次打回,音高变化的分数为%.4f,高于临界值%.4f' % (self.pat_step_dx, self.rollback_times, shift_score, self.train_data.ShiftConfidence.confidence_level)) self.rollback(8) self.rollback_times += 1 return # 3.主旋律结束阶段的检验。如果主旋律已经生成了8/10/12小节,且符合结束条件,则结束。如果达到12小节仍未达到结束条件,则打回重新生成 if self.pat_step_dx % 8 == 0 and self.pat_step_dx // 4 in [8, 10, 12]: if melody_end_check(self.melody_out_notes[-32:], self.tone_restrict): self.flag_end = True return if self.pat_step_dx // 4 >= 12: # 到达这里说明超过12小节,收束仍然不符合要求 DiaryLog.warn('在第%d个pattern, 主旋律第%02d次打回,收束不符合要求, 最后四拍音符为%s' % (self.pat_step_dx, self.rollback_times, repr(self.melody_out_notes[-32:]))) self.rollback(8) # 重新生成最后两小节的音乐 self.rollback_times += 1 # 被打回重新生成了一次
def __init__(self, all_keypress_pats): # 1.从sqlite中读取common_piano_guitar_pattern common_pattern_cls = CommonMusicPatterns( COMMON_PG_PAT_NUM) # 这个类可以获取常见的piano_guitar组合 common_pattern_cls.restore('PianoGuitar') # 直接从sqlite文件中读取 self.common_pg_pats = common_pattern_cls.common_pattern_list # 常见的bass组合列表 # 2.获取和弦的根音组合 self.pg_avr_root = get_nearest_number_multiple(PG_AVR_NOTE, 12) rc_pattern_cls = BaseMusicPatterns() rc_pattern_cls.restore('PGRC') self.all_rc_pats = rc_pattern_cls.common_pattern_list self.rc_pat_num = len(self.all_rc_pats) self.keypress_pat_num = len(all_keypress_pats) # 一共有多少种按键组合数据(步长为2拍) # 3.从sqlite中读取每首歌的piano_guitar的前后段差异,以及piano_guitar与同时期和弦的差异的合理的阈值 pg_confidence_config = PgConfidenceCheckConfig() self.PgConfidence = AccompanyConfidenceCheck(pg_confidence_config) self.PgConfidence.restore('piano_guitar') DiaryLog.warn( 'Restoring of piano_guitar associated data has finished!')
def __init__(self, song_info_list, skip_list): self.music_data = dict() # 整理好的music_data就在这个列表中 列表一共有4维:第一维是歌的编号 第二维是存储类型 第三维是小节序列 第四维是小节中每个音的音高 self.chord_data = dict() # 整理好的和弦列表。列表一共有3维,第一维是歌的编号,第二维是小节序列,第三维是小节中的和弦列表 self.melody_data = dict() # 整理好的主旋律列表。列比一共有三维 第一维是歌的编号 第二维是小节序列,第三维是小节中主旋律音符的列表 self.note_dict = [[-1]] # 音符的词典 即将音符及其组合整理成dict的形式。把第0项空出来,第0项是休止符。 # 1.变量定义及其初始化 scale_list = [None for t0 in range(TRAIN_FILE_NUMBERS)] tone_list = [None for t1 in range(TRAIN_FILE_NUMBERS)] bpm_list = [None for t2 in range(TRAIN_FILE_NUMBERS)] bias_time_list = [None for t3 in range(TRAIN_FILE_NUMBERS)] # 2.获取所有歌曲的信息 for song_it in range(TRAIN_FILE_NUMBERS): if song_info_list[song_it] is not None: scale_list[song_it] = song_info_list[song_it]['scale'] bpm_list[song_it] = song_info_list[song_it]['bpm'] tone_list[song_it] = song_info_list[song_it]['tone'] bias_time_list[song_it] = song_info_list[song_it]['bias'] # 3.读取所有的文件并将里面的音符转化为可训练的形式 for file_it in range(TRAIN_FILE_NUMBERS): if file_it not in skip_list: self.music_data[file_it] = dict() # 初始化训练数据的第一维。注意:第一维的下标是从0开始的,而不是从1开始的。因为python的字典不能同时初始化两维 file_path = '../Inputs/%02d/%03d.mid' % (ACTIVE_MUSIC_TYPE, file_it + 1) DiaryLog.warn('正在整理第%d首歌' % file_it) pianoroll_list = generate_data_from_midi_file(file_path, bias_time_list[file_it], scale_list[file_it]) self.get_music_data(file_it, pianoroll_list, time_step_dic={'piano_guitar': 0.25, 'string': 0.25}, eliminate=['main', 'intro', 'interlude', 'chord', 'others']) # 把main/intro/interlude和chord去掉意思就是主旋律和和弦部分单独处理 self.get_melody_data(file_it, pianoroll_list) if 'chord' in pianoroll_list: # 只有在这首歌有和弦的时候才训练和弦 self.get_chord_data(file_it, pianoroll_list, tone=tone_list[file_it]) # 4.把这些音符存储在sqlite中 self.save_music_data() self.save_chord_data() self.save_melody_data()
def __init__(self): # 1.获取加花数据的分类和组合 self.fill_type_pat_cls = FillClassifyAndPats(0) # 记录加花数据的分类和组合 self.fill_type_pat_cls.restore() # 2.获取前一拍无加花的情况,本拍是否加花的数据 self.all_fill_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillTotalCount.npy')) self.keypress_fill_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillKeypressCount.npy')) self.timecode_fill_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillTimecodeCount.npy')) self.sec_nfill_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillSecNCount.npy')) self.sameinsec_fill_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillSecYCount.npy')) assert self.all_fill_ary.shape == (4, ) assert self.keypress_fill_ary.shape == (4, 16) assert self.timecode_fill_ary.shape == (4, 8) assert self.sec_nfill_ary.shape == (3, 6) assert self.sameinsec_fill_ary.shape == (3, 6) # 3.获取前一拍有加花的情况,本拍是否加花的数据 self.all_fill_rep_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillRepTotalCount.npy')) self.keypress_fill_rep_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillRepKeypressCount.npy')) self.timecode_fill_rep_ary = np.load( os.path.join(PATH_PATTERNLOG, 'FillRepTimecodeCount.npy')) assert self.all_fill_rep_ary.shape == ( 6, ) # 六个数分别是上一拍的加花内容为第一类/第二类/第三类,本拍不延续;上一拍的加花内容为第一类/第二类/第三类,本拍延续 assert self.keypress_fill_rep_ary.shape == (6, 16) # 主旋律按键情况对应加花的频数 assert self.timecode_fill_rep_ary.shape == ( 6, 32) # 加花已延续拍数和时间编码对应加花的频数(高两位为加花已延续拍数-1,低三位为时间编码) DiaryLog.warn('Restoring of fill associated data has finished!')
def generate_1track(track_in, mark, *args): """ 根据track_in类型的generate方法来生成一个音轨。 如果连续生成失败超过max_fail_time次则抛出ValueError, 否则反复生成直到成功为止 :param mark: 音轨的标注 :param track_in: 输入的音轨生成方式类 :param args: 这个类的generate方法的相关参数 :return: 生成的音轨数据 """ for fail_time in range(MAX_GEN_1SONG_FAIL_TIME): try: track_out = track_in.generate(*args) return track_out except RuntimeError: DiaryLog.warn(traceback.format_exc()) DiaryLog.warn('%s音轨已连续生成失败%d次' % (mark, fail_time)) except IndexError: DiaryLog.warn(traceback.format_exc()) DiaryLog.warn('%s音轨已连续生成失败%d次' % (mark, fail_time)) raise RuntimeError
def check_1step(self, session): # 1.检查前奏的结束部分是否符合要求。在最后一个步长执行检验 if self.beat_dx == self.intro_bar_num * 4: if not intro_end_check(self.intro_out_notes, self.tone_restrict): # 检查前奏的结束阶段是否符合要求 DiaryLog.warn( '在第%d个pattern, 前奏第%02d次打回,前奏的结束阶段不符合要求, 最后四拍音符为%s' % (self.beat_dx, self.rollback_times, repr(self.intro_out_notes[-32:]))) self.rollback(8) self.rollback_times += 1 return # 2.检查前奏和主旋律的连接部分是否符合要求。在最后一个步长执行检验 if self.beat_dx == self.intro_bar_num * 4: shift_score = self.train_data.ShiftConfidence.evaluate( melody_list=self.melody_out_notes, intro_list=self.intro_out_notes[-64:]) if not self.train_data.ShiftConfidence.compare( shift_score): # 这两小节的音高变化是否在一定限制幅度内 DiaryLog.warn( '在第%d个pattern, 前奏第%02d次打回,和主旋律开始部分的连接情况得分为%.4f,高于临界值%.4f' % (self.beat_dx, self.rollback_times, shift_score, self.train_data.ShiftConfidence.confidence_level)) self.rollback(4) self.rollback_times += 1 return # 3.检查前奏平均音高的情况(前奏的平均音高要么跟主歌相差1以内,要么跟副歌相差1以内)。在最后一个步长执行检验 if self.beat_dx == self.intro_bar_num * 4: cluster_ary = self.melody_pipe_cls.melody_profile.get_melody_profile_by_song( session, self.intro_out_notes) # 计算前奏的cluster intro_cluster = np.mean(cluster_ary) flag_cluster_right = False for sec_it in range(len(self.melody_pipe_cls.section_data)): if self.melody_pipe_cls.section_data[sec_it][1] in [ "main", "sub" ] and abs(self.melody_pipe_cls.sec_profile_list[sec_it] - intro_cluster) <= 1.5: flag_cluster_right = True break if flag_cluster_right is False: # cluster校验不通过 DiaryLog.warn( '在第%d个pattern, 前奏第%02d次打回,cluster与主旋律的cluster相差太大, 前奏的cluster为%.4f, 各个乐段的cluster分别为%s' % (self.beat_dx, self.rollback_times, float(intro_cluster), repr(self.melody_pipe_cls.sec_profile_list))) rollback_beats = self.intro_bar_num * 4 # 回退多少小节 self.rollback(rollback_beats) self.rollback_times += 1
def __init__(self, tone_restrict=None): self.input_data = [] # 输入model的数据 self.output_data = [] # 从model输出的数据 self.continuous_bar_data = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 每一首歌连续的小节计数 self.continuous_bar_data_nres = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 每一首歌无限制的小节计数 self.keypress_pat_data = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 二维数组 第一维是歌曲列表 第二维是按键的组合(步长是2拍) self.all_keypress_pats = [[0 for t in range(16)]] # 主旋律按键组合的对照表 self.keypress_pat_count = [0] # 各种按键组合的计数 self.core_note_ary_nres = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 每一首歌每一拍的骨干音列表 self.core_note_pat_nres = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 骨干音组合数据 self.melody_pat_data = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 有调式限制的旋律组合数据 self.melody_pat_data_nres = [[] for t in range(TRAIN_FILE_NUMBERS) ] # 无调式限制的旋律组合数据 self.ShiftConfidence = ShiftConfidenceCheck() # 计算训练集中所有歌曲相邻两小节的音高变化情况 self.DiffNoteConfidence = DiffNoteConfidenceCheck( ) # 计算训练集中所有歌曲所有段落前半段和后半段的按键和音高上的差异 # 1.从数据集中读取所有歌曲的主旋律数据,并变更为以音符步长为单位的列表 self.raw_train_data = get_raw_song_data_from_dataset( 'main', tone_restrict) self.raw_melody_data = get_raw_song_data_from_dataset( 'main', None) # 没有旋律限制的主旋律数据 用于训练其他数据 self.section_data = get_section_data_from_dataset() for song_it in range(TRAIN_FILE_NUMBERS): if self.raw_train_data[song_it] != dict(): self.raw_train_data[song_it] = flat_array( self.raw_train_data[song_it]) else: self.raw_train_data[song_it] = [] # 对于没有主旋律的歌曲,将格式转化为list格式 if self.raw_melody_data[song_it] != dict(): self.raw_melody_data[song_it] = flat_array( self.raw_melody_data[song_it]) else: self.raw_train_data[song_it] = [] raw_melody_data_nres = copy.deepcopy(self.raw_melody_data) # 2.获取最常见的主旋律组合 common_pattern_cls = CommonMusicPatterns( COMMON_MELODY_PAT_NUM) # 这个类可以获取常见的主旋律组合 common_pattern_cls.train(self.raw_train_data, 0.125, 1, False) common_pattern_cls.store('melody') # 存储在sqlite文件中 self.common_melody_pats = common_pattern_cls.common_pattern_list # 常见的旋律组合列表 self.melody_pats_num_list = common_pattern_cls.pattern_number_list # 这些旋律组合出现的次数列表 # 3.逐歌曲获取连续不为空的小节列表/按键数据/骨干音数据/ for song_it in range(TRAIN_FILE_NUMBERS): # 3.1.获取没有调式限制的旋律数据 if raw_melody_data_nres[song_it]: # 获取相关没有调式限制的相关数据 # 3.1.1.获取旋律的按键数据 self.get_keypress_data( song_it, raw_melody_data_nres[song_it]) # 获取按键数据 当前有按键记为1 没有按键记为0 # 3.1.3.将它的主旋律编码为常见的旋律组合。如果该旋律组合不常见,则记为COMMON_MELODY_PAT_NUM+1 self.continuous_bar_data_nres[ song_it] = get_continuous_bar_cnt( raw_melody_data_nres[song_it]) # 3.1.2.获取骨干音及骨干音的常见组合列表 self.core_note_ary_nres[song_it] = melody_core_note( raw_melody_data_nres[song_it], self.continuous_bar_data_nres[song_it], self.section_data[song_it]) # 3.2.获取有调式限制的旋律数据 if self.raw_train_data[song_it]: # 3.2.1.获取歌曲的连续不为空的小节序号列表 self.continuous_bar_data[song_it] = get_continuous_bar_cnt( self.raw_train_data[song_it]) # 3.3.存储按键数据的组合列表 keyperss_pattern_cls = BaseMusicPatterns() keyperss_pattern_cls.common_pattern_list = self.all_keypress_pats keyperss_pattern_cls.pattern_number_list = self.keypress_pat_count keyperss_pattern_cls.store('keypress') # 4.获取无调式限制的常见骨干音组合列表 core_note_pattern_cls = CommonMusicPatterns(COMMON_CORE_NOTE_PAT_NUM) core_note_pattern_cls.train(self.core_note_ary_nres, 0.125, 2) core_note_pattern_cls.store('core_note') # 存储在sqlite文件中 self.common_corenote_pats = core_note_pattern_cls.common_pattern_list # 常见的旋律组合列表 # 5.根据常见的音符组合,对原始的旋律音符和骨干音列表进行编码 for song_it in range(TRAIN_FILE_NUMBERS): # 5.1.编码无调式限制的主旋律及其骨干音 if raw_melody_data_nres[song_it]: # 没有调式限制的相关数据 self.melody_pat_data_nres[song_it] = MelodyPatternEncode( self.common_melody_pats, raw_melody_data_nres[song_it], 0.125, 1).music_pattern_list self.core_note_pat_nres[song_it] = CoreNotePatternEncode( self.common_corenote_pats, self.core_note_ary_nres[song_it], 0.125, 2).music_pattern_list # 5.2.编码有调式限制的主旋律。如果该旋律组合不常见,则记为COMMON_MELODY_PAT_NUM+1 if self.raw_train_data[song_it]: self.melody_pat_data[song_it] = MelodyPatternEncode( self.common_melody_pats, self.raw_train_data[song_it], 0.125, 1).music_pattern_list # 6.生成每首歌的旋律变化累积幅度的数据 # 6.1.生成每首歌的相邻两小节的音高变化情况,和每个段落的前半部分与后半部分的差异 for song_it in range(TRAIN_FILE_NUMBERS): if raw_melody_data_nres[song_it]: self.ShiftConfidence.train_1song( raw_melody_data=raw_melody_data_nres[song_it], section_data=self.section_data[song_it]) self.DiffNoteConfidence.train_1song( raw_melody_data=raw_melody_data_nres[song_it], section_data=self.section_data[song_it]) # 6.2.找出旋律变化和段落内差异前95%所在位置 self.ShiftConfidence.calc_confidence_level(0.95) self.DiffNoteConfidence.calc_confidence_level(0.95) self.ShiftConfidence.store( 'melody_shift') # 把shift_confidence_level和diff_note保存到sqlite中 self.DiffNoteConfidence.store('melody_diffnote') # 7.生成每首歌的训练数据 for song_it in range(TRAIN_FILE_NUMBERS): if self.raw_train_data[song_it]: self.get_model_io_data(self.melody_pat_data[song_it], self.continuous_bar_data[song_it]) np.save(os.path.join(PATH_PATTERNLOG, 'MelodyInputData.npy'), self.input_data) # 在generate的时候要比较生成数据和训练集是否雷同,因此这个也要存储 DiaryLog.warn('Generation of melody train data has finished!')
def check_1step(self, session): # 1.检查两小节内和弦的离调情况。在每两小节的末尾进行检验 if self.beat_dx >= 8 and self.beat_dx % 4 == 0 and not chord_check( self.chord_out[-8:], self.melody_out_notes[ (self.beat_dx - 8) * 8:self.beat_dx * 8]): # 离调和弦的比例过高 DiaryLog.warn( '在第%d拍, 和弦第%02d次打回,离调和弦比例过高, 最后八拍和弦为%s' % (self.beat_dx, self.rollback_times, repr(self.chord_out[-8:]))) self.rollback(4) self.rollback_times += 1 return # 2.和弦是否连续三小节不变化。在每小节的末尾检验 if self.beat_dx >= 12 and len(set(self.chord_out[-12:])) == 1: DiaryLog.warn( '在第%d拍, 和弦第%02d次打回,连续3小节和弦未变化, 始终为%d' % (self.beat_dx, self.rollback_times, self.chord_out[-1])) self.rollback(6) self.rollback_times += 1 return # 3.检查两小节内的主旋律对应和弦在训练集中出现的频率是否过低。每2小节检验一次 if self.beat_dx >= 8 and self.beat_dx % 8 == 0: self.confidence_cls.calc_confidence_level( session, self.core_note_pat_list) # 计算这两拍的主旋律骨干音对应的各类和弦的转化频率 chord_per_2beats = [ self.chord_out[-8:][t] for t in range(0, len(self.chord_out[-8:]), 2) ] cc_pat_per_2beats = self.cc_pat_out[-4:] # 生成的和弦从每1拍一个转化为每两拍一个 check_res, loss_value = self.confidence_cls.check_chord_ary( session, self.melody_out_notes[(self.beat_dx - 8) * 8:self.beat_dx * 8], self.core_note_pat_list[self.beat_dx // 2 - 4:self.beat_dx // 2], chord_per_2beats) # 检查此时的主旋律对应同期的和弦的转化频率是否过低 if not check_res: DiaryLog.warn( '在第%d拍, 和弦第%d次未通过同时期主旋律转化频率验证。和弦的损失函数值为%.4f,而临界值为%.4f' % (self.beat_dx, self.confidence_back_times[self.beat_dx // 4], loss_value, self.confidence_cls.confidence_level)) self.rollback(4) self.confidence_back_times[(self.beat_dx + 8) // 4] += 1 if loss_value < self.loss_bak[ (self.beat_dx + 8) // 4]: # 前面减了8之后 这里beat_dx都应该变成beat_dx+8 self.chord_choose_bak[(self.beat_dx + 8) // 4] = chord_per_2beats self.cc_pat_choose_bak[(self.beat_dx + 8) // 4] = cc_pat_per_2beats self.loss_bak[(self.beat_dx + 8) // 4] = loss_value if self.confidence_back_times[ (self.beat_dx + 8) // 4] >= 10: # 为避免连续过多次的回滚,当连续回滚次数达到10次时,使用前10次中相对最佳的和弦组合 DiaryLog.warn( '在第%d拍,由于连续10次未通过同时期主旋律转化频率的验证,和弦使用备选方案, 损失为%.4f' % (self.beat_dx, self.loss_bak[(self.beat_dx + 8) // 4])) chord_part_list = list( np.repeat( self.chord_choose_bak[(self.beat_dx + 8) // 4], 2)) for chord_it in range(len(chord_part_list)): chord_part_list[chord_it] = int( chord_part_list[chord_it]) self.chord_out.extend(chord_part_list) self.cc_pat_out.extend( self.cc_pat_choose_bak[(self.beat_dx + 8) // 4]) self.beat_dx += 8 else: return # 4.和弦结束的校验。最后一拍的和弦必须为1级大和弦(大调)或6级小和弦。在最后一个步长执行检验 if self.beat_dx in self.end_check_beats: if (self.tone_restrict == DEF_TONE_MAJOR and self.chord_out[-1] != 1) or (self.tone_restrict == DEF_TONE_MINOR and self.chord_out[-1] != 56): DiaryLog.warn('在第%d拍, 和弦第%02d次打回,收束不是1级大和弦或6级小和弦, 最后八拍和弦为%s' % (self.beat_dx, self.rollback_times, repr(self.chord_out[-8:]))) self.rollback(4) self.rollback_times += 1
def generate_by_step(self, session): # 1.如果这两拍没有主旋律 和弦直接沿用之前的 if self.melody_out_pats[self.beat_dx:self.beat_dx + 2] == [0, 0]: # 最近这2拍没有主旋律 则和弦沿用之前的 if self.beat_dx == 0: raise ValueError # 前两拍的主旋律不能为空 else: self.chord_out.extend([self.chord_out[-1], self.chord_out[-1]]) self.cc_pat_out.append(self.cc_pat_out[-1]) self.beat_dx += 2 return # 2.使用LstmModel生成一个步长的音符 # 2.1.逐时间步长生成test model输入数据 chord_prediction_input = list() for backward_beat_it in range(self.beat_dx - 8, self.beat_dx + 2, 2): cur_step = backward_beat_it // 2 # 第几个bass的步长 if backward_beat_it < 0: chord_prediction_input.append([ cur_step % 2, self.melody1_code_add_base, self.melody2_code_add_base, self.chord_code_add_base ]) # 这一拍的时间编码 这一拍的主旋律 下一拍的主旋律 elif backward_beat_it < 2: chord_prediction_input.append([ cur_step % 4, self.melody_out_pats[backward_beat_it] + self.melody1_code_add_base, self.melody_out_pats[backward_beat_it + 1] + self.melody2_code_add_base, self.chord_code_add_base ]) else: chord_prediction_input.append([ cur_step % 4, self.melody_out_pats[backward_beat_it] + self.melody1_code_add_base, self.melody_out_pats[backward_beat_it + 1] + self.melody2_code_add_base, self.cc_pat_out[cur_step - 1] + self.chord_code_add_base ]) # 2.2.生成输出数据 chord_predict = self.predict( session, [chord_prediction_input]) # LSTM预测 得到二维数组predict out_pat_dx = pat_predict_addcode( chord_predict, self.chord_code_add_base, 1, self.train_data.cc_pat_num ) # 将二维数组predict通过概率随机生成一维数组chord_out_vector,这个数组就是这两小节的和弦。每两小节生成一次和弦 self.cc_pat_out.append(out_pat_dx) # 和弦-和弦编码的输出 if self.train_data.all_cc_pats[out_pat_dx][ 0] != self.train_data.all_cc_pats[out_pat_dx][1]: DiaryLog.warn( '在第%d拍, 预测方法选择了两个不同的和弦,编码分别是%d和%d' % (self.beat_dx, self.train_data.all_cc_pats[out_pat_dx][0], self.train_data.all_cc_pats[out_pat_dx][1])) try: new_chord, out_pat_dx = get_1chord_2steps( self.train_data.all_cc_pats[out_pat_dx], self.melody_out_notes[self.beat_dx * 8:(self.beat_dx + 2) * 8], self.train_data.all_cc_pats) DiaryLog.warn('在第%d拍, 和弦已经被替换为编码%d, 它在数组中的位置为%d' % (self.beat_dx, new_chord[0], out_pat_dx)) except RuntimeError: DiaryLog.warn('在第%d拍, 和弦第%02d次打回,两拍和弦不相同且没法找到替代' % (self.beat_dx, self.rollback_times)) self.beat_dx -= 2 self.cc_pat_out = self.cc_pat_out[:(-1)] self.rollback_times += 1 return self.cc_pat_out[-1] = out_pat_dx self.chord_out.extend( self.train_data.all_cc_pats[out_pat_dx]) # 添加到最终的和弦输出列表中 self.beat_dx += 2 # 步长是2拍
def __init__(self, raw_melody_data, melody_pat_data, common_melody_pats, section_data, continuous_bar_data): self.input_data = [] # 输入model的数据 self.output_data = [] # 从model输出的数据 self.ShiftConfidence = IntroShiftConfidenceCheck() # 1.从数据集中读取intro/interlude信息,并变更为以音符步长为单位的列表 raw_intro_data = get_raw_song_data_from_dataset('intro') raw_interlude_data = get_raw_song_data_from_dataset('interlude') for song_it in range(TRAIN_FILE_NUMBERS): if raw_intro_data[song_it] != dict(): raw_intro_data[song_it] = flat_array(raw_intro_data[song_it]) else: raw_intro_data[song_it] = [ ] # 对于没有intro/interlude的歌曲,将格式转化为list格式 if raw_interlude_data[song_it] != dict(): raw_interlude_data[song_it] = flat_array( raw_interlude_data[song_it]) else: raw_interlude_data[song_it] = [] # 2.对数据进行处理,调整前奏/间奏音符的音高 防止出现前奏/间奏和主旋律音高差异过大的问题 for song_it in range(TRAIN_FILE_NUMBERS): if raw_intro_data[song_it] and raw_melody_data[song_it]: raw_intro_data[song_it] = adjust_intro_pitch( raw_melody_data[song_it], raw_intro_data[song_it]) if raw_interlude_data[song_it] and raw_melody_data[song_it]: raw_interlude_data[song_it] = adjust_intro_pitch( raw_melody_data[song_it], raw_interlude_data[song_it]) # 3.生成每首歌的旋律变化累积幅度数据 # 3.1.生成每段前奏/间奏的音高变化情况 for song_it in range(TRAIN_FILE_NUMBERS): if raw_intro_data[song_it]: self.ShiftConfidence.train_1song( raw_melody_data=raw_melody_data[song_it], raw_intro_data=raw_intro_data[song_it], continuous_bar_data=continuous_bar_data[song_it]) if raw_interlude_data[song_it]: self.ShiftConfidence.train_1song( raw_melody_data=raw_melody_data[song_it], raw_intro_data=raw_interlude_data[song_it], continuous_bar_data=continuous_bar_data[song_it]) # 3.2.找出旋律变化和段落内差异前90%所在位置 self.ShiftConfidence.calc_confidence_level(0.9) self.ShiftConfidence.store('intro_shift') # 4.获取前奏模型的输入输出数据 for song_it in range(TRAIN_FILE_NUMBERS): if raw_intro_data[song_it] and melody_pat_data[song_it]: intro_pat_data = MelodyPatternEncode(common_melody_pats, raw_intro_data[song_it], 0.125, 1).music_pattern_list self.get_intro_model_io_data(intro_pat_data, melody_pat_data[song_it], continuous_bar_data[song_it], section_data[song_it]) # 5.获取间奏模型的输入输出数据 for song_it in range(TRAIN_FILE_NUMBERS): if raw_interlude_data[song_it] and melody_pat_data[song_it]: interlude_pat_data = MelodyPatternEncode( common_melody_pats, raw_interlude_data[song_it], 0.125, 1).music_pattern_list # 前奏和间奏的common patterns沿用主旋律的 self.get_interlude_model_io_data(interlude_pat_data, melody_pat_data[song_it], continuous_bar_data[song_it]) np.save(os.path.join(PATH_PATTERNLOG, 'IntroInputData.npy'), self.input_data) # 在generate的时候要比较生成数据和训练集是否雷同,因此这个也要存储 DiaryLog.warn( 'Generation of intro and interlude train data has finished!')
def __init__(self, melody_pat_data, continuous_bar_data, corenote_pat_data, common_corenote_pats, chord_cls): """ :param melody_pat_data: 主旋律组合的数据 :param continuous_bar_data: 连续小节的计数数据 :param corenote_pat_data: 主旋律骨干音的组合数据 :param common_corenote_pats: 主旋律骨干音组合的对照表 :param chord_cls: 0.95开始这个参数变成和弦的类而不只是最后的和弦数据 因为需要用到和弦类的方法 """ self.input_data = [] # 输入model的数据 self.output_data = [] # 从model输出的数据 # 1.从数据集中读取歌的string数据 raw_string_data = [] # 四维数组 第一维是string的编号 第二维是歌曲id 第三维是小节列表(dict) 第四维是小节内容 part_count = 1 while True: string_part_data = get_raw_song_data_from_dataset('string' + str(part_count), None) # 从sqlite文件中读取的数据。三维数组,第一维是歌的id,第二维是小节的id,第三维是小节内容 if string_part_data == [dict() for t in range(TRAIN_FILE_NUMBERS)]: break raw_string_data.append(string_part_data) part_count += 1 del part_count for part_it in range(len(raw_string_data)): for song_it in range(TRAIN_FILE_NUMBERS): if raw_string_data[part_it][song_it] != dict(): raw_string_data[part_it][song_it] = flat_array(raw_string_data[part_it][song_it]) else: raw_string_data[part_it][song_it] = [] # 对于没有和弦的歌曲,将格式转化为list格式 # 2.获取根音组合及根音-和弦配对组合 self.string_avr_root = get_nearest_number_multiple(STRING_AVR_NOTE, 12) self.root_data, self.rc_pat_data, self.all_rc_pats, rc_pat_count = chord_cls.get_root_data(self.string_avr_root) self.rc_pat_num = len(self.all_rc_pats) rc_pattern_cls = BaseMusicPatterns() rc_pattern_cls.common_pattern_list = self.all_rc_pats rc_pattern_cls.pattern_number_list = rc_pat_count rc_pattern_cls.store('StringRC') # 3.将原数据转化成相对音高形式 rel_note_list = [[[] for t0 in range(TRAIN_FILE_NUMBERS)] for t in range(len(raw_string_data))] for part_it in range(len(raw_string_data)): for song_it in range(TRAIN_FILE_NUMBERS): if raw_string_data[part_it][song_it] and melody_pat_data[song_it]: rel_note_list[part_it][song_it] = one_song_rel_notelist_chord(raw_string_data[part_it][song_it], self.root_data[song_it], chord_cls.chord_data[song_it]) # 4.获取最常见的弦乐组合 common_pattern_cls = CommonMusicPatterns(COMMON_STRING_PAT_NUM) # 这个类可以获取常见的弦乐组合 common_pattern_cls.train(rel_note_list, 0.25, 2, multipart=True) common_pattern_cls.store('String') # 存储在sqlite文件中 self.common_string_pats = common_pattern_cls.common_pattern_list # 常见的旋律组合列表 # 5.获取用于验证的数据 # 5.1.生成每首歌的piano_guitar的前后段差异,以及piano_guitar与同时期和弦的差异 string_confidence_config = StringConfidenceCheckConfig() self.StringConfidence = AccompanyConfidenceCheck(string_confidence_config) for part_it in range(len(raw_string_data)): for song_it in range(TRAIN_FILE_NUMBERS): if chord_cls.chord_data[song_it] and raw_string_data[part_it][song_it]: self.StringConfidence.train_1song(raw_data=raw_string_data[part_it][song_it], chord_data=chord_cls.chord_data[song_it]) # 5.2.找出前90%所在位置 self.StringConfidence.calc_confidence_level(0.9) self.StringConfidence.store('string') # 6.将其编码为组合并获取模型的输入输出数据 for part_it in range(len(raw_string_data)): for song_it in range(TRAIN_FILE_NUMBERS): if raw_string_data[part_it][song_it] and melody_pat_data[song_it]: string_pat_data = StringPatternEncode(self.common_string_pats, rel_note_list[part_it][song_it], 0.25, 2).music_pattern_list self.get_model_io_data(string_pat_data, melody_pat_data[song_it], continuous_bar_data[song_it], corenote_pat_data[song_it], self.rc_pat_data[song_it]) DiaryLog.warn('Generation of string train data has finished!')
def run_epoch_one_hot(self, session, pattern_number=-1): assert len(self.train_data.input_data) == len( self.train_data.output_data) # 1.随机选出一些数据用于训练 其余的用于验证 all_disrupt_input = np.array( [[self.train_data.input_data[t], self.train_data.output_data[t]] for t in range(len(self.train_data.input_data))]) np.random.shuffle(all_disrupt_input) # 将原始数据打乱顺序 train_data_num = int( len(self.train_data.input_data) * TRAIN_DATA_RADIO) # 一共有多少组向量用于训练 train_data = all_disrupt_input[0:train_data_num] # 训练数据 valid_data = all_disrupt_input[train_data_num:] # 验证数据 # 2.进行训练 for epoch in range(self.config.max_max_epoch): # 2.1.训练数据 num_batch = len(train_data) // self.config.batch_size lr_decay = self.config.lr_decay**max( epoch + 1 - self.config.max_epoch, 0.0) # 求出学习速率的衰减值 train_disrupt_input = copy.deepcopy( train_data) # 每一次训练都重新打乱模型的输入数据 np.random.shuffle(train_disrupt_input) DiaryLog.warn( '%s的输入向量个数为%d, 一共分为%d个batch.' % (self.variable_scope_name, len(train_data), num_batch)) for batch in range(num_batch): batches_input = train_disrupt_input[ self.config.batch_size * batch:self.config.batch_size * (batch + 1), 0] batches_output = train_disrupt_input[ self.config.batch_size * batch:self.config.batch_size * (batch + 1), 1] loss, _, __ = session.run( [ self.train_model.total_loss, self.train_model.last_state, self.train_model.train_op, ], feed_dict={ self.input_data: batches_input, self.output_data: batches_output, self.learning_rate: self.config.learning_rate * lr_decay }) if batch % 10 == 0: DiaryLog.warn( 'Epoch: %d , batch: %d , training loss: %.6f' % (epoch, batch, loss)) # 2.2.用验证集评价训练结果 评价指标是准确率 num_right_predict = 0 # 一共有多少组数据验证正确/错误 num_wrong_predict = 0 num_valid_batch = len(valid_data) // self.config.batch_size DiaryLog.warn( '%s验证集的输入向量个数为%d, 一共分为%d个batch.' % (self.variable_scope_name, len(valid_data), num_valid_batch)) for batch in range(num_valid_batch): batches_input = valid_data[self.config.batch_size * batch:self.config.batch_size * (batch + 1), 0] batches_output = valid_data[self.config.batch_size * batch:self.config.batch_size * (batch + 1), 1] predict_matrix, __ = session.run( [ # 验证模型的输出结果 共batch_size行 input_size列 self.valid_model.prediction, self.valid_model.last_state ], feed_dict={ self.input_data: batches_input, self.output_data: batches_output }) right_num, wrong_num = self.valid( predict_matrix, batches_output, pattern_number) # 由于各个模型的评价方式并不完全相同 这里单独写个函数出来用于继承 num_right_predict += right_num num_wrong_predict += wrong_num accuracy = num_right_predict / ( num_right_predict + num_wrong_predict) # 正确率 DiaryLog.warn('%s的第%d个Epoch, 验证正确的个数为%d, 验证错误的个数为%d, 正确率为%.4f' % (self.variable_scope_name, epoch, num_right_predict, num_wrong_predict, accuracy))
def output_rawdata(session, stream_in): """ 输出使用算法生成的音符数据 输出很多段从模型输出的数据 :param session: :param stream_in: 经训练调整参数(或从文件中获取参数)之后的模型 :return: 很多段从模型输出的数据 """ stream_out = dict() while True: melody_fail_time = 0 intro_fail_time = 0 # 1.生成主旋律 while True: try: stream_out[ 'melody'], melody_out_pats, core_note_out_pats, keypress_out_pats = stream_in[ 'melody'].generate(session) break except RuntimeError: DiaryLog.warn('主旋律已连续生成失败%d次' % melody_fail_time) melody_fail_time += 1 stream_out['section'] = copy.deepcopy( stream_in['melody'].section_data) # 主旋律的乐段数据 # 2.生成前奏(melody短生成的情况下没有前奏和间奏) if SystemArgs.MelodyMethod != 'short': while True: try: stream_out[ 'intro'], intro_out_pats, intro_cn_out_pats, intro_kp_out_pats = stream_in[ 'intro'].generate(session, stream_out['melody'], melody_out_pats) break except RuntimeError: DiaryLog.warn('前奏已连续生成失败%d次' % intro_fail_time) intro_fail_time += 1 else: stream_out['intro'] = [] intro_out_pats = [] intro_cn_out_pats = [] intro_kp_out_pats = [] melody_intro_out = copy.deepcopy(stream_out['melody']) melody_intro_out.extend( stream_out['intro']) # 结合了主旋律和前奏的输出(注意是主旋律在前 前奏在后) melody_out_pats.extend(intro_out_pats) core_note_out_pats.extend(intro_cn_out_pats) keypress_out_pats.extend(intro_kp_out_pats) melody_beat_num = (len(stream_out['melody']) + len(stream_out['intro'])) // 8 # 主旋律一共有多少拍 end_check_beats = [len(stream_out['melody']) // 8] # 在哪几拍执行end_check # 3.生成其他的几个音轨(除了加花以外) try: stream_out['chord'] = generate_1track( stream_in['chord'], 'chord', session, melody_intro_out, melody_out_pats, stream_in['melody'].train_data.common_corenote_pats, core_note_out_pats, melody_beat_num, end_check_beats) # 在生成和弦和打击乐的时候也使用没有调式限制的主旋律pattern数据 stream_out['drum'] = generate_1track(stream_in['drum'], 'drum', session, melody_intro_out, melody_out_pats) stream_out['bass'] = generate_1track(stream_in['bass'], 'bass', session, melody_intro_out, keypress_out_pats, stream_out['chord'], melody_beat_num, end_check_beats) stream_out['pg'] = generate_1track(stream_in['pg'], 'piano_guitar', session, melody_intro_out, keypress_out_pats, stream_out['chord'], melody_beat_num, end_check_beats) stream_out['string'] = generate_1track( stream_in['string'], 'string', session, melody_intro_out, stream_out['chord'], core_note_out_pats, melody_beat_num, end_check_beats) except RuntimeError: continue # 4.生成加花 fill_fail_time = 0 while True: try: judge_fill_list = stream_in['fill'].judge_fill( stream_out['melody'], stream_in['melody'].section_data) if sum(judge_fill_list): # 加花不能为空 stream_out['fill'] = stream_in['fill'].generate( stream_out['melody'], stream_out['chord'][:len(stream_out['melody']) // 8], judge_fill_list) # 生成加花的具体内容 break else: fill_fail_time += 1 DiaryLog.warn('加花数据为空,重新判断,已经重试了%d次' % fill_fail_time) except IndexError: DiaryLog.warn('加花已连续生成失败%d次' % fill_fail_time) fill_fail_time += 1 return stream_out