Chapter1-DJ最爱的音频动感图像(I)基本流程图 & 操作DOM介面

在开始前,还没看过序章的朋友们,可以点击进去,教学大纲和主题方向都写在里面罗!

看完这章节,你会学到...

https://ithelp.ithome.com.tw/upload/images/20210830/20135197Ds3P8jLnp2.jpg

地基一定要打稳,如果基本的还不会的话,建议先去w3school恶补一下!

前言

我们要做的是一个音乐游戏,因此最重要的核心技术便是跟audio这个tag有关的语法,这章节就来把它!

咦?你问我不先设置环境吗,没错XD,由於这是写给入门者的文章,能简单的地方就简单,带大家从实作中学习,因此只需要有大家都有的edge或chrome,和一个文字编辑器(推荐VScode)就可以开始罗。

首先,audio这个元件大家应该不陌生,早在无名小站那个年代,几乎每个网站都会放一个音乐播放器,而且有不少都采用自动播放,笔者就经常不小心开到几个有歌网站,一次播放好几首歌,更甚者有时候还找不到在哪一个分页,堪称网页中的幽灵。

<audio id="Music" controls>
    <source src="music/nameOfMyMusic.mp3">
</audio>

既然有现成的控制器能用,我们就先加上controls,这样除错方便

初次认识 Web Audio API(观念篇)

在处理音频的讯号时,我们要用到AudioContextAnalyserNode,前者能作为我们的音频接口,後者能从中取出即时(当下)的频率资料。

不好懂对吧,就想像我们现在要给自己的笔电接一个新买的喇叭,如果少了AudioContext,就像是笔电上面根本没有插孔,这样喇叭接不上去根本不能用;如果少了AnalyserNode,就像是我喇叭的插头坏了,没办法从中取得音讯。(只是比喻不要太认真xd)

这样应该就不难理解,AudioContext只需要建立一次即可(除非你想在笔电上多挖几个洞XD),AnalyserNode就可以不只一次(可以用好几个不同的喇叭接收音讯),并且为了持续的取得音讯,需要多次用到AnalyserNode的方法,不过我们并没有要用到混音跟创作音乐,所以这次我们只需要各建立一个就好。

这边先咐上今天的流程图,就可以知道这两个元件在哪里作用了:
https://ithelp.ithome.com.tw/upload/images/20210909/2013519743AZq6gxeI.jpg

让我们先把目光放在黄色的菱形,在该流程图中,菱形是一个分歧点,如下图,比方说,我们可以检查使用者按下开始後,是否有先上传音乐,如果有,就走方案A,反之就用方案B。而这张图中则是利用监听事件EventListener来等待使用者进行动作。

https://ithelp.ithome.com.tw/upload/images/20210830/20135197W6iTkNyZKH.jpg

上传音乐实作

那麽,从流程图中,可以知道,用户能做的动作有四种,分别为Play、Pause、Select、Upload,今天就从最基本也最重要的上传开始吧!顺便帮大家暖身一下(可能有些人疫情宅在家都懒了,很久没写程序XD)
https://ithelp.ithome.com.tw/upload/images/20210909/20135197u1suY2Vo63.jpg

<button id="Upload-beautify">- 上传音乐 -</button>
<div class="hidden">
    <input id="Upload" type="file" value="- 上传音乐 -" accept="audio/*">
</div>
<script>
    document.querySelector("#Upload-beautify").addEventListener(
    "click", function(){
        document.querySelector("#Upload").click();
    }, false);
</script>

因为input不好客制化造型,这边让用户按下button後,再呼叫input

这边也要提醒一下,下面这两种写法都是一样的,如果你复制这段代码到开发者工具的console中,会得到"true",不过我会推荐大家用左边的写法,因为当自己或别人在读code时,从HTML文件看到了这个ID,要到JS中去搜寻的时候,比起"Upload",多了井字号"#Upload"几乎可以立刻找到,在维护上相对会轻松许多。

document.querySelector("#Upload") === document.getElementById("Upload")

console真的很好用,若没指定动作,会直接像console.log一样回传值给你,不过小心别打错字了,如果两边ID都打错,很可能也会得到true唷!因为都找不到有这个ID的元件,会回传null。

接着来实作上传机制,值得注意的是,input标签内的accept="audio/*"属性只是一个基本的(防笨)机制,实测就会发现,用户在上传档案的时候是"预设"找寻音讯档,然而用户还是能手贱(X)去传其他档案,更关键的问题是,在手机上浏览时,各个装置对这个设定的接受程度也不一,因此防范未然,待会最好还是作一个检查机制。

let Upload = document.querySelector("#Upload");
Upload.addEventListener("change", FileManager, false);
function FileManager(){
    console.log(this);      // 会印出呼叫该函式的人(初学者这样想就好)
    console.log(this.files);     // 会印出用户上传的所有档案
    console.log(this.files[0]);  // 会印出用户上传的第一个档案(或唯一的)
}

这边我们设计一个档案管理函式FileManager,等到用户上传档案後,才开始动作,也因为这里观念蛮重要的,所以通通印出来让大家看一下,这边的this的指的是这个input元件本身,关於this的用法又是一个大坑,只需要知道这边的this会指向这个「ID名为Upload的input元件」为就好,毕竟用onchange呼叫该事件的就是它,这个this不是它还能是谁呢,这个问题很有趣可以思考看看。

那麽档案会传到哪里去呢?当然是在这个物件身上罗,因此透过this.files能浏览用户上传的档案"们",为什麽我强调"们",是因为它是一个阵列,即使用户只上传一个档案,仍会以阵列的方式读取,第一笔资料会存在this.files[0]。

知道这点後,我们可以透过 URL.createObjectURL来取得档案的路径,如果你还是萌新,可能会想为什麽不直接用资料夹路径取得资料,写程序的时候不管是引入外部js、css、甚至图片影片等等,不都是直接提供相对路径吗?其实这是一个保护机制,上传档案的时候,用户的电脑只需要授权网页使用这一个档案,并提供一个假的路径(暂存)blob:接一串英文数字。若直接把整个资料夹路径奉上,岂不是被看光光了,而近年来浏览器更是透过CORS做了严格的限制,想要拿别人的资料,必须得有对方的同意。

function FileManager(){
    document.getElementById("Music").src = URL.createObjectURL(this.files[0]);
}

透过这样短短的一行就完成基本的上传音乐了,不过别忘了唷!我们还要来做检查机制,常见的音乐副档名有wav、mp3、m4a,因此我们先建立一个阵列,放进合格的附档名,接着透过this.files[0].name取得完整的档名(字串),只要把字串从中间的(.)给切开就能够取得我们要的副档名了,因此步骤很简单:

  1. string.lastIndexOf方法取得(.)在字串中的索引(index)
  2. string.substring方法,透过索引(index)切开字串得到副档名
  3. 如果附档名不是我们预设的音乐附档名,就跳出警示讯息
    (对这两个方法不熟悉的朋友们,可以点进连结去看MDN的示例)
function FileManager(){
    // 可接受的附档名Exts
    let validExts = new Array(".wav", ".mp3", ".m4a");
    let index = this.files[0].name.lastIndexOf('.')
    let fileExt = this.files[0].name.substring(index);
    if (validExts.indexOf(fileExt) < 0) {
      console.warn("档案类型错误,可接受的副档名有: " + validExts.toString());
      return; //可写可不写,取决於後面还有没有程序码要执行
    }
    let audio = document.getElementById("Music");
    audio.src = URL.createObjectURL(this.files[0]);
}

事件观念补充

接下来帮大家回忆一下,当我们仔细拆解一下监听事件的逻辑,就会得到三个关键:
WHO -- 目标是谁
WHEN -- 什麽时候
WHAT -- 要做什麽

就以我们刚刚的上传档案事件为例:

    Upload.addEventListener("change", FileManager, false);
//   (WHO)             (WHEN)      (What)

这样就可以搭配一开始给的流程图来理解了,我们要设计一个流程为「当用户上传音乐档案後,先取得该档案的路径,再检查是不是音乐,最後设定音乐的路径」,因此步骤就会很清楚:

  1. 先设计一个input栏位
  2. 事件监听-当input取得档案後(change),开始後续动作
    目标是谁---input
    什麽时候---change
    要做什麽---FileManager
  3. 取得路径 URL.createObjectURL
  4. 检查副档名 Array
  5. 设定音乐路径 audio.src

透过这个流程,可以清楚的知道分别需要那些语法来完成一连串的动作,这不管是在设计程序逻辑、或是在设计游戏上都相当重要,可以先评估自己在哪个阶段会遇到挑战,虽然有些人天生逻辑很好,可以在脑内想得很清楚,不过在团队合作中,沟通需要一个媒介,这也是LogicFlow存在的意义,不管是设计师、还是程序员,都应该懂得画流程图,

不过,对初学者来说肯定是边做边学习,可以先开始撰写程序码,到一个段落後,把逻辑试着画成一张图,讲给别人听,会是不错的练习唷!

後记

其实我也是为了这次铁人赛才画图的XD,选择的是特别单纯的画法(也为了让大家好懂),只有三个图形,分别是流程起点/终点(圆角矩形)、处理流程(矩形)、抉择点(棱形),之前独立开发,都在自己脑内想好即可,不过,这次透过画出来的方式,也让我有了一次重构程序码的机会,对於後续要加上的流程控制跟例外处理,更是能帮上大忙呢!

因为步骤讲得比较仔细,篇幅拉长,感谢大家耐心观看,希望能帮初学者解惑,如果有什麽问题欢迎在下面做询问!

回家作业(思考题)

Q. 请问在今天已经完成的上传流程中,在用户的哪个操作、或是哪段程序码可能会出现错误,甚至导致中断呢?请试着在留言区回答吧!(提示:其实用户不是你想的那麽聪明!)


<<:  Day5 - 新鲜人提升开发效率的方法(扩充套件篇)

>>:  day9 : logging集中(下)

CSS 命名基础介绍 DAY40

今天要来介绍 CSS 命名 首先先来介绍 驼峰式命名: https://zh.wikipedia.o...

[Day 10] 每家公司都有资料产品

(https://www.manmonthly.com.au/news/graphene-help...

D24 - 如何用 Apps Script 自动化地创造与客制 Google Sheet?(ㄧ)自动化创造图表并放到报告中

今天的目标: 要怎麽针对特定资料,固定地创造图表?现在用到图表的机会越来越多,很多时候我们会需要创造...

SQL Server Agent 权限 - 心得分享

DBABootcamp 没有 SA 权限的使用者,要如何管理 SQL Agent Jobs (作业)...

110/10 - Intent.ACTION_MEDIA_SCANNER_SCAN_FILE弃用

新增相片後,要发送通知给相簿应用程序,这样才能更新照片清单,这样才能在相簿看到新增加的照片,使用者体...