原文链接:Onset Detection Part 1: The Basics

在这篇文章中,我想开始一个小的循序渐进的系列,它将允许您为您的音乐游戏需求构建自己的起始点检测器(onset detector)。我们将从基础开始。一些事情类似阅读不同格式的音乐文件,我会留给你,因为已经有很多材料了。我将展示的是我用来启动起始点检测器的一些步骤。今天我用frap拍了几个视频,它们展示了90%的不同类型的节拍检测器:

视频1

视频2

请务必观看高清视频,否则您将无法看到细节。而且,Fraps搞砸了很多次,所以视频里面有很多挂起(hangs)。本视频展示的是3个频率子带(frequency subbands)上的3个检测函数(detection functions)。我们将在本系列的后面看到这意味着什么。现在你所需要知道的是,在绿线上的每一个红峰本质上是一个音符起始点,无论是鼓的开始(onset),吉他的开始,还是小提琴的开始。该系统自动适应音乐类型,并将选择(pick)它能找到的最具代表性的节奏起始点。我用这个检测方案检查了大约20首不同的歌曲,效果非常好。此外,它的实现非常简单。让我们从最基本的开始。

声音可以看作是空气、水等介质中的波。当我们记录声音时,我们记录麦克风上的介质的压力,更准确的说是波的振幅(more precisely the waves amplitude)。数字记录则更进一步,因为它必须离散这个记录的过程。麦克风以非常高的频率进行采样(checked),频率越高,录音质量越好。这个频率也称为采样率。标准CD音频采样率为44100Hz (Herz)。这意味着我们每秒获得44100次麦克风振幅的测量值。在这种情况下测量通常被称为采样,每个采样步骤返回给我们一个所谓的样本。样本表示麦克风在给定时间测量的振幅。样本的值必须以某种方式存储。这就是所谓的位深度(bit-depth)和编码(encoding)的作用。位深度告诉我们用多少位来表示一个样本的振幅值。这可以是一个字节,一个短字节(2字节)或一个字(4字节)。上面的任何东西通常都不用(Anything above is usually not used)。我们当然还需要定义振幅的取值范围。它可以是有符号的,也可以是无符号的,通常使用有符号的格式。样本也可以存储为整数或规范化(normalized)浮点值。整数样本的范围取决于样本使用的字节数。16位有符号的样本因此有−32768到32767的取值范围。浮点数样本几乎总是标准化到-1到1之间,这会比使用它们的整数计数器部分(integer counter parts)要容易一些。通过除法和乘法(by division and multiplication),我们可以很容易地在16位带符号整数样本和32位浮点样本之间进行转换(我把细节留给您:)。

为了了解纯PCM数据需要多少存储空间(==以某种位深度采样,使用某种编码,例如整数、浮点数等),让我们假设一首单声道歌曲(只有一个PCM通道)的采样率为44100Hz。样本存储为16位带符号整数。这首歌的总时长为180秒。由于采样率的关系,每秒钟我们有44100个样本。每个样本占用16位或2字节。也就是每秒88200字节,大约88Kb。这首歌的长度是180秒,所以我们有88100*180 = 15876000字节,大约15Mb。哇,这么一首双关语(punny)歌曲居然有这么多数据。如果我们将示例存储为32位浮点数,则所需内存将增加一倍,即30Mb。

因此,最流行的格式都对PCM数据进行某种压缩(compression),以将该大小缩小到可管理和可转移的大小。MP3或Ogg Vorbis等格式在这方面做得非常好,但代价是损失了一些音源质量(original quality)。这些格式的音频压缩算法都是有损的,即解压后无法100%恢复原始数据。不过这些压缩算法会被重造(constructed),使得这种损失对大多数听众来说是听不到的。

当我们想分析音乐时,我们并不能直接使用压缩格式。我们必须以某种方式获得直接的PCM数据。这就是解码器(decoder)的作用。Java, C, c#中有无数的库可以用来解码。我不会在这里详细介绍如何使用一个特定的解码器,因为有很多关于这个问题的材料。对于mp3有libmad和libmpg123,它们都是用C写的,Java中有JLayer。对于Ogg Vorbis有用C写的的libvorbis和用Java写的Jorbis。我强烈建议您选择一款适合您的,并阅读所有这些库附带的文档和示例代码。它们中的大多数都非常容易使用。

让我们假设内存中有从解码器获取的PCM数据。大多数歌曲都是立体声的(stereo),所以我们要做的第一件事就是合并两个通道(channel),对每个左声道样本和它对应的右声道样本取平均值。PCM数据也可能是16位带符号整数格式,因此我们还将它转换为浮点数组。我们的函数的返回值将是一个浮点数组,其中包含n个连续的PCM样本。这里有一些代码

public float[] convertPCM( short[] leftChannel, short[] rightChannel )
{float[] result = new float[leftChannel.length];for( int i = 0; i < leftChannel.length; i++ )result[i] = (leftChannel[i] + rightChannel[i]) / 2 / 32768.0f;return result;
}

我们传入两个short数组,一个存着左声道的振幅样本,另一个存着右声道的振幅样本,我们对两个声道的振幅样本取平均值并除以32768.0 f,就可以得到取值范围[-1,1]的振幅(amplitude)的浮点数组,(技术上由于带符号short的取值范围,我们应该对负样本除以32768,对正样本除以32767,但我们懒。。。)。

那么我们现在能拿这个振幅数组做些什么呢?让我们先来看看如何得到一个特定时间点的样本,比如歌曲开始的第10秒。为了获取这个特定时间的样本,我们需要知道采样率。解码器库会很乐意为我们提供这些信息,通常我们会从几乎所有的mp3和Oggs获取到44100Hz的采样率。从现在开始,我们假设固定采样率为44100Hz。我们记得采样率表示了每秒可取到多少个样本。在44100Hz下,每个样本编码(encodes)1 / 44100秒或~0.00002秒。如果我们有我们想要的以秒为单位的样本位置,那么计算样本在浮点数组中的索引是小菜一碟:

float time = 10; // 10 seconds into the song
float samplingRate = 44100.0f;
int index = (int)(time / samplingRate);

我们只要用给定的时间除以采样率就能得到编码(encodes)该时间点振幅的样本的索引。如果您只关心将PCM数据输出到声卡,那么我亲爱的朋友们(像Java Sound或Android上的mediaframework这样的库)几乎就是您需要知道的一切。在Android上,您可以通过AudioTrack类将PCM数据直接发送到声卡,Java声音也存在类似的机制。

为了好玩,让我们为一个440Hz的单声道声音创建PCM数据。这个频率相当于音乐中的A音。声音只不过是波而已,我们通常把它描述为正弦曲线(sinusoid)。在现实生活中,声音信号是许多许多这样的正弦信号的总和,这些正弦信号加在一起构成声音信号的整体音频。当我们想为音符A创建一个a的声音时,我们只需要生成一个周期(period)为1/440秒的正弦曲线。

图片

这个图中的x轴是时间,y轴是正弦波的振幅。周期是0.002秒。我们所说的周期是指从一个窥视点(peek)(在正y轴或负y轴上)到下一个窥视点所需的时间。

让我们编写一个函数,该函数可以为任何指定频率、采样率和持续时间(以秒为单位)的单声道音符生成PCM数据:

public float[] generateSound( float frequency, float samplingRate, float duration )
{float[] pcm = new float[(int)(samplingRate*duration)];float increment = 2 * (float)Math.PI * frequency / samlpingRate;float angle = 0;for( int i = 0; i < pcm.length; i++ ){pcm[i] = Math.sin( angle );angle += increment;}return pcm;
}

代码不是很多,但是其中的一些表达式似乎有些神秘。让我们分解一下。我们首先创建一个浮点数组,它将保存我们的PCM样本。数组的长度是由采样持续时间乘以采样率得出的,也就是每秒采样的数量。如果持续时间为5秒,则需要44100个样本乘以5。不难。下一行计算增量。什么的增量?稍后我们将传给Math.sin()的角度(angle)参数的增量。正如我之前说的,我们研究的是正弦波。为了得到正弦波,我们使用正弦函数。Java中的正弦函数输入一个以弧度表示的角,并输出这个角的正弦值。一个周期, 可以用2 *PI 或者,360°来表示。如果我们想要一个1Hz的声音,我们需要在44100个采样结束时跑完一个周期。对于440Hz,我们需要跑完440个周期等等。所以我们先用2 *PI乘以频率。然后再除以采样率就会得到每一次循环需要的弧度增量(increment)。在循环中,我们将当前样本(pcm[i])赋值为当前弧度的正弦值(Math.sin(angle)),然后将当前弧度(angle)增加一个我们之前计算得出的弧度增量(increment)。现在我们已经知道了写电子合成器(synthesizer)所需要的一切。电子合成器除了使用与上述方法非常相似的方法生成PCM样本外,再无其他。通过将一些函数组合在一起,合成器可以产生十分有趣和复杂的声音。它的基础全是正弦函数(或锯齿函数,或矩形函数)。我们的这个函数缺少的是一种操纵产生的正弦波的响度或音量的方法。sin()函数给出了单位圆的正弦值,因此我们得到了在[-1,1]范围内可能的最大振幅。如果我们想让产生的声音减半我们只要把sin乘以0.5。为了得到最大响度的四分之一,我们乘以0.25,以此类推。很简单不是吗?

本系列的第一部分到此结束。下次我们会看到傅里叶变换是关于什么的。不要担心,我们不会使用任何高等数学,甚至正弦:)。现在,研究一下Java声音库是如何工作的,让您的耳朵被自己生成的正弦波所震耳欲聋吧!

音符起始点检测(音频节奏检测)(1)相关推荐

  1. 音频节奏检测(Onset Detection)

    项目代码下载 https://download.csdn.net/download/weixin_43865690/39129840 1. 前言 最近市场上出现一些多个视频拼接而成MV,其原理是根据音 ...

  2. 音符起始点检测(音频节奏检测)(6)

    原文链接:Onset Detection Part 6: Onsets & Spectral Flux 在之前的一篇文章中,我从科学的角度讨论了起点/节拍检测.有很多不同的方案可以不同程度的做 ...

  3. 音符起始点检测(音频节奏检测)(4.5)

    原文链接:Onset Detection Part 4.5: What to expect (这篇文章没大翻译明白,建议阅读原文.大概内容就是在展示自制检测器对各种类型音乐的检测效果,以及和 audi ...

  4. 音符起始点检测(音频节奏检测)(2)

    原文链接:Onset Detection Part 2: A simple framework 好了,我刚刚为我们的起始点检测入门教程组合了一个简单的框架.它位于 http://code.google ...

  5. 音符起始点检测(音频节奏检测)(5)

    原文链接:Onset Detection Part 5: The (Discrete) Fourier Transform 警告:这是我理解离散傅里叶变换的方法,在一些地方可能是错误的.对于傅里叶变换 ...

  6. 音符起始点检测(音频节奏检测)(7)

    原文链接:Onset Detection Part 7: Thresholding & Peak picking 在上一篇文章中,我们看到了如何将一个随着时间的推移而演化为一个简单一维函数的复 ...

  7. python 声音强度检测_python检测音频中的静音

    #-*- coding: utf-8 -*- importosimportwavefrom time importsleepimportnumpy as np SUCCESS=0 FAIL= 1 #需 ...

  8. 音频算法检测发言者方位

    音频算法检测发言者方位:

  9. java mp3静音检测,音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

最新文章

  1. ICA算法处理后,ICA成分识别
  2. 联络中心的发展方向是SOA
  3. PHP实现一个ip(如:127.0.0.1)和多个域名(虚拟主机)的绑定
  4. shell脚本调试中打开set选项
  5. Java黑皮书课后题第10章:10.21(被5或6整除)找出能被5或6整除的大于Long.MAX_VALUE的前10个数字
  6. # 20175213 2018-2019-2 《Java程序设计》第1周学习总结
  7. 她半年内举报了755篇问题论文,专挑中国“下手”?还牵扯到北大副校长.........
  8. 玩cf出现outofmemory_CF画质粗糙平衡感人,却能历经十年经久不衰,靠的是什么?...
  9. 一篇搞定导航守卫(vue-router源码学习)
  10. 【C++入门】C++多态
  11. HTMLCSS学习笔记(三)----标签类型转换、样式重置
  12. JAVA JSP学生助学金管理系统 jsp学生资助管理系统jsp学生管理系统jsp贷款管理系统jsp大学生贷款管理系统
  13. 多旋翼飞行器设计与控制(四) —— 动力系统建模与估计
  14. Android 系统字体
  15. 《GAMES203:三维重建和理解》1 三维视觉(3D Vision)介绍
  16. 16 | 把大象装进冰箱:HTTP传输大文件的方法
  17. 【数据结构与算法】- 排序(算法)
  18. java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END问题查找
  19. 记录安装tensorflow-gpu,版本选择问题,短平快解决战斗
  20. java的LocalDateTime类来获取当天时间、前一天时间,本周的开始和结束时间、本月的开始和结束时间

热门文章

  1. html手机陀螺仪,手机中的陀螺仪竟然也不值得信任
  2. activated钩子函数
  3. shell遍历多个数组
  4. java jtextarea边框_java swing中的JTextArea边框
  5. Android FTP功能开发基于swiftp
  6. Android 上实现像微信一样的用Fragment来实现的Tab切页效果 提供源码下载
  7. Java中线程的状态。
  8. 软考中级【数据库系统工程师】第1章:计算机系统知识,自学软考笔记,备考2022年5月份软考,计算机硬件系统CPU组成指令寄存器组总线输入输出的程序控制方式计算机体系结构与存储系统加密技术流水线技术
  9. hypermesh 连接单元_基于HYPERMESH的抗扭拉杆悬置自由模态分析研究
  10. 博客设计展示:25个优秀博客设计