开源五线谱打谱软件MuseScore代码分析之四:MuseScore 的播放模式

时间:2024-03-04 21:33:14

 

这次讨论MuseScore的播放功能,它包含两块内容,管出声的和管画图的。
管出声的:
MuseScore的Midi发声支持多种方式,JACK audio,linux下使用的ALSA,跨平台的PortAudio库,默认使用PortAudio。
JACK类似于windows下的ASIO技术,顾名思义,jack指的是音频插头,它能在系统级维护一些通路,让各个音频软件互相联通,传送音频数据或midi数据。
PortAudio是一个开源的多媒体库,由一系列audio或midi的子库组成,这里用的是PortMidi,它没有JACK那样醒目的logo,重在功能,具体可到http://portmedia.sourceforge.net/ 了解。
由于跨平台定时器精度问题,没有使用PortMidi中的midi out列表设备,并给midi out实时发送midi消息的方式,而是生成一份PortMidi需要的Seq数据,即PortMidi定义的midi内存数据结构,将定时功能交给PortMidi。这两句话看起来说的不够明白,其实就是,自己不定时,交给PortMidi含有时间的数据,让PortMidi去定时,并接收PortMidi的回调。MuseScore还使用FluidSynth的SoundFont合成功能,将SoundFont包含的audio数据以及和midi信息的对应关系告诉PortAudio端口,由PortMidi内部的定时器驱动,时间到了,合成音频数据输出到PortAudio端口。
Seq类负责实现播放逻辑,管理不同的midi out接口方式,获取一些播放信息,并在播放时能实时改变速度、音量等。
管画图的:
MuseScore在播放模式时,光标变成整个行高,播放到的音符还会点亮,播放过的音符还会点灭,这是怎么实现的呢?这是由下面流程执行的。
Driver向Seq发送当前tick位置(tick是midi标准中的时间单位,请参考MIDI规范),调用Seq::seek()
 - Seq::moveCursor() //通过tick位置,找到光标所处的位置及相关音符,移动光标
 - Seq::addRefresh(QRectF&) //把游标和相关音符的外框都加到刷新区域,标记相关音符为高亮状态
 - 发送 dataChanged  消息,使得界面刷新,开始重绘。 在ScoreView::paint()中,QPainter设置颜色为元素当前颜色Element::curColor(),高亮状态的颜色和普通状态不同,这样就实现了音符的点亮。过一会音符播放完毕,将音符状态设置回普通状态,再次刷新该区域,就是音符的点灭。
界面还能根据当前播放的位置,还会在适当的时候进行跳转,使得当前播放的部分总是可见的,实现了翻页的功能。

这次讨论MuseScore的播放功能,它包含两块内容,管出声的和管画图的。

  • 管出声的

MuseScore的Midi发声支持多种方式,JACK audio,linux下使用的ALSA,跨平台的PortAudio库,默认使用PortAudio。
JACK类似于windows下的ASIO技术,顾名思义,jack指的是音频插头,它能在系统级维护一些通路,让各个音频软件互相联通,传送音频数据或midi数据。
PortAudio是一个开源的多媒体库,由一系列audio或midi的子库组成,这里用的是PortMidi,它没有JACK那样醒目的logo,重在功能,具体可到http://portmedia.sourceforge.net/ 了解。由于跨平台定时器精度问题,没有使用PortMidi中的midi out列表设备,并给midi out实时发送midi消息的方式,而是生成一份PortMidi需要的Seq数据,即PortMidi定义的midi内存数据结构,将定时功能交给PortMidi。这两句话看起来说的不够明白,其实就是,自己不定时,交给PortMidi含有时间的数据,让PortMidi去定时,并接收PortMidi的回调。
MuseScore还使用FluidSynth的SoundFont合成功能,将SoundFont包含的audio数据以及和midi信息的对应关系告诉PortAudio端口,由PortMidi内部的定时器驱动,时间到了,合成音频数据输出到PortAudio端口。



Seq类负责实现播放逻辑,管理不同的midi out接口方式,获取一些播放信息,并在播放时能实时改变速度、音量等。

  • 管画图的:

MuseScore在播放模式时,光标变成整个行高,播放到的音符还会点亮,播放过的音符还会点灭,这是怎么实现的呢?这是由下面流程执行的。Driver向Seq发送当前tick位置(tick是midi标准中的时间单位,请参考MIDI规范),调用Seq::seek():
   - Seq::moveCursor()  //通过tick位置,找到光标所处的位置及相关音符,移动光标
  - Seq::addRefresh(QRectF&)//把游标和相关音符的外框都加到刷新区域,标记相关音符为高亮状态
  - 发送 dataChanged  消息,使得界面刷新,开始重绘。
在ScoreView::paint()中,QPainter设置颜色为元素当前颜色Element::curColor(),高亮状态的颜色和普通状态不同,这样就实现了音符的点亮。过一会音符播放完毕,将音符状态设置回普通状态,再次刷新该区域,就是音符的点灭。界面还能根据当前播放的位置,还会在适当的时候进行跳转,使得当前播放的部分总是可见的,实现了翻页的功能。