<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <script src="sketch.js"></script>
  </body>
</html>
html, body {
  margin: 20;
  padding: 0;
}
canvas {
  display: block;
  border: solid 1px;
}

/**
 * 波形: sin(2πft)
 * @param {number} t 時間
 * @param {number} freq 周波数 
 */
function waveSin2npiFunc(t, freq) {
  return Math.sin(2 * Math.PI * freq * t);
}

/**
 * 波形: cos(2πft)
 * @param {number} t 時間
 * @param {number} freq 周波数
 */
function waveCos2npiFunc(t, freq) {
  return Math.cos(2 * Math.PI * freq * t);
}

/**
 * ボタン類
 */
let playSinButton;
let playCosButton;

/**
 * 再生中の波形データ
 */
let playWaveData = [];

/**
 * sin波再生ボタン押下
 */
function pushSinPlayButton() {
  playWaveData = playSound((t) => waveSin2npiFunc(t, 441));
}

/**
 * cos波再生ボタン押下
 */
function pushCosPlayButton() {
  playWaveData = playSound((t) => waveCos2npiFunc(t, 441));
}

/**
 * 初期化処理
 */
function setup() {
  createCanvas(ScreenWidth, ScreenHeight);
  // ボタン設定
  playSinButton = createButton("Play Sin Wave");
  playSinButton.mousePressed(pushSinPlayButton);
  playSinButton.position(ScreenWidth - (playSinButton.width), 20);
  playCosButton = createButton("Play Cos Wave");
  playCosButton.mousePressed(pushCosPlayButton);
  playCosButton.position(ScreenWidth - (playCosButton.width), 48);
}

/**
 * 描画処理
 */
function draw() {
  background(255);
  drawGrid();
  drawWaveData(playWaveData);
}


// ===== サウンド再生 =====

/**
 * サンプルレート
 */
const SampleRate = 44100;

/**
 * 再生時間(秒)
 */
const PlaySec = 1;

/**
 * 再生中の波形データ
 */
let bufferSource = null;

/**
 * サウンド再生処理
 * 参考: https://developer.mozilla.org/ja/docs/Web/API/BaseAudioContext/createBuffer
 * @param {*} func 波形関数
 */
function playSound(func) {
    // stop playing sound.
    if (bufferSource) {
        bufferSource.stop();
    }

    // create context.
    let audioCtx = new AudioContext();
    audioCtx.sampleRate = SampleRate;

    // create mono channel buffer.
    let buffer = audioCtx.createBuffer(1, PlaySec * SampleRate, SampleRate);
    let channelData = buffer.getChannelData(0);
    for (var i = 0; i < buffer.length; i++) {
        let playSec = i / SampleRate;
        channelData[i] = func(playSec);
    }

    // set play wave data.
    let playWaveData = channelData;

    // play sound.
    bufferSource = audioCtx.createBufferSource();
    bufferSource.buffer = buffer;
    bufferSource.connect(audioCtx.destination);
    bufferSource.start();

    return playWaveData;
}

// ===== グリッド描画・プロット =====

/**
 * 画面設定
 */
const ScreenWidth = 400;
const ScreenHeight = 400;

/**
 * 座標の表示範囲
 */
const MinGridValueY = -5;
const MaxGridValueY = 5;
const MinGridValueX = 0;
const MaxGridValueX = 0.005;

/**
 * グリッドの描画
 */
function drawGrid() {
    strokeWeight(2);
  
    let dx = MaxGridValueX / 10;
    let dy = MaxGridValueY / 10;
    for (let x = MinGridValueX; x < MaxGridValueX; x+=dx) {
      for (let y = MinGridValueY; y < MaxGridValueY; y+=dy) {
        // 縦線
        stroke(240, 240, 240);
        drawLineCanvas(x+dx, y, x+dx, y+dy);
        // 横線
        stroke(180, 180, 180);
        drawLineCanvas(x, y+dy, x+dx, y+dy);
      }
    }
  
    // 中央線
    stroke(120, 120, 120);
    drawLineCanvas(MinGridValueX, 0, MaxGridValueX, 0);
    drawLineCanvas(0, MinGridValueY, 0, MaxGridValueY);
  }
  
  /**
   * 波形データを描画
   * @param {number[]} waveData 波形データ
   */
  function drawWaveData(waveData) {
    strokeWeight(4);
    stroke(0, 0, 255);
  
    for (let i = 0; i < waveData.length; i++) {
      // 最大秒数を超えたらスキップ
      if (i / SampleRate > MaxGridValueX) {
        break;
      }
      if (i == waveData.length - 1) {
        break;
      }
      // 次のデータと繋げる
      let data = waveData[i];
      let time = i / SampleRate;
      let nextData = waveData[i + 1];
      let nextTime = (i + 1) / SampleRate;
      drawLineCanvas(time, data, nextTime, nextData);
    }
  }
  
  /**
   * 関数の描画
   * @param {(t: number) => number} func 関数 
   */
  function drawFunc(func) {
    strokeWeight(4);
    stroke(0, 0, 255);
    let dx = MaxGridValueX / 100;
    for (let x = 0; x < MaxGridValueX; x+=dx) {
      drawLineCanvas(x, func(x), (x+dx), func(x+dx));
    }
  }
  
  /**
   * 受け取った座標位置で線を繋げて表示する
   * @param {number} fromX 
   * @param {number} fromY 
   * @param {number} toX 
   * @param {number} toY 
   */
  function drawLineCanvas(fromX, fromY, toX, toY) {
    fromX *= ScreenWidth/(MaxGridValueX-MinGridValueX);
    fromY *= ScreenHeight/(MaxGridValueY-MinGridValueY);
    toX *= ScreenWidth/(MaxGridValueX-MinGridValueX)
    toY *= ScreenHeight/(MaxGridValueY-MinGridValueY);
    let offset = [0, ScreenHeight/2]; // Y軸は反転させる
    line(fromX+offset[0], ScreenHeight-(fromY+offset[1]), toX+offset[0], ScreenHeight-(toY+offset[1]));
  }

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.