【Win 10 应用开发】MIDI 音乐合成——更改乐器音色
在开始今天的吹 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 音乐合成——更改乐器音色的更多相关文章
- 【Win 10 应用开发】MIDI 音乐合成——音符消息篇
在上一篇中,老周介绍了一些乐理知识,有了那些常识后,进行 MIDI 编程就简单得多了.尽管微软已经把 API 封装好,用起来也很简单,但是,如果你没有相应的音乐知识基础,你是无法进行 MIDI 编程的 ...
- 【Win 10 应用开发】MIDI 音乐合成——乐理篇
针对 MIDI 音乐的 API ,其实在 Win 8.1 的时候就出现.在UWP中采用了新的驱动模式,MIDI 消息传递更加高效. 首先得说明的是,UWP 的 MIDI 相关 API 不是针对 MID ...
- 【Win 10 应用开发】在代码中加载文本资源
记得前一次,老周给大伙,不,小伙伴们介绍了如何填写 .resw 文件,并且在 XAML 中使用 x:Uid 标记来加载.也顺便给大伙儿分析了运行时是如何解析 .resw 文件的. 本来说好了,后续老周 ...
- 【Win 10 应用开发】启动远程设备上的应用
这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...
- 【Win 10 应用开发】导入.pfx证书
这个功能其实并不常用,一般开发较少涉及到证书,不过,简单了解一下还是有必要的. 先来说说制作测试证书的方法,这里老周讲两种方法,可以生成用于测试的.pfx文件. 产生证书,大家都知道有个makecer ...
- 【Win 10应用开发】Adaptive磁贴模板的XML文档结构
在若干天之前,老周给大家讲了Adaptive Toast通知的XML模板,所以相应地,今天老周给大家介绍一下Adaptive磁贴的新XML模板. 同样道理,你依旧可以使用8.1时候的磁贴模板,在win ...
- 【Win 10 应用开发】RTM版的UAP项目解剖
Windows 10 发布后,其实SDK也偷偷地在VS的自定义安装列表中出现了,今天开发人员中心也更新了下载.正式版的SDK在API结构上和以前预览的时候是一样的,只是版本变成10240罢了,所以大家 ...
- 【Win 10应用开发】认识一下UAP项目
Windows 10 SDK预览版需要10030以上版本号的Win 10预览版系统才能使用.之前我安装的9926的系统,然后安装VS 2015 CTP 6,再装Win 10 SDK,但是在新建项目后, ...
- 【Win 10应用开发】延迟共享
延迟共享是啥呢,这么说吧,就是在应用程序打开共享面板选择共享目标时,不会设置要共享的数据,而是等到共享目标请求数据时,才会发送数据,而且,延迟操作可以在后台进行. 这样说似乎过于抽象,最好的诠释方法, ...
随机推荐
- 11g使用非duplicate方式创建物理standby要注意的问题总结
在上篇博文中,使用了duplicate方式来创建物理standby http://blog.csdn.net/aaron8219/article/details/38434579 今天来说说在11g中 ...
- EBS採购模块中的级联接收和级联接收事务
EBS採购模块中的级联接收和级联接收事务 (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习.请注明出处:否则请与本人联系.违者必究) 级联接收和级联接收事务 级联功能对来自于同一个供应商 ...
- Python 爬虫 爬校花网!!
爬虫:是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本 1.福利来了 校花网 ,首先说为什么要爬这个网站呢,第一这个网站简单爬起来容易不会受到打击,第二呢 你懂得... 1.第一步,需要下载 ...
- Android中build target,minSdkVersion,targetSdkVersion,maxSdkVersion概念区分
问题引入: 有一个下载功能,在Android 5.x设备上运行正常,Android 6.x上运行异常,现象是下载进度卡在0%. 问题排查发现,是sdk的target设置为23导致,修改为21则 ...
- 关于python的itertools模块
这是一个强大的模块 先来看一下它都有什么工具 无穷循环器 迭代器 参数 结果 ...
- Github开源项目(企业信息化基础平台)
JEEPlatform 一款企业信息化开发基础平台,可以用于快速构建企业后台管理系统,集成了OA(办公自动化).SCM(供应链系统).ERP(企业资源管理系统).CMS(内容管理系统).CRM(客户关 ...
- 这么说吧,java线程池的实现原理其实很简单
好处 : 线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配.调优和监控,有以下好处: 1.降低资源消耗: 2.提高响应速度: 3.提高线 ...
- 常见.NET功能代码汇总 (3)
33,彻底关闭Excel进程 .NET中使用Excel属于使用非托管资源,使用完成后一般都要用GC回收资源,但是,调用GC的位置不正确,Excel进程可能无法彻底关闭,如下面的代码: static v ...
- 《TCP-IP详解卷3:TCP 事务协议、HTTP、NNTP和UNIX域协议》【PDF】下载
TCP-IP详解卷3:TCP 事务协议.HTTP.NNTP和UNIX域协议>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062539 ...
- 简述ES6其他的东西
第一是修饰器是ES7的一个提案,现在Babel转码器已经支持.那么什么是修饰器呢,修饰器是对类的行为的改变,在代码编译时发生的,而不是在运行时发生的且修饰器只能用于类和类的方法.修饰器可以接受三个函数 ...