使用js实现音谱,网页音谱实现

更新日期: 2023-05-05阅读: 870标签: 效果

实现思路:

1、创建音频上下文。
2、创建音频乐谱分析仪
3、将分析出的数据画到canvas上。

实现步骤:

1、创建音频上下文

this.audioContext = new AudioContext()

2、创建源及乐谱分析仪,并链接

// 创建音频源节点
this.source = this.audioContext.createMediaElementSource(this.audioElement)
// 创建乐谱分析器
this.analyser = this.audioContext.createAnalyser()
// 设置分析精度
this.analyser.fftSize = this.options.fftSize
// 将源链接到分析器
this.source.connect(this.analyser)
//将分析器链接到音频上下文目的地
this.analyser.connect(this.audioContext.destination)

3、绘制到canvas上

// 清除画布
const { width, height } = this.canvasElement
this.ctx.clearRect(0, 0, width, height)
// 让分析器节点将分析出的数据放到存储容器内
this.analyser.getByteFrequencyData(this.dataArray)
// 计算要绘制的区域
const l = this.dataArray.length * (this.options.range / 100)
// 计算每个块的宽度
const barWidth = width / l / 2
this.ctx.fillStyle = this.options.color
for (let i = 0; i < l; i++) {
  // 获取每个的数据,此值小于256
  const item = this.dataArray[i]
  // 计算块高度
  const h = (item / 255) * height
  // 计算块开始的y
  const y = height - h
  // 计算块开始的x,对称
  const x_r = i * barWidth + width / 2
  const x_l = width - x_r
  this.ctx.fillRect(x_r, y, barWidth, height)
  this.ctx.fillRect(x_l, y, barWidth, height)
}

4、使用requestAnimationFrame重复绘制

this.animateId = requestAnimationFrame(this.draw.bind(this))

完整代码

music-score.ts

interface IOptionsType {
  // audio元素|audio元素选择器|网络音频地址
  audio: string | htmlAudioElement
  canvas: string | HTMLCanvasElement
  fftSize?: number
  color?: string
  range?: number
}

export class MpMusicScore {
  private options: Partial<IOptionsType> = {
    fftSize: 512,
    color: '#9adcff',
    range: 70
  }
  private audioElement: HTMLAudioElement
  private canvasElement: HTMLCanvasElement
  private audioContext: AudioContext
  private analyser: AnalyserNode
  private source: MediaElementAudioSourceNode
  private dataArray: Uint8Array
  private animateId: number
  private ctx: CanvasRenderingContext2D
  private isInitAudio: boolean = false

  constructor(options: IOptionsType) {
    this.options = { ...this.options, ...options }
    this.init()
  }

  // 初始化参数
  private init() {
    // 获取音频元素
    if (typeof this.options.audio === 'string') {
      if (
        this.options.audio.includes('//') &&
        this.options.audio.endsWith('.mp3')
      ) {
        this.audioElement = new Audio(this.options.audio)
      } else {
        this.audioElement = document.querySelector(this.options.audio)
      }
    } else if (this.options.audio instanceof HTMLAudioElement) {
      this.audioElement = this.options.audio
    }
    if (!(this.audioElement instanceof HTMLAudioElement)) {
      throw '音频不存在!'
    }

    // 获取画布元素
    if (typeof this.options.canvas === 'string') {
      this.canvasElement = document.querySelector(this.options.canvas)
    } else if (this.options.canvas instanceof HTMLCanvasElement) {
      this.canvasElement = this.options.canvas
    }
    if (!(this.canvasElement instanceof HTMLCanvasElement)) {
      throw '画布不存在!'
    }

    if (Math.log2(this.options.fftSize) % 1 !== 0) {
      throw 'fftSize为2的n次幂!'
    }

    // 初始化画布及画布大小
    this.ctx = this.canvasElement.getContext('2d')
    const { clientWidth, clientHeight } = this.canvasElement
    this.canvasElement.width = clientWidth
    this.canvasElement.height = clientHeight

    // 添加绘制事件
    this.audioElement.addEventListener('play', this.draw.bind(this))
    this.audioElement.addEventListener('pause', this.cancelDraw.bind(this))
  }

  // 初始化音频
  private initAudio() {
    if (this.isInitAudio) {
      return
    }
    this.isInitAudio = true
    // 创建audio上下文
    this.audioContext = new AudioContext()
    // 创建音频源节点
    this.source = this.audioContext.createMediaElementSource(this.audioElement)
    // 创建乐谱分析器
    this.analyser = this.audioContext.createAnalyser()
    // 设置分析精度
    this.analyser.fftSize = this.options.fftSize
    // 将源链接到分析器
    this.source.connect(this.analyser)
    //将分析器链接到音频上下文目的地
    this.analyser.connect(this.audioContext.destination)
    // 创建乐谱存储容器
    this.dataArray = new Uint8Array(this.analyser.frequencyBinCount)
  }

  // 绘制乐谱
  private draw() {
    // 初始化Audio
    this.initAudio()
    this.animateId = requestAnimationFrame(this.draw.bind(this))
    // 清除画布
    const { width, height } = this.canvasElement
    this.ctx.clearRect(0, 0, width, height)
    // 让分析器节点将分析出的数据放到存储容器内
    this.analyser.getByteFrequencyData(this.dataArray)
    // 计算要绘制的区域
    const l = this.dataArray.length * (this.options.range / 100)
    // 计算每个块的宽度
    const barWidth = width / l / 2
    this.ctx.fillStyle = this.options.color
    for (let i = 0; i < l; i++) {
      // 获取每个的数据,此值小于256
      const item = this.dataArray[i]
      // 计算块高度
      const h = (item / 255) * height
      // 计算块开始的y
      const y = height - h
      // 计算块开始的x,对称
      const x_r = i * barWidth + width / 2
      const x_l = width - x_r
      this.ctx.fillRect(x_r, y, barWidth, height)
      this.ctx.fillRect(x_l, y, barWidth, height)
    }
  }

  // 取消绘制
  private cancelDraw() {
    cancelAnimationFrame(this.animateId)
  }

  // 播放
  public play() {
    this.audioElement.play()
  }

  // 暂停
  public pause() {
    this.audioElement.pause()
  }

  // 获取audio元素
  public getAudioElement() {
    return this.audioElement
  }
}

使用方法:

接收一个配置对象,有5个可配置属性:
audio(audio元素 | audio元素选择器 | 网络音频地址)、
canvas(canvas元素或选择器)、
fftSize(精度,越大越精细,必须为2的n次幂)、
color(乐谱块颜色)、
range(乐谱的取值,因乐谱后面的频率一般为0,没有显示,可以通过此值,只取前面部分)。


使用案例:

new MpMusicScore({
  audio: 'audio',
  canvas: 'canvas',
  color: '#58ee43',
  range: 50,
  fftSize: 1024
})
const score = new MpMusicScore({
  audio: 'http://127.0.0.1:5173/1.mp3',
  canvas: 'canvas',
  color: 'red',
  range: 75,
  fftSize: 2048
})
setTimeout(() => {
  score.play()
}, 1000)

结语:

文章参考:https://v.douyin.com/B7tMkcK/

来源:https://www.ljhmp.com/article-detail.html?id=29

链接: https://fly63.com/article/detial/12462

使用 JavaScript 实现分屏视觉效果

今天这篇文章就来讲讲使用JavaScript来实现这种分屏的视觉UI效果。现在在网站上这种分屏视觉效果应用的也非常广泛,比如 Corsair website。

CSS3的过渡效果,使用transition实现鼠标移入/移出效果

在css中使用伪类虽然实现了样式的改变,但由于没有过渡效果会显得很生硬。以前如果要实现过渡,就需要借助第三方的js框架来实现。现在只需要使用CSS3的过渡(transition)功能,就可以从一组样式平滑的切换到另一组样式。

js如何实现新手引导效果?

js最近有个小伙伴问到了怎么实现新手引导的效果,然后便去网上找了下实现方案。可以通过css的border来实现。

css3 斜切角/斜边的实现方式

设计图含有斜切角的效果时,我们一般想到的方法是切出四个角为背景,然后用border连起来,这样就能显示出该效果了,那么直接使用css呢?下面就整理css做斜边的效果。

JavaScript 实现打字机效果,跑马灯效果

这篇文章在不使用任何插件的情况,以最简洁的原生javascript来实现打字机效果和跑马灯效果。打字效果即把一段话一个字一个字的显示出来。

CSS遮罩效果(模糊效果,阴影效果,毛玻璃效果)

一般遮罩加上透明度opacity就是阴影效果。阴影效果和一般遮罩一样,唯一不同的是设置.mask遮罩的背景色用rgba()表示,当然hsla()也是可以的。模糊效果(毛玻璃效果) 通过 filter来实现

纯css实现气泡效果

主要运用的是1.border 组成的直角三角形。2,before 和 after 伪元素 。3,z-index属性;将元素的长宽设置为0,并且将border的3条边设置为透明的,就会出现border颜色的直角三角形

css文字选中效果

文字选中效果,这个可能很少有人注意过。在默认状态先一般选中的文本颜色是白字蓝底的,不过可以通过CSS进行设置。::selection定义元素上的伪选择器,以便在选定元素时设置其中文本的样式。

text-fill-color:仿苹果官网介绍效果 CSS设置文字渐变效果 文字背景图遮罩

发布iPhone XR的时候 各种心动 去官网看了一遍又一遍。闲着无聊发现 里面的介绍很用大篇幅的有背景文字来介绍。Like this:看着挺酷炫的还不错 就看了下实现方式。还挺简单的。

Vue 中多个元素、组件的过渡,及列表过渡

多元素之间如何实现过渡动画效果呢?这么写行不行呢?肯定是不行的,因为 Vue 在两个元素进行切换的时候,会尽量复用dom,就是因为这个原因,导致现在动画效果不会出现。如果不让 Vue 复用dom的话,应该怎么做呢?只需要给这两个div不同的key值就行了

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!