精华内容
下载资源
问答
  • 钢琴MIDI数据集

    2021-03-09 15:45:26
    这里写自定义目录标题欢迎使用Markdown编辑器了解下全球最大钢琴MIDI数据集(万余首钢琴作品、一千多个小时,字节跳动发布)数据集特点 欢迎使用Markdown编辑器 了解下全球最大钢琴MIDI数据集(万余首钢琴作品、一千...

    了解下全球最大钢琴MIDI数据集(万余首钢琴作品、一千多个小时,字节跳动发布)

    论文地址:https://arxiv.org/abs/2010.07061
    项目地址:https://github.com/bytedance/GiantMIDI-Piano

    数据集特点

    GiantMIDI-Piano 数据集具备以下特点:

    包含来自 2,784 位作曲家 10,854 首作品的 MIDI 文件。
    包含 34,504,873 个音符。
    所有的曲目都是不同的,MIDI 文件的总时长为 1,237 小时。
    由高精度转谱系统转谱音频而成。转谱的 MIDI 文件包括音符的起始时间、力度和踏板信息。
    GiantMIDI-Piano 的转谱相对错误率为 0.094,在 Maestro 钢琴数据集上的转谱 F1 值为 96.72%。
    所有 MIDI 文件都有统一的格式,文件名格式为「姓_名_曲目名_youtubeID.mid」。
    包含作曲家国籍和出生年份信息。
    数据集大小为 193 Mb。
    使用许可为 CC BY 4.0。

    更多详情:

    https://zhuanlan.zhihu.com/p/269218623

    展开全文
  • 数据集中筛选并转谱了来自 2,786 位作曲家的 10,854 部钢琴作品,不同曲目的总时长达到了 1,237 小时,是谷歌 MAESTRO 数据集的 14 倍,填补了计算机音乐领域一直缺少一个大规模的钢琴 MIDI 数据集的空白。...
    本文转载自公众号:字节跳动技术范儿

    ‍‍‍‍‍‍‍在上海纽约大学的 Music X Lab,一架自动演奏钢琴正在播放古典钢琴独奏曲《钟》。原本只有两个人的实验室里,不断有人循着钢琴声加入,大家安静地听着,直至曲终。

    听众里的孔秋强来自字节跳动,作为 GiantMIDI-Piano(钢琴转谱)研究项目的牵头人,是他的团队创造了这首可以通过录音转谱、被自动演奏的MIDI琴曲。

     通过自动演奏钢琴播放转谱MIDI重构李云迪演奏的《钟》

    “之前从来没想象过,播放一瞬间才感受到钢琴家亲自演奏的震撼,这种感觉非常满足。”

    不久后,一则「钢琴转谱」相关的科技新闻在国内外引发了关注。

    全球最大古典钢琴数据集发布

    10月,字节跳动发布全球最大的古典钢琴数据集 GiantMIDI-Piano,开发并开源了一套高精度钢琴转谱系统。

    数据集中筛选并转谱了来自 2,786 位作曲家的 10,854 部钢琴作品,不同曲目的总时长达到了 1,237 小时,是谷歌 MAESTRO 数据集的 14 倍,填补了计算机音乐领域一直缺少一个大规模的钢琴 MIDI 数据集的空白。

    GiantMIDI-Piano 的用途包括但不限于音乐信息检索自动作曲智能音乐创作计算机音乐学等。

    简单说,GiantMIDI-Piano 把国际音乐数字图书馆上能找到,并能在网上搜索到的所有古典钢琴曲转换成了 MIDI 文件,并且在数据规模和精度上做到了世界顶级。

    “我们发表的第一篇论文介绍了钢琴转谱系统,被 Twitter 上一位硅谷科技博主转发推广后,收到了很多正面反馈。”孔秋强注意到不少人对这套钢琴转谱系统的易用性和精确性大加赞叹。

     团队发表的第一篇论文

    团队紧接着发表了第二篇论文《GiantMIDI-Piano: A large-scale MIDI dataset for classical piano music》,公布了数据集、将数据和代码进行了开源,在计算机音乐科学领域接连投下重磅惊喜。

    这套数据集和相关论文一经发布,就收获了来自硅谷和世界各地的科技大V、音乐家、作曲家、研究学者和科技媒体的转发和好评。

    很多音乐家、作曲家试用了这个钢琴转谱系统的工具包,体验了在1-2分钟里搭建起一个系统、把自己的曲子转换成 MIDI 的过程,使用门槛大大降低了。部分使用者认为,GiantMIDI-Piano 比市场上现有的转谱系统要更加流畅、自然。

    另外,他们发现这套转谱系统里包含了对钢琴按键力度、钢琴踏板等等这些非常复杂、对钢琴的表现力也非常重要的数据维度,整体更加精确。

     美国纽约知名钢琴家、作曲家Dan Tepfer“点赞”

    有了这个数据集,想把钢琴曲谱转换成 MIDI 文件,可能只需要点几下鼠标就可以下载。不少人表示“梦想了这么多年的事,终于有人实现了!”、“这是计算机音乐史上一个重要的里程碑!”

    不仅要追赶领先者,也要成为领先者

    去年12月,在英国萨里大学拿到电子信息工程博士学位的孔秋强经过小半年实习后,正式加入字节跳动 Speech, Audio and Music Intelligence (SAMI)组,这是一支从硅谷成长壮大,base在美国、欧洲和国内三地的国际化团队,致力于通过智能音频技术赋能内容互动和创作

    在和众多拥有博士学位、曾供职于 Google、Spotify 等业内顶尖公司的专家的共事中,秋强专注于对通用声音的识别、监测和分类的工作。

    除了支持集团业务需求之外,围绕潜在的实际场景,团队也会布局更长期的前沿研究工作。

    做钢琴转谱这个项目的动机,就是通过基础性研究,与 Google、Facebook 这些业内顶尖的公司一道,运用技术解决本质的问题,甚至实打实地在研究领域去引领一些方向,“我们不仅要跟随别人,也要让别人跟随我们。”

    抱着这样的想法,孔秋强把目光投向了他一直颇感兴趣、并随时关注着最新研究动态的计算机音乐领域。

    “如果稍微熟悉这个领域就会发现,在图像和声音领域分别有 ImageNet 和 AudioSet 等大型数据集,在业界的影响力都非常大。但是在音乐领域,缺少一个这样量级的数据集。”

    做 GiantMIDI-Piano,就是希望能引领未来 10 年甚至 30 年里计算机音乐学的发展。我们为大家准备好数据材料,方便后续的研究者们在这个领域做更深入的研究。

    除了数据集,在技术层面,最让行业内的专家觉得惊喜的是,GiantMIDI-Piano 的转谱系统可以将钢琴按键的时间精确到 1 毫秒这个量级,突破了之前 Google 算法 32 毫秒识别精度的限制。

    “这是一个非常平滑的预测,实现了任意时间精度的音符检测,所以我们叫做高精度预测。”

    媒体报道后,孔秋强收到了几十个人发来的邮件,向他表示祝贺、希望交流分享等等。也有不少朋友表示希望加入字节跳动,一些在海外科技大厂工作的博士生同学说,“在 ByteDance 可以做这么有意思的事情,影响未来发展,非常了不起。”

     机器之心海外版报道

    对于技术的未来前景,机器之心海外版引用业内专家的分析——

    “这项工作无疑是令人振奋的,字节跳动可能会将这项技术应用于后疫情时代的线上音乐直播、智能音乐创作中。”

    “能做GiantMIDI-Piano,是因为我们有领先的技术”

    孔秋强坦言,在 2018 年读博的时候就有了这个 idea,但受限于当时的技术手段无法实现。

    “很多人都意识到制作一个大型 MIDI 数据库的必要性,可是受到技术制约做不出来。做这个事情是需要技术积累的,我们现在拥有领先的技术,充足的计算资源,顶级的科学家团队,才有能力制作 GiantMIDI-Piano。”

    从今年1月份开始,有了不断的积累沉淀和团队的支持,觉得技术手段上允许了,他开始着手准备 GiantMIDI-Piano 的工作。

    GiantMIDI-Piano 项目涉及到的技术问题非常庞杂,其中最困难的部分是怎么把所有的 idea 组织起来、做好顶层设计,并一一拆解技术难点。

    经过从上往下的梳理,团队列出了想要做成 GiantMIDI-Piano 必须攻克的难关——

    • 如何做钢琴转谱?

    • 如何把古典钢琴音乐家的信息收集起来?

    • 如何找到钢琴曲对应的音频并保证正确?

    • 如何设计钢琴独奏检测系统?

    • 如何评价转谱结果?

    这些难点,每一个都可以拆开成无数个更小的点,需要一一解决。比如「钢琴转谱」这一项,可能随便数一下就有一系列小的挑战——

    • 动态范围大:钢琴琴键按下的时间非常精确,差 0.1 秒,听着都会不舒服;

    • 力度估量难:有功力的人听一段钢琴曲可能就能扒谱子扒下来,但是很难判断每个音的音量;

    • 复音乐系统:钢琴是复音乐器,不同的按键随机组合有上万种可能,很难精准识别;

    • 风格多样:巴赫、李斯特、近现代等等不同的钢琴流派和音乐风格之间,跨度很大;

    • 踏板因素:钢琴踏板这个因素在之前的研究里很少被触及,但其实很重要。

     GiantMIDI-Piano 中前 100 位不同作曲家的曲目数量分布

    为了搞定这些技术难点,做到高精度,GiantMIDI-Piano 的技术方案中用到了诸多当今领先的技术。

    • 提出通过回归具体的时间,而不是分类去实现高精度,在训练过程中采用了回归距离的方法,去代替 Google 用的分类的方法,让训练的目标能以任意精度描述钢琴的起始时间和结束时间,这是一个创新,而且这个方法同时运用到了 onset 和 offset 当中,比分类的方法要稳定;

    • 测试过程中提出了一种数学解析的算法,能够以任意精度预测 onset 开始点和 offset 结束的时间位置信息,同样这套高精度的算法也运用到了钢琴踏板转谱的技术里。

     第1-3行:前人提出方法的训练目标
    第4行:我们提出方法的训练目标

    低头做事的同时,也别忘了仰望星空

    团队认为,GiantMIDI-Piano 这项研究的意义在于,通过研究钢琴转谱,可以反哺基础技术的研究。

    比如在音乐信息检索的场景中,钢琴转谱反哺的音频事件检测技术可以帮助识别一首歌曲中哪一部分是歌声,另外在短视频和特效领域也值得期待。

    说起 GiantMIDI-Piano 在未来可能的应用场景,孔秋强难掩激动,描述了通过转谱后的 MIDI 文件来重构伟大钢琴家们演出场景的可能性。

    转谱之后,把谱子用U盘插到特制的钢琴上,就可以现场听,立体声环绕。以后大家就不用买钢琴 CD 了,相当于把音乐家请到自己家里来。

    比如今年是贝多芬诞辰 250 周年,把观众聚集到一个音乐厅,台上放一架可以自动演奏的钢琴,插上U盘,听众身临其境,真实还原钢琴现场演奏的美妙。

     通过自动钢琴播放转谱MIDI与演奏原版的比对合辑

    在研究过程中,他们发现古典钢琴音乐的数据集竟然只有 1 万部钢琴作品,比之前想的少很多。

    “通用的、大规模的、音乐领域的音频数据集,有很多可以专注的方向和可能性。这个项目叫 GiantMIDI-Piano,加一个 Piano 的后缀,其实就是给之后留空间,这个后缀说不定也可以是交响乐。”

    此外,GiantMIDI-Piano 的研究也是学术界和工业界合作的典型案例。“学术界的很多想法不能顺利落地,就是因为缺少公司合作渠道和实际场景的驱动。”

    我们能把这个事情做成,是因为有足够的计算资源、优秀的人才、良好的机制和创新氛围,这些都是我们公司的优势。

    未来是大数据的时代,需要学术界和工业界紧密合作。

    GiantMIDI-Piano 项目的成果发表后,同事们都在祝贺孔秋强和项目团队。他有些不好意思:

    “团队有很多博士,每个人都会发起科研项目,公司会提供空间鼓励大家找到自己专注的研究领域。除了钢琴转谱之外,SAMI 团队在语音合成、音频处理和识别、音乐理解和创作等等方向都有很多积累和创新。其实大家做得都非常好、非常棒。”

    但让团队感触最深还是 leader 给他们的激励——希望大家在低头做事的时候,也要抬头仰望星空。

    延伸阅读

    • “High-resolution Piano Transcription with Pedals by Regressing Onsets and Offsets Times”.
      论文地址:https://arxiv.org/abs/2010.01815

    • “GiantMIDI-Piano: A large-scale MIDI dataset for classical piano music”.
      论文地址:https://arxiv.org/abs/2010.07061

    ↓点击投递,加入字节跳动技术团队!

    展开全文
  • midi-dataset, 创建 MIDI ground数据集的代码 MIDI数据集项目的目标是匹配和对齐很大的MIDI文件集合,以便使MIDI数据可以用于推断音频信息。 这里外,这个存储库还包含了在 [1] 中重现大多数结果的代码,它描述了这...
  • 生成更改和损坏的 MIDI 摘录数据集的工具 - ACME数据集。 用于清理自动音乐转录系统输出的基线模型。 随附的论文“MIDI 降级工具包:符号音乐增强和校正”详细描述了该工具包及其动机。 有关从纸上复制结果的说明...
  • 使用pretty_midi库将MIDI文件转化成稀疏矩阵,通过PyTorch的Dataset类来构建数据集

    前言

    上篇文章中,我介绍了如何通过编写爬虫来从 Free Midi Files Download 网站上爬取海量的MIDI数据。本篇文章介绍的是使用 pretty_midi 库来将MIDI文件转化成矩阵,并通过PyTorch的Dataset类来构建数据集,为之后的训练与测试中传入张量做准备。

    实施过程

    将MIDI文件转化成稀疏矩阵信息并存储

    构建数据集的第一步是将MIDI文件中的音乐信息以(时间,音高)的矩阵形式提取出来,并以稀疏矩阵的形式来保存到npz文件中。pretty_midi库提供了在每一个音轨中遍历音符(Note),并得到每个音符的音高(pitch),音符开始时间(note_on)和音符结束时间(note_off),将开始和结束时间分别除以十六分音符的长度(60秒 / 120BPM / 4),就可以得到开始和结束的时间在矩阵中对应的位置。

    代码详见 MusicCritique/util/data/create_database.py

    def generate_nonzeros_by_notes():
        root_dir = 'E:/merged_midi/'
    
        midi_collection = get_midi_collection()
        genre_collection = get_genre_collection()
        for genre in genre_collection.find():
            genre_name = genre['Name']
            print(genre_name)
            npy_file_root_dir = 'E:/midi_matrix/one_instr/' + genre_name + '/'
            if not os.path.exists(npy_file_root_dir):
                os.mkdir(npy_file_root_dir)
    
            for midi in midi_collection.find({'Genre': genre_name, 'OneInstrNpyGenerated': False}, no_cursor_timeout = True):
                path = root_dir + genre_name + '/' + midi['md5'] + '.mid'
                save_path = npy_file_root_dir + midi['md5'] + '.npz'
                pm = pretty_midi.PrettyMIDI(path)
                # segment_num = math.ceil(pm.get_end_time() / 8)
                note_range = (24, 108)
                # data = np.zeros((segment_num, 64, 84), np.bool_)
                nonzeros = []
                sixteenth_length = 60 / 120 / 4
                for instr in pm.instruments:
                    if not instr.is_drum:
                        for note in instr.notes:
                            start = int(note.start / sixteenth_length)
                            end = int(note.end / sixteenth_length)
                            pitch = note.pitch
                            if pitch < note_range[0] or pitch >= note_range[1]:
                                continue
                            else:
                                pitch -= 24
                                for time_raw in range(start, end):
                                    segment = int(time_raw / 64)
                                    time = time_raw % 64
                                    nonzeros.append([segment, time, pitch])
    
                nonzeros = np.array(nonzeros)
                np.savez_compressed(save_path, nonzeros)
                midi_collection.update_one({'_id': midi['_id']}, {'$set': {'OneInstrNpyGenerated': True}})
                print('Progress: {:.2%}'.format(
                    midi_collection.count({'Genre': genre_name, 'OneInstrNpyGenerated': True}) / midi_collection.count({'Genre': genre_name})), end='\n')
    
    
    • 为了方便存储,我将每个MIDI文件以四个小节为单位进行分割,考虑到的最短时长单位是十六分音符,这样每个矩阵的第一维度大小是64(4*16),代表音符在时间上的分布情况。
    • MIDI文件音高数值范围在0~127,可以存储从A0到G9的横跨10个八度的音高,对应关系可以参考 MIDI NOTE NUMBERS AND CENTER FREQUENCIES 。在这些音里面很多音符是几乎不会出现在真实的音乐中的。为了使得到的矩阵更为稠密,在处理的过程中忽略了过大和过小的数值,只提取了数值在24-108的音符,即C1-C8这84个音高,基本上与钢琴的音域相同。
    • 最后,同样为了矩阵更为稠密,提高训练效果,我将除去鼓轨外的所有乐器音轨合成到一起,统一记录音符,而不区分乐器种类。

    考虑到以上三点,根据每一个MIDI文件得到的矩阵形式即[包含的四小节乐段数*1*64*84]。为了降低空间占用,保存在文件中的信息是矩阵中每一个非零点的坐标信息,后面可以通过这些坐标来构建稀疏矩阵。

    合并某个风格的所有稀疏矩阵

    通过上一步,我们已经将MIDI文件中的音乐信息以稀疏矩阵坐标的形式存储在了单独的npz文件中,为了方便构造数据集,我尝试将每个风格的所有稀疏矩阵统一存储。
    代码详见 MusicCritique/util/data/create_database.py

    def merge_all_sparse_matrices():
        midi_collection = get_midi_collection()
        genre_collection = get_genre_collection()
        root_dir = 'E:/midi_matrix/one_instr/'
    
        time_step = 64
        valid_range = (24, 108)
    
        for genre in genre_collection.find({'DatasetGenerated': False}):
            save_dir = 'd:/data/' + genre['Name']
            if not os.path.exists(save_dir):
                os.mkdir(save_dir)
            print(genre['Name'])
            whole_length = genre['ValidPiecesNum']
    
            shape = np.array([whole_length, time_step, valid_range[1]-valid_range[0]])
    
            processed = 0
            last_piece_num = 0
            whole_num = midi_collection.count({'Genre': genre['Name']})
    
            non_zeros = []
            for midi in midi_collection.find({'Genre': genre['Name']}, no_cursor_timeout=True):
    
                path = root_dir + genre['Name'] + '/' + midi['md5'] + '.npz'
                valid_pieces_num = midi['PiecesNum'] - 1
    
                f = np.load(path)
                matrix = f['arr_0'].copy()
                print(valid_pieces_num, matrix.shape[0])
                for data in matrix:
                    try:
                        data = data.tolist()
    
                        if data[0] < valid_pieces_num:
                            piece_order = last_piece_num + data[0]
                            non_zeros.append([piece_order, data[1], data[2]])
                    except:
                        print(path)
    
                last_piece_num += valid_pieces_num
                processed += 1
    
                print('Progress: {:.2%}\n'.format(processed / whole_num))
    
            non_zeros = np.array(non_zeros)
            print(non_zeros.shape)
            np.savez_compressed(save_dir + '/data_sparse' + '.npz', nonzeros=non_zeros, shape=shape)
    
            genre_collection.update_one({'_id': genre['_id']}, {'$set': {'DatasetGenerated': True}})
    
    

    这个函数中genre的ValidPiecesNum域是之前添加的,意义是某一类的所有MIDI文件的四小节数目之和,并从这之中扣除了最后不满一小节的部分。

    将稀疏矩阵转化为矩阵

    由于所有的非零的坐标信息已经保存在了npz文件中,通过遍历这些坐标信息并将这些坐标点的数值设置为1.0,就可以得到矩阵。

    def generate_sparse_matrix_of_genre(genre):
        npy_path = 'D:/data/' + genre + '/data_sparse.npz'
        with np.load(npy_path) as f:
            shape = f['shape']
            data = np.zeros(shape, np.float_)
            nonzeros = f['nonzeros']
            for x in nonzeros:
                data[(x[0], x[1], x[2])] = 1.
        return data
    

    继承Dataset类,编写自定义数据集

    通过继承PyTorch的Dataset类,并对几个重要函数进行重写,参考官方文档
    代码详见 MusicCritique/util/data/dataset.py

    class SteelyDataset(data.Dataset):
        def __init__(self, genreA, genreB, phase, use_mix):
            assert phase in ['train', 'test'], 'not valid dataset type'
    
            sources = ['metal', 'punk', 'folk', 'newage', 'country', 'bluegrass']
    
            genre_collection = get_genre_collection()
    
            self.data_path = 'D:/data/'
    
            numA = genre_collection.find_one({'Name': genreA})['ValidPiecesNum']
            numB = genre_collection.find_one({'Name': genreB})['ValidPiecesNum']
    
            train_num = int(min(numA, numB) * 0.9)
            test_num = min(numA, numB) - train_num
            if phase is 'train':
                self.length = train_num
    
                if use_mix:
                    dataA = np.expand_dims(generate_sparse_matrix_of_genre(genreA)[:self.length], 1)
                    dataB = np.expand_dims(generate_sparse_matrix_of_genre(genreB)[:self.length], 1)
                    mixed = generate_sparse_matrix_from_multiple_genres(sources)
                    np.random.shuffle(mixed)
                    data_mixed = np.expand_dims(mixed[:self.length], 1)
    
                    self.data = np.concatenate((dataA, dataB, data_mixed), axis=1)
    
                else:
                    dataA = np.expand_dims(generate_sparse_matrix_of_genre(genreA)[:self.length], 1)
                    dataB = np.expand_dims(generate_sparse_matrix_of_genre(genreB)[:self.length], 1)
    
                    self.data = np.concatenate((dataA, dataB), axis=1)
            else:
                self.length = test_num
                dataA = np.expand_dims(generate_sparse_matrix_of_genre(genreA)[:self.length], 1)
                dataB = np.expand_dims(generate_sparse_matrix_of_genre(genreB)[:self.length], 1)
    
                self.data = np.concatenate((dataA, dataB), axis=1)
    
    
        def __getitem__(self, index):
            return self.data[index, :, :, :]
    
        def __len__(self):
            return self.length
    

    继承的重点是重写初始化函数、getitem函数和len函数。在构建数据库的时候,为了方便调用数据,我将dataA和dataB合并到了一起,并取较小数据集的数目来确定总体数据集数目,以保证两种数据大小一致,在这过程中使用了Numpy库中的expand_dims函数来增加维度,concatenate函数来把两个矩阵合并到新增的维度上。

    数据集分享

    大家需要的话可以通过 百度云 下载这一数据集,提取码:nsfi。如在使用过程中遇到问题,请在下面评论,感谢阅读!

    展开全文
  • 用于机器学习的MAPS(MIDI对齐的钢琴声音)数据集python API 该API集成了: pytorch / torchvision:多核数据加载器 mimi / mido:midi文件解析 librosa:数据预处理 有关此项目的当前状态 我还没做 在哪里对MAPS...
  • pretty_midi包含用于处理MIDI数据的实用程序函数/类,因此它采用易于修改和提取信息的格式。 文档可。 您还可以找到Jupyter笔记本教程。 pretty_midi可通过或脚本获得。 为了使用fluidsynth合成MIDI数据,您需要...
  • 通过cookies和Session来爬取免费MIDI文件资源,使用pretty_midi和music21库进行后期处理,构造元数据丰富、质量好的音乐训练数据集。(文末有百度云下载链接)

    前言

    1. 由于毕业设计的课题是通过CycleGAN搭建一个音乐风格转换系统,需要大量的音乐文件来训练神经网络,而MIDI文件作为最广泛使用的一种电脑编曲保存媒介,十分容易搜集资源,也有很多成熟的Python库来对MIDI文件进行处理。
    2. 如今,相关领域的研究最常用的数据集是The Lakh MIDI Dataset 。这一数据集包括十万余个MIDI格式文件,数据的数量是足够使用了,可是通过个人的测试,其质量没有达到我的预期,主要的原因是一首歌对应多个MIDI文件,而且元数据同数据相比远远不足,这两点导致了使用时的困难和不顺手,让我萌生了自己搭建数据集的想法。下面便是我从 Free Midi Files Download 这个网站爬取数据,构建数据集的过程,涉及简单的爬虫知识,和MongoDB数据库的Python API, PyMongo的简单操作。
    3. 如果您对爬虫实施过程中Session和Cookies的使用有疑惑之处,本篇文章也可以为您提供借鉴。若对爬虫的内容不感兴趣,尽可以滚动到本篇文章底部的资源链接,通过百度网盘下载这一数据集,使用时请注明本文链接,希望本文内容可以帮助到您!
    4. 下面的实施过程源代码地址在 josephding23/Free-Midi-Library

    实施过程

    编写爬虫

    爬虫的代码在 Free-Midi-Library/src/midi_scratch.py
    在搜索MIDI资源的过程中,我浏览了很多网站,其中 Free Midi Files Download 这个网站从资源数量以及资源组织形式这两个方面来看都是最优秀的一个,包含的音乐风格有17种之多,这之中摇滚乐的MIDI文件数目达到了9866个,通过结构化的爬取操作,这些文件的元数据(风格、歌手、歌名)都十分完整地保存在了MongoDB数据库中,方便之后的训练和测试。
    爬虫的过程分为以下三个阶段:

    1. 爬取音乐风格信息,得到每个风格的艺术家名称、链接等 ,将信息添加到数据库,通过以下函数实现,比较简单:
    def free_midi_get_genres():
        genres_collection = get_genre_collection()
        for genre in get_genres():
            if genres_collection.count({'name': genre}) != 0:
                continue
            url = 'https://freemidi.org/genre-' + genre
            text = get_html_text(url)
            soup = BeautifulSoup(text, 'html.parser')
            urls = []
            performers = []
            for item in soup.find_all(name='div', attrs={'class': 'genre-link-text'}):
                try:
                    href = item.a['href']
                    name = item.text
                    urls.append(href)
                    performers.append(name)
                except:
                    pass
            genres_collection.insert_one({
                'name': genre,
                'performers_num': len(urls),
                'performers': performers,
                'performer_urls': urls
            })
            print(genre, len(urls))
    
    
    1. 构建艺术家数据表,将所有艺术家的信息添加进去,这一步不需要爬虫,而是为之后的爬取工作做准备:
    def free_midi_get_performers():
        root_url = 'https://freemidi.org/'
        genres_collection = get_genre_collection()
        performers_collection = get_performer_collection()
        for genre in genres_collection.find({'Finished': False}):
            genre_name = genre['Name']
            performers = genre['Performers']
            performer_urls = genre['PerformersUrls']
            num = genre['PerformersNum']
            for index in range(num):
                name = performers[index]
                url = root_url + performer_urls[index]
                print(name, url)
    
                performers_collection.insert_one({
                    'Name': name,
                    'Url': url,
                    'Genre': genre_name,
                    'Finished': False
                })
            genres_collection.update_one(
                {'_id': genre['_id']},
                {'$set': {'Finished': True}})
            print('Progress: {:.2%}\n'.format(genres_collection.count({'Finished': True}) / genres_collection.count()))
    
    1. 爬取每个艺术家的页面,将每个艺术家的所有作品信息添加到新建的MIDI数据表,注意这一步需要针对该网站的反爬虫机制来编辑响应头,重点在于Cookie的设置,具体方法在这里不详细展开,若有不解的地方可以在下面评论,与我交流。
    def get_free_midi_songs_and_add_performers_info():
        root_url = 'https://freemidi.org/'
        midi_collection = get_midi_collection()
        performer_collection = get_performer_collection()
        while performer_collection.count({'Finished': False}) != 0:
            for performer in performer_collection.find({'Finished': False}):
                num = 0
                performer_url = performer['Url']
                performer_name = performer['Name']
                genre = performer['Genre']
                try:
                    params = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
                        'Cookie': cookie_str,
                        'Referer': root_url + genre,
                        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                        'Accept-Encoding': 'gzip, deflate, br',
                        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                        'Connection': 'keep-alive'
                    }
                    text = get_html_text(performer_url, params)
                    if text == '':
                        print('connection error')
                        continue
                    soup = BeautifulSoup(text, 'html.parser')
                    # print(soup)
                    for item in soup.find_all(name='div', attrs={'itemprop': 'tracks'}):
                        try:
                            download_url = root_url + item.span.a['href']
                            name = item.span.text
                            if midi_collection.count({'Genre': genre, 'Name': name}) == 0:
                                midi_collection.insert_one({
                                    'Name': name.replace('\n', ''),
                                    'DownloadPage': download_url,
                                    'Performer': performer_name,
                                    'PerformerUrl': performer_url,
                                    'Genre': genre,
                                    'Downloaded': False
                                })
                            num = num + 1
                        except:
                            pass
                    if num != 0:
                        performer_collection.update_one(
                            {'_id': performer['_id']},
                            {'$set': {'Finished': True, 'Num': num}}
                        )
                        time.sleep(uniform(1, 1.6))
                        print('Performer ' + performer_name + ' finished.')
                        print('Progress: {:.2%}\n'.format(performer_collection.count({'Finished': True}) / performer_collection.count()))
                except:
                    print('Error connecting.')
    
    1. 最后的一步便是爬取MIDI文件,这一步是最复杂的一步,也是我与该网站的反爬虫机制斗志斗勇体验最深刻的一步。因为该网站没有为MIDI资源提供一个直接的下载链接,而是一个getter链接,需要通过GET响应头来获得response中的下载链接。在此处通过响应头来设置cookies已经不足以解决问题,故我使用了Session来维护cookies的一致,这样就更好地模拟了“真人”访问网页时的情况,使得反爬虫无法侦察我的爬虫行为。当然这一方法也不是万无一失,经常遇到的情况是显示错误并循环多次后才能开始下载,有一些无法下载的内容只能从数据库中移除了。
    def download_free_midi():
        root_url = 'https://freemidi.org/'
        root_path = 'E:/free_MIDI'
        cookie_path = './cookies.txt'
        params = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
            # 'Cookie': cookie,
            'Sec-Fetch-Mode': 'navigate',
            'Sec-Fetch-Site': 'same-origin',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Connection': 'keep-alive'
        }
        midi_collection = get_midi_collection()
    
        session = requests.Session()
        requests.packages.urllib3.disable_warnings()
        session.headers.update(params)
        session.cookies = cookies
        while midi_collection.count({'Downloaded': False}) != 0:
            for midi in midi_collection.find({'Downloaded': False}, no_cursor_timeout = True):
                performer_link = midi['PerformerUrl']
                download_link = midi['DownloadPage']
                name = midi['Name']
                genre = midi['Genre']
                performer = midi['Performer']
                try:
                    params = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
                        # 'Cookie': cookie_str,
                        'Referer': performer_link,
                        'Sec-Fetch-Mode': 'navigate',
                        'Sec-Fetch-Site': 'same-origin',
                        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                        'Accept-Encoding': 'gzip, deflate, br',
                        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                        'Connection': 'keep-alive'
                    }
                    session.headers.update({'Referer': performer_link})
                    r = session.get(download_link, verify=False, timeout=20)
                    # r.encoding = 'utf-8'
                    if r.cookies.get_dict():
                        print(r.cookies.get_dict())
                        session.cookies = r.cookies
                    if r.status_code != 200:
                        print('connection error ' + str(r.status_code))
                    soup = BeautifulSoup(r.text, 'html.parser')
                    r.close()
                    try:
                        getter_link = root_url + soup.find(name='a', attrs={'id': 'downloadmidi'})['href']
                        print(getter_link)
                        download_header = {
                            # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                            'Accept-Encoding': 'gzip, deflate, br',
                            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                            'Referer': download_link,
                            # 'Cookie': cookie_str,
                            'Sec-Fetch-Mode': 'navigate',
                            'Sec-Fetch-Site': 'same-origin',
                            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
                        }
                        session.headers.update(download_header)
                        dir = root_path + '/' + genre
                        if not os.path.exists(dir):
                            os.mkdir(dir)
                        rstr = r'[\\/:*?"<>|\r\n\t]+'  # '/ \ : * ? " < > |'
                        name = re.sub(rstr, '', name).strip()
                        performer = re.sub(rstr, '', performer).strip()
                        file_name = name + ' - ' + performer + '.mid'
                        path = dir + '/' +  file_name
                        try:
                            with open(path, 'wb') as output:
                                with session.get(getter_link, allow_redirects=True, verify=False, timeout=20) as r:
                                    if r.history:
                                        print('Request was redirected')
                                        for resp in r.history:
                                            print(resp.url)
                                        print('Final: ' + str(r.url))
                                    r.raise_for_status()
                                    if r.cookies.get_dict():
                                        print(r.cookies)
                                        session.cookies.update(r.coo![在这里插入图片描述](https://img-blog.csdnimg.cn/20200327182437531.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RydWVkaWNrRGluZw==,size_16,color_FFFFFF,t_70)kies)
                                    output.write(r.content)
                            time.sleep(uniform(2, 3))
                            # cookie_opener.open(getter_link)
                            # cj.save(cookie_path, ignore_discard=True)
                            if is_valid_midi(path):
                                print(file_name + ' downloaded')
                                midi_collection.update_one(
                                    {'_id': midi['_id']},
                                    {'$set': {'Downloaded': True, 'GetterLink': getter_link}}
                                )
                                print('Progress: {:.2%}\n'.format(midi_collection.count({'Downloaded': True}) / midi_collection.count()))
                            else:
                                print('Cannot successfully download midi.')
                                os.remove(path)
                        except:
                            print(traceback.format_exc())
                    except:
                        print('Found no download link')
                except:
                    print(traceback.format_exc())
    

    文件名哈希化

    爬取到的MIDI文件夹结构如下,其中每个子文件夹代表不同的风格:
    midifiles
    每个子文件夹内就包含该风格的所有MIDI文件:
    midi_files
    为了方便处理,我把所有的文件名通过md5算法加密了,并将对应的哈希码保存在数据表,可以通过简单的find语句来查找,哈希化代码保存在 Free-Midi-Library/src/md5_reorganize.py/
    在这里插入图片描述

    统一速度和调性

    为了使得训练效果更佳,我将所有的MIDI音乐的速度调整到120bpm,并转调到C调,这两种操作的代码在 src/unify_tempo.pysrc/transpose_tone.py 可以找到。

    1. 转调到C
      关键函数:
    def transpose_to_c():
        root_dir = 'E:/free_midi_library/'
        transpose_root_dir = 'E:/transposed_midi/'
        midi_collection = get_midi_collection()
        for midi in midi_collection.find({'Transposed': False}, no_cursor_timeout = True):
            original_path = os.path.join(root_dir, midi['Genre'] + '/', midi['md5'] + '.mid')
    
            if not os.path.exists(os.path.join(transpose_root_dir, midi['Genre'])):
                os.mkdir(os.path.join(transpose_root_dir, midi['Genre']))
    
            transposed_path = os.path.join(transpose_root_dir, midi['Genre'] + '/', midi['md5'] + '.mid')
            try:
                original_stream = converter.parse(original_path)
    
                estimate_key = original_stream.analyze('key')
    
                estimate_tone, estimate_mode = (estimate_key.tonic, estimate_key.mode)
    
                c_key = key.Key('C', 'major')
                c_tone, c_mode = (c_key.tonic, c_key.mode)
                margin = interval.Interval(estimate_tone, c_tone)
                semitones = margin.semitones
    
                mid = pretty_midi.PrettyMIDI(original_path)
                for instr in mid.instruments:
                    if not instr.is_drum:
                        for note in instr.notes:
                            if note.pitch + semitones < 128 and note.pitch + semitones > 0:
                                note.pitch += semitones
    
                mid.write(transposed_path)
                midi_collection.update_one({'_id': midi['_id']}, {'$set': {'Transposed': True}})
                print('Progress: {:.2%}\n'.format(midi_collection.count({'Transposed': True}) / midi_collection.count()))
            except:
                print(traceback.format_exc())
    

    这一函数中,首先通过music21.converter库中的调性分析函数来得到MIDI文件的调性,并根据与C调的距离来将其转调到C大调或C小调
    实例
    转调前:
    1
    转调后:2

    1. 统一速度(BPM)
      关键函数:
    def tempo_unify_and_merge():
    
        midi_collection = get_midi_collection()
        root_dir = 'E:/transposed_midi/'
        merged_root_dir = 'E:/merged_midi/'
    
        for midi in midi_collection.find({'MergedAndScaled': False}, no_cursor_timeout = True):
            original_path = os.path.join(root_dir, midi['Genre'] + '/', midi['md5'] + '.mid')
            try:
                original_tempo = get_tempo(original_path)[0]
                changed_rate = original_tempo / 120
    
                if not os.path.exists(os.path.join(merged_root_dir, midi['Genre'])):
                    os.mkdir(os.path.join(merged_root_dir, midi['Genre']))
    
                pm = pretty_midi.PrettyMIDI(original_path)
                for instr in pm.instruments:
                    for note in instr.notes:
                        note.start *= changed_rate
                        note.end *= changed_rate
    
                merged_path = os.path.join(merged_root_dir, midi['Genre'] + '/', midi['md5'] + '.mid')
                merged = get_merged_from_pm(pm)
                merged.write(merged_path)
    
                midi_collection.update_one({'_id': midi['_id']}, {'$set': {'MergedAndScaled': True}})
    
                print('Progress: {:.2%}\n'.format(midi_collection.count({'MergedAndScaled': True}) / midi_collection.count()))
                
            except:
                pass
    

    这一函数使用了 pretty_midi 库支持的对MIDI文件的操作,根据源文件的BPM与120BPM的比例,来对所有Note的起始时间和终止时间来进行改变。
    实例:
    统一速度后:
    3

    下载链接

    百度云下载链接,提取码:fm8f
    资源介绍:
    resource

    • unhashed:MIDI文件没有加密,格式为“歌名-艺术家名”
    • raw_midi:仅对文件名进行MD5加密后的文件
    • transposed_midi:转为C大调之后的MIDI文件
    • merged_midi:转调后并且将速度设置为120BPM的文件
    • meta:从MongoDB导出的JSON文件,分为genre、performers和midi三个表,可以通过mongoimport命令来导入MongoDB数据库
    展开全文
  • midi格式音乐大合集

    2015-08-29 14:13:48
    整合了网上大多数mid音乐,包括“173首流行midi铃声”“490首midi四和弦”以及几百个经典游戏midi配乐或主题曲,足够让你回忆起塞班时代的种种经典...
  • 该项目旨在在Midi数据集上训练不同的神经网络以生成音乐 语境 该存储库包含我在计算机学院攻读硕士论文的硕士论文代码““具有深度概率模型的音乐完成” 报告,摘要和演示文稿的幻灯片可 档案文件 bayesian-opt.py ...
  • 深度学习开源数据集大全

    千次阅读 2019-01-01 19:48:11
    skymind.ai网站上有一份十分全面的开源数据集,涵盖自然图像数据集、面部数据集等多个领域,为方面大家找到自己需要的数据集,将skymind.ai整理的数据集编译如下:     自然图像数据集   MNIST: handwritten...
  • 部分数据集

    2020-01-09 11:27:01
    大学公开数据集 (Stanford)69G大规模无人机(校园)图像数据集【Stanford】 http://cvgl.stanford.edu/projects/uav_data/ 人脸素描数据集【CUHK】 http://mmlab.ie.cuhk.edu.hk/archive/facesketch.html 自然...
  • POP909数据集,用于音乐编曲生成 这是论文的数据集存储库: ISMIR 2020中的。 数据集压缩文件结构 index.xlsx:它包含一个描述每个索引文件夹/文件的baisc信息的列表(名称,每小节拍数,每小节的颤音数和修改时间)...
  • 数据集包括标注好的数据集,包括干声、标注文件乐谱、midi文件、标注文件lab。数据集质量高,音准。测试过可以训练出很好的模型。
  • 用于分析和量化Midi数据集的python脚本。 该脚本从中音中提取和弦,并使用1/12节拍的时间单位量化定时。 见scripts/process.py 一个现成的数据集,包含Jazz类型的近300个经过分析和量化的Midi文件。 查看data/ 与...
  • 深度学习开放数据集

    千次阅读 2018-05-02 10:44:28
    欢迎您为本页列表推荐新的数据集!您还可以在维基百科等其他地方找到类似的数据集一览表。最近添加开源生物特征识别数据谷歌Audioset:包含取自YouTube视频的2,084,320条人工标记的10秒声音片段,数据集本体由6...
  • muisc_sheet_to_MIDIFile 问题描述 尝试从muisc表中读取音符并将其转换...数据集 结果 这是Jupyter Notebook,包括主要代码和结果 演示: 讨论 您遇到了什么问题? 我花了很多时间了解如何阅读乐谱 尽管该算法并不是很
  • 【积累】非常全面的开源数据集

    万次阅读 2018-09-05 08:46:04
    非常全面的开源数据集 由skymind.ai公布 非常全面的开源数据集 最近新增数据集 自然图像数据集 地理空间数据 人工数据集 人脸数据集 视频数据集 文本数据集 问答数据集 情感数据集 推荐和排名系统 语音数据集 ...
  • 金融美国劳工部统计局官方发布数据上证A股日线数据,1999.12.09 至 2016.06.08,前复权,1095支股票深证A股日线数据,1999.12.09 至 2016.06.08,前复权,1766支股票深证创业板日线数据,1999.12.09 至 2016.06.08,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,751
精华内容 1,900
关键字:

midi数据集