TonyChyi

我在那一角落患过抽风

修复了 LMMS Pitch Bend Bug
2017年01月04日
 

 

@leo_song 对我说,搞报表平台的代码不如研究 LMMS

LMMS 有一个存在了很长时间的 BUG:导入 Midi 文件的时候,对 Pitch Bend 不能很好的处理导致走音。

定位

在 Cakewalk 中测试,如果是单纯调整 Pitch Bend,最大范围是 ±2 个半音,而我这里有个文件1甚至可以达到 1 个八度。

经过一番测试,发现是 RPN(0, 12) 这个参数在起作用:

0, 0, Header, 1, 7, 960
1, 0, Start_track
1, 0, Control_c, 0, 101, 0
1, 0, Control_c, 0, 100, 0
1, 0, Control_c, 0, 6, 12
1, 0, Control_c, 0, 38, 0
……

也就是上面的 Contol_c 事件,先以 101100 控制器发送值 02,然后再用 6 控制器发送 123,最后以 38 控制器发送 0

再看 LMMS 的代码:plugins/MidiImport/MidiImport.cpp

从 461 行开始有以下代码

……
if( ccid <= 128 )
{
	double cc = evt->get_real_value();
	AutomatableModel * objModel = NULL;

	switch( ccid ) 
	{
		case 0:
			if( ch->isSF2 && ch->it_inst )
			{
				objModel = ch->it_inst->childModel( "bank" );
				printf("BANK SELECT %f %d\n", cc, (int)(cc*127.0));
				cc *= 127.0f;
			}
			break;

		case 7:
			objModel = ch->it->volumeModel();
			cc *= 100.0f;
			break;

		case 10:
			objModel = ch->it->panningModel();
			cc = cc * 200.f - 100.0f;
			break;

		case 128:  // 作者强行把 Pitch Bend 事件当成第 128 号控制器了
			objModel = ch->it->pitchModel();
			cc = cc * 100.0f;
			break;
		default:
			//TODO: something useful for other CCs
			break;
	}
	……
}
……

可以很清楚的看到,LMMS 很偷懒,根本没有处理 RPN 事件。那么接下来的事情就好办了。

解决

我就此问题联系了 @liushuyu4,经过讨论后,思路如下

  1. 当读取到 100 和 101 控制器时,读取其中的值 cc
  2. 如果读到上述两个控制器且两个 cc 都为 0,即 PitchBendRange,设定一个标记
  3. 当读取到 6 控制器时,读取其中的值 cc,且标记有效,就设定 pitchRangeModel() 为 floor(cc * 128)
  4. Pitch 事件的值 cc 乘以 100,再乘以 pitchRangeModel() 的值

我和 @liushuyu 分别解决了此问题。@liushuyu 的代码已提交到上游等待合并。

区别在于,我设定了两个 flag,当这两个值都为 true 才确定为发送了 RPN 事件,而 @liushuyu 则是记录了一个 prevCcid并在发送 100 和 101 之后将 prevCcid 改为一个特殊的值,然后在当前 ccid 为 6 的时候进行判断。


  1. 将文本以Base64反解码即可得到文件本体 [return]
  2. 即PitchBendRange/PitchBendSensitivity [return]
  3. 即半音数,12恰好是一个八度 [return]
  4. 安同开源社区成员,LMMS开发者之一 [return]
Tags: #LMMS · #MIDI

 

TonyChyi © 2018 GPLv2