フェルマータ

個人用のメモ。ソフトウェアの導入とかが多くなる予定。

WORLD のラッパー書いたので抽象度高めに音声の時間軸をいじれるようにした

ポエム

ちょうど一年前に WORLD を抽象度高めに利用できるラッパー実装を作ったのでその応用コードを書いた。ラッパー実装は下記の投稿の通り。

shurabap.hatenablog.jp

作ったけど応用編がなくて何も嬉しくない実装だったし、せっかく抽象度高めのコードを書いたのならと、音声を切り取ったり引き伸ばしたりできるサンプルを作った。これができると音声の切り貼りツールが作れる四歩手前くらいに来られるので一旦記事にする。別段音程変えたりとかもできるのだが一旦時間軸の変換にフォーカスした。 F0 をどうするとかの設計見えてないし。

できることのイメージ

端的に言って音声に対して下記のような編集が可能になる。

f:id:shurabaP:20210531220912p:plain
時間軸を切り取ったり伸ばしたりできるイメージ

これ単品で嬉しいかと言われると微妙なのだが、音程を変えたり音声同士をいい感じに混ぜてつなげることができれば、音声継ぎ接ぎ式の音声合成器が作れるのでその四歩手前の道具くらいにはなっている。

作ったもの

単純に音声を音程を変えずに二倍の長さに変えるコードを書いた。せっかくなので今年お迎えした琴葉姉妹の妹さんに手伝ってもらった。

// Copyright 2021 Hal@shurabaP.  All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

#include <stdio.h>
#include <SynthesizeSegment.hpp>
#include "data/Waveform.hpp"
#include "spectrogram/StretchedPartialSpectrogram.hpp"
#include "spectrogram/WaveformSpectrogram.hpp"
#include "world/SynthesizeImpulseResponseWithWORLD.hpp"
#include "world/SynthesizeSegmentWithWORLD.hpp"

using namespace uzume::vocoder;
using namespace uzume::vocoder::world;

class SimpleDoubledTimeAxisMap : public uzume::vocoder::TimeAxisMap {
public:
    SimpleDoubledTimeAxisMap() = delete;
    SimpleDoubledTimeAxisMap(double msLength) : _msLength(msLength) { }
    ~SimpleDoubledTimeAxisMap() = default;
    double at(double ms) const { return ms / 2.0; }
    double msLength() const { return _msLength; }
private:
    const double _msLength;
};

int main(int argc, char *argv[]) {
    if(argc < 2 || 3 < argc) {
        printf("usage: uzume_vocoder_sample (in filepath) (out filepath)<optional>");
        exit(-1);
    }
    const char *inPath = argv[1];
    const char *outPath = argc == 3 ? argv[2] : "output.wav";

    // simply analyze spectrogram from waveform.
    auto *in = Waveform::read(inPath);
    auto *inSpec = new WaveformSpectrogram(in);

    // prepare time axis map, output spectrogram and output waveform.
    auto *tam = new SimpleDoubledTimeAxisMap(inSpec->msLength() * 2.0);
    auto *outSpec = new StretchedPartialSpectrogram(inSpec, tam);
    auto *out = new Waveform(in->length * 2, in->samplingFrequency);

    // set synthesizer parameters
    SynthesizeImpulseResponseWithWORLD irs(outSpec->fftSize(), out->samplingFrequency);
    SynthesizeSegmentWithWORLD synthesize(&irs);

    SegmentSignal s(out->data, /* indexMin = */ 0, /* indexMax = */ out->length, out->samplingFrequency);
    SegmentParameters p(outSpec, /* startPhase = */ 0.0, /* startFractionalTimeShift = */ 0.0);

    // synthesize spectrogram in `outSpec` into output waveform in `out`.
    synthesize(&s, &p);

    // save waveform.
    out->save(outPath);

    return 0;
}

uzume_vocoder/main.cpp at master · haruneko/uzume_vocoder · GitHub

cmake があればビルドできるように作ってあるつもりだが cmake はずぶの素人なので動かなかったらごめんなさい。まだナイーブな実装が見え隠れするが、だいぶ抽象度高い状態で音声を扱えるようにしている。配列等々の扱いは一切しなくてよくなったので、さらなる応用時にはメモリ周りの処理を隠蔽するパーソンを作ってより抽象度を高めたい所存。 コードとしては、任意の時刻の F0、周期性成分、非周期性成分を取り出せるインタフェースを Spectrogram と大胆にも呼ぶこととし、音声の波形を入れると WORLD を使って Spectrogram を実装してくれる WaveformSpectrogram を実装した。さらに、任意の Spectrogram の時間軸を切り取り・伸縮することで Spectrogram を実装する StretchedPartialSpectrogram を実装した。サンプルではこれを使って読み込んだ波形を音程を変えずに二倍の長さにしているが、伸縮の加減は任意の関数を利用できるので比較的自由に扱える。これでナイーブな処理をだいぶ隠蔽できたので今回は満足。