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

《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 创作你的第一首电子音乐

尝试修改以下参数创造独特声音:

  1. 混合不同波形:
matlab 复制代码
y = 0.6*sawtooth(2*pi*freq*t) + 0.4*square(2*pi*freq*t);
  1. 添加滤波器效果:
matlab 复制代码
[b,a] = butter(2, [200/(Fs/2), 2000/(Fs/2)], 'bandpass');
y = filter(b, a, y);
  1. 使用频率调制(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输入/输出功能

🎵 开发自动伴奏系统

记住,莫扎特曾说:"音乐不在音符之中,而在音符之间的寂静里。"编程也是如此------不仅在于代码本身,更在于代码所创造的可能性。现在,就让你的创意在这数字与音乐的交叉地带自由翱翔吧!

趣味挑战:尝试用你的电子琴模拟以下经典音效:

  1. 《星际迷航》传送器音效(尝试带滤波器的锯齿波)
  2. 老式电话铃声(两个正弦波叠加)
  3. UFO降落音效(下滑的扫频正弦波)
相关推荐
已经成为了代码的形状21 分钟前
牛客周赛 Round 91
开发语言·c++·算法
炯哈哈22 分钟前
【上位机——MFC】对象和控件绑定
开发语言·c++·mfc·上位机
forestsea36 分钟前
深入理解Java三大特性:封装、继承和多态
java·开发语言
babytiger1 小时前
如何用命令行判断一个exe是不是c#wpf开发的
开发语言·c#·wpf
xinruoqianqiu1 小时前
shell脚本--2
linux·运维·开发语言·前端·c++·chrome
我爱C编程2 小时前
基于BPSK调制解调和LDPC编译码的单载波相干光传输系统matlab误码率仿真
matlab·ldpc编译码·bpsk调制解调·单载波相干光传输
qq_297504612 小时前
【解决】VsCode C++异常【terminate called after throwing an instance of ‘char const‘】
开发语言·c++
dot to one2 小时前
C++ set和map系列(关联式容器)的介绍及使用
开发语言·数据结构·c++·visual studio·红黑树
cykaw25902 小时前
C++ string的使用
开发语言·c++
CodeJourney.2 小时前
MATLAB三维可视化技术解析
数据库·人工智能·算法·matlab