19

作为一个有趣的家庭研究项目的一部分,我试图找到一种方法来减少/将歌曲转换为类似嗡嗡声的音频信号(我们人类在听歌曲时感知到的基本旋律)。在我进一步描述我对这个问题的尝试之前,我想提一下,尽管我在分析图像和视频方面有很多经验,但我对音频分析是完全陌生的。

谷歌搜索了一下,我发现了一堆旋律提取算法。给定一首歌曲的复音音频信号(例如:.wav 文件),它们会输出一个音高音轨 --- 在每个时间点,他们估计主要音高(来自歌手的声音或某些旋律生成乐器)并跟踪主要音高随着时间的推移。

我读了几篇论文,他们似乎计算了歌曲的短时间傅立叶变换,然后对频谱图进行了一些分析,以获取和跟踪主导音高。旋律提取只是我正在尝试开发的系统中的一个组件,所以我不介意使用任何可用的算法,只要它在我的音频文件上做得不错并且代码可用。由于我是新手,我很高兴听到任何关于哪些算法已知运行良好以及在哪里可以找到它的代码的建议。

我发现了两种算法:

  1. Yaapt 音高跟踪
  2. 旋律

我选择了 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++)提出建议,但我想我的问题更笼统——如何在每个时间戳处生成波形?

我脑海中的一些想法/修复:

  1. 通过从原始歌曲的时域信号中获取幅度的平均/最大估计值来设置幅度。
  2. 完全改变我的方法——计算歌曲音频信号的频谱图/短时傅立叶变换。除了我的音高轨道(或靠近我的音高轨道)中的频率之外,几乎/零输出或柔和地切断所有其他频率。然后计算逆短时傅里叶变换得到时域信号。
4

4 回答 4

5

如果我理解正确,您似乎已经对音高有了准确的表示,但您的问题是您生成的内容“听起来不够好”。

从你的第二种方法开始:过滤掉任何东西,但音高不会带来任何好处。通过删除与您的本地音高估计相对应的几个频率箱以外的所有内容,您将失去输入信号的纹理,这使它听起来不错。事实上,如果你把它发挥到极致,除了与音高相对应的一个样本之外,去掉所有的东西并取一个 ifft,你会得到一个正弦曲线,这就是你目前正在做的事情。如果您无论如何都想这样做,我建议您通过对您的时间信号应用一个滤波器来执行所有这些操作,而不是进出频域,这更加昂贵和麻烦。滤波器将在您想要保留的频率附近有一个小的截止,这也将允许声音具有更好的质感。

但是,如果您已经有了满意的音高和持续时间估计,但您想改进声音渲染,我建议您只替换您的正弦波 - 无论您如何,它总是听起来像愚蠢的哔哔声按摩它们 - 为音阶中的每个频率使用一些实际的嗡嗡声(或小提琴或长笛或任何你喜欢的东西)样本。如果记忆是一个问题,或者如果你所代表的歌曲不属于一个很好的音阶(例如考虑中东歌曲),而不是为音阶的每个音符都有一个嗡嗡声样本,你只能有一个嗡嗡声样本几个频率。然后,您可以通过对这些嗡嗡声样本之一进行采样率转换来导出任何频率的嗡嗡声。有几个样本可供选择以进行样本转换,您可以选择与您需要产生的频率具有“最佳”比率的样本,因为采样转换的复杂性取决于该比率。显然,与仅从一组样本中挑选相比,添加采样率转换将需要更多的工作和计算要求。

使用一组真实样本将对渲染的质量产生很大影响。它还可以让您对您演奏的每个新音符进行逼真的攻击。

那么是的,就像你建议的那样,你可能还想通过跟随输入信号的瞬时幅度来播放幅度,以产生更细微的歌曲渲染。

最后,我还会使用您所拥有的持续时间估计值,以便您从一种声音到另一种声音的过渡更平滑。根据您对我非常喜欢的音频文件的演奏(哔哔哔哔哔哔哔哔哔哔哔哔)和您显示的图表猜测,看起来您在歌曲的渲染中插入了许多中断。您可以通过延长持续时间估计以消除任何短于 0.1 秒的静默来避免这种情况。这样,您可以保留原始歌曲的真正静音,但避免切断歌曲的每个音符。

于 2013-03-18T04:42:48.077 回答
3

虽然我无权访问您的 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

请注意,虽然这为您提供了连续波形,但频率转换是瞬时的。如果您希望频率从 time_n 逐渐变为 time_n+1,那么您将不得不使用更复杂的东西,例如 McAulay-Quatieri 插值。但无论如何,如果你的片段足够短,这听起来应该足够好。

关于其他评论,如果我理解正确,您的目标只是能够听到频率序列,而不是让它听起来像原始来源。在这种情况下,幅度并不那么重要,您可以将其保持不变。

如果你想让它听起来像原始来源,那是一个完全不同的故事,可能超出了本次讨论的范围。

希望这能回答你的问题!

于 2013-03-18T16:35:33.870 回答
1

你至少有2个问题。

首先,正如您所推测的,您的分析已经丢弃了原始频谱旋律部分的所有幅度信息。您将需要一种算法来捕获该信息(而不仅仅是复音输入的整个信号的幅度,或者任何自然音乐声音的 FFT 音高箱的幅度)。这是一个重要的问题,介于旋律音高提取和盲源分离之间。

其次,声音具有音色,包括泛音和包络,即使在恒定频率下也是如此。你的合成方法只是创建一个单一的正弦波,而哼唱可能会产生一堆更有趣的泛音,包括很多比音高更高的频率。对于稍微更自然的声音,您可以尝试分析自己哼唱单个音高的频谱,并尝试在合成每个频率时间戳时重新创建所有这几十个泛音正弦波,而不是一个,每个都具有适当的相对幅度在你的分析中。您还可以查看自己哼唱一个简短音符的幅度包络,并使用该包络来调制合成器的幅度。

于 2013-03-18T15:33:48.257 回答
0

使用 libfmp.c8 声化值

import IPython.display as ipd
import libfmp.b
import libfmp.c8
data = vamp.collect(audio, samplerate, "mtg-melodia:melodia", parameters=params)
hop, melody = data['vector']
timestamps=np.arange(0,len(melody)) * float(hop)
melody_pos = melody[:]
melody_pos[melody<=0] = 0   #get rid off - vals
d = {'time': ts, 'frequency':pd.Series(melody_pos) }
df=pd.DataFrame(d)
traj = df.values
x_traj_mono = libfmp.c8.sonify_trajectory_with_sinusoid(traj, len(audio), sr, smooth_len=50, amplitude=0.8)
ipd.display(ipd.Audio(x_traj_mono+y, rate=sr))```
于 2022-01-07T19:09:20.647 回答