仿网易云-360度混响

news2025/2/24 23:48:14

一直在用网易云音乐听歌,感觉他的这个动效还是挺不错的,最近也是想试试canvas绘图相关的。尝试了几次之后感觉效果还不错,不过距离网易云的还是有些差距。

本期准备仿照制作如下效果:

偷偷使用最近比较流行的罗刹海市的音乐来展示这个效果。
请添加图片描述

效果展示如下:
请添加图片描述

效果展示网站
参考文档

具体的流程大体上就是获取音频数据,然后根据音频数据绘制在canvas上,不同的绘制方式就能有很多惊艳的效果,毕竟还是数学最令人着迷了。

提取音频数据

普通的audio标签无法提取音频数据,查了一下需要使用到 AudioContext 这个浏览器内置对象。
使用来说也比较简单,大体上就是创建,加载音频,播放等几个环境,在播放的时候通过链接音频处理节点获取音频数据。

几个比较重要的点如下:

window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.audioCtx = new AudioContext()

// 创建播放节点
this.bufferSourceNode = this.audioCtx.createBufferSource()
this.bufferSourceNode.connect(this.audioCtx.destination)

// 创建分析器,从这个分析器中能够得到频域跟时域的数据,不过频域的数据分散不够均匀,感觉还是时域的展示效果好一些
this.analyser = this.audioCtx.createAnalyser()
this.analyser.fftSize = this.sampleRate
this.analyser.smoothingTimeConstant = 1
this.player.bufferSourceNode.connect(this.analyser)

this.analyser.getByteTimeDomainData(data)

剩下的就是把这些内容给组织起来了。

class Player {
  constructor(canvasName) {
    window.AudioContext = window.AudioContext || window.webkitAudioContext;

    this.audioCtx = new AudioContext()
    this.bufferSourceNode = this.audioCtx.createBufferSource()
    this.canvas = document.getElementById(canvasName)
    this.audioBuffer = null
    
    this.initEffect()
  }

  stop() {
    this.audioCtx.suspend()
  }

  resume() {
    this.audioCtx.resume()
  }

  initEffect() {
    this.bufferSourceNode = this.audioCtx.createBufferSource()
    this.bufferSourceNode.connect(this.audioCtx.destination)
    // 显示效果,可以随时替换
    this.effect = new DefaultEffect(this)
  }
  
  play(url) {
    let that = this

    if (that.audioBuffer) {
      that.initEffect()
    }

    fetch(url, {
      method: 'get',
      responseType: 'arraybuffer'
    }).then(res => {     
      return res.arrayBuffer();
    }).then(arraybuffer => {
      that.audioCtx.decodeAudioData(arraybuffer, function(buffer) {
        that.audioBuffer = buffer
        that.bufferSourceNode.buffer = buffer
        that.bufferSourceNode.start(0)
      });
    })
  }
}

360混响效果

看画面他像从圆心发射的线条,只不过用一个内圆把中间给盖上了。
那么接下来就是先画一个放射线,只需要按照均衡的角度来画一下就完事了。
本例子这个效果把园分了128条线,并且把其实坐标给从圆心偏移到内圆的边上。
canvas画线的API如下:

let jiaodu = i * 360 / count
let sx = Math.sin(jiaodu * Math.PI / 180) * minRadius
let sy = Math.cos(jiaodu * Math.PI / 180) * minRadius

let d = Math.max(0, this.lastData[i] - 127)
// let d = data[i] - 90
let endRatio = (minRadius + (d / 128) * 100) / minRadius

let ex = Math.sin(jiaodu * Math.PI / 180) * minRadius * endRatio
let ey = Math.cos(jiaodu * Math.PI / 180) * minRadius * endRatio

sx += centerX
sy += centerY
ex += centerX
ey += centerY

this.ctx.beginPath();
this.ctx.moveTo(ex, ey); // 起点
this.ctx.lineWidth = 4;
this.ctx.lineCap = "round"; // 圆角,看起来更好看一点
this.ctx.lineTo(lex, ley); // 终点
this.ctx.strokeStyle = 'rgba(255,0,0,0.1)'; // 颜色
this.ctx.stroke();

效果如下:
请添加图片描述

为了让这个更好看一点,看到好像能够在这个线的外层再画一层,只不过颜色看起来比较浅。

另外接上时域数据之后,发现跳动比较大,然后加了一层缓存,减缓曲线的回落速度。

for(let i = 0; i < this.sampleRate; i++) {
  if (this.lastData[i]> 8) this.lastData[i] -= 8
  this.lastData[i] = Math.max(this.lastData[i], data[i])
}

整体效果代码如下:

class DefaultEffect {
  constructor(player) {
    this.player = player
    this.width = player.canvas.width
    this.height = player.canvas.height
    this.ctx = player.canvas.getContext('2d')
    this.sampleRate = 128
    this.audioCtx = player.audioCtx
    this.lastData = new Uint8Array(this.sampleRate)
    
    this.analyser = this.audioCtx.createAnalyser()
    this.analyser.fftSize = this.sampleRate
    this.analyser.smoothingTimeConstant = 1
    this.player.bufferSourceNode.connect(this.analyser)
  }

  // 当暂停的时候使用这个输出默认的效果,是动画不那么呆板
  idleData(delta) {
    let data = []
    for(let i = 0; i < this.sampleRate; i++) {
      data.push((Math.sin(i + delta/1000) + 1) * 20 + 127)
    }
    return data
  }

  getData(delta) {
    var data = new Uint8Array(this.sampleRate)
    
    if (this.audioCtx) {
      if (this.audioCtx.state == 'running') {
        this.analyser.getByteTimeDomainData(data)
      } else {
        data = this.idleData(delta)
      }
    } else {
      data = this.idleData(delta)
    }
    return data
  }

  draw(delta) {
    this.ctx.clearRect(0,0,this.width,this.height)

    let data = this.getData(delta)

    for(let i = 0; i < this.sampleRate; i++) {
      if (this.lastData[i]> 8) this.lastData[i] -= 8
      this.lastData[i] = Math.max(this.lastData[i], data[i])
    }

    let centerX = this.width/2
    let centerY = this.height/2

    let minRadius = 150

    let count = 128
    for(let i = 0; i < count; i++) {
      let jiaodu = i * 360 / count
      let sx = Math.sin(jiaodu * Math.PI / 180) * minRadius
      let sy = Math.cos(jiaodu * Math.PI / 180) * minRadius

      let d = Math.max(0, this.lastData[i] - 127)
      // let d = data[i] - 90
      let endRatio = (minRadius + (d / 128) * 100) / minRadius

      let ex = Math.sin(jiaodu * Math.PI / 180) * minRadius * endRatio
      let ey = Math.cos(jiaodu * Math.PI / 180) * minRadius * endRatio

      sx += centerX
      sy += centerY
      ex += centerX
      ey += centerY

      this.ctx.beginPath();
      this.ctx.moveTo(sx, sy);
      this.ctx.lineWidth = 4;
      this.ctx.lineCap = "round";
      this.ctx.lineTo(ex, ey);
      this.ctx.strokeStyle = 'red';
      this.ctx.stroke();

      let lex = Math.sin(jiaodu * Math.PI / 180) * (minRadius * endRatio + 30)
      let ley = Math.cos(jiaodu * Math.PI / 180) * (minRadius * endRatio + 30)
      lex += centerX
      ley += centerY

      this.ctx.beginPath();
      this.ctx.moveTo(ex, ey);
      this.ctx.lineWidth = 4;
      this.ctx.lineCap = "round";
      this.ctx.lineTo(lex, ley);
      this.ctx.strokeStyle = 'rgba(255,0,0,0.1)';
      this.ctx.stroke();
    }
  }
}

使用的时候

初始化一个player,然后定时刷新canvas以显示效果。

var player;
var effectName;
onMounted( () => {
  player = new Player('myCanvas', effectName)
  setInterval(() => {
    if (player) {
      requestAnimationFrame(player.effect.draw.bind(player.effect))
    }
  }, 50);
  player.play(url)
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1031208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【EasyExcel】excel表格的导入和导出

【EasyExcel】excel表格的导入和导出 【一】EasyExcel简介【二】EasyExcel使用【1】EasyExcel相关依赖【2】写Excel&#xff08;1&#xff09;最简单的写(方式一)&#xff08;2&#xff09;最简单的写(方式二)&#xff08;3&#xff09;排除模型中的属性字段&#xff08;4&…

服务器数据恢复-LINUX操作系统下各文件系统误删除/格式化数据的恢复方案

服务器数据恢复环境&#xff1a; 基于EXT2/EXT3/EXT4/Reiserfs/Xfs文件系统的Linux操作系统。 服务器故障&#xff1a; LINUX操作系统下误删除/格式化数据。 服务器数据恢复过程&#xff1a; 1、首先会检测服务器是否存在硬件故障&#xff0c;如果检测出硬件故障&#xff0c;交…

ARM64 SMP多核启动详解2(psci)

1. 支持psci情况 上面说了pin-table的多核启动方式&#xff0c;看似很繁琐&#xff0c;实际上并不复杂&#xff0c;无外乎主处理器唤醒从处理器到指定地址上去执行指令&#xff0c;说他简单是相对于功能来说的&#xff0c;因为他只是实现了从处理器的启动&#xff0c;仅此而已…

智慧公厕预见幸福生活、美好未来

随着城市化的加速发展&#xff0c;公共厕所作为城市基础设施的重要组成部分&#xff0c;对于提升城市形象和居民生活质量起着至关重要的作用。智慧公厕作为智慧城市建设的一部分&#xff0c;正逐渐成为城市管理的新宠儿&#xff0c;能有效助力网络强国、数字中国、智慧社会的建…

第九章 常用服务器的搭建

第九章 常用服务器的搭建 1.配置FTP服务器 1.1.FTP简介 ​ FTP&#xff08;File Transfer Protocol&#xff0c;文件传送协议&#xff09;是TCP/IP网络上两台计算机间传送文件的协议&#xff0c;FTP是在TCP/IP网络和Internet上最早使用的协议之一&#xff0c;它属于网络协议…

形式化验证方法研究综述

摘要&#xff1a;形式化验证是证明软件、硬件或系统正确性的一种方法&#xff0c;近年来受到了越来越多的关注。 本文对形式化验证的研究进行了综述。首先介绍了形式化验证的基本概念&#xff0c;然后重点介绍了形式化验证的三种技术&#xff0c;包括模型检测、定理证明和等价性…

Redis中是如何实现分布式锁的?

分布式锁常见的三种实现方式&#xff1a; 数据库乐观锁&#xff1b; 基于Redis的分布式锁&#xff1b; 基于ZooKeeper的分布式锁。 本次面试考点是&#xff0c;你对Redis使用熟悉吗&#xff1f;Redis中是如何实现分布式锁的。 要点 Redis要实现分布式锁&#xff0c;以下条件应…

JS基础语法

JS是一门面向对象的编程语言&#xff0c;运行在客户端的脚本语言&#xff0c;可以基于Node.js进行服务器端编程 JS的作用: 表单动态校验网页特效服务端开发 浏览器执行JS&#xff1a; 浏览器分为两部分&#xff1a;渲染引擎和JS引擎 渲染引擎用来解析HTML和CSS&#xff0c;…

2023中国智能产业高峰论坛丨文档图像大模型的思考与探索

# 前言 随着人工智能技术的不断发展&#xff0c;尤其是深度学习技术的广泛应用&#xff0c;多模态数据处理和大模型训练已成为当下研究的热点之一&#xff0c;这些技术也为文档图像智能处理和分析领域带来了新的发展机遇。 近期&#xff0c;2023第十二届中国智能产业高峰论坛…

打印由数字组成的金字塔图案——python

1222 33333 4444444 555555555打印由数字组成的金字塔图案。但n9时&#xff0c;如下图所示。 输入格式: 输入一个整数n&#xff08;1<A<9&#xff09;。 输出格式: 输出由数字组成的金字塔图案。 输入样例: 在这里给出一组输入。例如&#xff1a; 5输出样例: 在这…

软件设计模式系列之十二——外观模式

在软件设计中&#xff0c;经常会遇到需要与复杂子系统进行交互的情况。为了简化客户端与子系统之间的交互&#xff0c;提高系统的可维护性和可用性&#xff0c;外观模式应运而生。外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供一个统…

.NET超简单轻量级的HTTP请求组件Flurl

简介 Flurl是一个用于构建基于HTTP请求的C#代码的库。它的主要目的是简化和优雅地处理网络请求&#xff08;只用很少的代码完成请求&#xff09;。Flurl提供了一种简单的方法来构建GET、POST、PUT等类型的请求&#xff0c;以及处理响应和异常。它还提供了一些高级功能&#xf…

nndeploy:一款最新上线的支持多平台、简单易用、高性能的机器学习部署框架

项目地址&#xff1a;https://github.com/Alwaysssssss/nndeploy 介绍 nndeploy 是一款最新上线的支持多平台、高性能、简单易用的机器学习部署框架。做到一个框架就可完成多端(云、边、端)模型的高性能部署。 作为一个多平台模型部署工具&#xff0c;我们的框架最大的宗旨就…

Mac 上安装yt-dlp 和下载视频的操作

安装 打开终端&#xff0c;在终端输入 cd python的路径&#xff0c;然后输入pip3 install yt-dlp&#xff0c;如下图&#xff1b; 出现 如Successfully installed yt-dlp-2023.7.6 的时候&#xff0c;说明下载成功 下载 下载命令&#xff1a; yt-dlp --list-formats https…

来自华为的暴击?传高通裁员赔偿N+7 | 百能云芯

自9月20日起&#xff0c;高通裁员的新闻在网络上持续发酵。尽管市场充满了关于裁员细节的传言&#xff0c;但截至目前&#xff0c;高通官方尚未对此发表评论。 消息称&#xff0c;高通此次裁员的“重灾区”在上海的无线研发部门&#xff0c;甚至有传言称将直接关闭上海研发中心…

干货 | 汽车行业研发效能提升的挑战与实践案例

在 9 月 15 日的第七届 CSN 大会上&#xff0c;思码逸研发效能专家王艳萍受邀分享了《汽车行业研发效能提升的挑战与实践案例》。演讲包含了思码逸对多家汽车企业服务过程中总结出的行业痛点、解决方案&#xff0c;以及实践案例。 以下为演讲实录&#xff1a; 思码逸与很多知名…

基于微信小程序的校园生活管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境学生微信端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝1…

Aztec交易架构解析

1. 引言 前序博客有&#xff1a; Aztec的隐私抽象&#xff1a;在尊重EVM合约开发习惯的情况下实现智能合约隐私完全保密的以太坊交易&#xff1a;Aztec网络的隐私架构Aztec.nr&#xff1a;Aztec的隐私智能合约框架——用Noir扩展智能合约功能Account Abstraction账号抽象——…

【@胡锡进】大模型量化分析- 药明康德 603259.SH

我将使用不同的预测方法进行药明康德股票未来3天价格的预测。以下是每种方法的预测方法、详细代码和预测价格&#xff08;根据提供的数据进行模拟&#xff09;。 SARIMA模型预测&#xff1a; SARIMA&#xff08;季节性自回归移动平均&#xff09;模型适用于具有明显季节性的时…

PHP8中调换数组中的键值和元素值-PHP8知识详解

在php8中使用array_flip()函数可以调换数组中的键值和元素值。 在PHP8中使用array_flip()函数可以调换数组中的键值和元素值&#xff0c;示范代码如下&#xff1a; <?php$stu array("子涵"> 001,"欣怡"> 002,"梓涵">003,"晨曦…