Chapter3 - 动感DJ续篇 进一步操作阵列,让音乐嗨起来

打了2000字消失了怎麽办呢(´・_・`) 先去上个厕所压压惊,恳请IT邦邦忙快优化界面

在编辑介面有许多的连结藏在各个角落,而且不是设计成另开分页,直接无情跳转,好几次这样太可怕了。

不管了先发再说

发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发财罗

好,还有50分钟,不错,是时候发挥每分钟中打100字的实力了,分分钟就5000字(X)


今天我们从这两个应用来讲解
https://jerry-the-potato.github.io/ChapterX-demo/
https://jerry-the-potato.github.io/MusicPlayer%20v1.1/test.html

Array

那麽首先我们分析一下第一个应用,上中下共有三个频谱图,其中最下方是我们在第一章节带大家做的dataArray,那上面两个是怎麽来的呢?其实,这种即时的讯号分析,改变是一个很大的重点,像是在影像处理领域中,就会用到前後相减的技巧,来找到正在移动的物体,作为辅助的动态追踪。

Array.prototype.map

使用map方法,会根据阵列的长度,来决定回圈的次数,在遍历阵列的当下,回传每一个索引的新值,组成新的阵列,假如今天想要对过滤频率的信号,在0~255的范围中,只保留50~255,其余的部分归零,可以这麽写:

dataArray = dataArray.map((value) => {
    return (value >= 50) ? value : 0;
});

用for的形式呈现也可以更好懂:

for(N = 0; N < dataArray.length; N++){
    let value = dataArray[N];
    if(value < 50) value = 0;
    dataArray[N] = value;
}

不过在这个例子中,for的做法是直接修改原本阵列中的值,有些微的不同

前後相减就可以这麽做,除了可以拿value来用,也有索引值index可用:

dataArray.delta = dataArray.next.map((value, index) => {
    return value - dataArray.pre[index];
});
dataArray.pre = dataArray.next; // 储存上一次的频谱信号 

这也是因为两个阵列师出同源,长度本来就相等

Array.prototype.reduce

取得音量差後,我们会发现,频谱的变化如脉冲般变化猛烈,几乎是出现0.1秒内就消失了,难以观察,因此我们可以根据时间关系图把它在画出来,这边的做法就是把所有的音量差值,不管正的或是负的,都相加在一起总和:

let result = dataArray.delta.reduce((a,b) => a+b, 0)

看起来乾净呢,a是目前的累计值,b是阵列中的值,a+b是累加公式、而0是初始值
不好懂对吧?还是搭配for回圈来看比较直接

let a = 0;   // 对应初始值为0
for(N = 0; N < dataArray.delta; N++){
    let b = dataArray.delta[N];  // 对应阵列中的值
    a = a + b;                   // 对应累加公式
}
let result = a;

Array.prototype.push

接下来别忘了,还要将该值放到新的阵列中,绘制时间关系图,准备好一个名为dataArray.volume的空阵列,接着可以用最懒惰的方法,直接就这麽写一行:dataArray.volume.push(result);,0就会被排列到阵列的最後面,这个用法算特别简单的就不多介绍了,不过会有个很大的缺点,阵列会一直无止尽的排下去,最後会越来越长,占用记忆体,因此,通常会搭配切割阵列的方法

Array.prototype.splice

比如说,搭配刚刚的写法可以这麽写:

dataArray.volume.push(result);  // 塞入一个新的值到阵列长度 + 1的地方
dataArray.volume.splice(0, 1);  // 从索引值为0处算起,删除1个值

这麽做的用处是,可以有明确的先来後到排队机制,越慢被push进来的,就在阵列的越後面,并且概念上就像是,把一系列堆叠的高高的木箱,从下面用力抽出一个後,上面全部都掉下来的,使得全部位置向下一位。

至於我们想要根据时间绘制图形,就是为了要看清楚,因此不希望原本的数据会左右移动,并且还要设置一个范围,因此会先定义一个index,来随着时间增加,替换每个时间对应的音量信号,写成这样:

let t = 11000;
(dataIndex.volume > t) ? dataIndex.volume = 0 : dataIndex.volume++;
dataArray.volume.splice(dataIndex.volume, 1, result);
// 从dataIndex.volume的位置算起,删除1个值,并插入result

峰值问题

由於我们刚刚直接用map遍历阵列後,加总所有音量差值,无法预期图形的高峰在哪里,因此要去控制它,否则画一画会超出原本限定的绘制范围,可以用刚刚学习到的ruduce方法进行比对,找到阵列中最大的值:

let maxDelta = dataArray.delta.reduce((a,b) => Math.max(a,b), 50);
let maxVolume = dataArray.volume.reduce((a,b) => Math.max(a,b), 1);

接着最容易的做法,就是当要绘图的时候,将每一个信号都除以峰值,使其控制在0-1之间,再由频谱图形的规定总高度,去分配每个直方图的高度即可 let value = array[N] / max * height;

补充一下

初始定义

let dataArray = {'pre': new Uint8Array(bufferLength).fill(0),
                 'next': new Uint8Array(bufferLength).fill(0),
                 'delta': new Uint8Array(bufferLength).fill(0),
                 'volume': new Array(bufferLength).fill(0)};
let dataIndex = {'volume': 0};

绘图方程

如图,跟第一章节的大同小异,主要是透过max值来动态控制频谱的高度
https://ithelp.ithome.com.tw/upload/images/20210921/201351979EQDTPGimJ.jpg


<<:  Day15:Channel 的第一堂课

>>:  LeetCode解题 Day20

(急)毕业问卷填写

https://docs.google.com/forms/d/1YL9riLzCASVo7hkaO...

Day-12 决策树(decision tree)

排序的速度 Quicksort,需要 heapsort,需要 merge sort,需要 inser...

Day02 - 修改 Rails console edit 编辑模式

前言 在 rails console 中,若一次贴行数较多的 code 时,有时会失败,变成要逐段复...

Day-7 Divide-and-Conquer-2 : 求解递回式

如何求解递回式 目前主要有三种方法来求解递回式(至今没有任何一个好的演算法可以有效地解决递回式) 代...

【IntelliJ IDEA 入门指南】Java 开发者的神兵利器

天下武功 唯快不破 目录 前言 IntelliJ 特点 Android 与 Python 下载与安装...