建模音乐(音符)以在特定时间快速搜索音符的最佳方法

Posted

技术标签:

【中文标题】建模音乐(音符)以在特定时间快速搜索音符的最佳方法【英文标题】:Best way to model music (notes) for fast searching notes at a particular time 【发布时间】:2012-04-09 21:31:29 【问题描述】:

我正在开发一个 ios 音乐应用程序(用 C++ 编写),我的模型看起来或多或少是这样的:

--Song
----Track
----Track
------Pattern
------Pattern
--------Note
--------Note
--------Note

所以基本上一个Song 有多个Tracks,一个Track 可以有多个Patterns 和一个Pattern 有多个Notes. 这些东西中的每一个都由一个类表示,除了歌曲对象,它们都存储在向量中。

每个Note 都有一个"frame" 参数,以便我可以计算何时应该播放一个音符。例如,如果我每秒有 44100 个样本,并且特定音符的帧是 132300,我知道我需要在第三秒开始时使用该音符。

我的问题是我应该如何表示这些音符以获得最佳性能?现在我正在考虑将注释存储在每个模式的矢量数据成员中,然后循环 Song 的所有 Tracks,而不是查看 Patterns,然后循环 Notes 以查看哪个有大于 132300 且小于 176400(第 4 秒开始)的帧数据成员。

如您所知,循环次数很多,而一首歌曲可能长达 10 分钟。所以我想知道这是否足够快来计算所有帧并按时将它们发送到缓冲区。

【问题讨论】:

“我的问题是我应该如何表示这些笔记以获得最佳性能?”这取决于您要如何处理数据。 好的,请勾选:[.] "I'm reinventing MOD files"[.] "I'm reinventing MIDI files"。注意MOD players already exist for smart phones 这是一个学校项目,我怀疑他们会对这个答案感到满意。老实说,我不明白为什么这很重要.. 你只是假设我正在制作已经存在的东西的精确副本,而我知道情况并非如此。如今,任何事物都很难做到 100% 原创,但这并不是制作好产品的必要条件。 @SigTerm Sled 询问如何使用标准 C++ 容器将数据存储在内存中。 MIDI 和 MOD 是用于在磁盘上存储相同数据的二进制格式,它们与优化性能和实际播放存储在其中的音乐没有任何关系。 【参考方案1】:

您应该记住的一件事是,为了提高性能,通常必须增加内存消耗。在这种情况下,这也是相关的(并且是合理的),因为我相信您希望以不同的方式两次存储相同的数据。

首先,你应该拥有一首歌曲的基本结构:

map<Track, vector<Pattern>> tracks;

它将每个Track 映射到Patterns 的向量。地图很好,因为你不关心轨道的顺序。

遍历Tracks 和Patterns 应该很快,因为它们的数量不会很高(我假设)。主要的性能问题是循环数以千计的音符。以下是我建议的解决方法:

首先,对于每个Pattern 对象,您应该有一个vector&lt;Note&gt; 作为您的主要数据存储。您将首先将Pattern的内容的所有更改写入此vector&lt;Note&gt;

vector<Note> notes;

出于性能考虑,您可以采用第二种存储笔记的方式:

map<int, vector<Notes>> measures;

这会将Pattern 中的每个度量(按其编号)映射到该度量中包含的Notes 的向量。每次主notes 存储中的数据更改时,您将对measures 中的数据应用相同的更改。您也可以在播放前每次只执行一次,甚至在播放时在单独的线程中执行。

当然,您只能将音符存储在小节中,而无需同步两个数据源。但是当您必须对一堆音符应用大量运算时,使用它可能不太方便。

在播放过程中,在下一个小节开始之前,(大致)会发生以下算法:

    在每个音轨中,找到所有模式,其中pattern-&gt;startTime &lt;= [current playback second] &lt;= pattern-&gt;endTime。 对于每个模式,计算当前度量值并从measures 映射中获取对应度量值的vector&lt;Notes&gt;。 现在,在下一个小节(第二个?)开始之前,您只需循环播放当前小节的音符。

【讨论】:

我强烈建议不要使用std::list。数据局部性通常是性能中最重要的部分,std::list 会破坏这一点。我从未在自己的代码中发现std::list 的性能优于std::vectorstd::deque。即使您偶尔插入到中间并担心如果您使用std::vector 必须将其他所有内容转移过来,搜索std::list 所需的时间几乎总是会花费更长的时间。见幻灯片 43:ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf @David 谢谢!更新了我的答案。【参考方案2】:

只需保持这些向量排序即可。

在播放过程中,您可以只为最后一个音符播放器在每个向量中保留一个指针(索引)。要搜索新注释,您必须检查每个向量中的以下注释,不需要循环遍历注释。

【讨论】:

【参考方案3】:

保持你的向量分类,并尝试一下 - 这更重要,你可以在这里得到任何答案。

对于你所有的问题,你应该寻求然后用测试原型来回答,然后你就会知道你是否有问题.而且在尝试的过程中,您会看到通常仅凭理论看不到的东西。

【讨论】:

【参考方案4】:

我的模型看起来或多或少是这样的:

您的模型中缺少几个至关重要的概念:

    节奏。 Dynamics。 Pedal 仪器 Time signature。 (可选)音调。 效果(混响/合唱,音高轮)。 立体定位。 歌词。 和弦图。 作曲者信息/标题。

每个音符都有一个“帧”参数,这样我就可以计算出一个音符应该在什么时候播放。

您的模型中缺少几个至关重要的概念:

    衔接。 触后。 音符时长。

我建议看看lilypond。它是排版软件,但也是以人类可读的文本格式表示音乐的最精确方式之一。

我的问题是我应该如何表示这些音符以获得最佳性能?

将它们全部放入std::map&lt;Timestamp, Note&gt; 并使用lower_bound/upper_bound 找到您想要播放的片段。或者,只要对数据进行排序,您就可以在平面 std::vector 中对它们进行二进制搜索。

除非您想制作“蜂鸣器”,否则制作音乐应用程序比您想象的要困难得多。我强烈建议尝试另一个项目。

【讨论】:

我实际上已经在实施您提到的大部分内容。不是全部,因为有些我根本不需要(踏板、效果器、歌词和和弦)

以上是关于建模音乐(音符)以在特定时间快速搜索音符的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

是否可以转换带有一些音符的音频文件?

从 MIDI 中提取音符开始

在 Python 中,如何演奏音符? [复制]

如何从 .NET 中的字符串中删除变音符号(重音符号)?

如何从 .NET 中的字符串中删除变音符号(重音符号)?

C++ 在音符中转换 MIDI 音符 ID