0

我正在开发一个简单的 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 事件)正在从这些轨道正确捕获。我知道这是一个小众问题,但有人对此有任何见解吗?

4

2 回答 2

0

他们似乎没有与他们相关的蜱虫

他们是否我们只需要一些数学来使用下面的逻辑在正确的时间和地点添加这些事件

如何将时间(以微秒为单位)转换为刻度,反之亦然?

这取决于2个因素

  1. 序列的格式 [PPQ 或 SMTP]

  2. 曲目的节奏[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 个事件组成

  1. 更改音序器库的控制器事件

  2. 实际程序 ID

这两个信息都可以从仪器的补丁中获得

想想位于网格中的每个工具,它的 X 坐标是程序 ID,它的 Y 坐标是它的银行 ID

程序 ID 范围从 0 到 128,因此它可以通过程序更改事件进行注册,但存储库范围从 0 16385[some what close],即 2 个字节,但 ShortMessage 只能保存 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 文件中获得一些没有分配任何乐器的轨道(没有程序更改事件)

某些曲目仅包含歌曲的元数据,例如版权信息或文本/速度更改事件。程序更改事件通常在同一音轨中后跟音符,因此每个音轨不必包含这些事件

于 2022-02-23T11:26:54.020 回答
0

我面临的一个问题是,有时我会在 MIDI 文件中获得一些没有分配任何乐器的轨道(没有程序更改事件)

是的,这可能发生。Midi 规范不强制使用程序更改。这真的取决于设计 Midi 文件的目的。为了获得最大的便携性,通常最好为每个使用的 Midi 通道添加 GM Program Changes。请注意,对于非 GM Midi 设备,您通常使用 Bank SelectProgram Changes 消息指定乐器。

此外,您可能会在 Web 上找到为指定合成器或 VST 设计的 Midi 文件,它使用合成器特定的 SysEx 消息来选择乐器。

他们似乎没有与他们相关的蜱虫

是的,他们有。虽然滴答声通常为 0,因为程序更改通常在歌曲的开头。但碰巧有些歌曲在歌曲中间改变了乐器,所以刻度位置> 0。

如果您想要一个完整的开源 Midi 文件解析器的示例:https ://github.com/jjazzboss/JJazzLab-X/tree/master/Midi/src/org/jjazz/midi/api/parser

于 2021-12-07T18:34:39.933 回答