音乐合成

在始发前日的吹 BB 博文此前,说点题外话。

先是,上次老周给大伙伴们介绍完发送 MIDI
音符,本来说好的跟着说一下怎么更改乐器音色,为何这么久都没更新呢。特特来解释一下,如今老周接了一个
ASP.NET Core
的档次,所以辛苦了一段时间。项目不大,一个人独自完成的话感到特好。

匡助,族中一位兄弟高校毕业了,他径直想找一个网页前端的。然后她看看不少招聘音信上写着要求你明白1、2、3、4、5、6、7、8、9、10、11、12、……
一大堆框架。然后她问我,哥,你能精晓这些框架吗?

我答复:能,我了然各大搜索引擎,只要有追寻引擎,每个框架我得以三分钟学会,然后径直运用,用完直接忘记。人类历史上最不要脸的选聘音讯就是用“理解”二字。老周也说过,这多少个合作社都是精神病高发群体。

毕竟,病根在于浮躁,其实您只要基础扎实,什么事物你都足以现学现用,用完忘记。尽管前年再出现十个
JS
框架也无妨,依然老办法,用的时候学,学完就用,用完扔掉。比如,Bootstrap
老周就是这般的,做页面要排版,用起来挺方便,于是直接进他官网,看完文档看示例,看完示例
Run 一下。然后间接用到花色中,用完事后吧,忘了。

成千上万时候,负担都是你协调给协调创办的,心思压力也是友好折腾出来的。

见到前几天众多毕业生求职,又回忆老周当年。求职千万不要紧张,也无须睡不着觉,车到山前必有路,走出个通天大道宽又阔。总能找到活干的,放心好了。同时,也毫不因为自己是毕业生,就总以为自己全身是劣势,甚至被面试官问几句就很恐慌。

并非怕的,面试人士算个吗,他又不敢吃了你,你怕啥。心绪不佳的时睺,你也得以拿面试官来出出气的。记得
2011
年换工作的时候,老周也嘲弄过面试官。很搞笑的是,我调侃他,他竟是录用了本人。反正,他问吗我都能答,全是戏说。忽悠是一项双向社会工程,你忽悠我,我忽悠你,各得其乐罢了。公司忽悠员工,员工忽悠集团,公司忽悠媒体,媒体忽悠社会公众——忽悠生态链。

哦,是了,下面提到了做 ASP.NET Core 项目,这一个其实比传统的 ASP.NET
还要简单,即便跨平台了,但作风依旧很微软的,传承了微软的优良基因——简单易用效能高。.net
Core 的情节网上广大,老周也不细说了,如今一两年,到处都是 Core
在刷屏,教程相当的多,只要您基础硬,哪怕不看其他科目,只看官方文档,一钟头就能学会。

此间老周提一下的时,在Linux上测试时,可能您会想到在虚拟机里装 Linux
系统。其实历来并非,虚拟机不仅损耗性能,而且也折腾。最简易快速的主意就是启用
Windows 10 的 Linux 子系统(Bash功效),然后您到使用商店安装一下 Ubuntu
或者其他两个版本。那一个子系统很 TNND 好用,而且可以间接访问 Windows
目录和文件,用来测试 ASP.NET Core 项目特别有利。

假如您不熟知 Linux
不掌握怎么弄,没涉及,前边老周会写一篇烂文,详细告知您怎么玩,放心啊,很粗略的,你打探老周的,老周没有写这多少个鬼都看不懂的事物。不过,前日的主题仍旧延续我们的
MIDI 合成。

韦德国际app官方,=====================================================================

 

好,F
话说得太多了,担心有人会扔砖头,老周并不怕被砖砸到,是顾虑你不知晓从哪些考古发掘现场偷来的砖,这容易引起法律责任,偷文物是不文明的。

于是的 MIDI
通道信息都有联袂特点,由两到六个字节组成,大部分是多少个字节,个别是四个字节,比如本文要介绍的那多少个改变乐器音色的
Program Change 音信,它就是六个字节组成的。

抱有通道信息的第一个字节都有两部分构成,我们清楚一个字节是 8
位,状态码占高 4 位,标识消息类型;通道编号占低 4 位。

Program Change 音讯的状态码(或者说命令标识码)是 1100
,这是二进制,十六进制是 0xC。然后大家面前说过,通道是 0 到 15
共十四个,即 0x0 –
0xF。于是,六个合起来正好是一个字节,比如我要改成第一个通道上的音色,Program
Change 音讯的首先个字节就是 0xC0,假若要改第二个通道上的音色,就是
0xC1。

其次个字节表示乐器的编号,只行使1-7位,所以有效值为 0 – 127,共 128
种音色。

是因为 UWP SDK 已经封装好 MidiProgramChangeMessage
类,所以用的时候,你不需要回想状态码,构造实例时,
你只提供六个字节就行了,第一个是能道编号,第二个是音色编号。

 

128 种音色列表你可以到 midi.org
上查看,假设您嫌洋鬼子的文字看不懂,这行,老周给您整理了一晃。如果你认为无聊,可以一向看后面的以身作则。

先是个表格,是说乐器的分类,如吹管类的,拨弦类的,打击类的。

韦德国际app官方 1

 

 第二个表是乐器的列表。

韦德国际app官方 2

韦德国际app官方 3

韦德国际app官方 4

韦德国际app官方 5

韦德国际app官方 6

韦德国际app官方 7

 

注意啊,下边列表是从 1 起先的,我们在写代码时要从 0 最先,到
127。就是地方的编号 – 1。

 

骨子里是很粗略的,一般我们不需要播放每个音符都发送 ProgramChange
信息,啥时候要改音色,就发送一条就行了,后边播放的音符都会动用这一个改变,直到你再发送
ProgramChange 音信去举行更改。

下边我们用弘一法师(李叔同)填词的一首歌来做示范,那首歌我们上学的时候都学过的——《送别》,“长亭外,古道边,芳草碧连天……”。

韦德国际app官方 8

下边大家在界面上用 ListBox 控件来彰显多少个乐器选项,老周并从未写上 128
种,仅仅是挑了多少个做示范。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Image Margin="10" Source="/Assets/1.png"/>
        <StackPanel Grid.Column="1" Margin="10">
            <TextBlock Text="选择一种乐器:" Margin="1,3"/>
            <ListBox Name="lbProgram" Height="280" SelectionMode="Single" >
                <ListBoxItem Tag="18">摇滚风琴</ListBoxItem>
                <ListBoxItem Tag="79">陶笛</ListBoxItem>
                <ListBoxItem Tag="56">小号</ListBoxItem>
                <ListBoxItem Tag="112">铃铛</ListBoxItem>
            </ListBox>
            <Button Margin="2,25,0,0" Content="演奏此曲" Click="OnClick"/>
        </StackPanel>
    </Grid>

接下来我们在页面类上声称一下变量。

        MidiSynthesizer synthesizer = null;
        bool isPlaying = false;

跟上一篇中的例 子一样,这多少个 bool 类型的变量是为了防避重复执行代码用的。

接下来先导化一下 MIDI 合成器,而且在距离页面时清理一下。

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            // 获得实例
            synthesizer = await MidiSynthesizer.CreateAsync();
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            // 释放实例
            synthesizer?.Dispose();
            synthesizer = null;
        }

 

紧接着,在页面类中弄六个自定义方法,方便后边调用。一个方法是开首 /
截止播放单个音符,另一个办法是广播一个音符列表。PlayNotesAsync
方法中会调用 PlaySingleNoteAsync 方法。

        async Task PlaySingleNoteAsync(Tuple<byte, TimeSpan> tp)
        {
            synthesizer.SendMessage(new MidiNoteOnMessage(0, tp.Item1, 127));
            await Task.Delay(tp.Item2);
            synthesizer.SendMessage(new MidiNoteOffMessage(0, tp.Item1, 127));
        }

        async Task PlayNotesAsync(IEnumerable<Tuple<byte, TimeSpan>> notes)
        {
            foreach (var ti in notes)
            {
                await PlaySingleNoteAsync(ti);
            }
        }

 

好,准备好那一个,可以拍卖按钮的 Click 事件,组装音符列表了。

        private async void OnClick(object sender, RoutedEventArgs e)
        {
            if (lbProgram.SelectedIndex == -1) return;
            if (isPlaying) return;

            // 更改音色一般在发送音符之前发送
            // 不必每个音符都发送 ProgramChange 消息
            // 它会自动保持,直到发送下一条 ProgramChange 消息

            // 获得列表框中选中的音色编号
            ListBoxItem item = lbProgram.SelectedItem as ListBoxItem;
            byte pc = Convert.ToByte(item.Tag);
            // 发送更改音色消息
            MidiProgramChangeMessage pcmsg = new MidiProgramChangeMessage(0, pc);
            // 这个示例只使用第一个通道,你也可以视不同情况使用其他通道
            synthesizer.SendMessage(pcmsg);

            double tempo = 60 / 80 * 1000;//节奏
            // 开始发送音符
            List<Tuple<byte, TimeSpan>> notelist = new List<Tuple<byte, TimeSpan>>();
            // 第一句
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo * 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo * 2d)));

            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(60, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(60, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo * 2d)));

            // 后面是两个休止符,我们可以用音符 0
            notelist.Add(new Tuple<byte, TimeSpan>(0, TimeSpan.FromMilliseconds(tempo * 2d)));

            // 第二句
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo / 2d)));
            // 以下音符有附点,时值为一拍,再延长原时值的一半,即 1.5 拍
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo * 1.5d)));
            notelist.Add(new Tuple<byte, TimeSpan>(71, TimeSpan.FromMilliseconds(tempo / 2d)));
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo)));
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo * 2d)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo / 2d)));//2
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));//3
            notelist.Add(new Tuple<byte, TimeSpan>(65, TimeSpan.FromMilliseconds(tempo * 1.5d)));//4 附点
            notelist.Add(new Tuple<byte, TimeSpan>(59, TimeSpan.FromMilliseconds(tempo / 2d)));//低音7
            notelist.Add(new Tuple<byte, TimeSpan>(60, TimeSpan.FromMilliseconds(tempo * 2d)));//1
            notelist.Add(new Tuple<byte, TimeSpan>(0, TimeSpan.FromMilliseconds(tempo * 2d)));// 0

            // 第三句
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo))); //6
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo * 2d)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(71, TimeSpan.FromMilliseconds(tempo)));//7
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo / 2d)));//6
            notelist.Add(new Tuple<byte, TimeSpan>(71, TimeSpan.FromMilliseconds(tempo / 2d)));//7
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo * 2d)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo / 2d)));//6
            notelist.Add(new Tuple<byte, TimeSpan>(71, TimeSpan.FromMilliseconds(tempo / 2d)));//7
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo / 2d)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo / 2d)));//6
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo / 2d)));//6
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo / 2d)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));//3
            notelist.Add(new Tuple<byte, TimeSpan>(60, TimeSpan.FromMilliseconds(tempo / 2d)));//1
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo * 2d)));//2
            // 休止
            notelist.Add(new Tuple<byte, TimeSpan>(0, TimeSpan.FromMilliseconds(tempo * 2d)));

            // 最后一句
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));//3
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo / 2d)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo * 1.5d)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(71, TimeSpan.FromMilliseconds(tempo / 2d)));//7
            notelist.Add(new Tuple<byte, TimeSpan>(69, TimeSpan.FromMilliseconds(tempo)));//6
            notelist.Add(new Tuple<byte, TimeSpan>(72, TimeSpan.FromMilliseconds(tempo)));//高音1
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo * 2d)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(67, TimeSpan.FromMilliseconds(tempo)));//5
            notelist.Add(new Tuple<byte, TimeSpan>(62, TimeSpan.FromMilliseconds(tempo / 2d)));//2
            notelist.Add(new Tuple<byte, TimeSpan>(64, TimeSpan.FromMilliseconds(tempo / 2d)));//3
            notelist.Add(new Tuple<byte, TimeSpan>(65, TimeSpan.FromMilliseconds(tempo * 1.5d)));//4 附点
            notelist.Add(new Tuple<byte, TimeSpan>(59, TimeSpan.FromMilliseconds(tempo / 2d)));//低音7
            notelist.Add(new Tuple<byte, TimeSpan>(60, TimeSpan.FromMilliseconds(tempo * 2d)));//1

            // 开始播放
            isPlaying = true;
            await PlayNotesAsync(notelist);
            isPlaying = false;
        }

 

再有一步很重要的,记得要添加一个扩张引用。

韦德国际app官方 9

 

那首曲子里面出现了截止符(0),你恐怕会想到发送 NoteOn 0
音符,对于一些乐器音色来说,0确实不失声,可有部分是会生出低沉的声响。下面的代码在添加音符列表时,用
0 表示休止符。现在不妨修改一下 PlayNotesAsync
方法的代码,跳过休止符,可是,该延时仍旧得延时,不然就达不到停顿的效率了。

        async Task PlayNotesAsync(IEnumerable<Tuple<byte, TimeSpan>> notes)
        {
            foreach (var ti in notes)
            {
                // 跳过休止符
                if(ti.Item1 == 0)
                {
                    await Task.Delay(ti.Item2);
                    continue;
                }
                await PlaySingleNoteAsync(ti);
            }
        }

 

如此这般就大功告成了,运行试试吧。

示例源代码下载地址

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图