在开始今天的吹 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 合成。

=====================================================================

好,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 上查看,如果你嫌洋鬼子的文字看不懂,那行,老周给你整理了一下。如果你觉得无聊,可以直接看后面的示例。

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

第二个表是乐器的列表。

注意啊,上面列表是从 1 开始的,我们在写代码时要从 0 开始,到 127。就是上面的编号 - 1。

其实是很简单的,一般我们不需要播放每个音符都发送 ProgramChange 消息,什么时候要改音色,就发送一条就行了,后面播放的音符都会应用这个更改,直到你再发送 ProgramChange 消息去进行更改。

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

下面我们在界面上用 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(, tp.Item1, ));
await Task.Delay(tp.Item2);
synthesizer.SendMessage(new MidiNoteOffMessage(, tp.Item1, ));
} 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 == -) return;
if (isPlaying) return; // 更改音色一般在发送音符之前发送
// 不必每个音符都发送 ProgramChange 消息
// 它会自动保持,直到发送下一条 ProgramChange 消息 // 获得列表框中选中的音色编号
ListBoxItem item = lbProgram.SelectedItem as ListBoxItem;
byte pc = Convert.ToByte(item.Tag);
// 发送更改音色消息
MidiProgramChangeMessage pcmsg = new MidiProgramChangeMessage(, pc);
// 这个示例只使用第一个通道,你也可以视不同情况使用其他通道
synthesizer.SendMessage(pcmsg); double tempo = / * ;//节奏
// 开始发送音符
List<Tuple<byte, TimeSpan>> notelist = new List<Tuple<byte, TimeSpan>>();
// 第一句
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d))); notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d))); // 后面是两个休止符,我们可以用音符 0
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d))); // 第二句
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
// 以下音符有附点,时值为一拍,再延长原时值的一半,即 1.5 拍
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 1.5d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 1.5d)));//4 附点
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//低音7
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));// 0 // 第三句
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo))); //
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//2
// 休止
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d))); // 最后一句
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 1.5d)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//高音1
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 1.5d)));//4 附点
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo / 2d)));//低音7
notelist.Add(new Tuple<byte, TimeSpan>(, TimeSpan.FromMilliseconds(tempo * 2d)));//1 // 开始播放
isPlaying = true;
await PlayNotesAsync(notelist);
isPlaying = false;
}

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

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

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

这样就大功告成了,运行试试吧。

示例源代码下载地址

【Win 10 应用开发】MIDI 音乐合成——更改乐器音色的更多相关文章

  1. 【Win 10 应用开发】MIDI 音乐合成——音符消息篇

    在上一篇中,老周介绍了一些乐理知识,有了那些常识后,进行 MIDI 编程就简单得多了.尽管微软已经把 API 封装好,用起来也很简单,但是,如果你没有相应的音乐知识基础,你是无法进行 MIDI 编程的 ...

  2. 【Win 10 应用开发】MIDI 音乐合成——乐理篇

    针对 MIDI 音乐的 API ,其实在 Win 8.1 的时候就出现.在UWP中采用了新的驱动模式,MIDI 消息传递更加高效. 首先得说明的是,UWP 的 MIDI 相关 API 不是针对 MID ...

  3. 【Win 10 应用开发】在代码中加载文本资源

    记得前一次,老周给大伙,不,小伙伴们介绍了如何填写 .resw 文件,并且在 XAML 中使用 x:Uid 标记来加载.也顺便给大伙儿分析了运行时是如何解析 .resw 文件的. 本来说好了,后续老周 ...

  4. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  5. 【Win 10 应用开发】导入.pfx证书

    这个功能其实并不常用,一般开发较少涉及到证书,不过,简单了解一下还是有必要的. 先来说说制作测试证书的方法,这里老周讲两种方法,可以生成用于测试的.pfx文件. 产生证书,大家都知道有个makecer ...

  6. 【Win 10应用开发】Adaptive磁贴模板的XML文档结构

    在若干天之前,老周给大家讲了Adaptive Toast通知的XML模板,所以相应地,今天老周给大家介绍一下Adaptive磁贴的新XML模板. 同样道理,你依旧可以使用8.1时候的磁贴模板,在win ...

  7. 【Win 10 应用开发】RTM版的UAP项目解剖

    Windows 10 发布后,其实SDK也偷偷地在VS的自定义安装列表中出现了,今天开发人员中心也更新了下载.正式版的SDK在API结构上和以前预览的时候是一样的,只是版本变成10240罢了,所以大家 ...

  8. 【Win 10应用开发】认识一下UAP项目

    Windows 10 SDK预览版需要10030以上版本号的Win 10预览版系统才能使用.之前我安装的9926的系统,然后安装VS 2015 CTP 6,再装Win 10 SDK,但是在新建项目后, ...

  9. 【Win 10应用开发】延迟共享

    延迟共享是啥呢,这么说吧,就是在应用程序打开共享面板选择共享目标时,不会设置要共享的数据,而是等到共享目标请求数据时,才会发送数据,而且,延迟操作可以在后台进行. 这样说似乎过于抽象,最好的诠释方法, ...

随机推荐

  1. Uber的成功绝非偶然

    拥有打造一个初创企业并将其做强做大的梦想并不是难事,困难的是怎样将该梦想变成现实.娱乐媒体行业经常将企业成功的过程进行美化,干净利落的将企业成功前所经历的艰苦时刻进行大刀阔斧的剪裁,让其刚好可以达到拍 ...

  2. response与request回顾学习

    一.response response是servlet.service方法的一个参数,它的类型是javax.servlet.http.HttpServletResponse,在客户端每发出一个请求时, ...

  3. Android中build target,minSdkVersion,targetSdkVersion,maxSdkVersion概念区分

    问题引入:   有一个下载功能,在Android 5.x设备上运行正常,Android 6.x上运行异常,现象是下载进度卡在0%.   问题排查发现,是sdk的target设置为23导致,修改为21则 ...

  4. (转)解决jdk1.8中发送邮件失败(handshake_failure)问题

    解决jdk1.8中发送邮件失败(handshake_failure)问题 作者 zhisheng_tian 2016.08.12 22:44* 字数 1573 阅读 2818评论 6喜欢 9 暑假在家 ...

  5. effective java笔记之java服务提供者框架

    博主是一名苦逼的大四实习生,现在java从业人员越来越多,面对的竞争越来越大,还没走出校园,就TM可能面临失业,而且对那些增删改查的业务毫无兴趣,于是决定提升自己,在实习期间的时间还是很充裕的,期间自 ...

  6. 【java设计模式】【创建模式Creational Pattern】建造模式Builder Pattern

    package com.tn.pattern; public class Client { public static void main(String[] args) { Director dire ...

  7. htpasswd 命令详解

    htpasswd参数 -c 创建passwdfile.如果passwdfile 已经存在,那么它会重新写入并删去原有内容. -n 不更新passwordfile,直接显示密码 -m 使用MD5加密(默 ...

  8. jsp程序设计:jstl之JSTL标签库

    转载自:http://www.blogjava.net/haizhige/archive/2008/10/26/236783.html,个人进行了一些修改. 前言:写一个taglib一般可以继承Sim ...

  9. csdn博客被删除联系客服恢复

    前几天写了篇"如何使用shadowsocksFQ",居然被删除,我很郁闷,客服给我的回答是"影响了客户体验,网安查的严,不能带有FQ的信息出现" 我一直很郁闷, ...

  10. centOS7 mini配置linux服务器(五) 安装和配置tomcat和mysql

    配置java运行环境,少不了服务器这一块,而tomcat在服务器中占据了很大一部分份额,这里就简单记录下tomcat安装步骤. 下载 首先需要下载tomcat7的安装文件,地址如下: http://t ...