【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应用开发】延迟共享
延迟共享是啥呢,这么说吧,就是在应用程序打开共享面板选择共享目标时,不会设置要共享的数据,而是等到共享目标请求数据时,才会发送数据,而且,延迟操作可以在后台进行. 这样说似乎过于抽象,最好的诠释方法, ...
随机推荐
- Uber的成功绝非偶然
拥有打造一个初创企业并将其做强做大的梦想并不是难事,困难的是怎样将该梦想变成现实.娱乐媒体行业经常将企业成功的过程进行美化,干净利落的将企业成功前所经历的艰苦时刻进行大刀阔斧的剪裁,让其刚好可以达到拍 ...
- response与request回顾学习
一.response response是servlet.service方法的一个参数,它的类型是javax.servlet.http.HttpServletResponse,在客户端每发出一个请求时, ...
- Android中build target,minSdkVersion,targetSdkVersion,maxSdkVersion概念区分
问题引入: 有一个下载功能,在Android 5.x设备上运行正常,Android 6.x上运行异常,现象是下载进度卡在0%. 问题排查发现,是sdk的target设置为23导致,修改为21则 ...
- (转)解决jdk1.8中发送邮件失败(handshake_failure)问题
解决jdk1.8中发送邮件失败(handshake_failure)问题 作者 zhisheng_tian 2016.08.12 22:44* 字数 1573 阅读 2818评论 6喜欢 9 暑假在家 ...
- effective java笔记之java服务提供者框架
博主是一名苦逼的大四实习生,现在java从业人员越来越多,面对的竞争越来越大,还没走出校园,就TM可能面临失业,而且对那些增删改查的业务毫无兴趣,于是决定提升自己,在实习期间的时间还是很充裕的,期间自 ...
- 【java设计模式】【创建模式Creational Pattern】建造模式Builder Pattern
package com.tn.pattern; public class Client { public static void main(String[] args) { Director dire ...
- htpasswd 命令详解
htpasswd参数 -c 创建passwdfile.如果passwdfile 已经存在,那么它会重新写入并删去原有内容. -n 不更新passwordfile,直接显示密码 -m 使用MD5加密(默 ...
- jsp程序设计:jstl之JSTL标签库
转载自:http://www.blogjava.net/haizhige/archive/2008/10/26/236783.html,个人进行了一些修改. 前言:写一个taglib一般可以继承Sim ...
- csdn博客被删除联系客服恢复
前几天写了篇"如何使用shadowsocksFQ",居然被删除,我很郁闷,客服给我的回答是"影响了客户体验,网安查的严,不能带有FQ的信息出现" 我一直很郁闷, ...
- centOS7 mini配置linux服务器(五) 安装和配置tomcat和mysql
配置java运行环境,少不了服务器这一块,而tomcat在服务器中占据了很大一部分份额,这里就简单记录下tomcat安装步骤. 下载 首先需要下载tomcat7的安装文件,地址如下: http://t ...