《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)
开篇:当MATLAB遇见音乐 - 一场数字与艺术的浪漫邂逅
想象一下,你正坐在一台古老的钢琴前,指尖在黑白琴键上舞动,悠扬的旋律在空气中流淌…等等!谁说这美妙的场景一定要发生在真实的钢琴上?今天,我要带你用MATLAB这个"理工科神器"打造一个独一无二的数字电子琴,让你体验工程师与音乐家的双重身份!
你可能不知道,MATLAB除了能做复杂的数学运算和数据分析,还是一个隐藏的"音乐制作大师"。从《星球大战》的光剑声到《盗梦空间》的BRAAAM效果,现代电影中许多标志性声音都源自数字合成技术。而今天,我们将从零开始,用代码谱写属于你的电子乐章!
第一章:声音的魔法 - 解密数字音频的奥秘
1.1 声音是如何被"数字化"的?
让我们做个有趣的小实验:
% 听听不同采样率下的效果对比
Fs_list = [8000, 11025, 22050, 44100];
for Fs = Fs_listt = 0:1/Fs:1;y = sin(2*pi*440*t);sound(y, Fs);pause(1.5);disp(['当前采样率:', num2str(Fs), 'Hz - 听出区别了吗?']);
end
运行这段代码,你会亲耳见证从"电话音质"到"CD音质"的奇妙转变!这就是著名的"奈奎斯特采样定理"在发挥作用——要完美重现一个频率,采样率至少需要它的两倍。
1.2 波形实验室:四种基础音色的听觉派对
我们准备了四种基础波形的"声音鸡尾酒",来场听觉实验吧!
% 波形对比演示
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:4subplot(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);endplot(t(1:500), y(1:500));title([names{i}, ' - ', waves{i}, ' wave']);xlabel('时间(s)'); ylabel('振幅');sound(y, 44100);pause(1.5);
end
看到那些奇妙的波形了吗?正是这些几何形状的差异,造就了千变万化的音色!方波的直角转折带来电子游戏般的复古感,而锯齿波的斜线上升则让人联想到科幻电影中的激光枪。
第二章:打造你的数字钢琴工作室
2.1 钢琴键盘的"基因密码"
你知道吗?钢琴上每个键的频率都遵循着一个优雅的数学规律:
% 钢琴键频率计算器
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包络设计
没有包络的声音就像没有调味的美食——单调乏味!让我们设计一个智能"声音造型师":
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 > lenratios = [attackTime, decayTime, releaseTime];ratios = ratios/sum(ratios) * 0.9; % 保留10%给sustainattackSamples = round(ratios(1)*len);decaySamples = round(ratios(2)*len);releaseSamples = round(ratios(3)*len);endsustainSamples = len - attackSamples - decaySamples - releaseSamples;% 起音阶段switch attackCurvecase '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 sustainVibratovibFreq = 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);elseenvelope(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 《欢乐颂》演奏指南
让我们用代码演奏这首经典旋律:
% 欢乐颂简谱 (以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 waveTypecase '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);endy = y * volume;y = applyEnvelope(y, Fs);% 添加音符间的小间隙if ~isempty(fullSignal)silence = zeros(1, round(0.02*Fs));fullSignal = [fullSignal, silence, y];elsefullSignal = y;endendsound(fullSignal, Fs);
end% 调用演奏 (速度=100BPM)
playMelody(notes, durations, 100);
5.2 创作你的第一首电子音乐
尝试修改以下参数创造独特声音:
- 混合不同波形:
y = 0.6*sawtooth(2*pi*freq*t) + 0.4*square(2*pi*freq*t);
- 添加滤波器效果:
[b,a] = butter(2, [200/(Fs/2), 2000/(Fs/2)], 'bandpass');
y = filter(b, a, y);
- 使用频率调制(FM)合成:
carrierFreq = freq;
modulatorFreq = freq * 1.5;
modulationIndex = 2;
y = sin(2*pi*carrierFreq*t + modulationIndex*sin(2*pi*modulatorFreq*t));
第四章:音乐创作实战 - 用你的电子琴谱写第一首作品
一把可以你由自己随意发挥、自由演奏的钢琴来了
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}});endend% 回调函数function setWaveType(src, ~)types = {'sine', 'square', 'sawtooth', 'triangle'};waveType = types{src.Value};endfunction setVolume(src, ~)volume = src.Value;endfunction setDuration(src, ~)duration = src.Value;endfunction toggleRecording(src, ~)if isRecordingisRecording = false;src.String = '开始录音';findobj('String', '播放录音').Enable = 'on';elseisRecording = true;recording = [];src.String = '停止录音';findobj('String', '播放录音').Enable = 'off';endendfunction playRecording(~, ~)if ~isempty(recording)sound(recording, Fs);endendfunction playNote(~, ~, note)freq = getNoteFrequency(note);t = 0:1/Fs:duration-1/Fs;switch waveTypecase '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 isRecordingif isempty(recording)recording = y;else% 在音符之间添加少量静音silence = zeros(round(0.05*Fs), 1)';recording = [recording, silence, y];endendendfunction 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;endfreq = noteMap(note);endfunction 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 > lenattackSamples = round(0.1 * len);decaySamples = round(0.2 * len);releaseSamples = round(0.2 * len);endsustainSamples = 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;endfunction closeFigure(~, ~)% 清理资源if ~isempty(audioPlayer)stop(audioPlayer);delete(audioPlayer);enddelete(gcf);end
end
运行上面程序,你就会得到如下的钢琴界面,然后就可以用你的鼠标开始演奏啦~~~
结语:从电子琴到音乐宇宙
恭喜你!现在你已经拥有了一个功能强大的MATLAB电子琴工作室。但这只是音乐编程世界的起点——你可以继续探索:
🎵 添加效果器:混响、延迟、失真…
🎵 实现MIDI输入/输出功能
🎵 开发自动伴奏系统
记住,莫扎特曾说:"音乐不在音符之中,而在音符之间的寂静里。"编程也是如此——不仅在于代码本身,更在于代码所创造的可能性。现在,就让你的创意在这数字与音乐的交叉地带自由翱翔吧!
趣味挑战:尝试用你的电子琴模拟以下经典音效:
- 《星际迷航》传送器音效(尝试带滤波器的锯齿波)
- 老式电话铃声(两个正弦波叠加)
- UFO降落音效(下滑的扫频正弦波)