一步一步教你实现iOS音频频谱动画(一)

本文介绍如何在iOS中实现音频频谱动画,包括频带划分、动画绘制及节奏匹配的调整优化。通过A计权处理降低低频部分的影响,使用加权平均法减少锯齿和动画闪动,实现与音乐节奏同步的视觉效果。
摘要由CSDN通过智能技术生成

如果你想先看看最终效果再决定看不看文章 ->
示例代码下载

 

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

本文是系列文章中的第二篇,上篇讲述了音频播放和频谱数据计算,本篇讲述数据处理和动画的绘制。

前言

在上篇文章中我们已经拿到了频谱数据,也知道了数组每个元素表示的是振幅,那这些数组元素之间有什么关系呢?根据FFT的原理, N个音频信号样本参与计算将产生N/2个数据(2048/2=1024),其频率分辨率△f=Fs/N = 44100/2048≈21.5hz,而相邻数据的频率间隔是一样的,因此这1024个数据分别代表频率在0hz、21.5hz、43.0hz….22050hz下的振幅。

那是不是可以直接将这1024个数据绘制成动画?当然可以,如果你刚好要显示1024个动画物件!但是如果你想可以灵活地调整这个数量,那么需要进行频带划分。

严格来说,结果有1025个,因为在上篇文章的FFT计算中通过fftInOut.imagp[0] = 0,直接把第1025个值舍弃掉了。这第1025个值代表的是奈奎斯特频率值的实部。至于为什么保存在第一个FFT结果的虚部中,请翻看第一篇。

频带划分

频带划分更重要的原因其实是这样的:根据心理声学,人耳能容易的分辨出100hz和200hz的音调不同,但是很难分辨出8100hz和8200hz的音调不同,尽管它们各自都是相差100hz,可以说频率和音调之间的变化并不是呈线性关系,而是某种对数的关系。因此在实现动画时将数据从等频率间隔划分成对数增长的间隔更合乎人类的听感。

 

 

image.png

 

图1 频带划分方式

 

打开项目AudioSpectrum02-starter,您会发现跟之前的AudioSpectrum01项目有些许不同,它将FFT相关的计算移到了新增的类RealtimeAnalyzer中,使得AudioSpectrumPlayerRealtimeAnalyzer两个类的职责更为明确。

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

查看RealtimeAnalyzer类的代码,其中已经定义了 frequencyBands、startFrequency、endFrequency 三个属性,它们将决定频带的数量和起止频率范围。

 

public var frequencyBands: Int = 80 //频带数量
public var startFrequency: Float = 100 //起始频率    
public var endFrequency: Float = 18000 //截止频率

现在可以根据这几个属性确定新的频带:

private lazy var bands: [(lowerFrequency: Float, upperFrequency: Float)] = {
    var bands = [(lowerFrequency: Float, upperFrequency: Float)]()
    //1:根据起止频谱、频带数量确定增长的倍数:2^n
    let n = log2(endFrequency/startFrequency) / Float(frequencyBands)
    var nextBand: (lowerFrequency: Float, upperFrequency: Float) = (startFrequency, 0)
    for i in 1...frequencyBands {
        //2:频带的上频点是下频点的2^n倍
        let highFrequency = nextBand.lowerFrequency * powf(2, n)
        nextBand.upperFrequency = i == frequencyBands ? endFrequency : highFrequency
        bands.append(nextBand)
        nextBand.lowerFrequency = highFrequency
    }
    return bands
}()

接着创建函数findMaxAmplitude用来计算新频带的值,采用的方法是找出落在该频带范围内的原始振幅数据的最大值:

private func findMaxAmplitude(for band:(lowerFrequency: Float, upperFrequency: Float), in amplitudes: [Float], with bandWidth: Float) -> Float {
    let startIndex = Int(round(band.lowerFrequency / bandWidth))
    let endIndex = min(Int(round(band.upperFrequency / bandWidth)), amplitudes.count - 1)
    return amplitudes[startIndex...endIndex].max()!
}

这样就可以通过新的analyse函数接收音频原始数据并向外提供加工好的频谱数据:

func analyse(with buffer: AVAudioPCMBu
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值