フェルマータ

個人用のメモ。ソフトウェアの導入とかが多くなる予定。ライセンスの気になる方はこのブログに載せたコードは修正 BSD に準ずるものと考えてください。

Emscripten を使って C++ のコードを JavaScript の世界に持っていく

ポエム

フロントエンドは大変苦手なのだが、まー最近は JavaScript 頑張らなければ、と思いつつもブラウザで何かを作るのはさほど興味がわかなかった。が、ここ数年で C++ のコードを JavaScript の世界に持ってくるのはだいぶスマートになったようで今回は Emscripten 使って過去に作った 今さら C++ で WORLD のラッパーライブラリを書いてみた - フェルマータ というやつを JavaScript の世界に持っていくのを試してみた。

できなかったこと

過去作ったコードは下記のように ExternalProject_add を通して依存性の解決をできるようにしていたが、 ExternalProject_add で作った static library を wasm から利用するのは厳しかった。

ExternalProject_add(uzume_vocoder
        PREFIX ${CMAKE_CURRENT_BINARY_DIR}/uzume_vocoder
        GIT_REPOSITORY https://github.com/haruneko/uzume_vocoder
        GIT_TAG master
        INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}
        CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}"
        )

emscripten を cmake で利用するためにはオプションに -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake (${EMSCRIPTEN} は emsdk への PATH )的なやつを使わねばならず、これをつけると cmake の add_library がなんのバイナリも作成しなくなってしまい依存性が解決できなくなってしまった。いくつか情報を調べたりしてみたが解決策が思いつかなかったので今回はこの方法は取らないことにした。

やったこと

github.com

結局こんな感じの実装にしてみたところ、 JavaScript から C++ のコードが呼べるのを確認できた。 CMakeLists.txt は下記のようにした。

cmake_minimum_required(VERSION 3.1)

project(uzume_emscripten)

include(ExternalProject)

ExternalProject_add(uzume_vocoder
        PREFIX ${CMAKE_CURRENT_BINARY_DIR}/uzume_vocoder
        GIT_REPOSITORY https://github.com/haruneko/uzume_vocoder
        GIT_TAG master
        INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}
        CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}"
        )

file(GLOB_RECURSE VOCODER_SOURCES ./lib/uzume_vocoder/src/*.cpp)
file(GLOB_RECURSE VOCODER_HEADERS ./lib/uzume_vocoder/src/*.hpp)

include_directories(uzume_emscripten PRIVATE ./lib/uzume_vocoder/src/)

add_executable(uzume_emscripten
        src/data/ContourBindings.cpp
        src/main.cpp
        ${VOCODER_SOURCES}
        ${VOCODER_HEADERS}
        )

if (EMSCRIPTEN)
    set(CMAKE_EXECUTABLE_SUFFIX ".html")
endif()

set_target_properties(uzume_emscripten PROPERTIES LINK_FLAGS "-s DEMANGLE_SUPPORT=1 --bind")

結局とっても野蛮なのだが落としてきたコードの install などは完全に無視して、直接ソースコードに依存することにした。もうちょいマシな方法はあるのかもしれないが、ネットの大海にも C++ の情報は少なく、子育てやっている状況で調べられないのでここまでとする。雑に実行した内容は下記の通り。

開発者コンソールでの実行の模様

一つハマりポインツとしてお伝えするのですが下記記事を参考にしたところ、 error: undefined symbol: _embind_register_class (referenced by top-level compiled C/C++ code) がバカスカ出てしまって困ってしまった。

qiita.com

下記記事で LINK_FLAGS ちゃんとするといいよと書かれておりこちらで解決した。まあ今回は本当に雑なのでここまで。

stackoverflow.com

Nodejs から C++ のコードを使うために CMake-js を使ってみた

ポエム

CMake 使ったプロジェクトでいい感じに WORLD のラッパー実装を作ってみたが、 GUI を作ろうとするとはたして C++GUI 作るのがいまでもいいのかどうか悩んでしまった。 Twitter を通して知人友人に色々聞いてみたのだが、下記の選択肢が挙がってきた。

ちょっと今回はいろいろ考えた結果 C++ で完結するよりは、目下興味のある Electron 側に舵を切ってみることにした。これをやるには nodejs から C/C++ のコードを読み出さなければならいので、さらに下記のような選択肢が出てきた。

んで、 WebAssembly は下記のような実装する必要があるよ、と教えてもらったのだが業務ならともかく私の余暇の時間ではちょっとしんどそうだったので諦めた。

t.co

本当は nodejs に依存しすぎるのも嫌だったので WebAssembly 使えたら良かったんだけれど、いかんせん C/C++ を使っていくには先人の知見が少なかったので今回は CMake-js を使ってみることにした。とりあえず書き残さないと先に進まなさそうだったので備忘録がてら記事にしておく。

実装

tutorial 通りなのでそのまま書いただけ。コアの部分は C/C++ に依存しているだけあって、結構シンプルにいけるっぽい。

  • index.js
#include <node_api.h>
#include <cassert>
#include <thread>

namespace {
    napi_value hello(napi_env env, napi_callback_info info)
    {
    napi_status status;
    napi_value js_value;
    status = napi_create_string_utf8(env, "Hello, cmake-js!", NAPI_AUTO_LENGTH, &js_value);
    assert(status == napi_ok);
    return js_value;
    }

    void export_func(napi_env env, napi_value exports, const char *name, napi_callback f)
    {
    napi_status status;
    napi_value func;
    status = napi_create_function(env, name, NAPI_AUTO_LENGTH, f, nullptr, &func);
    assert(status == napi_ok);
    napi_set_named_property(env, exports, name, func);
    }

    napi_value Init(napi_env env, napi_value exports)
    {
    export_func(env, exports, "hello", hello);
    return exports;
    }

    NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
}
var hello = require('./build/Release/hello.node');

console.log(hello.hello());
  • CMakeLists.txt
# From https://github.com/cmake-js/cmake-js/wiki/TUTORIAL-01-Creating-a-native-module-by-using-CMake.js-and-NAN

cmake_minimum_required(VERSION 3.0)

# Name of the project (will be the name of the plugin)
project(hello)

# Build a shared library named after the project from the files in `src/`
file(GLOB SOURCE_FILES "*.cc" "*.hh")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

# Gives our library file a .node extension without any "lib" prefix
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

# Essential include files to build a node addon,
# You should add this line in every CMake.js based project
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC})

# Essential library files to link to a node addon
# You should add this line in every CMake.js based project
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})

レポジトリ

github.com

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 を実装した。サンプルではこれを使って読み込んだ波形を音程を変えずに二倍の長さにしているが、伸縮の加減は任意の関数を利用できるので比較的自由に扱える。これでナイーブな処理をだいぶ隠蔽できたので今回は満足。

令和 3 年度川口市保育所探しのための自宅と駅からの距離算出 Google Apps Script を作成した

ポエム

普段ポエムと趣味を垂れ流しながらコードを書くブログが唐突に現実の話をするのだが、去年第一子が幸いなことに母子ともに無事産まれ妻の職場復帰を目指して保育所探しをすることになった。保育所を探すにあたり妻から保育所のリストを作成せよとの要望があったので、市のホームページから保育所一覧を出し我々の通いやすい保育所を探そうとした。我が家の保育所選びの条件として、そもそもゼロ歳児保育をしているかなどの諸々の条件はあったのが、結局の所近場に通うのが最も良いだろうということで、預けに行くときには家からの距離が迎えに行くときには駅からの距離が肝要という話になり、自宅からと駅からの距離が一定距離より近いことを挙げた。とはいえ 174 箇所もある保育所すべての距離を手で Google 先生に聞くのはそれなりに骨であったので、遊びを兼ねて Google Apps Script を作成し簡単に計算できるようにした。

作ったもの

// コピペ元:https://it-soudan.com/how-to-get-distance-with-google-map-api/

function calcDistance(src, dest) {
  var directionFinder = Maps.newDirectionFinder()
    .setOrigin(src)
    .setDestination(dest)
    .setMode(Maps.DirectionFinder.Mode.DRIVING)
    .setLanguage("ja");
  var directions = directionFinder.getDirections();
  var route0 = directions.routes[0];

  directionFinder.setAvoid(Maps.DirectionFinder.Avoid.HIGHWAYS);
  directions = directionFinder.getDirections();
  var route1 = directions.routes[0];

  return [route1.legs[0].distance.value / 1000, route0.legs[0].distance.value / 1000];
}

function setUpDistance() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var list = ss.getSheetByName("保育所リスト");
  var addresses = ss.getSheetByName("自宅と駅の場所");
  
  var home = addresses.getRange(1, 2).getValue();
  var station = addresses.getRange(2, 2).getValue();;

  var dat = list.getDataRange().getValues();

  // 各行を順に処理する
  for (var i = 1; i < dat.length + 1; i++){
    const src = list.getRange(i, 3).getValue().replace("(地図)", "");

    if(list.getRange(i, 4).getValue() !== true && !list.getRange(i, 7).getValue()) {
      list.getRange(i, 6).setValue(calcDistance(src, home));
      list.getRange(i, 7).setValue(calcDistance(src, station));
    }
  }
}

使い方

「自宅と駅の場所」シートに自宅・駅の住所を書き、上記スクリプトsetUpDistance 関数を実行すると諸々の距離が計算される。ただ、全部の保育所を二回計算すると Google の距離算出 API の呼び出し回数制限に引っかかってしまうので、試す場合は予め保育所を絞った上で実行したほうが良いかもしれない。また、川口市保育所としているがあくまでリストが川口市のものなだけで、他の地域のリストに変更すれば同様に距離を算出することはできる。中身については参考にした下記の記事を参照されたい。

it-soudan.com

普段あんまりこういう形でのコードは書かないのでそれなりに新鮮ではあった。ただ、この手のコードを解説するのはあんまりおもしろくなくやる気が全く出ないのがわかったのでもう少し込み入ったコードを書きたい。

Google Home ことはじめ

ポエム

半年前くらいに第一子が産まれたのでプライベートのタスクが爆増していたのだが、ようやく落ち着いてきたので趣味プロ兼実用のために Google Home で使えるアプリを作りたくいろいろ調べていた。というのも、子育てのタスクは絶妙に片手の半分が空く程度にしか手が使えず長く薄く注意を払う必要がある。こういうときにレシピの検索などをしたいが、入出力のデバイスとしてスマートフォンでは集中力が必要すぎるし、そうなると俄然スマートスピーカーによる音声入出力は魅力的に見えてきた。そこで冒頭のように Google Home を制御するにはどうしたらいいのかを調べてみた。 後述するがあまりちゃんとは調べていない。というのも Google Assistant を拡張する方法で Google Home をいじる方法を調べたが最初から実装するにはやや量が多く、手っ取り早く触ってみるに向いていないためぼくのような初心者が手を出すものではないと判断した。

やりたいこと

ぼくのやりたいことはシンプルで「冷蔵庫の中身を保持しておき残りものからよしなな料理とレシピを紹介してくれること」「入出力デバイスGoogle Home にする」である。後者をよくわかっていないのでとりあえず何を使えばいいのか知りたい。

結論

Actions on Google だと現段階ではできること&実装することが多すぎるので次は Dialogflow + Cloud Functions の構成を調査する。 Dialogflow + Cloud Functions の構築については下記のようなブログに詳しい。現時点で Dialogflow がどの部分を対応しているのかは把握していない。

qiita.com

調査内容

Actions on Google Smart Home

基本的には下記の公式ページにあたるのが良い。

developers.google.com

やること

Actions on Google を使って Google Home などのデバイスからの操作を受け付けるには Fulfillmentと呼ばれるやりとり を通じて下記のように応答を行う必要がある。

図 1. クラウドでの実行パス( https://developers.google.com/assistant/smarthome/concepts/fulfillment-authentication より)

この Developer Cloud なのだが Fulfillment だけならともかく、もろもろの実装要件がありなかなかこれがヘビーである。

  1. OAuth 2.0 の authorization code grant flow の実装
  2. OAuth 2.0 の authorization header による API の認可
  3. 認可したユーザーの CRUD 操作
  4. Smart device の Intent 処理(Fulfillment の実装)

だいたい 1. のあたりからちょっと面倒すぎる。その代わりすべての細かい処理を書ける(=書かなければならない)ので高度なことをやるのであれば対応すればよい。ただ、この辺でぼくの手には負えなさそう& Dialogflow + Cloud Fnctions でお手軽に実装できるという記事を観測していたので一旦調査を打ち切った。

終わりに

音声合成を C, C++ 言語でやっていたりするとよくあるのだが、波形ファイルを読み込もうとするとバイナリの形式の説明が出てくるマッチョイムズとは反対で、 web 系は how は非常にそろっている一方、すぐに検索できる記事では why が欠けてしまって疑問に思ったので調べた記事を残した。中身を詳しく見たわけではないので単品の記事としては微妙な感じだけれども折角調べましたし。

今さら C++ で WORLD のラッパーライブラリを書いてみた

ポエム

現実世界の不具合で出勤時間が消失した分体力が余っているのか、余暇に回す気力がだいぶ戻ってきたので 1000000000 年ぶりくらいに趣味プロをしている。とはいえ別段すごく作りたいものがあるわけではないので、惰性で昔作りたかった WORLD をいい感じに抽象化したライブラリを作ってみた。これ使って何しようかは浮かんではいないのだが、音声の波形を切り貼りするコードはぼくが遊ぶ分にはだいぶ書きやすくなるので一区切りして記事にしておく。

やったこと

WORLD を利用した C++ のラッパーを作った。その際昔 v.Connect 作ってて面倒くさかった周期性・非周期性成分の扱いを抽象化した。と言ってもなんのこっちゃなので下記のコードを見てほしい。

    void SpectralEnvelopeEstimation(double *x, int x_length, WorldParameters *world_parameters)
    {
        CheapTrickOption option;
        InitializeCheapTrickOption(world_parameters->fs, &option);

        // put default parameters into CheapTrickOption.
        option.q1 = -0.15;
        option.f0_floor = 71.0;

        // Parameters setting and memory allocation.
        world_parameters->fft_size =  GetFFTSizeForCheapTrick(world_parameters->fs, &option);
        world_parameters->spectrogram = new double *[world_parameters->f0_length];
        for (int i = 0; i < world_parameters->f0_length; ++i) {
            world_parameters->spectrogram[i] = new double[world_parameters->fft_size / 2 + 1];
        }

        DWORD elapsed_time = timeGetTime();
        CheapTrick(x, x_length, world_parameters->fs, world_parameters->time_axis, world_parameters->f0, world_parameters->f0_length, &option, world_parameters->spectrogram);
        std::cout << "CheapTrick: " << timeGetTime() - elapsed_time << " [msec]" << std::endl;
    }

このコードの大本はWORLD のサンプルコードである。元のコードは下記を参照のこと。

音声は、趣味で DX ライブラリ使ってゲームもどき作ったり仕事でウェブアプリを作るのと違いだいぶ扱いが面倒くさく(修羅場P調べ)、 v.Connect で歌声合成するときには時系列の波形データそのものでは扱いづらいため波形データから音の高さ・声の母音の成分・声のそれ以外の成分(雑に書くと正しくないのでこのあとはそれぞれ、F0、周期性成分、非周期性成分と言う)の3つの成分に WORLD を使ってばらしてから再合成していた。このばらした情報は先に挙げたコードだと例えば周期性成分は double **WorldParameters::spectrogram の中に二次元配列として格納される。この二次元配列はいわゆる音のスペクトルの画像なので、サンプルのコードのように小さいプログラムなら別にこの持ち方でも十分便利ではあり、 v.Connect も結局実装的には本当に楽なので内部的に波形を二次元配列に直しては切り貼りをする処理が書かれている。

しかしこのデータの持ち方、小規模のうちは特に気にならないのだが UTAU 音源を読み込んだりして波形の数が二桁以上になってくると二次元配列だけにバカスカメモリを食い始めて扱いが面倒になってくる。さらによく考えてみると二次元配列で持つかどうかは別段実装都合であって、翻って v.Connect みたいに UTAU 音源使って声を切り貼りするみたいな用途に注目すれば、単純にある時刻の F0、周期性成分、非周期性成分がわかりさえすればいいことに気づく。となると下記のインタフェースだけ実装されていれば音声の切り貼りをする歌声合成器を作るのには十分なのだ。

class Spectrogram {
public:
    /**
     * Spectrogram#pickUpSpectrumAt picks up an instant spectrum on the specified moment.
     */
    virtual bool pickUpSpectrumAt(Spectrum *destination, double ms) const = 0;

    /**
     * Spectrogram#f0At returns f0 value of spectrogram at ms[milli seconds].
     */
    virtual double f0At(double ms) const = 0;
};

裏側に二次元配列を持つかどうかは実装詳細の都合なので何でも良い。今回はバカスカメモリを食うのは嫌だったので、ある時刻の周期性成分と非周期性成分はスペクトルを引っ張ってくる pickUpSpectrumAt メソッド内で計算する実装に挑戦した。数年前のぼくがどうしても実力不足で作れなかったのだが数年ぶりに書いてみたらなんとかなったので大変うれしい。

できたものと使い方

github.com

実装者の環境は下記である。この書き方であってんのか? C++ の事情はよくわからんのでぼくの環境でしか動かないかもしれないが調べる余力はない。

harun@MyComputer MINGW64 /g/Projects/uzume/uzume_dsp
$ cmake --version
cmake version 3.14.4

CMake suite maintained and supported by Kitware (kitware.com/cmake).

harun@MyComputer MINGW64 /g/Projects/uzume/uzume_dsp
$ g++ --version
g++.exe (Rev2, Built by MSYS2 project) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

とりあえず波形の分析と合成の部分を自作のインタフェースで行えるところまで実装した。オリジナルの WORLD と音質は差がない(個人の感想です)になったので一旦満足。裏側の実装はどうでもいいと言ったが、波形を与えれば二次元配列を裏で確保することなく、その波形の特定の時刻の F0、周期性成分、非周期性成分を計算できるところまで作った。 使い方は CMakeLists.txt に下記のような依存を書く。この書き方が洗練されているかは C++ は普段遣いではないのでよく知らないので参考程度に。

## Dependencies
ExternalProject_add(uzume_dsp
        PREFIX ${CMAKE_CURRENT_BINARY_DIR}/uzume_dsp
        GIT_REPOSITORY https://github.com/haruneko/uzume_dsp
        GIT_TAG master
        INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}
        CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}"
        )
add_dependencies(your_app uzume_dsp)
link_directories(your_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lib)
target_include_directories(your_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)
target_link_libraries(your_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lib/libuzume_dsp.a)

んで、例えば /path/to/input.wav/path/to/output.wav に分析・再合成するのであれば下記のようなコードを書けばよい。

#include <algorithm>

#include "SynthesizeImpulseResponseWithWORLD.hpp"
#include "SynthesizePhraseWithWORLD.hpp"
#include "Waveform.hpp"
#include "WaveformSpectrogram.hpp"

using namespace uzume::dsp;

// This is a sample to use dsp directory.
int main() {
    const char *inputPath = "/path/to/input.wav";
    const char *outputPath = "/path/to/output.wav";
    Waveform *input = Waveform::read(inputPath);
    Waveform *output = new Waveform(input->length, input->samplingFrequency);
    WaveformSpectrogram spectrogram(input);

    SynthesizeImpulseResponseWithWORLD irs(spectrogram.fftSize(), input->samplingFrequency);
    SynthesizePhraseWithWORLD synthesize(&irs);

    for(unsigned int i = 0; i < output->length; i++) {
        output->data[i] = 0.0;
    }

    PhraseSignal s(output->data, /* indexMin = */ 0, /* indexMax = */ output->length, output->samplingFrequency);
    PhraseParameters p(&spectrogram, /* startPhase = */ 0.0, /* startFractionalTimeShift = */ 0.0);

    synthesize(&s, &p);

    output->save(outputPath);
}

肝は Spectrogram クラスで、このクラスをとりあえず実装して spectrogram に突っ込んでおけば上記のようなコードで音声が合成できる。上のコードでは WaveformSpectrogram という波形データをスペクトルとして扱えるようにするクラスを利用しているが、他の実装に変えて例えば大量の波形データの切り貼りしたスペクトルを返すような実装にしてあげれば、いい感じの切り貼りツールが作れる。

直リンク系ライブラリの CMakeLists.txt の書き方調べた。

ポエム

CMakeLists.txt は便利なのだが、ちゃんと書き方を考えるほど切羽詰まった場面が自分にはなかった。今回は書いていて気になったのでちゃんと書く方法を調べたりしてみた。正直知見がなかったので何でググったっけを書いておくメモみたいなものである。

やりたいこと

  • static に依存させるライブラリを作成したい
  • 作成したライブラリは ExternalProject などで github 上から依存解決をしたい
  • なるべくならグローバルを汚染しないで依存解決をしたい

要は cmake でのライブラリの書き方がわかれば良い。ただ、静的ライブラリであれば add_subdirectory すればよくね?という気持ちがないではない。今回は何が何でも静的リンクさせたかった、 cmake 経由でやれたことなかったからね。

やること

ライブラリ側

特殊なことは今回はしないのでこれだけ。target_sources コマンドでコンパイル対象を指定し、 set_property コマンドから PUBLIC_HEADER プロパティを指定してインストール時にインストールすべきヘッダを指定して、 target_include_directories で自身のヘッダファイルの場所を指定して、 install コマンドでインストール先を指定している。あとはこの辺の単語でググれば最終的にはリファレンスにぶつかって解決できると思う。

  • CMakeLists.txt
project(sample_lib VERSION 0.0.1 LANGUAGES CXX)

add_library(sample_lib STATIC)
target_sources(sample_lib 
        PRIVATE
        # .cpp ファイルはここに書く
        )
set_property(TARGET sample_lib
        PROPERTY PUBLIC_HEADER
        # .hpp ファイルはここに書く
        )
target_include_directories(sample_lib 
        INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> # ライブラリのビルド時に読み込むべき include ディレクトリ
        $<INSTALL_INTERFACE:include>) # ライブラリのインストール時(依存側で)読み込むべき include ディレクトリ
install(TARGETS sample_lib 
        EXPORT libsample_lib
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        PUBLIC_HEADER DESTINATION include)

利用する側

ExternalProject_add で CMakeLists.txt のあるディレクトリやレポジトリを指定すると、 cmake が走りよしなにビルドしたりインストールしてくれる。 INSTALL_DIR などでインストール先指定しているけれど、特段指定しなければデフォルトのディレクトリに突っ込まれる。が、 Windows 環境でやっていたのでインストールステップ時に C:\Program Files\ 下にライブラリをインストールしようとし、パーミッションが足りずに失敗する罠に引っかかったりするの利用側で指定してしまうのが、別段深い知見はないのだが、良いのではと思った。

  • CMakeLists.txt
project(test_app)

add_executable(test_app main.cpp)

include(ExternalProject)
## Dependencies
ExternalProject_add(sample_lib
        PREFIX ${CMAKE_CURRENT_BINARY_DIR}/sample_lib
        GIT_REPOSITORY https://path/to/sample_lib
        GIT_TAG master
        INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}
        CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}"
        )
add_dependencies(test_app sample_lib)
link_directories(test_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lib)
target_include_directories(test_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)
target_link_libraries(test_app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lib/libsample_lib.a)

あとがき

別段ベストプラクティスではないと思うので動くもの書いたくらい。 lib とか .a とか cmake 側でちゃんと生成して作ったほうがいいし、そも add_subdirectory と差があるんだっけとか、たぶん改善ポイントはすごく多い。 あと、今回作ってて依存側が依存先のことを超絶細かく知っていてもおかしくないという作りに本能が納得するまですごく時間がかかった。仕事では利用先のライブラリの名前とバージョン書いたら利用できることが多く、ここまでビルドやインストールの中身を意識したのが久々でたいへんつらかった。