• 前端音频处理之AudioMass调研


    主要工具:wavesurfer.js

    通过Web Audio Api 来对音频进行处理。

    文档资料:

    Web Audio API
    AudioMass源码
    AudioMass在线网页 (这个AudioMass编辑器跟一些专业的音频编辑器相比,还是差了很多东西)
    Web Audio 简易教程 (没了解过WebAudio的瞧瞧这个~)

    AudioMass源码目录结构:
    index.html引入文件:
    各种音频效果的数据计算 —去看–>actions.js文件最下面var FXBank = {...这里面的内容...}

    <!-- 根据音频文件绘制音频波形图 -->
    <script src="dist/wavesurfer.js"></script>
    <!-- 绘制音频波形图选框 -->
    <script src="dist/plugin/wavesurfer.regions.js"></script>
    <!-- 创建dom元素(传入内容,时间,类名),插入document -->
    <script src="oneup.js"></script>
    <!-- 音频编辑器实例 -->
    <script src="app.js"></script>
    <!-- 音频编辑器按键事件监听 -->
    <script src="keys.js"></script>
    <!-- 创建编辑器菜单 -->
    <script src="contextmenu.js"></script>
    <!-- 菜单栏点击后的弹窗 -->
    <script src="ui-fx.js"></script>
    <!-- 创建菜单栏 -->
    <script src="ui.js"></script>
    <!-- 弹窗等通用组件模型封装 -->
    <script src="modal.js"></script>
    <!-- 对操作进行历史记录 -->
    <script src="state.js"></script>
    <!-- 音频处理总的逻辑方法 -->
    <script src="engine.js"></script>
    <!-- 音频处理具体实现方法 -->
    <script src="actions.js"></script>
    <!-- 文件拖拽功能 -->
    <script src="drag.js"></script>
    <!-- 录音机功能 -->
    <script src="recorder.js"></script>
    <!-- 欢迎页面弹窗 -->
    <script src="welcome.js"></script>
    <!-- 麦克风录音 -->
    <script src="fx-pg-eq.js"></script>
    <!-- 源码里没有使用,注释了 -->
    <script src="fx-auto.js"></script>
    <!-- 本地存储功能 -->
    <script src="local.js"></script>
    <!-- 音频文件信息ID3解析 -->
    <script src="id3.js"></script>
    <!-- 实现编码/解码 LZ4 格式 -->
    <script src="lzma.js"></script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    各个功能模块实现:

    1. 下载音频

    格式: mp3, wav(44100hz)
    比特率:128kbps 192kbps 256kbps
    声道:Mono 单声道 Stereo 立体声
    范围:导出全部/选中部分

    方法使用Worker创建后台任务将音频转换为二进制流,拿到blob url后,通过a标签进行下载。

    2. 音频导入

    2-1、 本地文件夹上传/url输入

    const wavesurfer = WaveSurfer.create ({
      container: '#waveform', // dom容器
      scrollParent: false, // 是否滚动长波形容器
      hideScrollbar:true, // 是否隐藏滚动条
      partialRender:false, // 是否缓存解码的峰值数据以提高波形渲染速度
      fillParent:false, // 是否填充整个容器还是根据音频每秒最小像素数minPxPerSec绘制
      pixelRatio:1, // 像素比
      progressColor:'rgba(255,0,0,0.8)', 
      splitChannels: true, // 是否渲染多声道
      autoCenter:true, // 如果有滚动条,则波形居中放置
      height:w.innerHeight - 168, // 波形高度
      plugins: [    // 注册插件
        WaveSurfer.regions.create({  // 波形区域选框插件
          dragSelection: {
            slop: 5
          }
        })
      ]
    });
    
    // 音频url导入
    wavesurfer.load(url, peaks, preload)
    // 音频Blob or File 导入
    wavesurfer.loadBlob(url)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2-2、打开已保存的音频

    2-3、 麦克风录音

    使用MediaDevices.getUserMedia()拿到录音设备音频流。

    3. 编辑

    3-1、翻转通道 flip

    方法概述:通过Web Audio Api 中的AudioBuffer.getChannelData() 分别拿到左右声道的Float32Array格式数据,循环依次替换左右声道的数据,来实现左右声道的翻转。
    原始:
    在这里插入图片描述
    flip:
    在这里插入图片描述

    示例代码:

    if (val === 'flip')
    {
      const chan0 = source.buffer.getChannelData (0);
      const chan1 = source.buffer.getChannelData (1);
      const tmp   = 0;
      for (let j = 0; j < chan0.length; ++j)
      {
        tmp = chan0[j];
        chan0[j] = chan1[j];
        chan1[j] = tmp;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3-2、播放/暂停

    示例代码:

    // 暂停
    wavesurfer.stop ();
    // 播放
    wavesurfer.play ();
    
    • 1
    • 2
    • 3
    • 4

    3-3、全选/取消全选/选取片段

    使用wavesurfer的插件Regions
    示例代码:

    // 绘制全选框, 同选取片段
    wavesurfer.regions.add({
      start:0.000,
      end:wavesurfer.getDuration() - 0.00,
      id:'t'
    });
    // 取消全选框
    wavesurfer.regions.clear ();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3-4、单次/循环播放

    wavesurfer.seekTo( val );跳转进度。

    3-5、播放进度:前进/后退,开头/末尾

    前进:wavesurfer.skipForward ( val );
    后退:wavesurfer.skipBackward ( val );
    开头/末尾: wavesurfer.seekTo( val );

    3-6、拷贝/粘贴/裁剪/静音

    拷贝

    // 拿到选中段落的开始和结束时间
    const region = wavesurfer.regions.list[0];
    const start = q.TrimTo (region.start, 3);
    const end = q.TrimTo ((region.end - region.start), 3);
    
    // 拿到拷贝的数据
    function CopyBufferSegment( _offset, _duration ) {
      const originalBuffer = wavesurfer.backend.buffer;
    
      const new_len    = ((_duration/1) * originalBuffer.sampleRate) >> 0;
      const new_offset = ((_offset/1)   * originalBuffer.sampleRate) >> 0;
    
      const emptySegment = wavesurfer.backend.ac.createBuffer (
        wavesurfer.SelectedChannelsLen,
        new_len,
        originalBuffer.sampleRate
      );
    
      for (let i = 0, u = 0; i < wavesurfer.ActiveChannels.length; ++i) {
        if (wavesurfer.ActiveChannels[ i ] === 0) continue;
    
        emptySegment.getChannelData ( u ).set (
          originalBuffer.getChannelData ( i ).slice ( new_offset, new_len + new_offset )
        );
    
        ++u;
      }
      return (emptySegment);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    粘贴/裁剪/静音:同 4-14、插入/移除静音(Insert/Remove Silence)

    4. 效果

    4-1、设置音量大小(Gain)

    方法概述:通过BaseAudioContext.createGain() 创建GainNode。通过GainNode提供的方法设置音量大小。
    原始:
    在这里插入图片描述
    设置音量0.5倍:
    在这里插入图片描述

    示例代码:

    const gain = audio_ctx.createGain ();
    // curr.val:音量大小0 - 1
    gain.gain.setValueAtTime ( curr.val, audio_ctx.currentTime );
    
    • 1
    • 2
    • 3

    4-2、淡入/淡出

    原图----淡入-----淡出
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    方法概述:使用AudioParam.linearRampToValueAtTime() 来实现音量的淡入淡出。
    示例代码:

    const gain = audio_ctx.createGain ();
    // 淡入
    gain.gain.setValueAtTime (0, audio_ctx.currentTime);
    gain.gain.linearRampToValueAtTime (1, audio_ctx.currentTime + duration/1);
    // 淡出
    gain.gain.setValueAtTime (1, audio_ctx.currentTime);
    gain.gain.linearRampToValueAtTime (0, audio_ctx.currentTime + duration/1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4-(3,4)、 均衡器

    在介绍参数均衡器和图形均衡器之前,需要先了解下均衡器。
    均衡器,指通过调节输入信号不同频率成分的振幅来达到对声音的修饰,补偿等作用。本质就是:分析出声音的各个频率段,再对每个频段的音量进行调整。

    因为,一个声音是混杂着低频,中频和高频。如果直接调节音量,那么这三个频率的音量都会被一起调节。

    调节参数如下:
    ⅰ. Type (模式)
    ⅱ. Freq(频点)
    ⅲ. Gain(增益)
    ⅳ. Q(影响范围)

    1. Type (模式)
    声音可分为四部分:Low(低频80Hz—350Hz)、Low Mid(中低频350Hz—2000Hz)、High Mid(中高频2000Hz—6000Hz)、High(高频6000Hz—12000Hz)
    2. Freq(频点)
    可以直接在均衡器图中左右移动鼠标来修改频点,用来标明中心频率。
    3. Gain(增益)
    用于把设定好的频点提升或衰减多少音量。
    4. Q(影响范围)
    表示你要进行增益或衰减的频段的宽度。
    Q值越大,频段越窄。Q值越小,频段越宽。

    参考资料:
    EQ调节介绍以及Cubase中的EQ处理方法及操作
    Au中的EQ处理方法——图形均衡器和参数均衡器

    4-3、参数均衡器(paragraphic EQ) (注意区分,参数均衡器和图形均衡器)

    用参数均衡器处理的音频在各频段衔接的连续性较好。
    如下图:左右两边各有一个滑块,分别控制低通和高通的提升衰减量。

    如下图:横坐标为频率,纵坐标为音量
    高通:频率5800Hz,影响范围5.8,比这个频点高的都通过,低的全部切掉。
    低通:频率7060Hz,影响范围5,比这个频点低的区域都通过,高的全部切掉。
    在这里插入图片描述

    原始音波图:
    在这里插入图片描述

    如上图配置后音波图:
    在这里插入图片描述

    代码示例:自己看源码~

    4-4、图形均衡器(Graphic EQ)注意区分,参数均衡器和图形均衡器

    图形均衡器通常由一组用于增强或削减固定频带的滑块控件组成,如下图,可以直接通过拖拽按钮来调整对应频率的音量。
    图形均衡器的Q值和频率都是固定的,有10段,20段和30段,按自己的喜好使用。30段划分的频率段最细。
    图形均衡器10段:
    在这里插入图片描述

    图形均衡器20段:
    在这里插入图片描述

    一般来讲,100Hz以下是噪音,如果一开始去除了底噪,就不需要在EQ中修改噪音。
    提升125Hz和200Hz的频率可以使人声变得浑厚低沉。
    提升1kHz以上的频率会使声音变得明亮、空旷,但提升太多会带来磁带般尖锐的感觉,且同时会加大嗡嗡的噪音声。
    所以尽量不要将EQ调整的太多,以防声音失真,适当的提升声音中不足的成分即可。

    代码示例:同 4-3、参数均衡器(paragraphic EQ)
    参考资料:什么是图形均衡器

    4-5、音频/音量标准化(Normalize)

    最大不失真音量,将当前波形振幅值的最大值调整到最大电平规定的值内。
    可分为:对左右声道分别计算比例进行标准化 和 对左右声道计算总的比例进行标准化。
    方式概述:
    通过AudioBuffer.getChannelData() 拿到声道数据,循环数据获取最大值,根据normalize值计算得到调整比例。循环音频数据乘与调整比例,最后得到标准化后的音频数据。
    在这里插入图片描述

    代码示例:

    // 输入的调整大小
    const max_val = val[1] || 1.0;
    // 双声道是否一起计算调整比例
    const equally = val[0];
    // 最大峰值
    const max_peak = 0;
    
    for (let i = 0; i < source.buffer.numberOfChannels; ++i) {
      const chan_data = source.buffer.getChannelData (i);
    
      // 取间隔10 以加速计算
      for (let k = 1, len = chan_data.length; k < len; k = k + 10) {
        const curr = Math.abs ( chan_data [ k ] );
        if (max_peak < curr)
          max_peak = curr;
      }
    
      const diff = max_val / max_peak;
    
      if (!equally) {
        for (let k = 0, len = chan_data.length; k < len; ++k) {
          chan_data[ k ] *= diff;
        }
        max_peak = 0;
      }
    }
    
    if (equally) {
      const diff = max_val / max_peak;
    
      for (let i = 0; i < source.buffer.numberOfChannels; ++i) {
        const chan_data = source.buffer.getChannelData (i);
    
        for (let k = 0, len = chan_data.length; k < len; ++k) {
          chan_data[ k ] *= diff;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    4-6、压缩器(Compressor)

    压缩器有6个参数,前4个跟效果相关,后2个跟时间相关。
    ⅰ. 门限(Threshold)压缩器的工作起点。只有当电平超过门限后,压缩器才会开始工作。
    ⅱ. 压缩比(Ratio)压缩器以什么样的量来压缩超过门限的电平。
    ⅲ. 提升(Make up)是将电平整体提升。因为压缩在工作的过程中的原理是将高于门限的电平压低,因此肯定会不可避免地降低整体的音量。为了让音量的平均值恢复到原本的听觉水平甚至更高,我们就要使用Make up 将所有电平共同提升。
    ⅳ. 拐点(Knee)在门限上下交界的地方,是以一种非黑即白的方式压缩,还是有一条渐进的曲线连接起压缩与不压缩的区间。
    ⅴ. 启动时间(Attack)
    当一个声音超过了Threshold的时候,压缩器并不是立刻把压缩作用在这些超过部分的信号上,压缩比例是在一段时间内一点一点增加到我们设定的值。这一段时间就是Attack。
    ⅵ. 释放时间(Release)
    同上,当声音回落到Threshold的时候,压缩效果也不是立刻停止,而是一点一点从最大压缩比例回落。这一段时间就是Release。
    在这里插入图片描述

    方法概述:
    使用BaseAudioContext.createDynamicsCompressor() 设置相应属性
    示例代码:

    const compressor = audioCtx.createDynamicsCompressor();
    compressor.threshold.setValueAtTime(-50, audioCtx.currentTime);
    compressor.knee.setValueAtTime(40, audioCtx.currentTime);
    compressor.ratio.setValueAtTime(12, audioCtx.currentTime);
    compressor.attack.setValueAtTime(0, audioCtx.currentTime);
    compressor.release.setValueAtTime(0.25, audioCtx.currentTime);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4-7、 强硬限制(Hard Limiting)

    **也称限幅器,压限器,将声音信号限制在最大电平内。**
    ⅰ. Limit 
    ⅱ. Ratio
    ⅲ. 预测时间(Look Ahead)
    
    • 1
    • 2
    • 3
    • 4

    解决的问题:对超出起奏时间设置的大声信号开始时可能出现的瞬时峰值进行处理。
    延长预测时间会使压缩在音频变大声之前触发,从而确保振幅不会超过特定电平。相反的,要增强打击乐(如鼓乐)的效果,可能需要减少预测时间。
    在这里插入图片描述

    代码示例:

    const max_val = 1;  // limit to
    const ratio = 0.0;    // ratio
    const look_ahead = 15; // ms
    const max_peak = 0;
    
    const buffer = audio_ctx.createBuffer(
      source.buffer.numberOfChannels,
      source.buffer.length,
      source.buffer.sampleRate
    );
    
    look_ahead = (look_ahead * buffer.sampleRate / 1000) >> 0; //  >> 0 取整数值
    
    for (let i = 0; i < buffer.numberOfChannels; ++i) {
      const chan_data = buffer.getChannelData(i);
      chan_data.set(source.buffer.getChannelData(i));
    
      for (let b = 0, len = chan_data.length; b < len; ++b) {
        for (let k = 0; k < look_ahead; k = k + 10) {
          const curr = Math.abs(chan_data[b + k]);
          if (max_peak < curr)
            max_peak = curr;
        }
    
        const diff = (max_val / max_peak);
    
        for (let k = 0; k < look_ahead; ++k) {
          const orig_val = chan_data[b + k];
          const new_val = orig_val * diff;
    
          let peak_diff = max_val - Math.abs(new_val);
          peak_diff *= orig_val < 0 ? -ratio : ratio;
    
          chan_data[b + k] = (new_val + peak_diff);
        }
        b += look_ahead;
        max_peak = 0;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4-8、延时(Delay)

    单纯的把音频重复播放,起到把声音延长和重复的作用。
    注意区分:Delay(延时) 和 Reverb(混响)的区别。Reverb是制造出空间效果来。
    在这里插入图片描述

    代码示例:内容过多不贴,去看源码儿~

    4-9、失真效果(Distortion)

    输入和输出不相等,就代表信号出现了失真。
    失真效果器
    AudioContext.createWaveShaper()
    在这里插入图片描述

    Distortion音效参数有:edge(临界值), gain(增益),低通剪切值等。
    AudioMass里失真效果只有gain参数。

    方法概述:

    const wave_shaper = audio_ctx.createWaveShaper ();
    // val: [{val: 0.5}] Gain值
    const compute_dist = function ( val ) {
      const gain = parseInt ( (val / 1) * 100, 10);
      const n_samples = 44100;
      const curve = new Float32Array (n_samples);
      const deg = Math.PI / 180;
      let x;
    
      for (let i = 0; i < n_samples; ++i ) {
        x = i * 2 / n_samples - 1;
        curve[i] = (3 + gain) * x * 20 * deg / (Math.PI + gain * Math.abs(x));
      }
      return (curve);
    };
    
    
    for (let k = 0; k < val.length; ++k)
    {
      const curr = val[k];
      if (curr.length)
      {
        for (let i = 0; i < curr.length; ++i) {
          wave_shaper.curve.linearRampToValueAtTime (compute_dist(curr[i].val), audio_ctx.currentTime + curr[i].time);
        }
      }
      else
      {
        wave_shaper.curve = compute_dist (curr.val);
      }
    }
    
    source.connect (wave_shaper);
    wave_shaper.connect (destination);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    4-10、混响(Reverb)

    虚拟各种空间的特效:在一个大房间,大教堂,体育场或演唱会,各种不同环境造成的回音,空间感。
    Reverb详细大解说
    在这里插入图片描述

    代码示例:内容过多不贴,去看源码儿~

    4-11、倒置(Reverse)

     音频从后往前播放
     代码示例:
    
    • 1
    • 2
    for (let i = 0; i < source.buffer.numberOfChannels; ++i) {
    	Array.prototype.reverse.call( source.buffer.getChannelData (i) );
    }
    
    • 1
    • 2
    • 3

    4-12、播放速度(Speed)

    HTMLMediaElement.playbackRate
    浮点数1.0 是 “正常速度”, 比 1.0 小的值使媒体文件播放的慢于正常速度,比1.0大的值使播放变得快于正常速度。

    const val = 1.5; // 1.5倍速
    source.playbackRate.value = val;
    
    • 1
    • 2

    4-13、反相(Invert)

    将波形中的采样数据点以横轴为对称轴反相。
    反相前 -----> 反相后
    在这里插入图片描述
    在这里插入图片描述

    for (let i = 0; i < source.buffer.numberOfChannels; ++i) {
      const channel = source.buffer.getChannelData (i);
    
      for (let j = 0; j < channel.length; ++j)
        channel[j] *= -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4-14、插入/移除静音(Insert/Remove Silence)

    方法概述:创建一段静音音频数据插入到原音频数据指定位置
    代码示例:

    // 创建静音数据
    function MakeSilenceBuffer ( _duration ) {
      const originalBuffer = wavesurfer.backend.buffer;
      const emptySegment = wavesurfer.backend.ac.createBuffer(
        originalBuffer.numberOfChannels,
        _duration * originalBuffer.sampleRate,
        originalBuffer.sampleRate
      );
    
      return (emptySegment);
    }
    
    // 设置new_buffer为Wavesurfer渲染波形
    wavesurfer.loadDecodedBuffer ( new_buffer );
    // 波形图插入静音段落
    wavesurfer.regions.clear();
    wavesurfer.regions.add({
      start:dims[0], // 静音开始时间
      end:dims[1],   // 静音结束时间
      id:'t'
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4-15、降噪

    简易demo:
    提取码:ytgl

    二:视图

    1. Frequency Analyser(频率分析器)
      拿到wavesurfer.backend.FreqArr,用canvas绘制。
      在这里插入图片描述

    2. Spectrum Analyser(频谱分析器)
      拿到wavesurfer.backend.FreqArr,用canvas绘制。
      在这里插入图片描述

    3. Tempo Tools(节拍器)
      节拍器是一种规律发出声音的工具,用于帮助音乐人稳定拍速,也会运用在现场表演和录音室。
      打拍子的测量单位称为BPM(每分钟拍速),60BPM等于每秒打一拍,120BPM等于每秒打两拍。

    4. ID3 Tags (音频文件信息)
      音频文件格式结构

  • 相关阅读:
    计算机毕设 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python
    Android 10 如何在SurfaceFlinger中解决开机动画显示不全问题
    卷积神经网络梯度消失,神经网络中梯度的概念
    微信店铺小程序开通的效果是什么
    广州大学2023-2024学年第一学期《计算机网络》A卷
    又是把Java基础知识学废的一天,new 一个对象数组,操作时报空指针异常
    【Leetcode】190.颠倒二进制位
    卷积神经网络卷积计算,卷积网络计算公式
    链表 oj2 (7.31)
    进程间通信
  • 原文地址:https://blog.csdn.net/weixin_40693643/article/details/126476315