精华内容
下载资源
问答
  • hide(): 在 HTML 文档中, 为一个元素调用 hide() 方法将该元素 display 样式改为 none. 代码功能同 css(“display”, “none”); show(): 将元素 display 样式改为先前显示状态. 以上两个方法在不带任何参数...

    常用方法

    hide(): 在 HTML 文档中, 为一个元素调用 hide() 方法会将该元素的 display 样式改为 none. 代码功能同 css(“display”, “none”);

    show(): 将元素的 display 样式改为先前的显示状态.

    以上两个方法在不带任何参数的情况下, 作用是立即隐藏或显示匹配的元素, 不会有任何动画. 可以通过制定速度参数使元素动起来.

    以上两个方法会同时减少(增大)内容的高度, 宽度和不透明度.

    fadeIn(), fadeOut(): 只改变元素的透明度. fadeOut() 会在指定的一段时间内降低元素的不透明度, 直到元素完全消失. fadeIn() 则相反.

    slideDown(), slideUp(): 只会改变元素的高度. 如果一个元素的 display 属性为 none, 当调用 slideDown() 方法时, 这个元素将由上至下延伸显示. slideUp() 方法正好相反, 元素由下至上缩短隐藏. 

    toggle(): 切换元素的可见状态: 如果元素时可见的, 则切换为隐藏; 如果元素时隐藏的, 则切换为可见的. 

    slideToggle(): 通过高度变化来切换匹配元素的可见性. 

    fadeTo(): 把不透明度以渐近的方式调整到指定的值(0 – 1 之间). 

    <span style="font-size:14px;"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    	<head>
    		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    		<title>Untitled Document</title>
    		<link href="css/style.css" type="text/css" rel="stylesheet" />
    		<script type="text/javascript" src="scripts/jquery-1.7.2.js"></script>
    	
    		<script type="text/javascript">
    			//演示动画效果: show() 和 hide() 方法中传入毫秒数以达到动画的效果
    			/*
    			$(function(){ 
    				$(".head").toggle(function(){
    					$(".content").show(1000);
    				}, function(){
    					$(".content").hide(1000);
    				});
    			})
    			*/
    			
    			//只改变高度
    			/*
    			$(function(){ 
    				$(".head").toggle(function(){
    					$(".content").slideDown(500);
    				}, function(){
    					$(".content").slideUp(500);
    				});
    			})
    			*/
    			
    			//只改变透明度
    			/*
    			$(function(){ 
    				$(".head").toggle(function(){
    					$(".content").fadeIn(1000);
    				}, function(){
    					$(".content").fadeOut(1000);
    				});
    			})
    			*/
    			
    			//toggle() 可以切换元素的可见状态. 
    			//slideToggle(): 通过高度变化来切换匹配元素的可见性
    			//fadeToggle(): 通过透明度来切换元素的可见性.
    			
    			/* $(function(){ 
    				$(".content").show();
    				
    				$(".head").click(function(){
    					//$(".content").toggle();
    					$(".content").slideToggle();
    					//$(".content").fadeToggle();
    
    				}); */
    				//fadeTo(): 把不透明度以渐近的方式调整到指定的值 (0 – 1 之间). 几乎不怎么用
    				//疯狂点击颜色变淡
    				 $(function(){ 
    					$(".content").show();
    					var i = 1; 
    					
    					$(".head").click(function(){
    						
    						 $(".content").fadeTo("slow", i);
    						i = i - 0.1; 
    					}); 
    			})
    		</script>
    	
    	</head>
    	<body>
    		<div id="panel">
    			<h5 class="head">什么是jQuery?</h5>
    			<div class="content">
    				Jquery是继prototype之后又一个优秀的Javascript库。它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器。
    			</div>
    		</div>
    	</body>
    </html>
    </span>


    展开全文
  • 渲染线程分为main thread和compositor thread,如果css动画只改变transform和opacity,这时整个CSS动画得以在compositor trhead完成(而js动画则在main thread执行,然后出发compositor thread进行下一步操作...
    1. js动画代码相对复杂一些
    2.  动画运行时,对动画的控制程度上,js能够让动画暂停、取消、终止,css动画不能添加事件
    3. 动画性能看,js动画多了一个js解析的过程,性能不如css动画好




      面试题目的参考:
          

      渲染线程分为main thread和compositor thread,如果css动画只改变transform和opacity,这时整个CSS动画得以在compositor trhead完成(而js动画则会在main thread执行,然后出发compositor thread进行下一步操作),特别注意的是如果改变transform和opacity是不会layout或者paint的。

      区别:

      功能涵盖面,js比css大

      实现/重构难度不一,CSS3比js更加简单,性能跳优方向固定

      对帧速表现不好的低版本浏览器,css3可以做到自然降级

      css动画有天然事件支持

      css3有兼容性问题

    展开全文
  • 作者:Michael Nguyen编译:ronghuaiyang导读昨天文章中提到了Michael这篇文章,今天就来看看,做序列信号处理,离不开LSTM和GRU,很多人觉得这两个东西很复杂,特别是LSTM,里面一堆门,看看就头晕。...
    作者:Michael Nguyen编译:ronghuaiyang

    导读

    昨天的文章中提到了Michael的这篇文章,今天就来看看,做序列信号的处理,离不开LSTM和GRU,很多人会觉得这两个东西很复杂,特别是LSTM,里面一堆的门,看看就头晕。不过,其实只要帮你梳理一下,理解起来还是很清楚的,其实就是一个信息流动的过程,这次带给大家的分享更是通过动图的方式,让大家一次看个明白。
    0ac3160cabd8c016bd35ff03fc2f4e8f.gif

    大家好,欢迎来到长短期记忆(LSTM)和门控循环单元(GRU)的图解指南。我是Michael,我是AI语音助手空间的机器学习工程师。

    在这篇文章中,我们将从LSTM和GRU背后的直觉开始。然后,我将解释LSTM和GRU的内部机制。如果你想了解这两个网络背后发生了什么,那么这篇文章就是为你准备的。

    本来这里有个视频的,不过油管上不了,给个地址给大家,大家各自想办法吧:https://youtu.be/8HyCNIVRbSU。

    问题,短期记忆

    循环神经网络受短期记忆的影响,如果序列足够长,他们就很难将信息从早期的时间步传递到后期的时间步。因此,如果你想处理一段文字来做预测,RNN可能从一开始就遗漏了重要的信息。

    在反向传播过程中,循环神经网络存在梯度消失问题。梯度是用来更新神经网络权重的值。消失梯度问题是指,当梯度随着时间的推移而缩小时。如果梯度值变得非常小,那它对学习就没有太大的帮助了。

    67cdeba17d20f145604fd21026f56be4.png

    Gradient Update Rule

    所以在循环神经网络中,获得小梯度的层停止了学习。这些通常是较早的层。因此,由于这些层无法学习,RNN可能会忘记在较长的序列中之前看到的内容,从而产生短期记忆。

    解决方案就是LSTMs 和GRUs

    LSTMs 和 GRUs 可以用来解决短期记忆的问题。它们有一种叫做“门”的内部机制,可以调节信息的流动。

    367783d80290091b0f7c6e482e0d73cf.png

    这些门可以知道序列中哪些数据是重要的,是保留还是丢弃。通过这样做,它可以将相关信息沿着长链传递下去,从而做出预测。几乎所有的业界领先的循环神经网络都是通过这两种方式实现的。LSTM和GRU可以用于语音识别、语音合成和文本生成。你甚至可以用它们为视频生成标题。

    好了,在这篇文章的最后,你应该对LSTM和GRU擅长处理长序列的原因有了一个扎实的理解。现在,我将用直观的解释和说明来解决这个问题,并尽可能避免使用数学。

    直觉

    让我们从一个思想实验开始。假设你正在查看网上的评论,决定是否是否要买麦片,你会先看一下评论,看看别人认为它是好的还是坏的。

    8c0d99be9b6e209853a831b39ef41f81.png

    当你阅读评论时,你的大脑潜意识里只记住重要的关键词。你学会了“amzing”和“perfectly balanced breakfast”这样的词,你不太喜欢像“this”、“give”、“all”、“should”这样的词。如果第二天朋友问你评论说了什么,你可能不会逐字记住,你可能记得要点,比如“will definitely be buying again”。如果你和我差不多的话,其他的词就会从记忆中消失。

    cd7af7deb1136018582acde28bc10f13.gif

    这就是LSTM或GRU的本质。它可以学会只保留相关信息进行预测,而忘记无关的数据。在这种情况下,你记住的单词使你判断它是好的。

    复习一下循环神经网络

    为了理解LSTM或GRU是如何做到这一点的,让我们复习一下循环神经网络。RNN是这样工作的:将第一个单词转换成机器可读的向量,然后RNN逐个处理向量序列。

    880b6f28f60de62ebdeb036cfc921e76.gif

    Processing sequence one by one

    在处理过程中,它将前一个隐藏状态传递给序列的下一个步骤,隐藏状态充当神经网络存储器。它保存网络以前看到的数据的信息。

    54a56b7bc3fa6bbb8a747d09ab06fb0e.gif

    Passing hidden state to next time step

    我们看下RNN的一个cell,看看如何来计算隐藏状态。首先,将输入和之前的隐藏状态组合成一个向量。这个向量和当前输入和以前输入的信息有关。该向量经过tanh激活,输出是新的隐藏状态,即网络的记忆。

    8abac074f14bfdcbe526f65ab1c4839b.gif

    RNN Cell

    Tanh激活

    tanh激活函数用于调节流经网络的值,tanh函数压缩后值在-1和1之间。

    fc3fd3414ea6788e744e2c92d7573787.gif

    Tanh squishes values to be between -1 and 1

    当向量流经神经网络时,由于各种数学运算,它会进行许多转换。假设一个值连续的乘以3,你可以看到一些值是如何爆炸并变成天文数字的,从而导致其他值看起来就微不足道了。

    9954b6c9e15192b796e7afd3f6c841f0.gif

    vector transformations without tanh

    tanh函数确保值在-1和1之间,从而调节神经网络的输出。你可以看到在从前面来的值是如何通过tanh函数,将值保持在tanh函数允许的边界范围内的。

    b295d4cfac239302dda07cffe3870090.gif

    vector transformations with tanh

    这就是RNN,它内部的操作很少,但是在适当的环境下(比如短序列)可以很好地工作。RNN使用的计算资源比它的进化变种LSTM和GRU少得多。

    LSTM

    LSTM具有类似于循环神经网络的控制流。它在向前传播时处理传递信息的数据。不同之处在于LSTM cell内的操作。

    2f81dc627fd3125f68fb07f52a566752.png

    LSTM Cell and It’s Operations

    这些操作允许LSTM保存或忘记信息,现在看这些运算可能会有点难,所以我们会一步一步地过一遍。

    核心概念

    LSTM的核心概念是cell状态,还有各种各样的门。cell状态充当传输高速公路,沿着序列链传输相关信息。你可以把它看作网络的“存储器”。理论上,cell状态可以在整个序列处理过程中携带相关信息。因此,即使是早期时间步骤的信息也可以传递到后期时间步骤,从而减少短期记忆的影响。当cell状态运行时,信息通过门被添加到cell状态或从cell状态中删除。这些门是不同的神经网络,决定哪些信息是允许的cell状态。这些门可以在训练中学习哪些信息是相关的从而进行保持或忘记。

    Sigmoid

    门中包含sigmoid激活函数。sigmoid激活函数和tanh激活函数很类似,只是它压缩之后不是-1和1之间的值,而是0和1之间的值。这有助于更新或忘记数据,因为任何被乘以0的数字都是0,从而导致值消失或“被遗忘”。任何数乘以1都是相同的值,所以这个值保持不变。“网络可以知道哪些数据重不重要,因此可以忘记或保留那些数据。”

    3ba799663709b8f6bb018363d198909b.gif

    Sigmoid squishes values to be between 0 and 1

    我们再更深入地研究一下这些门在做什么,我们有三个不同的门来调节LSTM cell中的信息流,遗忘门,输入门,和输出门。

    遗忘门

    这个门决定了哪些信息应该丢弃或保留。来自以前隐藏状态的信息和来自当前输入的信息通过sigmoid函数进行传递。结果在0到1之间。越接近0表示忘记,越接近1表示保留。

    efb4a825d8ceba25d2856ca73a66fedd.gif

    Forget gate operations

    输入门

    我们使用输入门来更新cell状态。首先,我们将之前的隐藏状态和当前输入传递给一个sigmoid函数。它将值转换为0到1之间来决定更新哪些值。0表示不重要,1表示重要。你还需要将隐藏状态和当前的输入送到tanh函数中,以得到-1到1之间的值,帮助调节网络。然后将tanh输出与sigmoid输出相乘。sigmoid输出将决定从tanh的输出中保留哪些重要信息。

    42d78c55ca148603b5bd29318e0a1f7c.gif

    Input gate operations

    Cell状态

    现在我们有了足够的信息来计算cell的状态。首先,cell状态逐点乘以遗忘向量。如果将其乘以接近0的值,则有可能降低cell状态下的值。然后我们从输入门获取输出,并进行逐点加法,将cell状态更新为神经网络认为相关的新值。这就得到了新的cell状态。

    3b346a4b7d65c9436ab636c991294910.gif

    Calculating cell state

    输出门

    最后是输出门。输出门决定下一个隐藏状态应该是什么。请记住,隐藏状态中包含之前输入的信息,隐藏状态也用于预测。首先,我们将之前的隐藏状态和当前输入传递给一个sigmoid函数。然后我们将新修改的cell状态传递给tanh函数。我们将tanh输出与sigmoid输出相乘,以决定隐藏状态应该包含哪些信息,输出是隐藏状态。新的cell状态和新的隐藏状态然后被转移到下一个时间步骤。

    c03260b32819d23d7aa24c9ba45dc951.gif

    output gate operations

    回顾一下,遗忘门决定了前面的步骤什么是相关的需要保留的。输入门决定从当前步骤中添加哪些相关信息。输出门决定下一个隐藏状态应该是什么。

    编程Demo

    这里有一个使用python伪代码的示例。

    3e13761067d232baf53b756e5f63fcf1.png
    1. 首先,将以前的隐藏状态和当前输入连接起来。我们叫它组合向量。
    2. 把组合向量送到遗忘层,该层去掉了不相关的信息。
    3. 使用组合向量创建候选层,候选向量中包含了可能会添加到cell状态中的值。
    4. 组合向量也被输入到输入层,该层决定应该将来自候选向量的哪些数据添加到新cell状态。
    5. 在计算遗忘层、候选层和输入层之后,使用这些向量和之前的cell状态计算新的cell状态。
    6. 然后计算输出。
    7. 将输出与新的cell状态逐点相乘会得到新的隐藏状态。

    就是这样!LSTM网络的控制流程是几个张量操作和一个for循环,你可以使用隐藏状态进行预测。结合所有这些机制,LSTM可以选择在序列处理过程中哪些信息是需要记住的,哪些信息是需要忘记的。

    GRU

    现在我们知道了LSTM是如何工作的,让我们简单地看一下GRU。GRU是新一代的递归神经网络,与LSTM非常相似。GRU摆脱了cell状态,并使用隐藏状态来传输信息。它也只有两个门,一个复位门和一个更新门

    67592a6110b126090483c22cd5025e51.png

    GRU cell and it’s gates

    更新门

    更新门的作用类似于LSTM的遗忘门和输入门,它决定丢弃什么信息和添加什么新信息。

    复位门

    复位门用来决定有多少过去的信息要忘记。

    这就是GRU,GRU的张量运算更少,因此,比LSTM的训练要快一些。目前还没有一个明确的优胜者。研究人员和工程师通常同时尝试以确定哪种方法更适合他们的场景。

    就是这么多

    综上所述,RNN具有较好的序列数据处理能力和预测能力,但存在短时记忆问题。LSTM和GRU使用称为门的机制来缓解短期记忆问题。门就是控制信息在序列链中流动的神经网络。LSTM和GRU可以应用于语音识别、语音合成、自然语言理解等领域。

    原文链接:

    https://towardsdatascience.com/illustrated-guide-to-lstms-and-gru-s-a-step-by-step-explanation-44e9eb85bf21

    更多文章,请关注微信公众号:AI公园

    展开全文
  • 如果你想先看看最终效果再决定看不看文章 -> bilibili 示例代码下载 ...很久以前在电脑上听音乐时候,经常调出播放器一个小工具,里面柱状图随着音乐节奏而跳动,就感觉自己好专...
    如果你想先看看最终效果再决定看不看文章 -> bilibili
    示例代码下载

    第二篇:一步一步教你实现iOS音频频谱动画(二)

    基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱数据的获取,下篇将讲述数据处理和动画绘制。

    前言

    很久以前在电脑上听音乐的时候,经常会调出播放器的一个小工具,里面的柱状图会随着音乐节奏而跳动,就感觉自己好专业,尽管后来才知道这个是音频信号在频域下的表现。

    热身知识

    动手写代码之前,让我们先了解几个基础概念吧

    音频数字化

    • 采样: 总所周知,声音是一种压力波,是连续的,然而在计算机中无法表示连续的数据,所以只能通过间隔采样的方式进行离散化,其中采集的频率称为采样率。根据奈奎斯特采样定理 ,当采样率大于信号最高频率的2倍时信号频率不会失真。人类能听到的声音频率范围是20hz到20khz,所以CD等采用了44.1khz采样率能满足大部分需要。

    • 量化: 每次采样的信号强度也会有精度的损失,如果用16位的Int(位深度)来表示,它的范围是[-32768,32767],因此位深度越高可表示的范围就越大,音频质量越好。

    • 声道数: 为了更好的效果,声音一般采集左右双声道的信号,如何编排呢?一种是采用交错排列(Interleaved): LRLRLRLR ,另一种采用各自排列(non-Interleaved): LLL RRR

    以上将模拟信号数字化的方法称为脉冲编码调制(PCM),而本文中我们就需要对这类数据进行加工处理。

    傅里叶变换

    现在我们的音频数据是时域的,也就是说横轴是时间,纵轴是信号的强度,而动画展现要求的横轴是频率。将信号从时域转换成频域可以使用傅里叶变换实现,信号经过傅里叶变换分解成了不同频率的正弦波,这些信号的频率和振幅就是我们需要实现动画的数据。

    图1 (from nti-audio) 傅里叶变换将信号从时域转换成频域

    实际上计算机中处理的都是离散傅里叶变换(DFT),而快速傅里叶变换(FFT)是快速计算离散傅里叶变换(DFT)或其逆变换的方法,它将DFT的复杂度从O(n²)降低到O(nlogn)。 如果你刚才点开前面链接看过其中介绍的FFT算法,那么可能会觉得这FFT代码该怎么写?不用担心,苹果为此提供了Accelerate框架,其中vDSP部分提供了数字信号处理的函数实现,包含FFT。有了vDSP,我们只需几个步骤即可实现FFT,简单便捷,而且性能高效。

    iOS中的音频框架

    现在开始让我们看一下iOS系统中的音频框架, AudioToolbox功能强大,不过提供的API都是基于C语言的,其大多数功能已经可以通过AVFoundation实现,它利用Objective-C/Swift对于底层接口进行了封装。我们本次需求比较简单,只需要播放音频文件并进行实时处理,所以AVFoundation中的AVAudioEngine就能满足本次音频播放和处理的需要。

    图2 (from WWDC16) iOS/MAC OS X 音频技术栈

    AVAudioEngine

    AVAudioEngine 从iOS8加入到AVFoundation框架,它提供了以前需要深入到底层AudioToolbox才实现的功能,比如实时音频处理。它把音频处理的各环节抽象成AVAudioNode并通过AVAudioEngine进行管理,最后将它们连接构成完整的节点图。以下就是本次教程的AVAudioEngine与其节点的连接方式。

    图3 AVAudioEngine和AVAudioNode连接图

    mainMixerNodeoutputNode都是在被访问的时候默认由AVAudioEngine对象创建并连接的单例对象,也就是说我们只需要手动创建engineplayer节点并将他们连接就可以了!最后在mainMixerNode的输出总线上安装分接头将定量输出的AVAudioPCMBuffer数据进行转换和FFT。

    代码实现

    了解了以上相关知识后,我们就可以开始编写代码了。打开项目AudioSpectrum01-starter,首先要实现的是音频播放功能。

    如果你只是想浏览实现代码,打开项目AudioSpectrum01-final即可,已经完成本篇文章的所有代码

    音频播放

    AudioSpectrumPlayer类中创建AVAudioEngineAVAudioPlayerNode两个实例变量:

    private let engine = AVAudioEngine()
    private let player = AVAudioPlayerNode()
    复制代码

    接下来在init()方法中添加如下代码:

    //1
    engine.attach(player)
    engine.connect(player, to: engine.mainMixerNode, format: nil)
    //2
    engine.prepare()
    try! engine.start()
    复制代码

    //1:这里将player挂载到engine上,再把playerenginemainMixerNode连接起来就完成了AVAudioEngine的整个节点图创建(详见图3)。
    //2:在调用enginestrat()方法开始启动engine之前,需要通过prepare()方法提前分配相关资源

    继续完善play(withFileName fileName: String)stop()方法:

    //1
    func play(withFileName fileName: String) {
        guard let audioFileURL = Bundle.main.url(forResource: fileName, withExtension: nil),
            let audioFile = try? AVAudioFile(forReading: audioFileURL) else { return }
        player.stop()
        player.scheduleFile(audioFile, at: nil, completionHandler: nil)
        player.play()
    }
    //2
    func stop() {
        player.stop()
    }
    复制代码

    //1:首先需要确保文件名为fileName的音频文件能正常加载,然后通过stop()方法停止之前的播放,再调用scheduleFile(_:at:completionHandler:)方法编排新的文件,最后通过play()方法开始播放。
    //2:停止播放调用playerstop()方法即可。

    音频播放功能已经完成,运行项目,试试点击音乐右侧的Play按钮进行音频播放吧。

    音频数据获取

    前面提到我们可以在mainMixerNode上安装分接头定量获取AVAudioPCMBuffer数据,现在打开AudioSpectrumPlayer文件,先定义一个属性: fftSize,它是每次获取到的buffer的frame数量。

    private var fftSize: Int = 2048
    复制代码

    将光标定位至init()方法中的engine.connect()语句下方,调用mainMixerNodeinstallTap方法开始安装分接头:

    engine.mainMixerNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(fftSize), format: nil, block: { [weak self](buffer, when) in
        guard let strongSelf = self else { return }
        if !strongSelf.player.isPlaying { return }
        buffer.frameLength = AVAudioFrameCount(strongSelf.fftSize) //这句的作用是确保每次回调中buffer的frameLength是fftSize大小,详见:https://stackoverflow.com/a/27343266/6192288
        let amplitudes = strongSelf.fft(buffer)
        if strongSelf.delegate != nil {
            strongSelf.delegate?.player(strongSelf, didGenerateSpectrum: amplitudes)
        }
    })
    复制代码

    在分接头的回调 block 中将拿到的 2048 个 frame 的 buffer 交由fft函数进行计算,最后将计算的结果通过delegate进行传递。

    按照 44100hz 采样率和 1 Frame = 1 Packet 来计算(可以参考这里关于channel、sample、frame、packet的概念与关系),那么block将会在一秒中调用44100/2048≈21.5次左右,另外需要注意的是block有可能不在主线程调用。

    FFT实现

    终于到实现FFT的时候了,根据vDSP文档,首先需要定义一个FFT权重数组(fftSetup),它可以在多次FFT中重复使用和提升FFT性能:

    private lazy var fftSetup = vDSP_create_fftsetup(vDSP_Length(Int(round(log2(Double(fftSize))))), FFTRadix(kFFTRadix2))
    复制代码

    不需要时在析构函数(反初始化函数)中销毁:

    deinit {
        vDSP_destroy_fftsetup(fftSetup)
    }
    复制代码

    最后新建fft函数,实现代码如下:

    private func fft(_ buffer: AVAudioPCMBuffer) -> [[Float]] {
        var amplitudes = [[Float]]()
        guard let floatChannelData = buffer.floatChannelData else { return amplitudes }
    
        //1:抽取buffer中的样本数据
        var channels: UnsafePointer<UnsafeMutablePointer<Float>> = floatChannelData
        let channelCount = Int(buffer.format.channelCount)
        let isInterleaved = buffer.format.isInterleaved
        
        if isInterleaved {
            // deinterleave
            let interleavedData = UnsafeBufferPointer(start: floatChannelData[0], count: self.fftSize * channelCount)
            var channelsTemp : [UnsafeMutablePointer<Float>] = []
            for i in 0..<channelCount {
                var channelData = stride(from: i, to: interleavedData.count, by: channelCount).map{ interleavedData[$0]}
                channelsTemp.append(UnsafeMutablePointer(&channelData))
            }
            channels = UnsafePointer(channelsTemp)
        }
        
        for i in 0..<channelCount {
            let channel = channels[i]
            //2: 加汉宁窗
            var window = [Float](repeating: 0, count: Int(fftSize))
            vDSP_hann_window(&window, vDSP_Length(fftSize), Int32(vDSP_HANN_NORM))
            vDSP_vmul(channel, 1, window, 1, channel, 1, vDSP_Length(fftSize))
            
            //3: 将实数包装成FFT要求的复数fftInOut,既是输入也是输出
            var realp = [Float](repeating: 0.0, count: Int(fftSize / 2))
            var imagp = [Float](repeating: 0.0, count: Int(fftSize / 2))
            var fftInOut = DSPSplitComplex(realp: &realp, imagp: &imagp)
            channel.withMemoryRebound(to: DSPComplex.self, capacity: fftSize) { (typeConvertedTransferBuffer) -> Void in
                vDSP_ctoz(typeConvertedTransferBuffer, 2, &fftInOut, 1, vDSP_Length(fftSize / 2))
            }
        
            //4:执行FFT
            vDSP_fft_zrip(fftSetup!, &fftInOut, 1, vDSP_Length(round(log2(Double(fftSize)))), FFTDirection(FFT_FORWARD));
            
            //5:调整FFT结果,计算振幅
            fftInOut.imagp[0] = 0
            let fftNormFactor = Float(1.0 / (Float(fftSize)))
            vDSP_vsmul(fftInOut.realp, 1, [fftNormFactor], fftInOut.realp, 1, vDSP_Length(fftSize / 2));
            vDSP_vsmul(fftInOut.imagp, 1, [fftNormFactor], fftInOut.imagp, 1, vDSP_Length(fftSize / 2));
            var channelAmplitudes = [Float](repeating: 0.0, count: Int(fftSize / 2))
            vDSP_zvabs(&fftInOut, 1, &channelAmplitudes, 1, vDSP_Length(fftSize / 2));
            channelAmplitudes[0] = channelAmplitudes[0] / 2 //直流分量的振幅需要再除以2
            amplitudes.append(channelAmplitudes)
        }
        return amplitudes
    }
    复制代码

    通过代码中的注释,应该能了解如何从buffer获取音频样本数据并进行FFT计算了,不过以下两点是我在完成这一部分内容过程中比较难理解的部分:

    1. 通过buffer对象的方法floatChannelData获取样本数据,如果是多声道并且是interleaved,我们就需要对它进行deinterleave, 通过下图就能比较清楚的知道deinterleave前后的结构,不过在我试验了许多音频文件之后,发现都是non-interleaved的,也就是无需进行转换。┑( ̄Д  ̄)┍
    图4 interleaved的样本数据需要进行deinterleave
    1. vDSP在进行实数FFT计算时利用一种独特的数据格式化方式以达到节省内存的目的,它在FFT计算的前后通过两次转换将FFT的输入和输出的数据结构进行统一成DSPSplitComplex。第一次转换是通过vDSP_ctoz函数将样本数据的实数数组转换成DSPSplitComplex。第二次则是将FFT结果转换成DSPSplitComplex,这个转换的过程是在FFT计算函数vDSP_fft_zrip中自动完成的。

      第二次转换过程如下:n位样本数据(n/2位复数)进行fft计算会得到n/2+1位复数结果:{[DC,0],C[2],...,C[n/2],[NY,0]} (其中DC是直流分量,NY是奈奎斯特频率的值,C是复数数组),其中[DC,0]和[NY,0]的虚部都是0,所以可以将NY放到DC中的虚部中,其结果变成{[DC,NY],C[2],C[3],...,C[n/2]},与输入位数一致。

    图5 第一次转换时,vDSP_ctoz函数将实数数组转换成DSPSplitComplex结构

    再次运行项目,现在除了听到音乐之外还可以在控制台中看到打印输出的频谱数据。

    图6 将结果通过Google Sheets绘制出来的频谱图

    好了,本篇文章内容到这里就结束了,下一篇文章将对计算好的频谱数据进行处理和动画绘制。

    资料参考
    [1] wikipedia,脉冲编码调制, zh.wikipedia.org/wiki/%E8%84…
    [2] Mike Ash,音频数据获取与解析, www.mikeash.com/pyblog/frid…
    [3] 韩 昊, 傅里叶分析之掐死教程, blog.jobbole.com/70549/
    [4] raywenderlich, AVAudioEngine编程入门,www.raywenderlich.com/5154-avaudi…
    [5] Apple, vDSP编程指南, developer.apple.com/library/arc…
    [6] Apple, aurioTouch案例代码,developer.apple.com/library/arc…


    作者:potato04
    链接:https://juejin.im/post/5c1bbec66fb9a049cb18b64c
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    转载于:https://www.cnblogs.com/Free-Thinker/p/10880118.html

    展开全文
  • 第二篇:一步一步教你实现iOS音频频谱动画(二) 如果你想先看看最终效果再决定看不看文章 ->bilibili 示例代码下载 基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱数据获取,下篇...
  • css3动画与js动画对比

    千次阅读 2018-12-03 16:24:19
    如果CSS动画只是改变transforms和opacity,这时整个CSS动画得以在compositor thread完成(而JS动画则在main thread执行,然后触发compositor进行下一步操作) 在JS执行一些昂贵任务时,main thread繁忙,CSS动画...
  • 本文章为综合其它资料所得。...如果CSS动画只是改变transforms和opacity,这时整个CSS动画得以在compositor thread完成(而JS动画则在main thread执行,然后触发compositor进行下一步操作)在JS执...
  • 课程中我详细讲解每个动画效果实现的原理和所用的技术,并带你一步一步的实现每个动画效果,让你在学完本次课程后,能够举一反三,再也不必担心设计MM的设计你没法实现了,也再也不用担心,老板的脑洞无... ...
  • 课程中我详细讲解每个动画效果实现的原理和所用的技术,并带你一步一步的实现每个动画效果,让你在学完本次课程后,能够举一反三,再也不必担心设计MM的设计你没法实现了,也再也不用担心,老板的脑洞无......
  • css3 动画

    2021-01-23 15:54:24
    设置动画效果,必须先要设置一个关键帧,关键帧里设置了动画每一步的效果 @keyframes @keyframes name {} 设置关键帧 可选值: from 表示动画的开始状态 to 表示动画的结束状态 % 将动画过程百分化,可以设置 n 个...
  • 前言很久以前在电脑上听音乐时候,经常调出播放器一个小工具,里面柱状图随着音乐节奏而跳动,就感觉自己好专业,尽管后来才知道这个是音频信号在频域下表现。热身知识动手写代码之前,让我们先了解几个...
  • 比如,你往前迈一步,那么厉害原画师就会画二三十张图,每张仅改动细微变化,这样最后展示出动作才自然。宫崎骏老先生就曾批评他儿子,说他画的一个人物上楼梯动作非常不自然,偷工减料,张数不够。那么,...
  • CATransition动画精讲

    2016-08-18 15:46:41
    现在,不再等待,一步一步地学习其基础知识并开始尝试写一些常用动画效果。 如果您也一样迷茫,那就不要迷茫了,实践出真知!!! 基础知识 我们直接看官方声明: /** Transition animat
  • SVG+JS驱动波浪动画

    2016-05-23 04:16:38
    三次以上贝塞尔曲线,更加复杂函数控制SVG路径……路总要一步一步走,先总结一下当前实现效果吧! 功能:随意指定高度和宽度(在SVG pathd属性中填写相关参数),波浪高度在高度和-高度之间来变动,...
  • QTimeLine 控制动画

    2017-04-17 12:26:07
    QTimeLine顾名思义表示一条时间线,即一个时间序列,该时间序列按我们实现定义好的间隔一步一步的往前变化,并在每次变化时都会发出一个frameChanged()信号。所以,我们通常使用该类来驱动我们的动画。
  • 最后一步设置,再增加一个额外动画效果。在动画最后添加一个淡入效果,好开始。 添加一个新图层。注意,这个图层拷贝“background ”图层中所有信息,包括所有关键帧。 确保时间滑块在第200帧...
  • 关于iOS核心动画

    2019-05-22 22:16:50
    iOS开发者尼克·洛克伍德带你一步一步体验Core Animation框架,通过示例代码和图表加深理解。洛克伍德揭开核心动画API神秘面纱,同时教你如何使用 层和视图,绘图软件和硬件合成 图层几何,点击测试和剪辑 ...
  • 在完成了基础动画资源准备之后,下一步就是在引擎中使用这些原始动画资源(虚幻4中动画技术【1】中有讲解各种资源在外部ddc中制作)。不过在使用这些原始动画资源之前,需要对这些资源进行整合,或者分割...
  • 在cocos creator中使用帧动画(spriteFrame)时,如果只使用默认设置,帧以锚点为中心来刷新,比如一个角色一刀砍出,发现刀只伸了一半,角色向反方向移动了另一半;我们要不是这种效果。 先看一下演示...
  • 前端模拟排序动画

    2018-09-30 13:31:57
    Sort-the-animation 携程前端模拟排序动画,效果如下 第一种实现方式预览 第二种实现方式预览 第三种实现方式预览 ...通过排序把每一步的交换序列放入 sortDetail 中(后续位置发生变化,所以要用 ...
  • 如果你需要改变一些改变canvas状态设置(样式,变形之类)又要在每一帧之时都是原始状态话,你需要先保存一下 3.绘制动画图形(animated shapes) ​ 这一步才是重绘动画帧。 4.恢复canvas状态 如果已经...
  • 我们开机后看到一个图片(绿色机器人),下面有android字样,现在我们想改成我们自己图片LOGO,修改如下: 1)首先,我们需要一张PNG图片,用PS保存web所用格式,预设”项选择“PNG-24”(选PNG-8看起来特别...
  • 基于Android Studio模板Bottom ...这一步可以将该工程在手机上进行调试一下并点击一下发现底部并没有动画,但是当我们将底部导航栏按钮添加至4个及以上时便出现了底部选中按钮变大一个情况,那么遇到这种...
  • 方块颜色变化,是随着坐标变化而动态改变,我们写一个超简单 Shader 来实现。接下来,我们一步一步实现。我们先来分析一下这个效果,把问题拆成一个一个小问题,然后逐个解决掉。我们先来考虑一下 Sin 函数...

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 339
精华内容 135
关键字:

一步一步的画会动的画