Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/317.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 识别数组中在特定时间发生的项的最佳方法_C#_Arrays_Midi - Fatal编程技术网

C# 识别数组中在特定时间发生的项的最佳方法

C# 识别数组中在特定时间发生的项的最佳方法,c#,arrays,midi,C#,Arrays,Midi,我需要先说我没有接受过正式培训,所以即使18个月后我在三个主要程序中处理了大约100000行C代码,但都经历了反复试验和堆栈溢出。我发现,在过去的几个月里,我能够重新访问并大幅改进我的代码。希望你们中的一位接受过更多培训的人能够为我目前的问题提供更好的解决方案,而不必等到几个月后我才能自己赶上 我目前的项目是一个将音频播放到歌曲的程序,以及一个精心制作的代表音频的MIDI文件。该程序读取MIDI文件中的特定曲目和音符。我用NAudio来做到这一点。我关心的每个音符的值都存储在我为此创建的MIDI

我需要先说我没有接受过正式培训,所以即使18个月后我在三个主要程序中处理了大约100000行C代码,但都经历了反复试验和堆栈溢出。我发现,在过去的几个月里,我能够重新访问并大幅改进我的代码。希望你们中的一位接受过更多培训的人能够为我目前的问题提供更好的解决方案,而不必等到几个月后我才能自己赶上

我目前的项目是一个将音频播放到歌曲的程序,以及一个精心制作的代表音频的MIDI文件。该程序读取MIDI文件中的特定曲目和音符。我用NAudio来做到这一点。我关心的每个音符的值都存储在我为此创建的MIDINote类的列表中:

public class MIDINote
{
public int NoteNumber { get; set; }
public double NoteStart { get; set; }
public double NoteLength { get; set; }
public bool Played { get; set; }
}
NoteStart和NoteLength以秒为单位,从绝对时间到小数点后5位的转换非常完美,无需担心

现在我希望你能告诉我如何做得更好

我有一个计时器,它可以增加计数器的值。 在增加该值后,我想确切地知道哪些注释(如果有的话)在该点上处于打开状态,即,要么正好开始,要么已经开始,但仍然没有通过。然后我用笔记做各种各样的事情

我这样做的方式是,我迭代音轨中的每个音符,即我第一次读取MIDI时存储的每个音符,直到时间与当前计数器时间匹配。显示该注释。然后计时器滴答作响,计数器增加,然后重复。但是现在我们在计数时从轨道的开始重复,所以虽然到达轨道中的注释1会很快,但我可以假设到达注释2000的迭代量是巨大的,可能是多余的。 这是我要说的代码片段:

foreach (var note in track)
{
if (note.NoteStart + note.NoteLength < PlaybackSeconds || note.Played) continue;
if (note.NoteStart > PlaybackSeconds) break;
//do my stuff here with this note
note.Played = true;
}
即使我对事物内部运作的知识有限,我也能看出这不是管理这个过程的最佳方式。是否有一种更有效的方法来存储和访问这些信息,从而缩短处理时间

我的开发机器是Intel Core i7、16GB的DDR3 RAM和SSD,因此在我这方面运行得非常快,尽管只需几秒钟的延迟,即使我尝试过将计时器设置为50ms。但是一个朋友在他那端测试这个程序却被延迟了4秒,这是绝对不能接受的


任何关于如何改进的建议都将不胜感激。谢谢

有几种方法可以优化这一点:

1跟踪上次播放的音符的索引查看代码,我假设您已经按音符开始的升序对音符进行了排序,因为否则您的中断可能会丢失可播放窗口中的一些音符。代码看起来像这样

int lastPlayedIndex = 0;
for (int i = lastPlayedIndex; i < track.Length; i++)
{   
    // logic that you have already used to skip through nodes
    lastPlayedIndex = i;
}
您必须确保lastPlayedIndex实际存储在一个可以通过多次调用(可能是类成员)访问的位置

2根据PlaybackSeconds的粒度,您可以为每个间隔创建哈希表条目,并根据这些条目存储注释。例如,如果计数器每50毫秒增加一次,当您读取注释时,您可以创建一个哈希表,其中的条目逻辑上如下所示

0->从0开始但在50之前的注释列表

50->从50开始但在100之前的注释列表 等


当您阅读本文时,您可以轻松地阅读给定PlaybackSeconds值需要播放的子集。

最可能花费时间最多的是“使用此注释执行我的内容”部分中发生的事情。然而,通过您展示的内容,您可以做一些事情:

首先,为NoteEnd在MIDINote类上创建一个新属性。由于您要反复阅读笔记,因此将其作为属性将节省一些时间,因为您不必像我的下一个建议中那样每次都重新计算它

其次,您可以通过linq创建IEnumberable对象,该对象将替换您的if逻辑:

var currentNotes = from n in tracks 
                   where !n.Played && (n.NoteStart < PlaybackSeconds 
                                       && n.NoteStart + n.NoteLength > PlaybackSeconds)
                   select n;
这可能不会对性能有多大影响,但我确实做了一些清理。但是,如果您编写的代码是线程安全的,那么可以利用并行库来提高速度。如果您已经编写了这样的代码,那么您只需要将foreach循环更改为:

Parallel.ForEach(currentNotes, note =>
{
    //do my stuff here with this note with parallel processing
    note.Played = true;
});

请注意,如果您的代码不是线程安全的,并且您使用并行库,那么您将能够并且将获得由于数据竞争而极难跟踪的意外结果。

并行化该蛮力算法对性能较差的机器没有多大帮助,因为这些机器一开始没有很多内核。告诉初学者只写线程安全的代码是……不现实的。@CL我不会说不现实,但很少见。我已经编写了大约相同时间的代码,并构建了生产ETL系统 使用并行库。我没有告诉他写线程安全的代码,相反,我向他展示了一种可能加速方法的方法,特别是在他的机器上,如果他的代码能够处理它的话。如果不能,他至少知道如果他想稍后再改进它,另一个选择是什么。
Parallel.ForEach(currentNotes, note =>
{
    //do my stuff here with this note with parallel processing
    note.Played = true;
});