<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>傅里叶变换的动态展示效果</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
margin: 0;
padding: 0;
background: #f8f9fa;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin-bottom: 30px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.control-group {
display: flex;
flex-direction: column;
min-width: 200px;
}
label {
margin-bottom: 5px;
font-weight: bold;
}
input[type="range"], select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background: #2980b9;
}
.visualization {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
.canvas-container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
flex: 1;
min-width: 300px;
}
canvas {
width: 100%;
height: 300px;
background: #f1f1f1;
border-radius: 4px;
}
.explanation {
margin-top: 30px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.formula {
font-family: 'Cambria Math', serif;
text-align: center;
margin: 20px 0;
font-size: 18px;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.loading img {
width: 50px;
height: 50px;
}
@media (max-width: 768px) {
.canvas-container {
min-width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>傅里叶变换的动态展示效果</h1>
<div class="controls">
<div class="control-group">
<label for="waveType">波形类型</label>
<select id="waveType">
<option value="sine">正弦波</option>
<option value="square">方波</option>
<option value="sawtooth">锯齿波</option>
<option value="triangle">三角波</option>
<option value="custom">自定义波形</option>
</select>
</div>
<div class="control-group">
<label for="frequency">频率 (Hz)</label>
<input type="range" id="frequency" min="1" max="20" value="5" step="1">
<span id="frequencyValue">5 Hz</span>
</div>
<div class="control-group">
<label for="amplitude">振幅</label>
<input type="range" id="amplitude" min="0.1" max="1" value="0.5" step="0.1">
<span id="amplitudeValue">0.5</span>
</div>
<div class="control-group">
<label for="harmonics">谐波数量</label>
<input type="range" id="harmonics" min="1" max="20" value="5" step="1">
<span id="harmonicsValue">5</span>
</div>
<button id="updateBtn">更新波形</button>
<button id="computeBtn">计算傅里叶变换</button>
</div>
<div class="loading" id="loading">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzgiIGhlaWdodD0iMzgiIHZpZXdCb3g9IjAgMCAzOCAzOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2U9IiMzNDk4ZGIiPgogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxIDEpIiBzdHJva2Utd2lkdGg9IjIiPgogICAgICAgICAgICA8Y2lyY2xlIHN0cm9rZS1vcGFjaXR5PSIuNSIgY3g9IjE4IiBjeT0iMTgiIHI9IjE4Ii8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0zNiAxOGMwLTkuOTQtOC4wNi0xOC0xOC0xOCI+CiAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybQogICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIKICAgICAgICAgICAgICAgICAgICB0eXBlPSJyb3RhdGUiCiAgICAgICAgICAgICAgICAgICAgZnJvbT0iMCAxOCAxOCIKICAgICAgICAgICAgICAgICAgICB0bz0iMzYwIDE4IDE4IgogICAgICAgICAgICAgICAgICAgIGR1cj0iMXMiCiAgICAgICAgICAgICAgICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICAgICAgPC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+" alt="加载中...">
<p>计算中...</p>
</div>
<div class="visualization">
<div class="canvas-container">
<h3>原始波形</h3>
<canvas id="originalWave"></canvas>
</div>
<div class="canvas-container">
<h3>傅里叶变换频谱</h3>
<canvas id="fourierSpectrum"></canvas>
</div>
</div>
<div class="visualization">
<div class="canvas-container">
<h3>傅里叶级数合成波形</h3>
<canvas id="synthesizedWave"></canvas>
</div>
<div class="canvas-container">
<h3>相位谱</h3>
<canvas id="phaseSpectrum"></canvas>
</div>
</div>
<div class="explanation">
<h2>傅里叶变换原理</h2>
<p>傅里叶变换是一种将时域信号转换为频域表示的数学工具。它可以将任何周期性的函数分解为简单正弦波的叠加。</p>
<div class="formula">
<p>离散傅里叶变换 (DFT):</p>
<p>X(k) = Σ[n=0 to N-1] x(n) · e<sup>-j2πkn/N</sup></p>
<p>傅里叶级数:</p>
<p>f(t) = a<sub>0</sub>/2 + Σ[n=1 to ∞] [a<sub>n</sub>cos(nωt) + b<sub>n</sub>sin(nωt)]</p>
</div>
<p>在这个演示中,您可以通过控制面板选择不同的波形,并观察其傅里叶变换结果。傅里叶变换让我们能够看到波形中包含的各种频率成分及其振幅和相位。</p>
</div>
</div>
<script>
// DOM 元素
const waveTypeSelect = document.getElementById('waveType');
const frequencySlider = document.getElementById('frequency');
const frequencyValue = document.getElementById('frequencyValue');
const amplitudeSlider = document.getElementById('amplitude');
const amplitudeValue = document.getElementById('amplitudeValue');
const harmonicsSlider = document.getElementById('harmonics');
const harmonicsValue = document.getElementById('harmonicsValue');
const updateBtn = document.getElementById('updateBtn');
const computeBtn = document.getElementById('computeBtn');
const loading = document.getElementById('loading');
const originalCanvas = document.getElementById('originalWave');
const fourierCanvas = document.getElementById('fourierSpectrum');
const synthesizedCanvas = document.getElementById('synthesizedWave');
const phaseCanvas = document.getElementById('phaseSpectrum');
// 设置画布大小
function setupCanvas(canvas) {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
return canvas.getContext('2d');
}
const originalCtx = setupCanvas(originalCanvas);
const fourierCtx = setupCanvas(fourierCanvas);
const synthesizedCtx = setupCanvas(synthesizedCanvas);
const phaseCtx = setupCanvas(phaseCanvas);
// 更新UI显示值
frequencySlider.addEventListener('input', () => {
frequencyValue.textContent = `${frequencySlider.value} Hz`;
});
amplitudeSlider.addEventListener('input', () => {
amplitudeValue.textContent = amplitudeSlider.value;
});
harmonicsSlider.addEventListener('input', () => {
harmonicsValue.textContent = harmonicsSlider.value;
});
// 窗口调整大小时重新设置画布
window.addEventListener('resize', () => {
setupCanvas(originalCanvas);
setupCanvas(fourierCanvas);
setupCanvas(synthesizedCanvas);
setupCanvas(phaseCanvas);
drawWave();
});
// 生成不同类型的波形数据
function generateWaveData(type, frequency, amplitude, sampleCount) {
const data = new Array(sampleCount);
const period = sampleCount / frequency;
for (let i = 0; i < sampleCount; i++) {
const t = i / sampleCount;
const x = 2 * Math.PI * frequency * t;
switch (type) {
case 'sine':
data[i] = amplitude * Math.sin(x);
break;
case 'square':
data[i] = amplitude * (Math.sin(x) >= 0 ? 1 : -1);
break;
case 'sawtooth':
data[i] = amplitude * (2 * (x / (2 * Math.PI) - Math.floor(0.5 + x / (2 * Math.PI))));
break;
case 'triangle':
data[i] = amplitude * (2 * Math.abs(2 * (x / (2 * Math.PI) - Math.floor(0.5 + x / (2 * Math.PI)))) - 1);
break;
case 'custom':
// 自定义波形:基本正弦波加上一些谐波
data[i] = amplitude * (
Math.sin(x) +
0.5 * Math.sin(3 * x) +
0.3 * Math.sin(5 * x)
) / 1.8; // 归一化
break;
default:
data[i] = amplitude * Math.sin(x);
}
}
return data;
}
// 绘制波形
function drawWaveform(ctx, data, color = '#3498db') {
const width = ctx.canvas.width;
const height = ctx.canvas.height;
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 2;
const stepSize = width / (data.length - 1);
const centerY = height / 2;
const scale = height / 2 * 0.9;
for (let i = 0; i < data.length; i++) {
const x = i * stepSize;
const y = centerY - data[i] * scale;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
// 绘制x轴和y轴
ctx.beginPath();
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1;
ctx.moveTo(0, centerY);
ctx.lineTo(width, centerY);
ctx.stroke();
}
// 绘制频谱
function drawSpectrum(ctx, magnitudes, maxFreq = 20) {
const width = ctx.canvas.width;
const height = ctx.canvas.height;
ctx.clearRect(0, 0, width, height);
// 找出最大振幅以便归一化
const maxMagnitude = Math.max(...magnitudes.slice(1)); // 忽略直流分量
const barWidth = width / maxFreq;
const scale = height * 0.9;
// 绘制频谱柱状图
ctx.fillStyle = '#3498db';
for (let i = 0; i < Math.min(maxFreq, magnitudes.length); i++) {
const magnitude = i === 0 ? 0 : magnitudes[i] / maxMagnitude; // 忽略直流分量
const barHeight = magnitude * scale;
const x = i * barWidth;
const y = height - barHeight;
ctx.fillRect(x, y, barWidth * 0.8, barHeight);
}
// 绘制x轴
ctx.beginPath();
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1;
ctx.moveTo(0, height);
ctx.lineTo(width, height);
ctx.stroke();
// 绘制频率刻度
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
for (let i = 0; i < maxFreq; i += 5) {
const x = i * barWidth + barWidth / 2;
ctx.fillText(`${i} Hz`, x, height - 5);
}
}
// 绘制相位谱
function drawPhaseSpectrum(ctx, phases, maxFreq = 20) {
const width = ctx.canvas.width;
const height = ctx.canvas.height;
ctx.clearRect(0, 0, width, height);
const barWidth = width / maxFreq;
const centerY = height / 2;
const scale = height / 2 * 0.8;
// 绘制相位点和连线
ctx.beginPath();
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 2;
for (let i = 1; i < Math.min(maxFreq, phases.length); i++) {
const x = i * barWidth + barWidth / 2;
const y = centerY - phases[i] / Math.PI * scale;
if (i === 1) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
// 绘制圆点
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fill();
}
ctx.stroke();
// 绘制x轴和y轴
ctx.beginPath();
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1;
ctx.moveTo(0, centerY);
ctx.lineTo(width, centerY);
ctx.moveTo(0, centerY - scale);
ctx.lineTo(width, centerY - scale);
ctx.fillText('π', 10, centerY - scale);
ctx.moveTo(0, centerY + scale);
ctx.lineTo(width, centerY + scale);
ctx.fillText('-π', 10, centerY + scale);
ctx.stroke();
}
// FFT算法实现 (Cooley-Tukey)
function fft(x) {
const N = x.length;
if (N <= 1) {
return x;
}
// 确保N是2的幂
if (N & (N - 1)) {
throw new Error("FFT长度必须是2的幂");
}
// 分治法:分别计算偶数和奇数索引
const even = new Array(N / 2);
const odd = new Array(N / 2);
for (let i = 0; i < N / 2; i++) {
even[i] = x[i * 2];
odd[i] = x[i * 2 + 1];
}
// 递归计算
const evenResult = fft(even);
const oddResult = fft(odd);
// 合并结果
const result = new Array(N).fill().map(() => new Complex(0, 0));
for (let k = 0; k < N / 2; k++) {
const twiddle = Complex.fromPolar(1, -2 * Math.PI * k / N);
const oddTerm = Complex.multiply(twiddle, oddResult[k]);
result[k] = Complex.add(evenResult[k], oddTerm);
result[k + N / 2] = Complex.subtract(evenResult[k], oddTerm);
}
return result;
}
// 复数类
class Complex {
constructor(re, im) {
this.re = re;
this.im = im;
}
static add(a, b) {
return new Complex(a.re + b.re, a.im + b.im);
}
static subtract(a, b) {
return new Complex(a.re - b.re, a.im - b.im);
}
static multiply(a, b) {
return new Complex(
a.re * b.re - a.im * b.im,
a.re * b.im + a.im * b.re
);
}
static fromPolar(r, theta) {
return new Complex(
r * Math.cos(theta),
r * Math.sin(theta)
);
}
magnitude() {
return Math.sqrt(this.re * this.re + this.im * this.im);
}
phase() {
return Math.atan2(this.im, this.re);
}
}
// 计算傅里叶变换
function computeFourier(data) {
// 确保数据长度是2的幂
const nextPow2 = Math.pow(2, Math.ceil(Math.log2(data.length)));
const paddedData = new Array(nextPow2).fill(0);
for (let i = 0; i < data.length; i++) {
paddedData[i] = new Complex(data[i], 0);
}
for (let i = data.length; i < nextPow2; i++) {
paddedData[i] = new Complex(0, 0);
}
// 计算FFT
const result = fft(paddedData);
// 提取幅度和相位
const magnitudes = result.map(c => c.magnitude() / Math.sqrt(nextPow2));
const phases = result.map(c => c.phase());
return { magnitudes, phases, result };
}
// 使用傅里叶级数合成波形
function synthesizeWave(fourierResult, harmonics, sampleCount) {
const synthesized = new Array(sampleCount).fill(0);
const { magnitudes, phases, result } = fourierResult;
// 使用指定数量的谐波合成
for (let t = 0; t < sampleCount; t++) {
let sum = magnitudes[0] / 2; // 直流分量
for (let n = 1; n <= harmonics; n++) {
if (n < magnitudes.length) {
const amplitude = magnitudes[n];
const phase = phases[n];
const angle = 2 * Math.PI * n * t / sampleCount;
sum += amplitude * Math.cos(angle + phase);
}
}
synthesized[t] = sum;
}
return synthesized;
}
// 主波形绘制函数
function drawWave() {
const waveType = waveTypeSelect.value;
const frequency = parseInt(frequencySlider.value);
const amplitude = parseFloat(amplitudeSlider.value);
const harmonics = parseInt(harmonicsSlider.value);
const sampleCount = 1024;
// 生成原始波形
const waveData = generateWaveData(waveType, frequency, amplitude, sampleCount);
drawWaveform(originalCtx, waveData);
return { waveType, frequency, amplitude, harmonics, sampleCount, waveData };
}
// 计算并绘制傅里叶变换相关图像
function computeAndDrawFourier() {
loading.style.display = 'block';
setTimeout(() => {
const { waveType, frequency, amplitude, harmonics, sampleCount, waveData } = drawWave();
// 计算傅里叶变换
const fourierResult = computeFourier(waveData);
const { magnitudes, phases } = fourierResult;
// 绘制频谱
drawSpectrum(fourierCtx, magnitudes);
// 绘制相位谱
drawPhaseSpectrum(phaseCtx, phases);
// 合成波形
const synthesized = synthesizeWave(fourierResult, harmonics, sampleCount);
drawWaveform(synthesizedCtx, synthesized, '#27ae60');
loading.style.display = 'none';
}, 100);
}
// 事件监听
updateBtn.addEventListener('click', drawWave);
computeBtn.addEventListener('click', computeAndDrawFourier);
// 初始绘制
drawWave();
computeAndDrawFourier();
// AJAX功能 - 可以加载预设波形
function loadPresetWaveform(presetName) {
// 模拟AJAX请求
loading.style.display = 'block';
// 使用setTimeout模拟网络延迟
setTimeout(() => {
let presetConfig = {};
// 预设配置
switch (presetName) {
case 'speech':
presetConfig = {
waveType: 'custom',
frequency: 8,
amplitude: 0.8,
harmonics: 15
};
break;
case 'music':
presetConfig = {
waveType: 'sine',
frequency: 12,
amplitude: 0.7,
harmonics: 10
};
break;
case 'noise':
presetConfig = {
waveType: 'sawtooth',
frequency: 3,
amplitude: 0.9,
harmonics: 20
};
break;
default:
presetConfig = {
waveType: 'sine',
frequency: 5,
amplitude: 0.5,
harmonics: 5
};
}
// 更新UI
waveTypeSelect.value = presetConfig.waveType;
frequencySlider.value = presetConfig.frequency;
frequencyValue.textContent = `${presetConfig.frequency} Hz`;
amplitudeSlider.value = presetConfig.amplitude;
amplitudeValue.textContent = presetConfig.amplitude;
harmonicsSlider.value = presetConfig.harmonics;
harmonicsValue.textContent = presetConfig.harmonics;
// 重新绘制
drawWave();
computeAndDrawFourier();
loading.style.display = 'none';
}, 500);
}
// 添加预设按钮
const presetContainer = document.createElement('div');
presetContainer.className = 'control-group';
presetContainer.innerHTML = `
<label>预设波形</label>
<div style="display: flex; gap: 10px;">
<button id="preset-speech">语音</button>
<button id="preset-music">音乐</button>
<button id="preset-noise">噪声</button>
</div>
`;
document.querySelector('.controls').appendChild(presetContainer);
document.getElementById('preset-speech').addEventListener('click', () => loadPresetWaveform('speech'));
document.getElementById('preset-music').addEventListener('click', () => loadPresetWaveform('music'));
document.getElementById('preset-noise').addEventListener('click', () => loadPresetWaveform('noise'));
</script>
</body>
</html>
仅供参考,我也不懂数学。
傅里叶变换是一种将时域信号转换为频域表示的数学工具。它可以将任何周期性的函数分解为简单正弦波的叠加。
离散傅里叶变换 (DFT):
X(k) = Σ[n=0 to N-1] x(n) · e-j2πkn/N
傅里叶级数:
f(t) = a0/2 + Σ[n=1 to ∞] [ancos(nωt) + bnsin(nωt)]
在这个演示中,您可以通过控制面板选择不同的波形,并观察其傅里叶变换结果。傅里叶变换让我们能够看到波形中包含的各种频率成分及其振幅和相位。