cde*_*roy 19 python matlab signal-processing pitch-tracking audio-analysis
作为一个有趣的家庭研究项目的一部分,我试图找到一种方法来减少/转换一首像音频信号的嗡嗡声(我们人们在听一首歌时感知的基本旋律).在我进一步描述我对此问题的尝试之前,我想提一下,虽然我在分析图像和视频方面有很多经验,但我对音频分析还是全新的.
谷歌搜索后,我发现了一堆旋律提取算法.给定一首歌的复音音频信号(例如.wav文件),它们输出音高轨道---在每个时间点他们估计主音调(来自歌手的声音或某些旋律生成乐器)并追踪主导音乐随着时间推移.
我读了几篇论文,他们似乎计算了这首歌的短时傅立叶变换,然后对频谱图进行了一些分析,以获得并跟踪主导音调.旋律提取只是我正在尝试开发的系统中的一个组件,所以我不介意使用任何可用的算法,只要它在我的音频文件上做得不错并且代码可用.由于我是新手,我很高兴听到有关哪些算法运行良好以及我在哪里可以找到其代码的任何建议.
我发现了两种算法:
我选择Melodia作为不同音乐类型的结果看起来相当令人印象深刻.请检查以查看其结果.你听到的每首音乐的嗡嗡声基本上都是我感兴趣的.
"对于任何一首任意歌曲来说,这都是嗡嗡声的产生,我想在这个问题中帮助你".
该算法(可用作鞋面插件)输出音高轨道--- [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
Run Code Online (Sandbox Code Playgroud)
这段代码背后的基本逻辑如下:在每个时间戳,我合成一个短暂的波(比如一个正弦波),其频率等于在该时间戳上检测到的主导音高/频率,持续时间等于它与输入旋律矩阵中的下一个时间戳的差距.我只是想知道我是否正确行事.
然后我从这个功能中获取音频信号并用原始歌曲播放(左声道上的旋律和右声道上的原始歌曲).虽然生成的音频信号似乎可以很好地分割旋律生成源(语音/引导仪器) - 它的声音在其他地方是零的,而在其他任何地方都是零 - 信号本身远非嗡嗡声(我得到类似的东西)作者在他们的网站上显示的哔哔声beeeeep beeeeep beep beeep beeeeeeeep).具体地,下面是显示底部输入歌曲的时域信号和使用我的功能产生的旋律的时域信号的可视化.

一个主要问题是 - 虽然我得到了每个时间戳产生的波的频率以及持续时间,但我不知道如何设置波的幅度.现在,我将幅度设置为平坦/恒定值,我怀疑这是问题所在.
有没有人对此有任何建议?我欢迎使用任何程序语言(最好是MATLAB,python,C++)的建议,但我想这里的问题更为通用---如何在每个时间戳上生成波形?
我脑海中有一些想法/修复:
如果我理解正确的话,你似乎已经准确地表达了音高,但问题在于你所产生的只是"听起来不够好".
从你的第二种方法开始:过滤除了音高以外的任何东西都不会带来任何好处.通过移除除了与您的本地音高估计相对应的几个频率箱之外的所有频率,您将失去输入信号的纹理,这听起来很好.事实上,如果你把它拿到一个极端并除去了与音高相对应的一个样本并取出了一个ifft,那么你将得到一个正弦曲线,这就是你现在正在做的事情.无论如何你想要这样做,我建议你只需要对时间信号应用滤波器而不是进出频域,这样做更加昂贵和繁琐.滤波器在您想要保持的频率周围有一个小的截止点,这样也可以产生质地更好的声音.
但是,如果你已经有音高和持续时间的估计,你很满意,但你想改善声音渲染,我建议你只需要更换你的正弦波 - 无论你有多少,它总是听起来像傻嘟嘟嘟嘟按摩它们 - 用一些实际的嗡嗡声(或小提琴或长笛或任何你喜欢的)样本用于刻度中的每个频率.如果记忆是一个问题,或者你所代表的歌曲没有达到一个良好的调和范围(例如,思考中东歌曲),而不是每个音阶的音符都有一个嗡嗡声的样本,你只能有一个哼唱样本几个频率.然后,您可以通过从这些嗡嗡声样本中的一个进行采样率转换,以任何频率获得嗡嗡声.从样本转换中选择一些样本将允许您选择与您需要生成的频率倾向于"最佳"比率的样本,因为采样转换的复杂性取决于该比率.显然,与仅提供一组样本相比,添加采样率转换将会产生更多的工作和计算要求.
使用一组真实样本会对您渲染的内容质量产生很大影响.它还可以让你对你演奏的每个新音符进行逼真的攻击.
然后是的,就像你建议的那样,你可能还希望通过跟随输入信号的瞬时幅度来播放振幅,以产生更加细微的歌曲渲染.
最后,我还会使用您拥有的持续时间估计值,以便从一个声音到下一个声音的转换更平滑.根据您对音频文件的表现进行猜测,我非常喜欢(哔哔哔哔哔哔哔哔哔哔声)和您显示的图表,看起来您在歌曲的渲染中插入了很多中断.你可以通过延长持续时间估计来摆脱任何比1秒更短的沉默来避免这种情况.这样你就可以保留原始歌曲的真实沉默,但避免切断你歌曲的每一个音符.
虽然我无权访问您的 Synth() 函数,但根据它所需的参数,我想说您的问题是因为您没有处理该阶段。
也就是说,仅将波形片段连接在一起是不够的,您必须确保它们具有连续的相位。否则,每次连接两个波形片段时都会在波形中产生不连续性。如果是这种情况,我的猜测是您一直听到相同的频率,并且它听起来更像是锯齿波而不是正弦波 - 我是对的吗?
解决方案是将片段 n 的起始阶段设置为片段 n-1 的结束阶段。下面是如何连接两个具有不同频率的波形而不产生相位不连续性的示例:
fs = 44100; % sampling frequency
% synthesize a cosine waveform with frequency f1 and starting additional phase p1
p1 = 0;
dur1 = 1;
t1 = 0:1/fs:dur1;
x1(1:length(t1)) = 0.5*cos(2*pi*f1*t1 + p1);
% Compute the phase at the end of the waveform
p2 = mod(2*pi*f1*dur1 + p1,2*pi);
dur2 = 1;
t2 = 0:1/fs:dur2;
x2(1:length(t2)) = 0.5*cos(2*pi*f2*t2 + p2); % use p2 so that the phase is continuous!
x3 = [x1 x2]; % this should give you a waveform without any discontinuities
Run Code Online (Sandbox Code Playgroud)
请注意,虽然这会为您提供连续波形,但频率转换是瞬时的。如果您希望频率逐渐从 time_n 变为 time_n+1,那么您必须使用更复杂的方法,例如 McAulay-Quatieri 插值。但无论如何,如果你的片段足够短,这听起来应该足够好。
关于其他评论,如果我理解正确的话,您的目标只是能够听到频率序列,而不是让它听起来像原始来源。在这种情况下,幅度并不那么重要,您可以将其保持固定。
如果您想让它听起来像原始来源,那是一个完全不同的故事,并且可能超出了本讨论的范围。
希望这能回答您的问题!