《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)

开篇:当MATLAB遇见音乐 - 一场数字与艺术的浪漫邂逅
想象一下,你正坐在一台古老的钢琴前,指尖在黑白琴键上舞动,悠扬的旋律在空气中流淌...等等!谁说这美妙的场景一定要发生在真实的钢琴上?今天,我要带你用MATLAB这个"理工科神器"打造一个独一无二的数字电子琴,让你体验工程师与音乐家的双重身份!
你可能不知道,MATLAB除了能做复杂的数学运算和数据分析,还是一个隐藏的"音乐制作大师"。从《星球大战》的光剑声到《盗梦空间》的BRAAAM效果,现代电影中许多标志性声音都源自数字合成技术。而今天,我们将从零开始,用代码谱写属于你的电子乐章!
第一章:声音的魔法 - 解密数字音频的奥秘
1.1 声音是如何被"数字化"的?
让我们做个有趣的小实验:
matlab
% 听听不同采样率下的效果对比
Fs_list = [8000, 11025, 22050, 44100];
for Fs = Fs_list
t = 0:1/Fs:1;
y = sin(2*pi*440*t);
sound(y, Fs);
pause(1.5);
disp(['当前采样率:', num2str(Fs), 'Hz - 听出区别了吗?']);
end
运行这段代码,你会亲耳见证从"电话音质"到"CD音质"的奇妙转变!这就是著名的"奈奎斯特采样定理"在发挥作用------要完美重现一个频率,采样率至少需要它的两倍。
1.2 波形实验室:四种基础音色的听觉派对
我们准备了四种基础波形的"声音鸡尾酒",来场听觉实验吧!
matlab
% 波形对比演示
freq = 440; % A4标准音
t = 0:1/44100:1;
waves = {'sin', 'square', 'sawtooth', 'triangle'};
names = {'正弦波(纯净如笛)', '方波(8-bit复古风)', '锯齿波(科幻感十足)', '三角波(柔和梦幻)'};
figure('Position', [100,100,800,600]);
for i = 1:4
subplot(2,2,i);
switch waves{i}
case 'sin'
y = sin(2*pi*freq*t);
case 'square'
y = square(2*pi*freq*t);
case 'sawtooth'
y = sawtooth(2*pi*freq*t);
case 'triangle'
y = sawtooth(2*pi*freq*t, 0.5);
end
plot(t(1:500), y(1:500));
title([names{i}, ' - ', waves{i}, ' wave']);
xlabel('时间(s)'); ylabel('振幅');
sound(y, 44100);
pause(1.5);
end
看到那些奇妙的波形了吗?正是这些几何形状的差异,造就了千变万化的音色!方波的直角转折带来电子游戏般的复古感,而锯齿波的斜线上升则让人联想到科幻电影中的激光枪。
第二章:打造你的数字钢琴工作室
2.1 钢琴键盘的"基因密码"
你知道吗?钢琴上每个键的频率都遵循着一个优雅的数学规律:
matlab
% 钢琴键频率计算器
pianoKeys = 1:88; % 标准钢琴88键
frequencies = 440 * 2.^((pianoKeys-49)/12);
% 让我们找出中央C(C4)的频率
c4_freq = frequencies(40);
disp(['中央C的频率是:', num2str(c4_freq), ' Hz']);
% 可视化频率分布
figure;
semilogy(pianoKeys, frequencies);
xlabel('琴键编号'); ylabel('频率(Hz)');
title('钢琴键盘的频率分布(对数坐标)');
grid on;
这个简单的指数关系,正是音乐与数学完美结合的证明!十二平均律让每个半音都精确地相差2^(1/12)倍,这种数学美感令人惊叹。
2.2 给声音穿上"时尚外衣" - ADSR包络设计
没有包络的声音就像没有调味的美食------单调乏味!让我们设计一个智能"声音造型师":
matlab
function y = applyEnvelope(y, Fs)
% 参数创意配置:
attackCurve = 'exp'; % 尝试改为 'lin' 或 'log' 感受不同效果
sustainVibrato = true; % 是否添加颤音效果
len = length(y);
envelope = ones(1, len);
% 动态调整各阶段比例
attackTime = 0.05 + rand()*0.05; % 随机变化增加自然感
decayTime = 0.1 + rand()*0.1;
releaseTime = 0.2 + rand()*0.1;
attackSamples = round(attackTime * Fs);
decaySamples = round(decayTime * Fs);
releaseSamples = round(releaseTime * Fs);
% 确保不超过信号长度
total = attackSamples + decaySamples + releaseSamples;
if total > len
ratios = [attackTime, decayTime, releaseTime];
ratios = ratios/sum(ratios) * 0.9; % 保留10%给sustain
attackSamples = round(ratios(1)*len);
decaySamples = round(ratios(2)*len);
releaseSamples = round(ratios(3)*len);
end
sustainSamples = len - attackSamples - decaySamples - releaseSamples;
% 起音阶段
switch attackCurve
case 'lin'
envelope(1:attackSamples) = linspace(0, 1, attackSamples);
case 'exp'
envelope(1:attackSamples) = exp(linspace(log(0.01), log(1), attackSamples);
case 'log'
envelope(1:attackSamples) = log(linspace(exp(0.01), exp(1), attackSamples));
end
% 衰减阶段
sustainLevel = 0.6 + 0.2*rand(); % 随机变化
envelope(attackSamples+1:attackSamples+decaySamples) = ...
linspace(1, sustainLevel, decaySamples);
% 持续阶段(添加颤音)
if sustainVibrato
vibFreq = 5 + 2*rand(); % 颤音频率
vibDepth = 0.05 + 0.05*rand(); % 颤音深度
vib = vibDepth * sin(2*pi*vibFreq*(0:sustainSamples-1)/Fs);
envelope(attackSamples+decaySamples+1:attackSamples+decaySamples+sustainSamples) = ...
sustainLevel * (1 + vib);
else
envelope(attackSamples+decaySamples+1:attackSamples+decaySamples+sustainSamples) = ...
sustainLevel;
end
% 释音阶段
envelope(end-releaseSamples+1:end) = ...
linspace(envelope(end-releaseSamples), 0, releaseSamples);
% 应用包络
y = y .* envelope;
% 可视化包络
if false % 设为true可查看包络形状
figure;
plot(envelope);
title('动态ADSR包络');
xlabel('采样点'); ylabel('振幅');
legend(['Attack: ',num2str(attackTime),'s Decay: ',num2str(decayTime),...
's Sustain: ',num2str(sustainLevel),' Release: ',num2str(releaseTime),'s']);
end
end
这个增强版包络加入了随机变化和颤音效果,让你的电子琴音色更加生动自然,就像真实的乐器演奏一样!
第三章:音乐创作实战 - 用你的电子琴谱写第一首作品
5.1 《欢乐颂》演奏指南
让我们用代码演奏这首经典旋律:
matlab
% 欢乐颂简谱 (以C大调)
notes = {'E4', 'F4', 'G4', 'G4', 'F4', 'E4', 'D4', 'C4', 'C4', 'D4', ...
'E4', 'E4', 'D4', 'D4', 'E4', 'F4', 'G4', 'G4', 'F4', 'E4', ...
'D4', 'C4', 'C4', 'D4', 'E4', 'D4', 'C4', 'C4'};
durations = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
1.5, 0.5, 1, 1, 1, 1, 1, 1, 1, 1, ...
1, 1, 1, 1, 1, 1, 1.5, 0.5]; % 节拍
% 演奏函数
function playMelody(notes, durations, tempo)
Fs = 44100;
waveType = 'sine';
volume = 0.7;
beatDuration = 60/tempo; % 每拍秒数
fullSignal = [];
for i = 1:length(notes)
freq = getNoteFrequency(notes{i});
t = 0:1/Fs:durations(i)*beatDuration-1/Fs;
switch waveType
case 'sine'
y = sin(2*pi*freq*t);
case 'square'
y = square(2*pi*freq*t);
case 'sawtooth'
y = sawtooth(2*pi*freq*t);
case 'triangle'
y = sawtooth(2*pi*freq*t, 0.5);
end
y = y * volume;
y = applyEnvelope(y, Fs);
% 添加音符间的小间隙
if ~isempty(fullSignal)
silence = zeros(1, round(0.02*Fs));
fullSignal = [fullSignal, silence, y];
else
fullSignal = y;
end
end
sound(fullSignal, Fs);
end
% 调用演奏 (速度=100BPM)
playMelody(notes, durations, 100);
5.2 创作你的第一首电子音乐
尝试修改以下参数创造独特声音:
- 混合不同波形:
matlab
y = 0.6*sawtooth(2*pi*freq*t) + 0.4*square(2*pi*freq*t);
- 添加滤波器效果:
matlab
[b,a] = butter(2, [200/(Fs/2), 2000/(Fs/2)], 'bandpass');
y = filter(b, a, y);
- 使用频率调制(FM)合成:
matlab
carrierFreq = freq;
modulatorFreq = freq * 1.5;
modulationIndex = 2;
y = sin(2*pi*carrierFreq*t + modulationIndex*sin(2*pi*modulatorFreq*t));
第四章:音乐创作实战 - 用你的电子琴谱写第一首作品
一把可以你由自己随意发挥、自由演奏的钢琴来了
matlab
function matlab_synth_piano
% 创建主窗口
fig = figure('Name', 'MATLAB电子琴 (2016b版)', ...
'NumberTitle', 'off', ...
'Position', [100, 100, 1000, 600], ...
'MenuBar', 'none', ...
'ToolBar', 'none', ...
'Color', [0.9 0.9 0.9], ...
'CloseRequestFcn', @closeFigure);
% 全局变量
Fs = 44100; % 采样率
duration = 0.5; % 默认音符持续时间(秒)
volume = 0.5; % 默认音量(0-1)
waveType = 'sine'; % 默认波形类型
recording = []; % 录音缓冲区
isRecording = false; % 录音状态标志
% 创建UI控件
createUI();
% 初始化音频播放器对象
audioPlayer = [];
% 创建钢琴键盘
createPianoKeyboard();
% UI创建函数
function createUI()
% 波形选择
uicontrol('Style', 'text', ...
'String', '波形类型:', ...
'Position', [20, 550, 80, 20], ...
'BackgroundColor', [0.9 0.9 0.9]);
waveTypePopup = uicontrol('Style', 'popupmenu', ...
'String', {'正弦波', '方波', '锯齿波', '三角波'}, ...
'Position', [100, 550, 100, 20], ...
'Callback', @setWaveType);
% 音量控制
uicontrol('Style', 'text', ...
'String', '音量:', ...
'Position', [220, 550, 80, 20], ...
'BackgroundColor', [0.9 0.9 0.9]);
volumeSlider = uicontrol('Style', 'slider', ...
'Min', 0, 'Max', 1, 'Value', volume, ...
'Position', [280, 550, 150, 20], ...
'Callback', @setVolume);
% 持续时间控制
uicontrol('Style', 'text', ...
'String', '持续时间(秒):', ...
'Position', [450, 550, 100, 20], ...
'BackgroundColor', [0.9 0.9 0.9]);
durationSlider = uicontrol('Style', 'slider', ...
'Min', 0.1, 'Max', 2, 'Value', duration, ...
'Position', [550, 550, 150, 20], ...
'Callback', @setDuration);
% 录音按钮
recordButton = uicontrol('Style', 'pushbutton', ...
'String', '开始录音', ...
'Position', [720, 550, 100, 30], ...
'Callback', @toggleRecording);
% 播放按钮
playButton = uicontrol('Style', 'pushbutton', ...
'String', '播放录音', ...
'Position', [830, 550, 100, 30], ...
'Callback', @playRecording, ...
'Enable', 'off');
end
% 钢琴键盘创建函数
function createPianoKeyboard()
% 钢琴键参数
whiteKeyWidth = 40;
whiteKeyHeight = 200;
blackKeyWidth = 24;
blackKeyHeight = 120;
% 白键位置(从左到右)
whiteKeys = {'C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2', ...
'C3', 'D3', 'E3', 'F3', 'G3', 'A3', 'B3', ...
'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', ...
'C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5'};
% 黑键位置(从左到右)
blackKeys = {'C#2', 'D#2', 'F#2', 'G#2', 'A#2', ...
'C#3', 'D#3', 'F#3', 'G#3', 'A#3', ...
'C#4', 'D#4', 'F#4', 'G#4', 'A#4', ...
'C#5', 'D#5', 'F#5', 'G#5', 'A#5'};
% 创建白键
for i = 1:length(whiteKeys)
posX = 20 + (i-1)*whiteKeyWidth;
uicontrol('Style', 'pushbutton', ...
'String', whiteKeys{i}, ...
'Position', [posX, 300, whiteKeyWidth, whiteKeyHeight], ...
'BackgroundColor', 'white', ...
'ForegroundColor', 'black', ...
'Callback', {@playNote, whiteKeys{i}});
end
% 创建黑键(跳过白键E和B的位置)
blackKeyPositions = [1, 2, 4, 5, 6, 8, 9, 11, 12, 13, ...
15, 16, 18, 19, 20, 22, 23, 25, 26, 27];
for i = 1:length(blackKeys)
keyIdx = blackKeyPositions(i);
posX = 20 + keyIdx*whiteKeyWidth - blackKeyWidth/2;
uicontrol('Style', 'pushbutton', ...
'String', blackKeys{i}, ...
'Position', [posX, 300+whiteKeyHeight-blackKeyHeight, blackKeyWidth, blackKeyHeight], ...
'BackgroundColor', 'black', ...
'ForegroundColor', 'white', ...
'Callback', {@playNote, blackKeys{i}});
end
end
% 回调函数
function setWaveType(src, ~)
types = {'sine', 'square', 'sawtooth', 'triangle'};
waveType = types{src.Value};
end
function setVolume(src, ~)
volume = src.Value;
end
function setDuration(src, ~)
duration = src.Value;
end
function toggleRecording(src, ~)
if isRecording
isRecording = false;
src.String = '开始录音';
findobj('String', '播放录音').Enable = 'on';
else
isRecording = true;
recording = [];
src.String = '停止录音';
findobj('String', '播放录音').Enable = 'off';
end
end
function playRecording(~, ~)
if ~isempty(recording)
sound(recording, Fs);
end
end
function playNote(~, ~, note)
freq = getNoteFrequency(note);
t = 0:1/Fs:duration-1/Fs;
switch waveType
case 'sine'
y = sin(2*pi*freq*t);
case 'square'
y = square(2*pi*freq*t);
case 'sawtooth'
y = sawtooth(2*pi*freq*t);
case 'triangle'
y = sawtooth(2*pi*freq*t, 0.5);
end
% 应用音量
y = y * volume;
% 应用包络(ADSR)减少爆音
y = applyEnvelope(y, Fs);
% 播放声音
sound(y, Fs);
% 如果正在录音,添加到录音缓冲区
if isRecording
if isempty(recording)
recording = y;
else
% 在音符之间添加少量静音
silence = zeros(round(0.05*Fs), 1)';
recording = [recording, silence, y];
end
end
end
function freq = getNoteFrequency(note)
% 音符到频率的映射
noteMap = containers.Map();
% 定义钢琴键频率(A4=440Hz)
notes = {'C2', 'C#2', 'D2', 'D#2', 'E2', 'F2', 'F#2', 'G2', 'G#2', 'A2', 'A#2', 'B2', ...
'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3', 'B3', ...
'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', ...
'C5', 'C#5', 'D5', 'D#5', 'E5', 'F5', 'F#5', 'G5', 'G#5', 'A5', 'A#5', 'B5'};
for i = 1:length(notes)
n = i + 12; % 钢琴键编号从C2开始(n=28)
freq = 440 * 2^((n-49)/12);
noteMap(notes{i}) = freq;
end
freq = noteMap(note);
end
function y = applyEnvelope(y, Fs)
% 简单的ADSR包络(Attack, Decay, Sustain, Release)
len = length(y);
attackTime = 0.05; % 起音时间(秒)
decayTime = 0.1; % 衰减时间(秒)
releaseTime = 0.2; % 释音时间(秒)
attackSamples = round(attackTime * Fs);
decaySamples = round(decayTime * Fs);
releaseSamples = round(releaseTime * Fs);
sustainLevel = 0.7; % 持续电平
% 确保不超过信号长度
if attackSamples + decaySamples + releaseSamples > len
attackSamples = round(0.1 * len);
decaySamples = round(0.2 * len);
releaseSamples = round(0.2 * len);
end
sustainSamples = len - attackSamples - decaySamples - releaseSamples;
% 创建包络
envelope = zeros(1, len);
% 起音阶段(线性增长)
envelope(1:attackSamples) = linspace(0, 1, attackSamples);
% 衰减阶段(线性衰减到持续电平)
envelope(attackSamples+1:attackSamples+decaySamples) = linspace(1, sustainLevel, decaySamples);
% 持续阶段(保持电平)
envelope(attackSamples+decaySamples+1:attackSamples+decaySamples+sustainSamples) = sustainLevel;
% 释音阶段(线性衰减到0)
envelope(attackSamples+decaySamples+sustainSamples+1:end) = linspace(sustainLevel, 0, releaseSamples);
% 应用包络
y = y .* envelope;
end
function closeFigure(~, ~)
% 清理资源
if ~isempty(audioPlayer)
stop(audioPlayer);
delete(audioPlayer);
end
delete(gcf);
end
end
运行上面程序,你就会得到如下的钢琴界面,然后就可以用你的鼠标开始演奏啦~~~
结语:从电子琴到音乐宇宙
恭喜你!现在你已经拥有了一个功能强大的MATLAB电子琴工作室。但这只是音乐编程世界的起点------你可以继续探索:
🎵 添加效果器:混响、延迟、失真...
🎵 实现MIDI输入/输出功能
🎵 开发自动伴奏系统
记住,莫扎特曾说:"音乐不在音符之中,而在音符之间的寂静里。"编程也是如此------不仅在于代码本身,更在于代码所创造的可能性。现在,就让你的创意在这数字与音乐的交叉地带自由翱翔吧!
趣味挑战:尝试用你的电子琴模拟以下经典音效:
- 《星际迷航》传送器音效(尝试带滤波器的锯齿波)
- 老式电话铃声(两个正弦波叠加)
- UFO降落音效(下滑的扫频正弦波)