19

自宅で楽しむ研究プロジェクトの一環として、私は曲を音声信号のようなハミング(曲を聴いたときに人間が知覚する基本的なメロディー)に変換/変換する方法を見つけようとしています。この問題の試みについて説明する前に、私は画像やビデオの分析に多くの経験がありますが、オーディオ分析にはまったく慣れていないことを述べておきます。

少しグーグルした後、私はたくさんのメロディー抽出アルゴリズムを見つけました。曲のポリフォニックオーディオ信号(例:.wavファイル)が与えられると、ピッチトラックを出力します---各時点で、支配的なピッチ(歌手の声またはメロディー生成機器から来る)を推定し、支配的なピッチを追跡します時間の経過とともにピッチします。

私はいくつかの論文を読みましたが、それらは曲の短時間フーリエ変換を計算し、スペクトログラムで分析を行って支配的なピッチを取得して追跡しているようです。メロディー抽出は、私が開発しようとしているシステムのコンポーネントにすぎないため、オーディオファイルで適切な処理を実行し、コードが利用可能である限り、利用可能なアルゴリズムを使用してもかまいません。私はこれに慣れていないので、どのアルゴリズムがうまく機能することがわかっているか、そしてそのコードをどこで見つけることができるかについての提案を聞いてうれしいです。

私は2つのアルゴリズムを見つけました:

  1. Yaaptピッチトラッキング
  2. Melodia

さまざまな音楽ジャンルの結果が非常に印象的だったので、私はMelodiaを選びました。結果を確認するには、これを確認してください。曲ごとに聞こえるハミングは、基本的に私が興味を持っているものです。

「この質問であなたの助けを求めているのは、任意の曲に対するこのハミングの生成です」。

アルゴリズム(vampプラグインとして利用可能)はピッチトラックを出力します--- [time_stamp、pitch / frequency] --- Nx2マトリックス。最初の列はタイムスタンプ(秒単位)で、2番目の列はドミナントピッチです。対応するタイムスタンプで検出されます。以下に示すのは、曲の時間領域信号(上)と紫色でオーバーレイされたアルゴリズムから得られたピッチトラックの視覚化であり、スペクトログラム/短時間フーリエです。負の値-ピッチ/周波数の値は、無声/非メロディックセグメントのアルゴリズムの支配的なピッチ推定を表します。したがって、すべてのピッチ推定値> = 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

このコードの背後にある基本的なロジックは次のとおりです。各タイムスタンプで、そのタイムスタンプで検出された支配的なピッチ/周波数に等しい周波数の短命の波(たとえば正弦波)を、次の時間に合成します。入力メロディマトリックスの次のタイムスタンプとのギャップ。私はこれを正しくやっているかどうかだけ疑問に思います。

次に、この関数から取得したオーディオ信号を取得して、元の曲(左チャンネルのメロディーと右チャンネルの元の曲)で再生します。生成されたオーディオ信号は、メロディーを生成するソース(音声/リード楽器)をかなりうまくセグメント化しているように見えますが、音声が存在する場所ではアクティブであり、他の場所ではゼロですが、信号自体はハミングとはほど遠いです( beep beep beeeeep beep beeep beeeeeeeep)著者が自分のWebサイトに表示します。具体的には、下の入力曲の時間領域信号と、私の関数を使用して生成されたメロディーの時間領域信号を示す視覚化です。

ここに画像の説明を入力してください

主な問題の1つは、各タイムスタンプで生成される波の周波数と持続時間が与えられているにもかかわらず、波の振幅を設定する方法がわからないことです。今のところ、振幅をフラット/一定の値に設定しましたが、これが問題の原因であると思われます。

誰かがこれについて何か提案がありますか?任意のプログラム言語(MATLAB、Python、C ++が望ましい)での提案を歓迎しますが、ここでの私の質問はより一般的だと思います---各タイムスタンプでウェーブを生成する方法は?

私の心の中のいくつかのアイデア/修正:

  1. 元の曲の時間領域信号から振幅の平均/最大推定値を取得して、振幅を設定します。
  2. 私のアプローチを完全に変えてください---曲のオーディオ信号のスペクトログラム/短時間フーリエ変換を計算してください。ピッチトラック内の周波数(またはピッチトラックに近い周波数)を除く他のすべての周波数をほとんど/ゼロアウトまたはソフトにカットオフします。次に、逆短時間フーリエ変換を計算して、時間領域信号を取得します。
4

4 に答える 4

5

私が正しく理解していれば、あなたはすでにピッチの正確な表現を持っているように見えますが、あなたの問題は、あなたが生成するものが単に「十分に良く聞こえない」ということです。

2番目のアプローチから始めます。ピッチ以外のものを除外しても、良いものにはなりません。ローカルピッチの推定値に対応するいくつかの周波数ビンを除くすべてを削除すると、入力信号のテクスチャが失われ、サウンドが良くなります。実際、それを極端に取り、ピッチに対応する1つのサンプルを除いてすべてを削除し、ifftを取得すると、現在行っているのとまったく同じ正弦波が得られます。とにかくこれを実行したい場合は、周波数領域に出入りするのではなく、時間信号にフィルターを適用するだけでこれらすべてを実行することをお勧めします。これは、より高価で面倒です。フィルターには、維持したい周波数の周りに小さなカットオフがあり、それにより、より良いテクスチャーのサウンドも可能になります。

ただし、満足のいくピッチと持続時間の見積もりがすでにあるが、サウンドレンダリングを改善したい場合は、正弦波を置き換えることをお勧めします。正弦波は、いくら送っても常に愚かなビープ音のように聞こえます。それらをマッサージします-スケールの各周波数の実際のハミング(またはバイオリンやフルートなど)のサンプルを使用します。記憶が懸念される場合、または表現する曲が十分に調整された音階に分類されない場合(たとえば、中東の歌を考えてください)、音階の各音符にハミングサンプルを設定する代わりに、いくつかの周波数。次に、これらのハミングサンプルの1つからサンプルレート変換を実行することにより、任意の周波数でハミングサウンドを導出します。サンプル変換を行うために選択するサンプルがいくつかあると、サンプリング変換の複雑さはその比率に依存するため、生成する必要のある頻度で「最良の」比率に傾くサンプルを選択できます。明らかに、サンプルレート変換を追加することは、選択するサンプルのバンクを持っているだけの場合と比較して、より多くの作業と計算が必要になります。

実際のサンプルのバンクを使用すると、レンダリングするものの品質に大きな違いが生じます。また、新しいノートを演奏するたびに現実的な攻撃を行うことができます。

そうです、あなたが提案するように、入力信号の瞬間的な振幅を追跡して、曲のより微妙なレンダリングを生成することによって、振幅を試してみることもできます。

最後に、あるサウンドから次のサウンドへの移行がスムーズになるように、継続時間の見積もりも試してみます。私がとても楽しんだオーディオファイルのパフォーマンス(beep beep beeeeep beep beeep beeeeeeeep)と表示するグラフから推測すると、曲のレンダリングに多くの中断が挿入されているように見えます。期間の見積もりを延長して、たとえば.1秒より短い無音部分を取り除くことで、これを回避できます。そうすれば、元の曲の本当の沈黙を維持しながら、曲の各音符を切り落とさないようにすることができます。

于 2013-03-18T04:42:48.077 に答える
3

私はあなたのsynth()関数にアクセスできませんが、それが取るパラメーターに基づいて、あなたの問題はあなたがフェーズを処理していないためだと思います。

つまり、波形スニペットを連結するだけでは不十分です。それらが連続的な位相を持っていることを確認する必要があります。そうしないと、2つの波形スニペットを連結するたびに波形に不連続性が生じます。もしそうなら、あなたはいつも同じ周波数を聞いていて、それは正弦波というよりはのこぎり波のように聞こえると思います-私は正しいですか?

解決策は、スニペットnの開始フェーズをスニペットn-1の終了フェーズに設定することです。位相の不連続性を作成せずに、周波数の異なる2つの波形を連結する方法の例を次に示します。

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ピッチビンの振幅だけではありません)。これは、メロディックピッチ抽出とブラインドソース分離の間のどこかで、重要な問題です。

第二に、音には、一定の周波数であっても、倍音やエンベロープなどの音色があります。あなたの合成方法は単一の正弦波を作成するだけですが、ハミングはおそらくピッチだけではなく多くのより高い周波数を含む、より興味深い倍音の束を作成します。もう少し自然なサウンドを得るには、各周波数のタイムスタンプを合成するときに、単一のピッチをハミングする自分のスペクトルを分析し、それぞれが適切な相対振幅で1つだけではなく、数十の倍音正弦波をすべて再現してみてください。あなたの分析で。また、1つの短い音をハミングする時間の経過に伴う振幅エンベロープを確認し、そのエンベロープを使用してシンセサイザーの振幅をモジュレートすることもできます。

于 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 に答える