如何将音调轨迹从旋律提取算法转换为类似嗡嗡声的音频符号

2024-09-29 02:28:18 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

我读了几篇论文,他们似乎在计算歌曲的短时傅里叶变换,然后对频谱图做一些分析,以得到并跟踪主音的音调。旋律提取只是我正在开发的系统中的一个组件,所以我不介意使用任何可用的算法,只要它对我的音频文件做得很好,代码是可用的。由于我是新手,我很乐意听到任何关于哪些算法可以很好地工作以及在哪里可以找到它的代码的建议。在

我发现了两种算法:

  1. Yaapt pitch tracking
  2. Melodia

我选择了Melodia,因为不同音乐类型的结果看起来相当令人印象深刻。请检查this to see its results。你听到的每一段音乐的嗡嗡声本质上就是我感兴趣的。在

“这是这一代哼唱任意歌曲,我希望你在这个问题上的帮助”。在

算法(可用作vamp插件)输出一个音调轨迹---[time_stamp,pitch/frequency]——一个Nx2矩阵,其中第一列是时间戳(以秒为单位),第二列是在相应的时间戳处检测到的主音高。下面显示的是从算法获得的音调轨迹的可视化显示,该算法用紫色覆盖歌曲的时域信号(上图)及其频谱图/短时傅里叶。基音/频率的负值表示非浊音/非旋律段的主要基音估计算法。所以所有的音高估计值>;=0与旋律相对应,其余的对我来说并不重要。在

Pitch-track overlay with a song's waveform and spectrogram

现在我想把这个音高轨迹转换成类似嗡嗡声的音频信号——就像作者在他们的网站上看到的那样。在

下面是我编写的一个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

这段代码背后的逻辑是:在每个时间戳,我合成一个短波(比如正弦波),频率等于在该时间戳检测到的主音节/频率,持续时间等于它与输入旋律矩阵中下一个时间戳的间隔。我只想知道我是否做得对。在

然后,我从这个函数中获取音频信号,并与原始歌曲一起播放(左声道的旋律和右声道的原始歌曲)。虽然生成的音频信号似乎很好地分割了产生旋律的源(声音/导联乐器),但它在声音所在的地方是活跃的,而其他地方都是零的——信号本身远不是作者在他们的网站上显示的嗡嗡声(我得到了一些类似于哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔。具体地说,下面是一个可视化显示,在底部输入歌曲的时域信号和使用我的函数生成的旋律的时域信号。在

enter image description here

一个主要问题是——虽然我得到了在每个时间戳产生的波的频率和持续时间,但我不知道如何设置波的振幅。现在,我将振幅设置为平坦/恒定值,我怀疑这就是问题所在。在

有人对此有什么建议吗?我欢迎任何程序语言的建议(最好是Matlab、Python、C++),但我想我这里的问题更一般——如何在每一个时间戳上产生波?在

我的一些想法/解决方法:

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

Tags: to算法time信号轨迹时间音频歌曲
3条回答

如果我没弄错,你似乎已经对音高有了准确的描述,但你的问题是你所产生的音高“听起来不够好”。在

从你的第二种方法开始:过滤掉所有的东西,但这并不能带来任何好的结果。除了一些与本地音高估计相对应的频率盒之外,去掉所有的东西,你就会失去输入信号的纹理,这使得它听起来很好。事实上,如果你把这一点发挥到极致,除了一个与音高相对应的样本,然后进行ifft,你就会得到一个正弦波,这就是你目前所做的。 如果你想这样做,我建议你只需对你的时间信号应用一个滤波器,而不是进入和退出频域,这是更昂贵和麻烦的。滤波器会在你想要保持的频率附近有一个很小的截止值,这也会让声音有更好的质感。在

但是,如果你已经有了你满意的音高和持续时间估计,但你想改进声音渲染,我建议你只需更换你的正弦波,无论你如何按摩他们与一些实际的哼唱(或小提琴或长笛或任何你喜欢的)样本,在每一个频率的规模。如果记忆是一个问题,或者如果你所代表的歌曲不属于一个好脾气的音阶(比如中东歌曲),你就不能为音阶中的每个音符提供哼唱样本,你只能有几个频率的哼唱样本。然后你可以从这些嗡嗡声样本中的一个进行采样率转换,得到任何频率下的嗡嗡声。有几个样本可以从中进行采样转换,这样就可以根据需要的频率选择一个倾斜到“最佳”比率的样本,因为采样转换的复杂性取决于该比率。显然,与只需从一组样本中进行选择相比,添加采样率转换将需要更多的工作和计算要求。在

使用一个真实的样本库将对渲染的质量产生很大的影响。它也将允许你有现实的攻击每一个新的音符你发挥。在

然后是的,就像你所说的,你可能还想通过跟踪输入信号的瞬时振幅来播放振幅,从而产生更细致的歌曲渲染效果。在

最后,我还将使用你的持续时间估计,以便你有一个平稳的过渡到下一个声音。从你对我非常喜欢的音频文件的性能(哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔。你可以通过延长持续时间来避免这种情况,比如说,一秒钟以内的沉默。这样你就可以保留原歌真实的沉默,但避免切断歌曲的每个音符。在

虽然我无法访问synth()函数,但根据它所使用的参数,我认为您的问题是因为您没有处理阶段。在

也就是说,仅仅将波形片段连接在一起是不够的,必须确保它们具有连续的相位。否则,每次连接两个波形片段时都会在波形中创建不连续性。如果是这样的话,我猜你一直都在听同一个频率,听起来更像是锯齿而不是正弦波-对吗?在

解决方案是将snippetn的起始阶段设置为snippetn-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

注意,虽然这给你一个连续的波形,频率转换是瞬时的。如果你想让频率从时间到时间逐渐变化,你就必须使用更复杂的方法,比如麦考利四次插值法。但无论如何,如果你的代码片段足够短,这听起来就足够好了。在

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

如果你想让它听起来像原始资料,那就完全不同了,可能超出了本文讨论的范围。在

希望这能回答你的问题!在

你至少有两个问题。在

首先,正如你所推测的,你的分析已经丢弃了原始谱旋律部分的所有振幅信息。你需要一种算法来捕捉这些信息(而不仅仅是合成音输入的整个信号的振幅,或者任何自然音乐声音的FFT音高单元的振幅)。这是一个非常重要的问题,介于旋律音调提取和盲源分离之间。在

第二,声音有音色,包括泛音和包络,即使在一个固定的频率。你的合成方法只是创造一个单一的正弦波,而哼唱可能会产生一堆更有趣的泛音,包括很多比音调更高的频率。为了获得一种更自然的声音,你可以尝试分析你自己哼唱一个音调的频谱,然后在合成分析中的每个频率时间戳时,尝试重新创建所有这些泛音正弦波,而不是一个,每个都在适当的相对振幅。你也可以看着自己哼唱一个短音符的振幅包络线,然后用这个包络线来调节合成器的振幅。在

相关问题 更多 >