我知道如何在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;
答案 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使用stdcall
或safecall
。更改为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;