-3

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

1 回答 1

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。有关调用约定的更多信息,请参阅文档向下滚动到调用约定。

改变节奏

最后,要更改设置中的速度,播放音符由计时器驱动,您显然需要更改计时器间隔。请注意,根据文档,不推荐使用 timeSetEvent,您应该改用 CreateTimerQueueTimer。但是,由于我没有这方面的经验,我将继续在 timeSetEvent 上进行构建。要达到每分钟 120 次节拍 (BPM),您需要计算 60000 div Bpm,即 500 毫秒。将两个变量添加到您的数据中,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;
于 2015-01-13T19:07:51.110 回答