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

Can someone help me with tempo MIDI command on delphi 7?

我知道如何在 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;

在撰写此答案时,您已经介绍了 TimeCallBack 过程以及如何调用 TimeSetEvent。以下问题需要您更正。

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

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

2.You 混合了程序逻辑和 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的目的是什么,但基本上你只需要检查节拍器是否应该运行,然后改变速度(我会return到此为止)。 运行 你叫

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

(Bpm 是一个保存每分钟节拍数的变量,见下文)

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

改变节奏

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

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;