【微实验】圆周运动的正弦信号发生仿真及其衍生(一)

做这个小实验的起因,是在刷b站的时候,看到了一个视频,里面讲了点在圆周上匀速运动,它在直径上的投影与时间的函数是一个正弦函数。当这个圆变成六边形、五边形甚至五角星,按照同样的逻辑,产生的波形会有其他神奇的变化。

关于正弦波被圆周上运动的点发生,其理论基础与代码实践(Python)已有前辈总结整理,这里贴出两个链接,请诸位参考。

正弦波是一个旋转的圆在直线上的投影_正弦波与圆的关系-CSDN博客

正弦波与单位圆关系的可视化 包括源码_圆和正弦波-CSDN博客

在我的实验中,着重探索的是点在不同“形状”的图线上匀速运动对波形发生的影响,同时希望生成频谱,来观察不同形状对波形频域的调节作用。

为了定义准确,笔者使用极坐标系进行几何图线的定义,“匀速”所指的是关于极点的幅角匀速增加,使用动画显示点的运动和波形的生成过程,另外增设实时傅里叶变换显示频谱。

代码注释齐全,具体参数可结合实际情况调整。

代码如下:

% 极坐标系下点沿任意光滑闭合曲线以幅角匀速运动并生成频谱分析
% 清空工作区和命令窗口
clear; clc; close all;

% 参数设置
omega = 2*pi;             % 角速度 (rad/s),幅角随时间匀速增加
t_total = 4;              % 总仿真时间 (s),一个周期的时间
dt = 0.002;               % 时间步长
t = 0:dt:t_total;         % 时间向量
N = length(t);            % 原始采样点数

% 定义极坐标曲线函数 r = f(theta)
% 示例1:圆(r = 常数)
% r_fun = @(theta) 0.8 * ones(size(theta));

% 示例2:心形线
% r_fun = @(theta) 0.8 * (1 + cos(theta));

% 示例3:玫瑰线 (n=3)
% r_fun = @(theta) 0.8 * cos(3*theta);

% 示例4:椭圆
% a = 0.8; b = 0.5;
% r_fun = @(theta) (a*b)./sqrt((b*cos(theta)).^2 + (a*sin(theta)).^2);

% 示例5:复杂闭合曲线
r_fun = @(theta) 0.7 + 0.2*cos(5*theta)+0.4./sqrt((0.7*cos(theta)).^2 + (0.4*sin(theta)).^2);

% 计算幅角和极径
theta = omega * t;      % 幅角随时间匀速增加
r = r_fun(theta);       % 极径

% 转换为笛卡尔坐标
x = r .* cos(theta);
y = r .* sin(theta);

% 傅里叶变换参数 
fft_expansion = 8;            % FFT扩展倍数,越大分辨率越高
fft_size = 2^nextpow2(N) * fft_expansion;  % 大幅增加FFT尺寸
fs = 1/dt;                     % 采样频率

% 创建动画窗口
figure('Name','极坐标运动与实时频谱','Position',[100 100 1400 600]);

% 生成完整曲线用于绘图
theta_full = linspace(0, 2*pi, 1000);
r_full = r_fun(theta_full);

% 预设最大幅值,避免初始阶段错误
max_amp_initial = 0.5;
current_max_amp = max_amp_initial;

% 循环更新动画
for i = 1:length(t)
    clf;
    
    % 1. 极坐标系绘制
    ax1 = polaraxes('Position',[0.05 0.1 0.3 0.8]);
    hold(ax1,'on');
    polarplot(ax1, theta_full, r_full, 'b-', 'LineWidth', 2);
    polarplot(ax1, theta(i), r(i), 'ro', 'MarkerSize',10, 'LineWidth',2);
    polarplot(ax1, [theta(i), theta(i)], [0, r(i)], 'g--', 'LineWidth',1.5);
    title(ax1, '极坐标系下的运动');
    hold(ax1,'off');
    
    % 2. 时域波形
    ax2 = subplot(1,3,2);
    hold(ax2,'on');
    plot(ax2, t(1:i), y(1:i), 'b-', 'LineWidth',1.5);
    plot(ax2, t(i), y(i), 'ro', 'MarkerSize',10);
    title(ax2, '竖直方向时域波形');
    xlabel(ax2, '时间 (s)');
    ylabel(ax2, 'y坐标');
    xlim(ax2, [0 t_total]);
    ylim(ax2, [-1.2*max(r) 1.2*max(r)]);
    grid(ax2, 'on');
    hold(ax2,'off');
    
    % 3. 实时频谱图
    ax3 = subplot(1,3,3);
    hold(ax3,'on');
    
    % 对当前数据进行傅里叶变换
    y_current = y(1:i);
    y_padded = zeros(1, fft_size);
    y_padded(1:i) = y_current - mean(y_current);  % 去除直流分量
    
    Y = fft(y_padded);
    f = (0:fft_size-1)*(fs/fft_size);  % 频率轴
    amp = abs(Y) / i;                  % 幅度归一化
    
    % 处理初始阶段可能出现的幅值异常
    if i > 1 && ~isnan(max(amp)) && max(amp) > 0
        current_max_amp = max(current_max_amp, max(amp));
    end
    
    % 绘制正频率部分
    plot(ax3, f, amp, 'b-', 'LineWidth',1.2);
    xlim(ax3, [0 5]);% 限制频率范围
    ylim(ax3, [0 current_max_amp * 1.1]);
    title(ax3, '实时频谱图');
    xlabel(ax3, '频率 (Hz)');
    ylabel(ax3, '幅值');
    grid(ax3, 'on');
    hold(ax3,'off');
    
    % 控制动画速度
    drawnow;
    if i < length(t)
        pause(dt*5);
    end
end

%% 对最终波形进行空间延拓并计算无限周期傅里叶变换
% 1. 波形周期延拓(扩展10个周期以近似无限周期)
num_periods = 10000;  % 延拓的周期数
y_periodic = repmat(y, 1, num_periods);  % 周期延拓
t_periodic = 0:dt:(num_periods*t_total);  % 延拓后的时间向量

% 2. 计算延拓后信号的傅里叶变换
fft_size_large = 2^nextpow2(length(y_periodic)) * 2;  % 更大的FFT尺寸
y_padded_large = zeros(1, fft_size_large);
y_padded_large(1:length(y_periodic)) = y_periodic - mean(y_periodic);  % 去除直流分量

Y_periodic = fft(y_padded_large);
f_large = (0:fft_size_large-1)*(fs/fft_size_large);  % 频率轴
amp_periodic = abs(Y_periodic) / length(y_periodic);  % 幅度归一化

% 3. 创建新窗口显示最终频谱
figure('Name','周期延拓后的最终频谱','Position',[200 200 800 600]);
plot(f_large, amp_periodic, 'b-', 'LineWidth',1.5);
xlim([0 20]);  % 与实时频谱保持相同频率范围
ylim([0 max(amp_periodic)*1.1]);
title('无限周期延拓后的傅里叶频谱');
xlabel('频率 (Hz)');
ylabel('幅值');
grid on;
set(gca, 'FontSize', 12);

% 4. 标记主要频率分量
[peaks, locs] = findpeaks(amp_periodic, 'MinPeakHeight', max(amp_periodic)*0.1);
hold on;
plot(f_large(locs), peaks, 'ro', 'MarkerSize',8);
text(f_large(locs), peaks, arrayfun(@(x) sprintf('  %.2f Hz',x), f_large(locs), 'UniformOutput', false));
hold off;

代码可能有冗余,只是为了实现功能,没有关注时间复杂度,因此运行后,可能显示“正忙”比较长时间才能出现结果。 

你也可以自定义奇奇怪怪的极坐标下的函数来进行其他波形上的尝试。

因为周期是1s,因此1Hz的频谱最高,但会产生很多旁瓣。
当第二个波形周期趋于完整后,0-1Hz范围内出现了两个峰值,即2个周期后,基频也可以0.5Hz
当第三个周期的完整波形基本出现后,曾经0.5Hz处的一个瓣分类为两个瓣,
可以看到频域里0-1Hz中出现3个峰值,如果为了防止频谱泄露,可以适当加窗

关于窗函数的相关内容,详见笔者另一篇文章

【微实验】窗函数?-CSDN博客 

这个代码的后半部分是进行延拓后的傅里叶变换求得的频谱。

这个便是上面奇形怪状的波形对应的频谱​​​​​​
​代码中有标记主要频率分量,但因为没有延拓到无限周期,
1Hz附近的分量比其他分量幅值更高,所以高频标记有缺失

后面或许会更新优化代码,另外这个代码的功能,是已知极坐标图线生成波形与频谱,接下来的更新会对波形采样,变换出极坐标图线,这或许会给语音学音色的研究提供一个新的维度,同时也为符号域的人工智能乐谱生成与理解提供一个新的思路。

未完待续~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值