我正在开发一个简单的MIDI解析器,它可以将MIDI文件转换为不同的格式,我面临的一个问题是,偶尔我会在MIDI文件中得到一些没有任何指定乐器的曲目(没有程序更改事件(
以下是代码中专门查找程序更改事件的截断部分。请原谅我的格式混乱,我是新手,在粘贴代码时遇到了一些困难,但希望总体思路仍然清晰。
private static final int PROGRAM_CHANGE = 0xC0;
ArrayList<Note> noteSequence = new ArrayList<Note>();
int trackNumber = 0;
for (Track track : sequence.getTracks()) {
trackNumber++;
for (int i = 0; i < track.size(); i++) {
MidiEvent event = track.get(i);
MidiMessage message = event.getMessage();
currentTick = event.getTick();
ShortMessage sm = (ShortMessage) message;
if (sm.getCommand() == PROGRAM_CHANGE) {
noteSequence.add(new Note(currentTick+1,sm.getData1(),0,trackNumber,0,"PROGRAM_CHANGE",0,0,0, 2));
}}
我可能面临的主要问题之一就是以错误的方式获取程序更改事件。它们似乎没有与之相关的刻度,所以我只是给了它们上一个刻度+1的时间事件。我不确定这是否是正确的方法,所以这可能会引起一些问题。
例如,我运行的一个MIDI文件有23首曲目。除了第8首曲目外,每首曲目都有一个指定的乐器,我不知道为什么第8首没有指定乐器。我认为这是为了继承基于另一首曲目的乐器,但我不太清楚这将如何工作。
然而,诸如NOTE_ON事件之类的其他事件正被正确地从这些轨道捕获。我知道这是一个小众问题,但有人对此有什么见解吗?
我面临的一个问题是,偶尔我会在没有任何指定乐器的MIDI文件(没有程序更改事件(
是的,这是可能发生的。Midi规范不强制使用程序更改。这实际上取决于Midi文件的设计目的。为了实现最大的可移植性,通常最好为每个使用的Midi频道添加GM程序更改。请注意,对于非GM Midi设备,您通常使用"银行选择"one_answers"程序更改"消息指定仪器。
此外,您可能会在web上找到为指定的synth或VST设计的Midi文件,该文件使用特定于synth的SysEx消息来选择仪器。
他们似乎没有与相关的勾号
是的。虽然tick通常是0,因为程序更改通常在歌曲的开头。但碰巧有些歌曲在歌曲中间改变了乐器,所以勾号位置>0.
如果您想要一个完整的开源Midi文件解析器示例:https://github.com/jjazzboss/JJazzLab-X/tree/master/Midi/src/org/jjazz/midi/api/parser
它们似乎没有与相关的记号
它们确实存在,我们只需要一些数学运算就可以在正确的时间添加这些事件&使用下方的逻辑放置
如何将时间(以微秒为单位(转换为刻度,反之亦然
这取决于的两个因素
-
序列的格式[PPQ或SMTP]
-
曲目的节奏[BPM]
以下是这些转换的代码
//convert tick's to Microseconds
static long toMicroSeconds(float ticksPerSecond,long tick){return (long)((1E6/ticksPerSecond)*tick);}
/*
get resolution of the track
->if format is PPQ[Pulses per quater note] then this value depends on the tempo of the Sequencer
->Temp scale is nothing but tempoInBPM() & tempoScale() see Sequencer documentation for more details
*/
static float ticksPerSecond(Sequence sequence,float tempoScale)
{
float divisionType=sequence.getDivisionType();
int resolution=sequence.getResolution();
if(divisionType==Sequence.PPQ){return resolution*(tempoScale/60.0f);}
else
{
float framesPerSecond;
if(divisionType==Sequence.SMPTE_24){framesPerSecond=24;}
else if(divisionType==Sequence.SMPTE_25){framesPerSecond=25;}
else if(divisionType==Sequence.SMPTE_30){framesPerSecond=30;}
else{framesPerSecond=29.97f;}
return resolution * framesPerSecond;
}
}
//Convert time[micro second] to ticks
static long toTicks(float ticksPerSecond,long microSeconds){return (long)((ticksPerSecond/1E6)*microSeconds);}
这些转换的数学可以在这里找到
使用上述方法,您应该能够在正确的时间捕获任何事件,包括程序更改
我可能面临的主要问题之一就是抢程序以错误的方式更改事件
程序更改事件实际上由2个事件的组成
一个控制器事件,它改变了序列器的组
实际程序ID
这两个信息都可以从仪器的补丁中获得
想象一下位于网格中的每个仪器,其中X坐标是程序id,Y坐标是银行id
程序ID范围从0到128,因此它可以与程序更改事件一起注册,但组范围从0 16385[有些接近],这是2个字节,但短消息只能容纳1个字节,因此需要2个控制更改事件来更改组
这个实用程序功能应该做到
void registerInstrument(Track track,Instrument instrument,int channel,int tick)
{
Patch patch=instrument.getPatch();
int
programID=patch.getProgram(),
bank=patch.getBank()
//bank is usually 0 for the first 128 instruments after which an controller event is required
if(bank>0)
{
MidiEvent c1=new MidiEvent(new ShortMessage(ShortMessage.CONTROL_CHANGE,channel,0,bank>>7),tick);
track.add(c1);
MidiEvent c2=new MidiEvent(new ShortMessage(ShortMessage.CONTROL_CHANGE,channel,32,bank&0x7f),tick);
track.add(c2);
}
track.add(new MidiEvent(new ShortMessage(ShortMessage.PROGRAM_CHANGE,channel,progrmID,0),tick));
}
我面临的一个问题是,偶尔我会在没有任何指定乐器的MIDI文件(没有程序更改事件(
有些曲目只包含歌曲的元数据,如版权信息或文本/节奏变化事件。程序更改事件通常在同一轨道中紧跟注释,因此每个轨道都不必包含这些事件