有人可以帮助我在delphi 7上使用tempo MIDI命令吗?

时间:2015-01-12 22:34:45

标签: delphi midi tempo

我知道如何在midi上产生声音和打击乐,但我需要上下调节速度,我不知道该怎么做,我正在使用多媒体计时器,但问题是我有当同时使用两个或多个声音时出现线程错误。

这是创建循环,从1到4。

procedure TimeCallBack(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
var
  nome: Integer;
begin
  nome := FrmMetrolima.ListBox1.ItemIndex;
  FrmMetrolima.panel518.Caption := FrmMetrolima.ListBox1.Items[nome];

  if (FrmMetrolima.panel518.Caption = 'Binário-2 (pág. 13)') then

  // Binário-2
  begin
    FrmMetrolima.panel3.Caption := '8';
    inc(tempo, 1);
    FrmMetrolima.Panel2.Caption := IntToStr(tempo);
    if FrmMetrolima.Panel2.Caption = '5' then
    begin
      FrmMetrolima.Panel2.Caption := '1';
      tempo := 1;
    end;
    // metrônomo
    if (FrmMetrolima.Panel2.Caption = '1') and
      (FrmMetrolima.Panel28.Caption = '1') and
      (FrmMetrolima.Label7.Caption = '1') then
    begin
      noteOn(9, 75, 127);
    end
    else if (FrmMetrolima.Panel2.Caption = '2') and
      (FrmMetrolima.Panel29.Caption = '2') and
      (FrmMetrolima.Label7.Caption = '1') then
    begin
      noteOn(9, 62, 127);
    end
    else if (FrmMetrolima.Panel2.Caption = '3') and
      (FrmMetrolima.Panel30.Caption = '3') and
      (FrmMetrolima.Label7.Caption = '1') then
    begin
      noteOn(9, 63, 127);
    end
    else if (FrmMetrolima.Panel2.Caption = '4') and
      (FrmMetrolima.Panel31.Caption = '4') and
      (FrmMetrolima.Label7.Caption = '1') then
    begin
      noteOn(9, 62, 127);
    end;

  end;
end;

用法:

if (ListBox1.ItemIndex = 0) then
// Subdivisão Binária
begin
  noteOn(9, 75, 127);
  mmResult := TimeSetEvent(60000 div StrToInt(FlatEdit2.Text) div 2, 0,
    @TimeCallBack, 0, TIME_PERIODIC);

end;

1 个答案:

答案 0 :(得分:1)

在撰写本答案时,您已经提供了TimeCallBack过程以及如何调用TimeSetEvent。您需要纠正以下问题。

1.TimeCallBack在计时器的线程中执行,但您正在访问主线程的UI元素。 VCL不是线程安全的,因此您无法直接访问计时器回调中的任何UI元素。如果你真的必须访问TimeCallBack中的UI我建议使用synchronize,这确保代码在主线程的上下文中执行

但是,有一种更好的方法可以引导我进入第二个问题

2.您正在混合程序逻辑和UI。这是一个广泛的主题,但很快: 为程序逻辑所需的数据创建数据结构。在这种情况下,它可以是一维的四元素记录数组。记录可以有字段来保存一个音符,即您传递给noteOn()的参数。

type
  TNote = record
    chan: byte;
    sound: byte;
    volume: byte;
  end;
  TNoteSequence = array of TNote;

此外,您还需要一个索引变量来跟踪TimeCallBack中要播放的元素。

因此,如果数组被称为NoteSeq并且索引变量被称为indx,则TimeCallBack过程将变为

procedure TimeCallBack(TimerID, Msg: UINT; dwUser, dw1, dw2: DWORD); stdcall;
begin
  with NoteSeq[indx] do
    NoteOn(Chan, Sound, Volume);
  indx := (indx +1) mod 4;
end;

如您所见,无需触摸UI元素,因此您无需使用同步。 我不知道各种Panelxx.caption的用途是什么,但基本上你只需要检查节拍器是否应该运行,并改变速度(我很快就会回到这里)。要运行节拍器,请致电

procedure TForm5.MidiPlayBtnClick(Sender: TObject);
begin
  mmResult := TimeSetEvent(60000 div Bpm, 0, @TimeCallBack, 0, TIME_PERIODIC);
end;

(Bpm是一个持有每分钟节拍的变量,见下文)

3. TimeCallBack过程的调用约定声明为pascal,这是不正确的。 Windows API使用stdcallsafecall。更改为stdcall。有关调用约定的更多信息,请参阅documentation向下滚动到“调用约定”。

改变速度

最后,为了改变你的设置中的节奏,在那里播放音符是由定时器驱动的,你显然需要改变定时器间隔。请注意,根据文档,不推荐使用timeSetEvent,您应该使用CreateTimerQueueTimer。但是,由于我没有这方面的经验,我将继续构建timeSetEvent。 要达到120 Beats Per Minute(BPM),您将计算60000 div Bpm,即500 ms。向数据中添加两个变量,Bpm:integer;和BpmChanged:布尔值;在UI中,您可以使用TUpDown更改Bpm,每当更改Bpm时,您将BpmChanged设置为true。要平稳改变速度,请更改TimeCallBack中的计时器。无法更改正在运行的计时器的间隔,因此您需要终止正在运行的计时器并启动一个新计时器。

procedure TimeCallBack(TimerID, Msg: UINT; dwUser, dw1, dw2: DWORD); stdcall;
begin
  with NoteSeq[indx] do
    NoteOn(Chan, Sound, Volume);
  indx := (indx +1) mod 4;
  if BpmChanged then
  begin
    timeKillEvent(mmResult);
    mmResult := TimeSetEvent(60000 div Bpm, 0, @TimeCallBack, 0, TIME_PERIODIC);
    BpmChanged := False;
  end;
end;