作为一个有趣的家庭研究项目的一部分,我试图找到一种方法来减少/将歌曲转换为类似嗡嗡声的音频信号(我们人类在听歌曲时感知到的基本旋律)。在我进一步描述我对这个问题的尝试之前,我想提一下,尽管我在分析图像和视频方面有很多经验,但我对音频分析是完全陌生的。
谷歌搜索了一下,我发现了一堆旋律提取算法。给定一首歌曲的复音音频信号(例如:.wav 文件),它们会输出一个音高音轨 --- 在每个时间点,他们估计主要音高(来自歌手的声音或某些旋律生成乐器)并跟踪主要音高随着时间的推移。
我读了几篇论文,他们似乎计算了歌曲的短时间傅立叶变换,然后对频谱图进行了一些分析,以获取和跟踪主导音高。旋律提取只是我正在尝试开发的系统中的一个组件,所以我不介意使用任何可用的算法,只要它在我的音频文件上做得不错并且代码可用。由于我是新手,我很高兴听到任何关于哪些算法已知运行良好以及在哪里可以找到它的代码的建议。
我发现了两种算法:
我选择了 Melodia,因为不同音乐流派的结果看起来相当令人印象深刻。请检查此以查看其结果。你听到的每首音乐的嗡嗡声本质上是我感兴趣的。
“这是任何任意歌曲的嗡嗡声的产生,我希望你在这个问题上提供帮助”。
该算法(可作为 vamp 插件使用)输出音高轨道 --- [time_stamp, pitch/frequency] --- Nx2 矩阵,其中第一列是时间戳(以秒为单位),第二列是主要音高在相应的时间戳上检测到。下面显示的是从算法中获得的音高轨道的可视化效果,该算法以紫色覆盖了歌曲的时域信号(上图)及其频谱图/短时傅立叶。音高/频率的负值表示算法对非浊音/非旋律片段的主要音高估计。所以所有音高估计> = 0对应于旋律,其余的对我来说并不重要。
现在我想将这个音高音轨转换回像嗡嗡声一样的音频信号——就像作者在他们的网站上一样。
下面是我为此编写的一个 MATLAB 函数:
function [melSignal] = melody2audio(melody, varargin)
% melSignal = melody2audio(melody, Fs, synthtype)
% melSignal = melody2audio(melody, Fs)
% melSignal = melody2audio(melody)
%
% Convert melody/pitch-track to a time-domain signal
%
% Inputs:
%
% melody - [time-stamp, dominant-frequency]
% an Nx2 matrix with time-stamp in the
% first column and the detected dominant
% frequency at corresponding time-stamp
% in the second column.
%
% synthtype - string to choose synthesis method
% passed to synth function in synth.m
% current choices are: 'fm', 'sine' or 'saw'
% default='fm'
%
% Fs - sampling frequency in Hz
% default = 44.1e3
%
% Output:
%
% melSignal -- time-domain representation of the
% melody. When you play this, you
% are supposed to hear a humming
% of the input melody/pitch-track
%
p = inputParser;
p.addRequired('melody', @isnumeric);
p.addParamValue('Fs', 44100, @(x) isnumeric(x) && isscalar(x));
p.addParamValue('synthtype', 'fm', @(x) ismember(x, {'fm', 'sine', 'saw'}));
p.addParamValue('amp', 60/127, @(x) isnumeric(x) && isscalar(x));
p.parse(melody, varargin{:});
parameters = p.Results;
% get parameter values
Fs = parameters.Fs;
synthtype = parameters.synthtype;
amp = parameters.amp;
% generate melody
numTimePoints = size(melody,1);
endtime = melody(end,1);
melSignal = zeros(1, ceil(endtime*Fs));
h = waitbar(0, 'Generating Melody Audio' );
for i = 1:numTimePoints
% frequency
freq = max(0, melody(i,2));
% duration
if i > 1
n1 = floor(melody(i-1,1)*Fs)+1;
dur = melody(i,1) - melody(i-1,1);
else
n1 = 1;
dur = melody(i,1);
end
% synthesize/generate signal of given freq
sig = synth(freq, dur, amp, Fs, synthtype);
N = length(sig);
% augment note to whole signal
melSignal(n1:n1+N-1) = melSignal(n1:n1+N-1) + reshape(sig,1,[]);
% update status
waitbar(i/size(melody,1));
end
close(h);
end
该代码背后的基本逻辑如下:在每个时间戳处,我合成一个短寿命波(例如正弦波),其频率等于在该时间戳处检测到的主要音高/频率,持续时间等于它与输入旋律矩阵中的下一个时间戳的差距。我只是想知道我这样做是否正确。
然后我把从这个函数中得到的音频信号和原曲一起播放(左声道的旋律和右声道的原曲)。尽管生成的音频信号似乎很好地分割了旋律生成源(语音/主奏乐器)——它在语音所在的地方是活跃的,而在其他地方为零——信号本身远不是嗡嗡声(我得到类似的东西哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔)作者在他们的网站上展示的。具体来说,下图是底部输入歌曲的时域信号和使用我的函数生成的旋律的时域信号的可视化。
一个主要问题是——尽管我得到了每个时间戳生成的波频率以及持续时间,但我不知道如何设置波的幅度。现在,我将幅度设置为平坦/恒定值,我怀疑这就是问题所在。
有人对此有什么建议吗?我欢迎使用任何程序语言(最好是 MATLAB、python、C++)提出建议,但我想我的问题更笼统——如何在每个时间戳处生成波形?
我脑海中的一些想法/修复:
- 通过从原始歌曲的时域信号中获取幅度的平均/最大估计值来设置幅度。
- 完全改变我的方法——计算歌曲音频信号的频谱图/短时傅立叶变换。除了我的音高轨道(或靠近我的音高轨道)中的频率之外,几乎/零输出或柔和地切断所有其他频率。然后计算逆短时傅里叶变换得到时域信号。