フェルマータ

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

音声を伸縮したりピッチを変えたり編集するツールを試作してみた

概要

A.I. Voice の琴葉姉妹を購入したので、 Text2Speech で作成した音声をあとからちょっと色付けできるツールを作ってみた。ただ、私としてそこまで Text2Speech をやりたかったわけではないので、これが使えるツールになっているのかはわからない。どちらかというと下記の記事の続きものであり技術検証じみた趣味の開発であって、自身のニーズすらないところで作っているので的外れ感はあるかもしれない。

shurabap.hatenablog.jp

成果物とやれること

スクショ
* haruneko.github.io

44.1kHz 16bit の音声ファイルを下記のように編集できる

また、有声・無声区間を分けることで簡単な区間わけができるようになっている。 undo, redo とかでは実装する気力がなくてやれていない。

技術的所感

はじまりは自身の C++ のライブラリを TypeScript から使いたかったので emscripten を使い始めた。たしかに emscriptenC++ のごとき操作性を TypeScript の世界に持ち込んでくれる。が、いささか過保護であり C++ の世界観を持ち込むために wasm の限界を忘れているように思う。というのも wasm は VM とその VM 上で使える関数とメモリ領域を持っているだけなのに、あたかも C++ のように見せたいがため wasm 以外の JavaScript の領域にあまりにはみ出しすぎている。その流儀も C 言語の流儀に近く、 wasm が売れたとしてでは emscripten ごと受け入れられるのか?と言われるとやや懐疑的に見える。結局 wasm の限界をちゃんと見定めないと今はいいけどあとで死にそうだなと思った。あくまで wasm はプリミティブで処理速度の必要な範囲でとても小さく作るべきだなと思った。 一方で react はとても使いやすかった。理解しやすいし作りやすい。ただ、状態管理を行う際にシャローコピーやディープコピーといったプリミティブなところを結局気にしなければいけなかったり、 react 自身の暗黙の副作用を理解する必要があってつらいなあと思った。まあでもこれは過去 Qt を使って SIGNAL/SLOT を使っていたときよりはだいぶ楽になったと思う。 総じて見れば家事・育児を半分引き受けつつ仕事をしながら三ヶ月でこれくらいのものが作れるのであれば、案外余裕はあるんだなという所感を得た。一方で家族と話したり顔を見たりする時間は確実に減るので両立できないもどかしさは残る。トレードオフなので仕方ないか。

React + emscripten で音声ファイルを編集できるサンプルを作ってみた

概要

C++vocoderGUI を作ろうと思ったが、 C++ with Qt とかだとあんまり一般的な知見を得られないな、じゃあ React を使おう。ということで軽く言ったもののあんまり中身は軽くなくて下記のように苦しんでいた。が、ようやく React の世界に持ってこられたのでサンプルを作ってみた。これが私の React 入門編です。正直に言うと React + emscripten は全然入門じゃないと思う。

shurabap.hatenablog.jp

作ったもの

https://haruneko.github.io/voice-editor-practical-impl/

github.com

やっていること

github.com

この C++ で書かれたライブラリを

github.com

emscripten を利用してここで npm package 化し

www.npmjs.com

ここに公開されたパッケージを読み込んで

github.com

いい感じに読み込んで使ってみている。

感想

ツールとしては微妙だけれどやりたかった C++ のコードを利用する GUI を React というか TypeScript で書くのはできた。思ったよりも WebAssembly が軽快ではないため、ちゃんと作るなら並列処理をどうにかやらないと厳しいかもしれない。また、 F0 を編集できるようにすれば今よりは使い出のあるツールになると思うのでその辺まで作ってみたい。並列化よりはそっちのが楽でしょうし。 しかし今回 emscripten/wasm も React も初めて触ったけどこれは断じて入門編ではないですね、作りたいものがこれだったから仕方ないしチュートリアルやったときは React は髪の毛ほども理解できなかったのが今回は理解できたしいいけれど…理解のためには React と emscripten 両方とも本を通しで読みましたので末尾に書いておきます。

www.amazon.co.jp

網羅的に書かれている。ただし emscripten の glue コードを web app から使うためのコードはなかった。なかったんです。

oukayuka.booth.pm

3まで全部読んだ。網羅的にやってくれているのでこれを読んだ後に web の記事漁りながらコード書くのが効率良さそう。ただ、 TypeScript 触ったことなかったらこの本読むだけでも詰んでたなという気持ちがある。

cmake プロジェクトのライブラリを emscripten を使って nodejs とブラウザで動作する npm package にする

概要

すでにタイトルがなろう小説並に重たい。下記 2 エントリの続き物で今回ようやっと納得行くところまで作れたのでブログ記事にしておく。

やることはタイトルに書いてある通りなのだけれど詰みポイントが何箇所かあったのと、結構いろいろ知らないといけないことが多くて全部を書くととてもまとまらないので、結論を先に書いてから個人的にすごくつらかった wasm を webpack に読ませる箇所の回避方法を紹介する。

成果物

www.npmjs.com

github.com

とりあえず今回の成果物。タイトル通りのことをやっているが、いかんせん C++ にせよ nodejs にせよ本職の範囲から離れているのでこれがいいやり方かは知らない。動いたからいいやくらいの精神の成果なので参考にする際はご注意を。

詰みポインツと回避方法

実は過去記事で nodejs から使うのはあっさりできていて、簡単だったのだけれど wasm と emscripten の glue コードを webpack (react-app)から読むのが大変だった。大した情報もネット上になかったのでこの問題を解決するのにだいぶ時間がかかった。問題は大まかに2つ。

  • emscripten の作成する glue コード(js ファイル)が wasm ファイルに依存しているので webpack で wasm を配信できるようにする必要がある
  • emscripten の作成する glue コードそのままだと webpack で配信する wasm ファイルのパスを渡せない

だいたい glue コードのせい。

wasm を webpack で配信する

react-app を利用していたので webpack の設定をいじるために cracoを利用した。 wasm ファイルをみたら javascript/auto で配信してね、と emscripten の glue コードに nodejs 用の path, fs を利用するコードが含まれているので無視するように設定している。

module.exports = {
    webpack: {
        configure:{
            // See https://github.com/webpack/webpack/issues/6725
            module:{
                rules: [{
                    test: /\.wasm$/,
                    type: 'javascript/auto',
                }]
            },
            resolve:{
                fallback: { "path": false, "fs": false }
            }
        }
    }
};

wasm が配信できないのはこれで解決。ただしこれだけでもダメで、 webpack が配信する際に wasm ファイルの名前を変えてしまうので emscripten の glue コードにファイル名を渡す必要がある。

glue コードに配信している wasm のファイル名を渡す

なかなか発狂ものなのだが react-app から emscripten の glue コードを直接使っている場合単純にこれができない。なので emscriptenコンパイル時に pre.js, post.js によしなな JavaScript を書いてファイル名を渡せるようにする。

  • pre.js
var uzumeInitJsPromise = undefined;

var factory = function (moduleConfig) {

    if (uzumeInitJsPromise){
      return uzumeInitJsPromise;
    }
    uzumeInitJsPromise = new Promise(function (resolveModule, reject) {
        var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {};
        var originalOnAbortFunction = Module['onAbort'];
        Module['onAbort'] = function (errorThatCausedAbort) {
            reject(new Error(errorThatCausedAbort));
            if (originalOnAbortFunction){
              originalOnAbortFunction(errorThatCausedAbort);
            }
        };

        Module['postRun'] = Module['postRun'] || [];
        Module['postRun'].push(function () {
            resolveModule(Module);
        });

        module = undefined;
  • post.js
        return Module;
    });

  return uzumeInitJsPromise;
}

if (typeof exports === 'object' && typeof module === 'object'){
    module.exports = factory;
    module.exports.default = factory;
}
else if (typeof define === 'function' && define['amd']) {
    define([], function() { return factory; });
}
else if (typeof exports === 'object'){
    exports["Module"] = factory;
}

もともと emscripten には本当は Module.locateFile を上書きして wasm のファイル名を解決する方法があるのだがブラウザビルドで利用できないので無理やりこじあけておく。その上で下記のように wasm のファイル名を渡せばよい。

// Required to let webpack 4 know it needs to copy the wasm file to our assets
// @ts-ignore
// eslint-disable-next-line import/no-webpack-loader-syntax
import uzumejsWasm from "!!file-loader?name=uzumewasm-[contenthash].wasm!uzumejs/resources/uzumewasm.wasm";

...(中略)...

const u = await uzume({ locateFile: () => uzumejsWasm });

終わりに

先人の挑戦がなければ無理でした。 nodejs, webpack 初心者にわからんよこんなの…下記の sql.js のコードを見てなんとか理解した感じがある。ここまでやる必要があるし、その上でパッケージ利用者に負担がかかるのか、と悲しい気持ちになりました。とりあえず Rust でやればいいんじゃね?という気持ちになりました。まあ C++, C のコードは TypeScript の世界で動かしたい放題にはなったのでそれはよかったということにしておきます。

github.com

CMake の C++ プロジェクトを nodejs, TypeScript の世界に持っていく

ポエム

プライベートの開発環境は Qt with C++ を cmake でプロジェクト管理というスタイルだったのだが、いかんせんウェブエンジニアである本職とあまりに乖離していたので、もう少し本職に寄せておきたかった。最近は WebAssembly なるものもあり聞けば C++ のコードを WebAssembly にコンパイルして JavaScript から扱うことができるという。なので自作の C++ ライブラリを JavaScript で利用できる形にして、さらに nodejs プロジェクトで TypeScript から呼び出せるようにすれば、 GUI は web の世界に持ってこられるだろうと思い、今回は C++ を WebAssembly にして nodejs, TypeScript の世界に持ってくることにした。

やったこと

いままで Windows 環境で Windows なりの環境構築が厳しくなってきたので、 WSL2 をインストールして WSL から Ubuntu 使って開発することにしたので特筆なければ下記でやった内容は WSL, Ubuntu でやったことと思ってください。 10 年前後昔の環境でやってたらさすがになあ。

emscripten

cmake プロジェクトを WebAssembly (以下 wasm)にコンパイルするのに必要。細かいコマンドまで見ると面倒だが試しに使う分にはインストール後 cmake のオプションからパスを指定するくらいなのであまり意識しなくて良かった。

emscripten.org

インストールに関しては難しいことはない。公式のコマンドコピペでおわり。

CLion

qiita.com

こちらの方のセットアップをそのまま利用した。 CLion だけはまだ WSL での開発になっていないのでここは Windows で実施した。 emscriptenJavaScript にしたい C/C++ の機能に目印をつける役割のコードは C/C++ で書くので設定が必要。

C++

emscripten.org

emscripten の機能である embind を利用して JavaScript にどの機能を使えるようにしていくか書いていく。例えば時系列のデータを格納する Contour クラスだと下記のような感じ。

// Copyright 2022 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 "data/Contour.hpp"
#include <emscripten/bind.h>

using namespace emscripten;

EMSCRIPTEN_BINDINGS(ContourBindings) {
    class_<uzume::vocoder::Contour>("Contour")
        .smart_ptr<std::shared_ptr<uzume::vocoder::Contour>>("shared_ptr<uzume::vocoder::Contour>")
        .constructor(&std::make_shared<uzume::vocoder::Contour, double, double>)
        .function("at", &uzume::vocoder::Contour::at)
        .function("msLength", &uzume::vocoder::Contour::msLength)
        .property("length", &uzume::vocoder::Contour::length);
}

これは割と公式ドキュメント通りなので困らない。インタフェース的にメソッドがいくつか足りない気がするが本筋でないので今回は対処していない。

CMakeLists.txt

cmake_minimum_required(VERSION 3.1)

project(uzumejs)

include(ExternalProject)

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

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

add_executable(uzumejs
        ... # 割愛
        )

if (EMSCRIPTEN)
    set(CMAKE_EXECUTABLE_SUFFIX ".js")
    set(lflags "--pre-js ${CMAKE_SOURCE_DIR}/src/resources/pre.js --post-js ${CMAKE_SOURCE_DIR}/src/resources/post.js -s WASM=1 -s MODULARIZE -s 'EXPORT_NAME=\"factory\"' --no-entry --bind")
    set_target_properties(uzumejs PROPERTIES LINK_FLAGS ${lflags})
endif()

ここは結構厄介だった。ポイントを追って書いていく。

  • set(CMAKE_EXECUTABLE_SUFFIX ".js")

emscripten を通したコンパイルは実行ファイルを作ってくれるわけだが、ネイティブと違って JavaScript の世界では実行ファイルはこれ、という明確なのは指定しづらい。のでコンパイル結果の拡張子でどのファイルを出力させるのかを emscripten にお伝えする必要がある。

suffix 出力内容
html HTML + JavaScript + wasm
js JavaScript + wasm

さらに行くと wasm だけ出力するオプションもあるのだが今回は割愛する。 .html が指定されたときには出力された HTML が そのまま C++ の main を実行するコンソールやスクリーンを提供してくれて、 HTML, JavaScript, wasm をホスティングすれば C/C++ のコードがそのままブラウザ上で実行できるよう出力される。私にとっては高機能だがとりあえず C/C++ のコードをどーんと web に持っていきたいならこれで良さそう。 .js を選ぶと、 HTML を除いたものが作成される。私は nodejs で利用したいので今回は .js にしている。.js ファイルは html 上で動作することを前提にしているように見えるので、 nodejs に持っていくには高機能に見えるが面倒ないので一旦このまま使うことにした。(@types/emscripten を利用すると全機能使えそうだが今回は面倒なので見送り)

  • --pre-js ${CMAKE_SOURCE_DIR}/src/resources/pre.js --post-js ${CMAKE_SOURCE_DIR}/src/resources/post.js

前述の .js ファイルの前後になにか書きたいときにはこんな感じでファイルを直接指定できる。今回は空のファイルを置いた。

  • -s MODULARIZE -s 'EXPORT_NAME=\"factory\"'

ここが結構なハマりポインツで、どうも HTML とセットの利用用途がメインなのか .js ファイルはもともと他のファイルから利用できない形になっている。そのため、このオプションを指定して .js 内部の Module を作成してくれるファクトリメソッドを外部に公開する必要がある。今回の指定だと export function factory();.js ファイルに埋め込まれる。これに気づくまで時間がかかった。ここで factory を指定しているのは意味があって tsembind が生成する型定義ファイルは暗黙にここが factory 指定されている前提に合わせている。

tsembind

github.com

npm i -g tsembind

インストールしたら cmake プロジェクトから出てきた .js, .wasm ファイルのあるディレクトリで下記のようにすると標準出力に TypeScript の型定義が出力される。

tsembind YOUR_JS_NAME.js

これでよしなな名前のファイルに書き込めば、私のプロジェクトだと uzumejs.d.ts, uzumejs.js, uzumejs.d.ts の TypeScript から利用するのに必要な 3 ファイルが手に入る。

nodejs

上述で作られたファイルなどをあわせて下記のような形のツリー構造にする。

projectRootDir/
 + pkg
 | + uzumejs.d.ts
 | + uzumejs.js
 | + uzumejs.wasm
 + src
   + index.ts

TypeScript のプロジェクトの構築は下記の感じ。とりあえず動かす用途なので細かい設定はしていない。真面目にパッケージ化するとかやるなら webpack の設定が必要そうだなーと思っているが初心者なので動くのを優先した。

qiita.com

index.ts を下記のようにした。

import uzumejs from "../pkg/uzumejs"

uzumejs().then(uzume => {
    const contour = new uzume.Contour(1000.0, 2.0);
    console.log(contour.msLength());
})
shuraba_p@MyComputer:~/projects/uzumejs$ npx ts-node src/index.ts
1002

というわけで C++ で書かれた機能が TypeScript のコードから動いた。やったね。

終わりに

nodejs 初心者なので本当に手間取った。あと私の開発環境古すぎ???問題があってだいぶ更新をすることになった。まあ悪いことではないがちょっと開発自体からおいていかれている感があってつらい。とりあえずこれで nodejs から動かしながら C++ のコードを修正したり、 nodejs 上で GUI 作ったりのスタートラインに立てた。なんかそう書くと全然先が長くて厳しいな。とりあえず現状のものは下記のレポジトリに置いた。久々にきついのをやったが子育てと仕事しながらならこんなもんか。

github.com

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