iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Modern Web

從零開始打造網頁遊戲-造輪子你也辦的到!系列 第 13

Chapter3 - 動感DJ續篇 進一步操作陣列,讓音樂嗨起來

  • 分享至 

  • xImage
  •  

打了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


上一篇
Chapter2 - 重構完了 還是覺得物件很複雜嗎?直接上圖,就明白物件讓你更輕鬆
下一篇
Chapter3 - canvas動畫續篇 加入Z軸也能使2D畫面產生立體的空間感
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言