# 麥克風測試

正式網址：https://newmediaart.course.monolab.world/snippets/mic-test

點一下畫面開始，畫一條跟著聲音跳的音量條；沒聲音時畫面上直接寫三個檢查點（瀏覽器權限、系統麥克風、OpenProcessing 要點進作品頁放大看，或改用 p5 網頁編輯器）。第 4 堂開播前先點開來測一次。

- **分類**：除錯
- **行數**：118 行（p5.js 1.x／2.x）
- **在哪一堂用到**：[第 4 堂](https://newmediaart.course.monolab.world/lesson/04)

貼進 OpenProcessing 或 p5 網頁編輯器，點一下畫面開始；看到條跟著聲音跳就 OK。條不跳時，畫面上的三個檢查點照著查。這支只測瀏覽器拿不拿得到麥克風，第 4 堂的示範是同一個權限。

```js
// 麥克風測試：點一下畫面開始（瀏覽器規定要使用者點一下，才會問你能不能用麥克風）
// 看到下面的條跟著聲音跳，麥克風就 OK。沒用 p5.sound，只用瀏覽器內建的 Web Audio，所以 p5 1.x、2.x 都能跑。
let audioCtx;
let analyser;
let samples;
let state = 'idle'; // idle 還沒點｜asking 等你按「允許」｜ok 拿到麥克風｜fail 沒拿到
let reason = ''; // 沒拿到的原因（瀏覽器回報的名字）
let level = 0; // 現在的音量（0 到 1，已平滑）
let peak = 0; // 最近的最大音量，會慢慢往下掉
let heardAt = 0; // 最後一次聽到聲音的時間（毫秒）

function setup() {
  createCanvas(600, 400);
  textWrap(CHAR); // 中文沒有空白，要用「逐字換行」text() 的寬度才會生效
  textSize(18);
}

function draw() {
  background(20);
  noStroke();
  fill(240);
  textAlign(CENTER, CENTER);

  if (state === 'idle') {
    textSize(24);
    text('點一下畫面開始測試麥克風', width / 2, height / 2);
    return;
  }
  if (state === 'asking') {
    text('請在瀏覽器跳出的視窗按「允許」', width / 2, height / 2);
    return;
  }
  if (state === 'fail') {
    textSize(24);
    text('沒有拿到麥克風', width / 2, 50);
    textSize(14);
    fill(160);
    text('原因：' + reason + '　（點一下畫面再試一次）', width / 2, 84);
    drawChecklist(120);
    return;
  }

  // state === 'ok'：量音量（RMS），畫成一條橫的條
  analyser.getFloatTimeDomainData(samples);
  let sum = 0;
  for (let i = 0; i < samples.length; i++) {
    sum += samples[i] * samples[i];
  }
  level = lerp(level, sqrt(sum / samples.length), 0.3);
  peak = max(peak * 0.985, level);
  if (level > 0.02) {
    heardAt = millis();
  }

  const barW = map(constrain(level, 0, 0.3), 0, 0.3, 0, 500);
  const peakX = 50 + map(constrain(peak, 0, 0.3), 0, 0.3, 0, 500);
  textSize(20);
  text('對著麥克風說話　看到條在跳就 OK', width / 2, 70);
  noFill();
  stroke(90);
  rect(50, 110, 500, 44);
  noStroke();
  fill(110, 220, 140);
  rect(50, 110, barW, 44);
  fill(255, 210, 90);
  rect(peakX - 2, 104, 4, 56);

  // 麥克風開了，但連續 3 秒都沒聽到聲音：條不會跳，把檢查點列出來
  if (millis() - heardAt > 3000) {
    fill(255, 210, 90);
    textSize(18);
    text('條沒有跳？照下面三點查', width / 2, 200);
    drawChecklist(225);
  }
}

// 三個檢查點（一格一行，textWrap 會自己折行）
function drawChecklist(y) {
  textAlign(LEFT, TOP);
  textSize(15);
  fill(230);
  text('1. 瀏覽器權限：網址列左邊的鎖頭或麥克風圖示，把麥克風改成「允許」，重新整理再點一次。', 40, y, 520);
  text('2. 系統麥克風：電腦的系統設定、隱私權、麥克風，這個瀏覽器要打開；輸入裝置選對（筆電內建或你的麥克風）。', 40, y + 46, 520);
  text('3. OpenProcessing：作品要點進全螢幕（或開新分頁）才會給麥克風；還是不行，就把程式貼到 p5 網頁編輯器（editor.p5js.org）。', 40, y + 92, 520);
}

function mousePressed() {
  if (state === 'idle' || state === 'fail') {
    startMic();
  }
}

function startMic() {
  state = 'asking';
  reason = '';
  const AC = window.AudioContext || window.webkitAudioContext;
  if (!AC || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    state = 'fail';
    reason = '這個瀏覽器或這個頁面不能用麥克風';
    return;
  }
  // AudioContext 要在「點一下」這一刻就建（Safari、手機規定），拿到麥克風之後才接上去
  audioCtx = audioCtx || new AC();
  audioCtx.resume();
  navigator.mediaDevices.getUserMedia({ audio: true })
    .then(function (stream) {
      analyser = audioCtx.createAnalyser();
      analyser.fftSize = 1024;
      samples = new Float32Array(analyser.fftSize);
      audioCtx.createMediaStreamSource(stream).connect(analyser);
      heardAt = millis();
      state = 'ok';
    })
    .catch(function (err) {
      state = 'fail';
      reason = err && err.name ? err.name : '不明';
    });
}
```

## 同類常用指令・除錯

- [左上角顯示 fps 與 frameCount](https://newmediaart.course.monolab.world/snippets/fps-overlay)
- [滑鼠十字線＋座標](https://newmediaart.course.monolab.world/snippets/mouse-crosshair)
- [每秒在 console 印一次變數](https://newmediaart.course.monolab.world/snippets/log-every-second)

[回到全部常用指令](https://newmediaart.course.monolab.world/snippets)

[到 Hahow 報名](https://hahow.in/cr/creative-code)。價格與優惠以 Hahow 頁面為準。
