<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;
}
/**
* 波形: ノイズ
* @param {number} t 時間
*/
function waveNoiseFunc(t) {
return Math.random() * 2 - 1;
}
/**
* 波形: 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);
}
/**
* 波形: フーリエ級数
* @param {number} t 時間
* @param {number} a0 定数関数
* @param {number[]} aArray cos関数の定数項
* @param {number[]} bArray sin関数の定数項
* @param {number[]} baseFreq 計算のベースとする周波数
*/
function waveFourierSeriesFunc(t, a0, aArray, bArray, baseFreq = 441) {
// cos値の計算
let cosValues = [];
for (let n = 0; n < aArray.length; n++) {
const an = aArray[n];
const freq = (n + 1) * baseFreq;
cosValues.push(an * waveCos2npiFunc(t, freq));
}
// sin値の計算
let sinValues = [];
for (let n = 0; n < bArray.length; n++) {
const bn = bArray[n];
const freq = (n + 1) * baseFreq;
sinValues.push(bn * waveSin2npiFunc(t, freq));
}
// フーリエ級数の形で計算して返却
const cosSum = cosValues.length == 0 ? 0 : cosValues.reduce((sum, x) => sum + x, 0);
const sinSum = sinValues.length == 0 ? 0 : sinValues.reduce((sum, x) => sum + x, 0);
return a0 / 2 + cosSum + sinSum;
}
/**
* フーリエ解析
* @param {(t: number) => number} f 波形関数
* @param {number} N 解析する定数項の数
* @param {number[]} baseFreq 計算のベースとする周波数
* @param {number} dotN 内積を行う回数(数値を上げると精度が向上する)
* @returns a0, aArray, bArray
*/
function fourierCoefficients(f, N, baseFreq = 441, dotN = 100) {
// 関数同士の内積
// 2 * ∮[0→1]f(t)g(t)
function dot_function(f, g, N) {
const dt = 1 / N;
let array = [];
for (let t = 0; t < 1; t += dt) {
array.push(f(t) * g(t) * dt);
}
return 2 * array.reduce((sum, x) => sum + x, 0);
}
// sin、cos関数との内積結果から係数を求める
let a0 = dot_function(f, (t) => 1 / 2, dotN);
let anArray = [];
let bnArray = [];
for (let n = 0; n < N; n++) {
const freq = (n + 1) * baseFreq;
anArray.push(dot_function(f, (t) => waveCos2npiFunc(t, freq), dotN));
bnArray.push(dot_function(f, (t) => waveSin2npiFunc(t, freq), dotN));
}
return {a0, anArray, bnArray};
}
/**
* ボタン類
*/
let playNoiseButton;
let playSinButton;
let playRectButton;
let playFoorierButton;
/**
* テキスト表示値
*/
let a0Value = 0;
let anValueArray = [];
let bnValueArray = [];
/**
* 再生中の波形データ
*/
let playWaveData = [];
/**
* ノイズ音再生ボタン押下
*/
function pushNoisePlayButton() {
playSoundAndCheckFourier(waveNoiseFunc);
}
/**
* sin波再生ボタン押下
*/
function pushSinPlayButton() {
playSoundAndCheckFourier((t) => waveSin2npiFunc(t, 441));
}
/**
* 短形波再生ボタン押下
*/
function pushRectPlayButton() {
let bArray = [];
const bCount = 30; // この数を上げるほど短形波に近づく
for (let n = 1; n <= bCount; n++) {
let b = n % 2 != 0 ? (4 / (n * Math.PI)) : 0;
bArray.push(b);
}
playSoundAndCheckFourier((t) => waveFourierSeriesFunc(t, 0, [], bArray));
}
/**
* フーリエ波形再生ボタン押下
*/
function pushFoorierPlayButton() {
playSoundAndCheckFourier((t) => waveFourierSeriesFunc(t, 0, [1, 2, 3], [1]));
}
/**
* サウンド再生とフーリエ解析を行う
* @param {(t: number) => number} waveFunc 波形関数
*/
function playSoundAndCheckFourier(waveFunc) {
// サウンド再生
playWaveData = playSound(waveFunc);
// フーリエ級数を求める
const checkFourierCount = 5;
let result = fourierCoefficients(waveFunc, checkFourierCount);
a0Value = result.a0;
anValueArray = result.anArray;
bnValueArray = result.bnArray;
}
/**
* 初期化処理
*/
function setup() {
createCanvas(ScreenWidth, ScreenHeight);
// ボタン設定
playNoiseButton = createButton("Play Noise");
playNoiseButton.mousePressed(pushNoisePlayButton);
playNoiseButton.position(ScreenWidth - (playNoiseButton.width), 20);
playSinButton = createButton("Play Sin Wave");
playSinButton.mousePressed(pushSinPlayButton);
playSinButton.position(ScreenWidth - (playSinButton.width), 48);
playRectButton = createButton("Play Rect Wave");
playRectButton.mousePressed(pushRectPlayButton);
playRectButton.position(ScreenWidth - (playRectButton.width), 76);
playFoorierButton = createButton("Play Foorier Wave");
playFoorierButton.mousePressed(pushFoorierPlayButton);
playFoorierButton.position(ScreenWidth - (playFoorierButton.width), 102);
}
/**
* 描画処理
*/
function draw() {
background(255);
drawGrid();
drawWaveData(playWaveData);
// テキスト描画
noStroke()
fill(0, 0, 0)
textSize(18)
text(`a0: ${Math.abs(a0Value).toFixed(1)}`, 20, 30)
text(`a[]: ${anValueArray.map(x => Math.abs(x).toFixed(1)).join(', ')}`, 20, 56)
text(`b[]: ${bnValueArray.map(x => Math.abs(x).toFixed(1)).join(', ')}`, 20, 82)
}
// ===== サウンド再生 =====
/**
* サンプルレート
*/
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]));
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.