TonyChyi

我在那一角落患过抽风

把 LMMS 的 Midi 导入功能改用 drumstick 实现了
2017年01月08日
 

 

啊,更好的方法就是,用他妈的什么 portsmf 用 drumstick 啦 ——@liushuyu

终于折腾好了

带着对 drumstick 的憧憬,我终于还是给自己挖了个大坑,把原来 LMMS 在 Midi 文件导入时用到的 portsmf 库换成了 drumstick。

然而这并不是一个有趣的过程。

portsmf 是按顺序返回 Midi 事件,而 drumstick 是基于 Qt 的 Signal/Slot 机制。

于是我先花费了一点时间搞明白 drumstick 是如何工作的。

然后对整个的 MidiImport 插件进行了大拆解——将混乱不堪的代码按照 class 分成了几个文件。

接着写了一个 midiReader 类,用来接收 Midi 事件,并添加到 LMMS 的轨道中。

那么接下来的工作就是实现 midiReader 的 Slot。

在 lmms 的导入过程中,用到了以下 Signal:

  • signalSMFTimeSig(int,int,int,int) “以四分音符为一拍,每小节四拍”
  • signalSMFTempo(int) 节奏,单位是 ms/quarter_note
  • signalSMFError(QString) 错误处理信息
  • signalSMFCtlChange(int,int,int) 控制器信息
  • signalSMFPitchBend(int,int) 弯音信息1
  • signalSMFNoteOn(int,int,int) 音符开始,但最后一个参数为 0 时等同于音符结束
  • signalSMFNoteOff(int,int,int) 音符结束
  • signalSMFProgram(int,int) 音色改变2
  • signalSMFHeader(int,int,int) Midi 文件头信息

这样就完全不用考虑导入的 Midi 文件是格式 0 还是格式 1 了,忽略掉 signalSMFTrackStart()signalSMFTrackEnd() 就可以。

顺带还优雅的解决了,当发送 RPN(0, n) 事件的时候,Pitch Bend 导入走音的问题。

代码已经提交在我的 GitHub 上了,tonychee7000/lmms:port_to_drumstick

一个让我比较蛋疼的问题

LMMS 的数据模型中,音符的参数是这样的

Note(length, start_time, pitch, vol)

而在 Midi 中,一个音符却是以一对 (NoteOn(pitch, vol), NoteOff(pitch, vol) or NoteOn(pitch, 0)) 的形式出现的。

可以维护一个列表来解决这个问题。然而一开始打算用静态内存来暂存音符参数,然后再加入到列表中

// 一但函数退出,这段内存就回收了,数据会变
int note[4] = {time, channel, pitch, vol};

只好改用动态内存分配了3

int *note = new int[4];

然后在音符对象生成后释放内存就可以了。

更新:更多的文件支持

drumstick 除去支持标准 MIDI 文件的导入,还支持 OvertureCakewalk 的工程文件导入,顺手做上了。

挑战:Midi文件导出

这个就有点难了……

已知问题

  • 貌似和 Qt4 的兼容是个坑

  1. 当成第128号控制器导入,而Midi控制器号最大是127 [return]
  2. 当成第129号控制器导入。 [return]
  3. 其实我不是很敢用动态内存分配,怕内存泄露。 [return]
Tags: #LMMS · #MIDI · #drumstick · #C++

 

TonyChyi © 2018 GPLv2