【微实验】使用MATLAB制作一张赛博古琴?

目录
零、总脚本:
一、核心功能:交互模块拆解
二、核心价值
一、初始化脚本:参数配置与启动界面
①废话不说,直接上代码
②代码模块拆解与详细解读
(一)基础参数定义模块:搭建古琴音准与演奏逻辑框架
1. 音高基础参数:定调与空弦音初始化
2. 演奏技法参数:定义左右手动作逻辑
3. 徽位参数:还原古琴 13 徽的物理特性
(二)可视化界面绘制模块:1:1 还原古琴视觉结构
1. 窗口初始化:适配屏幕尺寸的比例设计
2. 琴弦绘制:还原 7 根弦的位置与粗细差异
3. 徽位绘制:13 徽的位置与尺寸精准还原
4. 辅助设置:关闭坐标轴,专注视觉体验
(三)预留功能模块:为后续交互与音频合成铺路
二、演奏音符函数
①废话不说,先上代码
②核心输入参数与作用
核心逻辑:从参数到频率的计算
音频生成与播放流程
核心功能总结
三、拨弦发声函数
核心功能与输入输出
工作原理:从谐波叠加到真实音色
关键设计:贴近真实弦乐器的特性
三、其他函数
零、总脚本:
%%
run('ini.m');%初始化
%guqin_gui();
function guqin_gui()% 初始化状态:按音状态为 1,泛音状态为 0playing_mode = 1; mode_text = uicontrol('Style','text','String','按音','Position',[10,window_height-50,100,30],'FontSize',12,'ForegroundColor','white');arrow_left = uicontrol('Style','pushbutton','String','<','Position',[10,window_height-100,30,30],'Callback',@arrow_left_callback);arrow_right = uicontrol('Style','pushbutton','String','>','Position',[50,window_height-100,30,30],'Callback',@arrow_right_callback);pressed_mic = zeros(7,1); % 存储每根弦的按音徽位for i = 1:7pressed_mic(i) = 0; % 初始化为散音end% 为小键盘按键添加回调函数set(f, 'KeyPressFcn', @key_press_callback);function key_press_callback(hObject, event)% 获取按键的字符key = event.Keyswitch keycase '1'play_note(1);case '2'play_note(2);case '3'play_note(3);case '4'play_note(4);case '5'play_note(5);case '6'play_note(6);case '7'play_note(7);case '0'% 散音for i = 1:7pressed_mic(i) = 0;endupdate_display();case 'numpad1'pressed_mic(1) = 1;update_display();case 'numpad2'pressed_mic(2) = 2;update_display();case 'numpad3'pressed_mic(3) = 3;update_display();case 'numpad4'pressed_mic(4) = 4;update_display();case 'numpad5'pressed_mic(5) = 5;update_display();case 'numpad6'pressed_mic(6) = 6;update_display();case 'numpad7'pressed_mic(7) = 7;update_display();case 'numpad8'pressed_mic(1) = 8;update_display();case 'numpad9'pressed_mic(2) = 9;update_display();case 'add'pressed_mic(3) = 10;update_display();case 'divide'pressed_mic(4) = 11;update_display();case 'multiply'pressed_mic(5) = 12;update_display();case 'subtract'pressed_mic(6) = 13;update_display();% 'numlock' 'add'return' 'decimal' 'numpad0'case ' 'if playing_mode == 1playing_mode = 0;set(mode_text,'String','泛音');elseplaying_mode = 1;set(mode_text,'String','按音');endendendfunction arrow_left_callback(hObject, event)% 向左移动按音位置for i = 1:7if pressed_mic(i) > 0pressed_mic(i) = max(pressed_mic(i)-1,0);endendupdate_display();endfunction arrow_right_callback(hObject, event)% 向右移动按音位置for i = 1:7if playing_mode == 1 && pressed_mic(i) < 13pressed_mic(i) = min(pressed_mic(i)+1,13);endendupdate_display();endfunction update_display()for i = 1:7if playing_mode == 1if pressed_mic(i) == 0text(0.7, y_right_pos(i), '散音', 'FontSize', 10, 'Color', 'white');elsetext(0.7, y_right_pos(i), num2str(pressed_mic(i)), 'FontSize', 10, 'Color', 'white');endelsetext(0.7, y_right_pos(i), '泛音', 'FontSize', 10, 'Color', 'white');endendend% 音高与频率的变换函数
% freq=fr_of(note,octave)
% [note_str, octave,cent_offset] = nt_of(freq)end
这是古琴模拟系统的交互控制中枢,负责搭建可视化操作界面、绑定键盘 / 按钮事件,实现 “演奏模式切换、徽位选择、音位显示” 的完整交互逻辑,连接虚拟界面与发声功能。
核心功能:交互模块拆解
-
初始化交互状态
- 默认
playing_mode=1
(按音模式),界面显示 “按音” 文本; pressed_mic
数组存储 7 根弦的当前按音徽位(初始 0,即散音)。
- 默认
-
界面控件与事件绑定
- 模式切换:空格键切换 “按音 / 泛音”,同步更新界面文本;
- 徽位调整:左右箭头按钮整体增减所有弦的徽位(限制 1-13);
- 键盘控制:
- 主键盘 1-7:触发对应弦发声;
- 小键盘 1-9/+-*/:设置对应弦的徽位(1-13);
- 0 键:重置所有弦为散音。
-
状态显示更新
update_display
函数实时在界面标注每根弦的状态:按音时显示 “散音” 或徽位数字,泛音时显示 “泛音”,确保用户直观掌握当前演奏参数。
作为 “用户操作” 与 “play_note
发声函数” 的中间层,它将键盘 / 按钮输入转化为具体的 “弦号、演奏模式、徽位” 参数,实现 “所见即所控、所控即所发” 的虚拟演奏体验。
一、初始化脚本:参数配置与启动界面
①废话不说,直接上代码
以下算作初始化代码,创建脚本文件,命名为“ini.m”并存储。
%演奏方式:泛音、按音对应0和1
%徽位:1-13,0为不按
% 初始化音名和八度 %弦号:从低音到高音分别是1234567
%正宫调定弦
notes = {'C', 'D', 'F', 'G', 'A', 'C', 'D'};
octaves = [3, 3, 3, 3, 3, 4, 4];
% 徽位与琴弦右侧长度倍数关系的数据表
mic_positions = [1/8; 1/6; 1/5; 1/4; 1/3; 2/5; 1/2; 3/5; 2/3; 3/4; 4/5; 5/6; 7/8];
%徽位标准的大小
mic_size = [1/8 1/6 1/5 1/4 1/3 1/5 1/2 1/5 1/3 1/4 1/5 1/6 1/8];
% 七条琴弦粗细
line_widths = [3.2 3 2.6 2.3 2 1.6 1.3];
%指:(左手:取音法,右手:拨弦法
boxian=['挑','勾','抹','托','剔','劈','打','带','艹如一','撮','历','','','','','',''];
quyin=['大','食','中','名','跪','艹'];%散音,指左手不取音,默认空弦音
%另,上下进退复属于位置变更,
type=['按','泛'];%按音,泛音。延音?
marker_num=['一','二','三','四','五','六','七','八','九','十','十一','十二','十三'];
marker_tenth=['一','二','三','四','五','六','七','八','九','十'];
%初始七根弦的空弦音频率数组
base_freq=[0 0 0 0 0 0 0];
for i=1:7base_freq(i)=fr_of(notes{i},octaves(i));%计算填充
end% num_harmonics: 谐波数量% harmonic_amps: 谐波振幅比例,长度为 num_harmonics 的向量% harmonic_phases: 谐波相位,长度为 num_harmonics 的向量% decay_factor: 衰减系数,范围在 0 到 1 之间% duration: 声音持续时间% 生成时间向量 % 生成面板琴弦和徽位% 获取屏幕的大小screen_size = get(0,'ScreenSize');screen_width = screen_size(3); % 屏幕的宽度screen_height = screen_size(4); % 屏幕的高度% 计算窗口的高度,根据 125:22 的长宽比aspect_ratio = 125 / 22; window_height = screen_width / aspect_ratio;% 确保窗口的高度不超过屏幕的高度,如果超过,则根据屏幕高度计算窗口宽度if window_height > screen_heightwindow_height = screen_height;window_width = window_height * aspect_ratio;elsewindow_width = screen_width;end% 创建图形窗口,使其宽度充满屏幕或高度达到屏幕高度(取决于哪个先满足条件)f = figure('Position', [0, 0, window_width, window_height]);%宽:长=125:22% 设置窗口背景颜色为棕黑色set(f, 'Color', [0.2 0.1 0]); % 关闭菜单和工具栏set(f, 'MenuBar', 'none', 'ToolBar', 'none');% 绘制七条琴弦y_pos = linspace(0.9, 0.1, 7); % 七条琴弦的 y 位置x_start = 0.1;x_end = 0.9;width = 0.8; % 琴弦横向填充整个窗口height = width / aspect_ratio; y_pos = 0.5 + (y_pos - mean(y_pos)) * height / (max(y_pos) - min(y_pos));y_left_pos = 0.5 + (y_pos - 0.5) * 0.6;y_right_pos = 0.5 +(y_pos - 0.5) * 1.0;% 存储琴弦的线条句柄line_handles = zeros(1, 7);for i = 1:7line_handles(i) = plot([x_start, x_end], [y_left_pos(i), y_right_pos(i)], 'LineWidth', line_widths(i), 'Color', [1 1 0.8]); % 亮黄白色的琴弦text(0.9, y_right_pos(i), num2str(i), 'FontSize', 12, 'Color', 'white'); % 弦号白色hold on;end % 绘制徽位for j = 1:13x_mic = x_start + (x_end - x_start) * mic_positions(j);size=round(20.*mic_size(j));plot(x_mic, (y_right_pos(1)-y_left_pos(1))/(x_end-x_start)*(x_mic-y_left_pos(1))+y_pos(1)-0.5*(y_pos(2)-y_pos(1)), 'wo', 'MarkerSize',size, 'MarkerFaceColor', [240, 180, 50]./256);hold on;end %每根弦上,任意位置点的绘制%l代表有效长度,即该点到琴弦右侧的距离占总琴弦长度的比例
% for i = 1:7
% k=(y_left_pos(i)- y_right_pos(i))/(x_start-x_end);%琴弦的斜率
% for j = 1:13
% x_mic = x_start + (x_end - x_start) * mic_positions(j);
% l=mic_positions(14-j);
% y=-k*l*(x_end - x_start)+y_right_pos(i);
% plot(x_mic, y, 'ro', 'MarkerSize',line_widths(i)+2, 'MarkerFaceColor', 'r');
% hold on;
% end
% endaxis off; % 关闭坐标轴显示
这段 MATLAB 代码是一套古琴数字化模拟系统的基础模块,主要实现两大核心功能:一是通过计算初始化古琴 7 根琴弦的空弦音频率,定义泛音、按音等演奏参数;二是构建可视化的古琴虚拟界面,精准还原古琴的琴弦、徽位等物理结构,为后续的交互演奏(如点击徽位发声、模拟按弦动作)提供视觉载体。整体代码逻辑从 “音频参数定义” 到 “视觉界面绘制” 层层递进,是古琴数字化仿真项目的核心奠基代码。
②代码模块拆解与详细解读
(一)基础参数定义模块:搭建古琴音准与演奏逻辑框架
这部分代码是整个系统的 “数据字典”,从音高、演奏技法、徽位尺寸等维度,为古琴模拟建立标准化参数体系,具体分为 6 类核心参数:
1. 音高基础参数:定调与空弦音初始化
% 正宫调定弦(古琴经典调式,对应现代音高)
notes = {'C', 'D', 'F', 'G', 'A', 'C', 'D'}; % 7根弦的音名(从低音弦1到高音弦7)
octaves = [3, 3, 3, 3, 3, 4, 4]; % 对应八度(弦1-5为小字三组,弦6-7为小字四组)
% 初始化空弦音频率数组(调用fr_of函数计算音名对应的频率,如C3约130.81Hz)
base_freq=[0 0 0 0 0 0 0];
for i=1:7base_freq(i)=fr_of(notes{i},octaves(i));% 循环填充7根弦的空弦频率
end
- 关键作用:确定古琴的 “基准音高”,
base_freq
数组最终存储的是每根空弦音的具体频率(如弦 1 为 C3,频率约 130.81Hz),是后续计算泛音、按音频率的基础。 - 依赖函数:
fr_of
是自定义的 “音名转频率” 函数,核心是根据十二平均律,将 “音名 + 八度” 转化为具体的赫兹值(Hz)。
2. 演奏技法参数:定义左右手动作逻辑
% 右手拨弦法(共12种经典技法,空值为预留扩展位)
boxian=['挑','勾','抹','托','剔','劈','打','带','艹如一','撮','历','','','','','',''];
% 左手取音法(含散音逻辑:左手不取音时默认空弦音)
quyin=['大','食','中','名','跪','艹'];% “大/食/中/名/跪”对应不同手指按弦,“艹”为散音标识
% 演奏类型(核心两类:按音、泛音;延音为预留功能)
type=['按','泛'];
- 设计逻辑:为后续 “交互演奏” 预留接口 —— 比如用户点击界面时,系统可根据选择的 “右手技法(如挑)” 和 “左手取音法(如食指按弦)”,调用不同的音频合成逻辑(如按音有按压阻尼感,泛音更清亮)。
3. 徽位参数:还原古琴 13 徽的物理特性
% 徽位与琴弦右侧长度倍数关系(核心物理参数)
mic_positions = [1/8; 1/6; 1/5; 1/4; 1/3; 2/5; 1/2; 3/5; 2/3; 3/4; 4/5; 5/6; 7/8];
% 徽位视觉大小(13徽尺寸随位置变化,符合真实古琴徽位比例)
mic_size = [1/8 1/6 1/5 1/4 1/3 1/5 1/2 1/5 1/3 1/4 1/5 1/6 1/8];
% 徽位标识文字(1-10用“一-十”,11-13用“十一-十三”)
marker_num=['一','二','三','四','五','六','七','八','九','十','十一','十二','十三'];
marker_tenth=['一','二','三','四','五','六','七','八','九','十'];
- 核心原理:
mic_positions
是关键 —— 古琴 13 徽的位置并非均匀分布,而是根据 “弦长比例” 确定(如 7 徽在弦长 1/2 处,是泛音的核心位置),该数组存储的是 “徽位到琴弦右侧的距离占总弦长的比例”,后续绘制徽位和计算泛音频率都依赖此参数。
(二)可视化界面绘制模块:1:1 还原古琴视觉结构
这部分代码是 “虚拟古琴” 的视觉载体,通过计算屏幕尺寸、琴弦位置、徽位坐标,在 MATLAB 图形窗口中精准绘制古琴外观,具体分为 4 个核心步骤:
1. 窗口初始化:适配屏幕尺寸的比例设计
% 获取屏幕分辨率,计算窗口大小(遵循古琴长宽比125:22)
screen_size = get(0,'ScreenSize'); % 获取屏幕尺寸:[左,下,宽,高]
screen_width = screen_size(3); % 屏幕宽度
screen_height = screen_size(4); % 屏幕高度
aspect_ratio = 125 / 22; % 古琴标准长宽比(长:宽=125:22)
% 计算窗口高度(优先宽充满屏幕,若高度超屏幕则按高度适配)
window_height = screen_width / aspect_ratio;
if window_height > screen_heightwindow_height = screen_height;window_width = window_height * aspect_ratio;
elsewindow_width = screen_width;
end
% 创建图形窗口(无菜单/工具栏,背景为棕黑色,模拟古琴木胎颜色)
f = figure('Position', [0, 0, window_width, window_height]);
set(f, 'Color', [0.2 0.1 0]); % 背景色:棕黑色(RGB值)
set(f, 'MenuBar', 'none', 'ToolBar', 'none'); % 关闭菜单和工具栏,专注视觉
- 设计细节:严格遵循古琴的真实长宽比(125:22),同时适配不同屏幕分辨率,避免界面拉伸变形;棕黑色背景模拟古琴的木胎质感,提升视觉沉浸感。
2. 琴弦绘制:还原 7 根弦的位置与粗细差异
% 计算7根弦的Y轴位置(从高音弦到低音弦,纵向均匀分布)
y_pos = linspace(0.9, 0.1, 7); % 初始纵向比例(0.9为高音弦,0.1为低音弦)
x_start = 0.1; % 琴弦左端起点(相对窗口宽度的比例,0.1即10%处)
x_end = 0.9; % 琴弦右端终点(90%处)
width = 0.8; % 琴弦横向长度(占窗口宽度80%)
height = width / aspect_ratio; % 琴弦纵向适配高度
% 调整琴弦Y轴位置,确保居中且适配窗口高度
y_pos = 0.5 + (y_pos - mean(y_pos)) * height / (max(y_pos) - min(y_pos));
y_left_pos = 0.5 + (y_pos - 0.5) * 0.6; % 琴弦左端Y坐标(略微内收,模拟琴首)
y_right_pos = 0.5 +(y_pos - 0.5) * 1.0; % 琴弦右端Y坐标(模拟琴尾)% 循环绘制7根弦(存储句柄,便于后续交互)
line_handles = zeros(1, 7);
for i = 1:7% 绘制琴弦:亮黄白色(模拟蚕丝弦颜色),粗细随弦号变化(低音弦粗,高音弦细)line_handles(i) = plot([x_start, x_end], [y_left_pos(i), y_right_pos(i)], ...'LineWidth', line_widths(i), 'Color', [1 1 0.8]);% 在琴弦右端标注弦号(白色文字,1为低音弦,7为高音弦)text(0.9, y_right_pos(i), num2str(i), 'FontSize', 12, 'Color', 'white');hold on; % 保持绘图窗口,继续绘制后续元素
end
- 关键细节:
- 琴弦粗细:
line_widths = [3.2 3 2.6 2.3 2 1.6 1.3]
,严格遵循真实古琴 “低音弦粗、高音弦细” 的规律(弦 1 最粗 3.2pt,弦 7 最细 1.3pt); - 位置设计:琴弦左端(琴首)略微内收(
y_left_pos
乘以 0.6),模拟真实古琴 “琴首窄、琴尾宽” 的形态,视觉更逼真。
- 琴弦粗细:
3. 徽位绘制:13 徽的位置与尺寸精准还原
% 循环绘制13个徽位(从1徽到13徽)
for j = 1:13% 计算徽位X坐标(根据mic_positions的弦长比例,映射到窗口宽度)x_mic = x_start + (x_end - x_start) * mic_positions(j);% 计算徽位视觉大小(按mic_size比例缩放,默认基础20pt)size=round(20.*mic_size(j));% 绘制徽位:白色外圈+米黄色填充(模拟螺钿徽位,真实古琴常用材质)plot(x_mic, (y_right_pos(1)-y_left_pos(1))/(x_end-x_start)*(x_mic-y_left_pos(1))+y_pos(1)-0.5*(y_pos(2)-y_pos(1)), ...'wo', 'MarkerSize',size, 'MarkerFaceColor', [240, 180, 50]./256);hold on;
end
- 核心逻辑:
- 徽位位置:通过
x_mic = x_start + (x_end - x_start) * mic_positions(j)
,将 “弦长比例” 转化为窗口中的实际 X 坐标,确保 13 徽的位置与真实古琴完全一致(如 7 徽在 X 轴中间,对应弦长 1/2 处); - 徽位外观:白色外圈(
'wo'
即白色圆圈)+ 米黄色填充(RGB [240,180,50]/256),模拟真实古琴的 “螺钿徽位” 质感,同时尺寸随mic_size
变化(如 7 徽最大,1、13 徽最小),符合真实古琴的徽位大小规律。
- 徽位位置:通过
4. 辅助设置:关闭坐标轴,专注视觉体验
axis off; % 关闭坐标轴显示,避免网格、刻度干扰古琴视觉效果
- 作用:隐藏 MATLAB 默认的坐标轴、刻度和网格,让虚拟古琴界面更简洁,视觉上更接近真实乐器。
(三)预留功能模块:为后续交互与音频合成铺路
代码中注释掉的部分(如下)和未完善的音频参数(如谐波、衰减系数),是为后续功能扩展预留的接口。
- 扩展方向:
- 交互功能:解开注释后,可实现 “点击徽位显示红色按点”,模拟左手按弦动作;
- 音频合成:结合
base_freq
和徽位比例(如泛音频率 = 空弦频率 ×(1 / 徽位比例)),可计算出任意徽位的音高,再通过谐波参数合成对应的古琴音色。
二、演奏音符函数
①废话不说,先上代码
function note=play_note(string_num, play_style, fret_num)% string_num:弦号,从低音到高音分别是 1234567,弦号(string_num)为索引,调用 base_freq(string_num)即获取对应弦的基音频率% play_style:演奏方式,泛音、按音对应 1 和 2,如果是 1,则音色空灵,随机生成 num_harmonics: 谐波数量为 2-4;如果是 2 为按音,生成谐波数量 5-7% fret_num:徽位,1-13,假设按 fret_num 徽,% ①泛音模式下,弦的等分段发音,发音频率为发音弦基频的为 mic_size(string_num)^(-1)倍,% ②按音模式下,有效弦长发音,即发音弦基频的为 mic_positions(string_num)^(-1)倍,% 0为不按,发音为基频,以此获得 out_freq base_freq=[87.30705785825, 97.99885899543, 109.99943633644, 130.8127826503, 146.8323839587, 164.8137784564, 195.9977179909];%base_freq=[130.812782650299,146.832383958704,174.614115716502,195.997717990875,220,261.625565300599,293.664767917407]./2;% 徽位与琴弦右侧长度倍数关系的数据表mic_positions = [1/8; 1/6; 1/5; 1/4; 1/3; 2/5; 1/2; 3/5; 2/3; 3/4; 4/5; 5/6; 7/8];%徽位标准的大小mic_size = [1/8 1/6 1/5 1/4 1/3 1/5 1/2 1/5 1/3 1/4 1/5 1/6 1/8 ];% 初始化 fundamental_freqfundamental_freq = base_freq(string_num); % 根据演奏方式和徽位计算实际基频%有效弦长fl=floor(fret_num);if fl==0l=(fret_num-fl)/8;elseif fl==13l=7/8+(fret_num-fl)/8;elsel=mic_positions(fl)+(fret_num-fl)*(mic_positions(fl+1)-mic_positions(fl));endif play_style == 2%按音 fundamental_freq = fundamental_freq / l;num_harmonics = randi([2, 4]);elsefundamental_freq = fundamental_freq / l;num_harmonics = randi([5, 7]);end draw_point(string_num,l);note=nt_of(fundamental_freq);harmonic_amps = 1./(1:num_harmonics); % 谐波振幅比例,例如 1/nharmonic_phases = 2*pi*rand(1, num_harmonics); % 谐波相位,随机生成decay_factor = -1.5.*sqrt((1:num_harmonics)); % 衰减系数duration = 5; % 持续时间,单位为秒Fs = 44100; % 采样频率 % 调用 string_pluck 函数生成弦乐拨弦的声音[y, ~] = string_pluck(fundamental_freq, num_harmonics, harmonic_amps, harmonic_phases, decay_factor, duration, Fs);% 播放生成的声音player = audioplayer(y, Fs);% 播放音频play(player);% 等待 2 秒后停止pause(1);stop(player);
end% % 绘制生成的音频信号的时域波形
% figure;
% plot(t, y);
% title('弦乐拨弦的时域波形');
% xlabel('时间 (s)');
% ylabel('幅度');
% grid on;
该函数是古琴数字化模拟系统的 “发声控制核心”,作用是根据用户输入的 “弦号、演奏方式、徽位”,计算对应音高并生成、播放古琴音色,本质是 “参数映射→频率计算→音频合成→播放输出” 的完整流程。
②核心输入参数与作用
函数接收 3 个关键参数,直接决定发声的 “弦、法、位”,对应真实古琴演奏的核心要素:
- string_num(弦号):1-7(1 为低音弦,7 为高音弦),用于调用预设的
base_freq
数组,获取对应弦的空弦基频(如弦 1 基频约 87.31Hz); - play_style(演奏方式):1 = 泛音、2 = 按音,决定音色特点(泛音空灵、按音厚重)与谐波数量(泛音 2-4 个谐波,按音 5-7 个谐波);
- fret_num(徽位):0(空弦)或 1-13(具体徽位),用于计算 “有效弦长比例”,最终确定实际发声频率。
核心逻辑:从参数到频率的计算
函数最关键的步骤是 “根据徽位和演奏方式,把空弦基频转化为实际发声频率”,核心是 “有效弦长比例” 的计算:
- 徽位插值计算(l 值):
当fret_num
不是整数(如 1.5,代表 1 徽与 2 徽之间)时,通过 “线性插值” 计算精准的 “有效弦长比例 l”—— 空弦(fret_num=0)时 l=1/8,13 徽以上时 l=7/8 + 插值,1-13 徽间则基于mic_positions
(徽位弦长比例表)计算,确保任意位置的音高都精准; - 频率换算:
无论是泛音还是按音,均通过 “空弦基频 ÷ 有效弦长比例 l” 得到实际发声频率(原理:弦长越短,频率越高,符合物理规律),仅通过 “谐波数量” 区分两种演奏方式的音色(泛音谐波少更空灵,按音谐波多更厚重)。
音频生成与播放流程
频率确定后,函数完成 “音色合成→播放” 的闭环:
- 音色参数配置:
自动生成谐波相关参数 —— 振幅按 “1/n” 规律衰减(n 为谐波序号,符合真实弦振规律)、相位随机(避免音色单调)、衰减系数随谐波序号增大而减小(高频谐波先衰减,模拟古琴余音特点); - 音频合成与播放:
调用string_pluck
函数(弦振音频合成函数),基于上述参数生成时域音频信号;再通过audioplayer
创建播放器,播放音频(默认持续 1 秒后停止,避免余音过长干扰后续操作); - 视觉联动:
调用draw_point
函数,在虚拟古琴界面上标记当前演奏的 “弦 - 徽位置”(如弦 3 的 5 徽处画点),实现 “听觉 + 视觉” 的同步反馈。
核心功能总结
该函数的价值在于 “把抽象的演奏指令(弦、法、位)转化为可听的古琴声音”,既还原了真实古琴的物理发声规律(弦长与频率的关系、泛 / 按音的音色差异),又通过参数化设计实现了 “任意弦、任意徽、任意演奏方式” 的灵活发声,是连接 “虚拟界面交互” 与 “音频输出” 的关键桥梁。
三、拨弦发声函数
function [y, t] = string_pluck(fundamental_freq, num_harmonics, harmonic_amps, harmonic_phases, decay_factor, duration, Fs)% 输入参数:% fundamental_freq: 基频% num_harmonics: 谐波数量% harmonic_amps: 谐波振幅比例,长度为 num_harmonics 的向量% harmonic_phases: 谐波相位,长度为 num_harmonics 的向量% decay_factor: 衰减系数,范围在 0 到 1 之间% duration: 声音持续时间% Fs: 采样频率 % 生成时间向量t = 0:1/Fs:duration-1/Fs;y = zeros(size(t));% 生成弦乐拨弦的音色for k = 1:num_harmonicsfreq = k * fundamental_freq;amp = harmonic_amps(k);phase = harmonic_phases(k);decay=decay_factor(k);% 生成谐波分量y_harmonic = amp * sin(2*pi*freq*t + phase);% 应用衰减y_harmonic = y_harmonic.* exp(decay*t);% 叠加谐波分量y = y + y_harmonic;end
end
string_pluck
函数是古琴(或其他弦乐器)数字化模拟系统中的 “音色合成引擎”,其核心功能是根据输入的声学参数,生成具有真实弦振特性的音频信号。它通过数学建模的方式,模拟弦乐器被拨奏后的振动规律,最终输出可播放的时域音频数据。
核心功能与输入输出
- 核心作用:将抽象的声学参数(基频、谐波、衰减等)转化为具体的音频信号,模拟弦乐器拨弦发声的物理过程。
- 输入参数:包含 7 个关键参数,全面定义了声音的基本特征:
fundamental_freq
:基频(决定音高的核心参数)num_harmonics
:谐波数量(决定音色丰富度)harmonic_amps
:谐波振幅比例(决定音色特点)harmonic_phases
:谐波相位(影响音色细节)decay_factor
:衰减系数(决定声音的持续特性)duration
:声音持续时间(单位:秒)Fs
:采样频率(标准为 44100Hz,即音频的 “分辨率”)
- 输出结果:
y
:生成的音频信号(时域波形数据)t
:对应的时间向量(与音频信号同步的时间轴)
工作原理:从谐波叠加到真实音色
函数采用 “多谐波叠加” 的经典弦乐合成方法,模拟真实弦振动的物理特性,核心步骤分为 3 步:
-
时间向量生成
首先创建与音频时长匹配的时间轴t
,计算公式为t = 0:1/Fs:duration-1/Fs
,确保每个采样点都对应精确的时间位置(例如 44100Hz 采样率下,每个点间隔约 0.0000227 秒)。 -
谐波分量计算
循环生成每个谐波的振动波形(共num_harmonics
个):- 每个谐波的频率为 “基频的整数倍”(
freq = k * fundamental_freq
),符合弦振动的物理规律(弦振动会同时产生基频和整数倍谐波); - 每个谐波的振幅由
harmonic_amps(k)
决定(通常高频谐波振幅更低); - 每个谐波的初始相位由
harmonic_phases(k)
控制(随机相位可避免不同谐波间的固定干涉,使音色更自然)。
- 每个谐波的频率为 “基频的整数倍”(
-
衰减与叠加
- 对每个谐波分量应用衰减:
y_harmonic = y_harmonic .* exp(decay * t)
,模拟真实弦振动的能量损耗(高频谐波通常衰减更快); - 将所有谐波分量叠加:
y = y + y_harmonic
,最终形成完整的弦乐音色波形。
- 对每个谐波分量应用衰减:
关键设计:贴近真实弦乐器的特性
- 谐波结构:通过整数倍谐波模拟弦的振动模式,符合弦乐器 “基频 + 泛音” 的频谱特点;
- 振幅衰减:使用指数衰减函数
exp(decay * t)
模拟弦振动的能量耗散,使声音有 “起音 - 持续 - 衰减” 的自然过程; - 参数灵活性:通过调整谐波数量、振幅比例和衰减系数,可模拟不同弦乐器(古琴、吉他、小提琴等)或同一种乐器的不同演奏技法(如古琴的泛音、按音)。
string_pluck
函数是连接 “声学参数” 与 “可听声音” 的关键模块,它通过数学建模的方式,精准复现了弦乐器发声的物理原理。在古琴模拟系统中,它接收play_note
函数传递的音高和演奏方式参数,最终生成符合古琴音色特点的音频信号,为虚拟演奏提供了核心的声音输出能力。
三、其他函数
function draw_point(i, l)% 输入参数:% i: 琴弦弦号,从 1 到 7 的整数% l: 有效弦长,表示按音位置% 假设 x_start 和 x_end 是徽位图的起始和结束 x 坐标,y_left_pos 和 y_right_pos 是左右位置数组,line_widths 是线宽数组x_start = 0.1; x_end = 0.9;y_left_pos = [0.54224,0.52816,0.51408,0.50000,0.48592,0.47184,0.45776];y_right_pos = [[0.57040,0.54693,0.52346,0.50000,0.47653,0.45306,0.42960]];line_widths = [3.2 3 2.6 2.3 2 1.6 1.3];
% % 生成一个唯一的标记用于删除
% marker_id = ['marker_', num2str(i), '_', num2str(l)]
% % 检查是否存在之前绘制的标记,如果存在则删除
% if ishandle(findobj('Tag', marker_id))
% delete(findobj('Tag', marker_id));
% end % 生成一个唯一的标记用于删除marker_id = 'marker_'; % 检查是否存在之前绘制的标记,存在则删delete(findobj('Tag', marker_id)); % 绘制徽位图上的点h = plot(x_end - l*(x_end - x_start), (y_left_pos(i)- y_right_pos(i))*l+y_right_pos(i), 'ro', 'MarkerSize',line_widths(i)+2, 'MarkerFaceColor', 'r');% 为当前绘制的点设置标记set(h, 'Tag', marker_id);hold on;
end
% 读取 new.wav 文件
[audio, Fs] = audioread('new.wav');% 计算时频图
window = hamming(256); % 使用汉明窗,窗口长度为 256
noverlap = 128; % 窗口重叠长度为 128
nfft = 512; % FFT 点数为 512% 使用 spectrogram 函数计算并绘制时频图
spectrogram(audio, window, noverlap, nfft, Fs, 'yaxis');% 添加标题和轴标签
title('Time-Frequency Spectrogram of new.wav');
xlabel('Time (s)');
ylabel('Frequency (Hz)');% 添加颜色条
colorbar;
function freq = fr_of(note, octave)% 十二平均律中相邻半音的频率比semitone_ratio = 2^(1/12);% 以 A4 = 440Hz 为基准base_A4 = 440;% 计算 A 在不同八度的频率base_A = base_A4 * 2^(octave - 4);switch notecase 'C'freq = base_A / (semitone_ratio)^9;case 'Db' % 将 bD 等同于 #Ccase '#C'freq = base_A / (semitone_ratio)^8;case 'D'freq = base_A / (semitone_ratio)^7;case 'Eb' % 将 bE 等同于 #Dcase '#D'freq = base_A / (semitone_ratio)^6;case 'E'freq = base_A / (semitone_ratio)^5;case 'F'freq = base_A / (semitone_ratio)^4;case 'Gb' % 将 bG 等同于 #Fcase '#F'freq = base_A / (semitone_ratio)^3;case 'G'freq = base_A / (semitone_ratio)^2;case 'Ab' % 将 bA 等同于 #Gcase '#G'freq = base_A / (semitone_ratio)^1;case 'A'freq = base_A;case 'Bb' % 将 bB 等同于 #Acase '#A'freq = base_A * semitone_ratio;case 'B'freq = base_A * (semitone_ratio)^2;otherwiseerror('Invalid note name');endend
function [marker, tenth] = l_to_marker(l)% l: 等效弦长 % 徽位位置(距离琴弦右侧的有效长度为)% 徽位与琴弦右侧长度倍数关系的数据表mic_positions = [1/8; 1/6; 1/5; 1/4; 1/3; 2/5; 1/2; 3/5; 2/3; 3/4; 4/5; 5/6; 7/8];marker_num=['一','二','三','四','五','六','七','八','九','十','十一','十二','十三'];marker_tenth=['一','二','三','四','五','六','七','八','九']; % 找到徽位index = find(l < mic_positions, 1, 'first')-1;if isempty(index)index=13;%只有13徽读取不到。marker='十三';tenth_index = round((l - mic_positions(index))* 80);elseif index == 0marker='零';% 对于一徽之内,特殊处理,分为十等份tenth_index= round(l*80);else marker=marker_num(index);tenth_index = round((l - mic_positions(index)) / (mic_positions(index+1) - mic_positions(index)) * 10);endif tenth_index == 0tenth = '整';elseif tenth_index ==10tenth = '整';marker=marker_num(index+1);elsetenth = marker_tenth(tenth_index);end
end
function [noteout, note_str, octave, cent_offset] = nt_of(freq)% 十二平均律中相邻半音的频率比base_A4 = 440;octave = 0;% 将频率调整到 A4 所在的八度范围while freq < base_A4 / 2freq = freq * 2;octave = octave - 1;endwhile freq >= base_A4 * 2freq = freq / 2;octave = octave + 1;end% 计算相对于 A4 的音分数cents = 1200 * log2(freq / base_A4);% 计算半音数semitones = round(cents / 100); % 计算音分偏移量cent_offset = mod(cents, 100);if cent_offset >= 50cent_offset = cent_offset - 100;endif cent_offset <= -50cent_offset = cent_offset + 100;end% 处理八度变化if semitones >= 12octave = octave + floor(semitones / 12);semitones = mod(semitones, 12);elseif semitones < 0octave = octave + floor(semitones / 12);semitones = mod(semitones, 12);endswitch semitonescase 0note = 'A';case 1note = 'A#';case 2note = 'B';case 3note = 'C';case 4note = 'C#';case 5note = 'D';case 6note = 'D#';case 7note = 'E';case 8note = 'F';case 9note = 'F#';case 10note = 'G';case 11note = 'G#';endnote_str = note;octave = octave + 4; % 将八度调整到实际的音乐八度范围if cent_offset >= 0noteout = [note_str, num2str(octave), '+', num2str(abs(cent_offset))];elsenoteout = [note_str, num2str(octave), '-', num2str(abs(cent_offset))];endend
function [note_out, badu]=num2note(num,octave,key)
%key调:输入1234567
% CDEFGAB
% 1.5是#C或bD;2.5是#D或bC;4.5是#F或bG;5.5是#G或bA;6.5是#A或bB
%唱名到音名的转换
if (key>6.4 || key<2.1)badu=octave;%和C调一样
elsebadu=octave+1;
end
switch numcase 1num_offset=0;%Ccase 1.5num_offset=1;%C#case 2num_offset=2;%Dcase 2.5num_offset=3;%D#case 3num_offset=4;case 4num_offset=5;case 4.5num_offset=6;case 5num_offset=7;case 5.5num_offset=8;case 6num_offset=9;case 6.5num_offset=10;case 7num_offset=11;
end
num=num_offset;
switch keycase 1offset=0;%Ccase 1.5offset=1;%C#case 2offset=2;%Dcase 2.5offset=3;%D#case 3offset=4;case 4offset=5;case 4.5offset=6;case 5offset=7;case 5.5offset=8;case 6offset=9;case 6.5offset=10;case 7offset=11;
end
key=offset;
pitch=key+num;
badu_add=floor(pitch/12);
seminote=pitch - badu_add;switch seminote+3case 12note = 'A';case 13note = 'A#';case 14note = 'B';case 3note = 'C';case 4note = 'C#';case 5note = 'D';case 6note = 'D#';case 7note = 'E';case 8note = 'F';case 9note = 'F#';case 10note = 'G';case 11note = 'G#';otherwiseerror('Invalid key name'); endnote_out=note;
end
将所有函数和脚本文件按照对应名称存好放置于同一文件夹即可运行,快来试试你的赛博乐器吧~