備忘録的な

ゆる〜く技術全般。

Bark with Colab

はじめに

何か業務に活かせないかなぁと生成AI系にアンテナを張っている今日この頃なのですが
最近Barkというtransformerをベースとしたtext-to-audioのモデルが公開されました。 特徴としては、実際の口語に近い雰囲気(棒読みのような単一ではない感じ)をうまく再現できているなと感じました。
日本語も対応していますが、英語圏の人が話す日本語っぽい発音です。「す」が「th」の発音など。

また、プロンプトのテキストにをつけると歌ってくれたり、[laughs]をつけるとテキストの文言に馴染むように笑って発音します。 この辺はREADMEにサンプル音声付きで詳しく書いてあるのでそちらを参照してください。

尚、CC-BY 4.0 NCライセンスですので営利目的での利用はできません。(bark/LICENSE)

この記事でやること。

  • Bark 実行環境 [Google Colab]
  • Bark voice clone [Google Colab]

Bark 実行環境 [Google Colab]

以下をそのままColabに貼り付けて実行すればOKです。UIが立ち上がります。
makawy7/bark-webuiを盛大に参考にしています。

!pip install gradio git+https://github.com/suno-ai/bark.git
import gradio as gr
from bark.generation import SUPPORTED_LANGS
from bark import SAMPLE_RATE, generate_audio
from scipy.io.wavfile import write as write_wav
import os
from datetime import datetime


def generate_text_to_speech(text_prompt, selected_speaker, text_temp, waveform_temp):
    audio_array = generate_audio(text_prompt, selected_speaker, text_temp, waveform_temp)

    now = datetime.now()
    date_str = now.strftime("%m-%d-%Y")
    time_str = now.strftime("%H-%M-%S")

    outputs_folder = os.path.join(os.getcwd(), "outputs")
    if not os.path.exists(outputs_folder):
        os.makedirs(outputs_folder)

    sub_folder = os.path.join(outputs_folder, date_str)
    if not os.path.exists(sub_folder):
        os.makedirs(sub_folder)

    file_name = f"audio_{time_str}.wav"
    file_path = os.path.join(sub_folder, file_name)
    write_wav(file_path, SAMPLE_RATE, audio_array)

    return file_path


speakers_list = []

for lang, code in SUPPORTED_LANGS:
    for n in range(10):
        speakers_list.append(f"{code}_speaker_{n}")

input_text = gr.Textbox(label="Input Text", lines=4, placeholder="Enter text here...")
text_temp = gr.Slider(
    0.1,
    1.0,
    value=0.7,
    step=0.1,
    label="Generation Temperature",
    info="1.0 more diverse, 0.1 more conservative",
)
waveform_temp = gr.Slider(
    0.1,
    1.0,
    value=0.7,
    step=0.1,
    label="Waveform temperature", info="1.0 more diverse, 0.1 more conservative"
)
output_audio = gr.Audio(label="Generated Audio", type="filepath")
speaker = gr.Dropdown(speakers_list, value=speakers_list[60], label="Acoustic Prompt")


interface = gr.Interface(
    fn=generate_text_to_speech,
    inputs=[input_text, speaker, text_temp, waveform_temp],
    outputs=output_audio,
    title="Text-to-Speech using Bark",
    description="A simple Bark TTS Web UI.",
)

interface.launch()

Bark voice clone [Google Colab]

serp-ai/bark-with-voice-clone

こちらはBarkをforkしてクローン音声を生成できるようにしたやつです。
7秒未満の音声とテキストのペアを用いて、音声を複製することができます。つまり、任意の声で文章を読み上げることができます。

こちらはipynbが公開されているので、コピーしてそのまま各自任意のファイルを読み込むように変更するだけで動きます。
Colab環境ですと.npzの生成から音声の生成(generate_audio)まで10分ほどかかります☕

私が試した時には、サンプリング対象を変えたりハイパーパラメータを何度か変更し数時間粘ってみたものの、全く似ていなかったりノイズが混じっていたり文章を適切に読み上げない事が多々ありました。
tempを下げすぎる(4以下とかにする)と原型のないノイズ音のみになるので注意してください。

一応、数時間粘って一番似ていると思った音声を貼っておきます。

触ってみた所感としては

  • 生成される音声が安定してない。
  • けど7秒未満の短い音声サンプルでここまで出来るのは素晴らしい

今後また更新されるようなので、期待ですね。

追記

Hugging Faceというサイトで、様々なmodelやdatasetの公開がされています。 眺めているだけで1日終わりそうです。

RubyのObjectについて

Ruby で扱える全ての値はオブジェクト

とはよく言われるけど、これが実際には何なのか。
Rubyのオブジェクトチョットワカルって言いたいので調べた。

すごい長くなりそうなので(オブジェクトといっても構造体が複数種あるため)
まずは、自分たちが定義したクラスから生成されるオブジェクトの実体が何なのかから。

結論から言うと
クラス(RClass構造体)から生成されたオブジェクトの実体はRObject構造体である

???と言う事で、実際のコードを見てみる

// ruby/include/ruby/internal/core/robject.h#L48-L58
struct RObject {
    struct RBasic basic;
    union {
        struct {
            uint32_t numiv;
            VALUE *ivptr;
            void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */
        } heap;
        VALUE ary[ROBJECT_EMBED_LEN_MAX];
    } as;
};

これだけ見ると割とシンプル

  • RBasic・・・次で説明
  • numiv・・・インスタンス変数の数
  • ivptr・・・インスタンス変数の値へのポインタ。あくまで変数名は格納されず値のみ
  • iv_index_tbl・・・インスタンス変数の変数名とivptrの配列内の値を対応させるハッシュテーブル(st_table)へのポインタを格納。変数名はRClass構造体に格納される。

RBasicは

RBasic {
    VALUE flags;                /**< @see enum ::ruby_fl_type. */
    const VALUE klass;

#ifdef __cplusplus
  public:
    RBIMPL_ATTR_CONSTEXPR(CXX11)
    RBIMPL_ATTR_ARTIFICIAL()
    RBIMPL_ATTR_FORCEINLINE()
    RBIMPL_ATTR_NOALIAS()
    /**
     * We need to define this explicit constructor because the field `klass` is
     * const-qualified above,  which effectively  defines the  implicit default
     * constructor as "deleted"  (as of C++11) --  No way but to  define one by
     * ourselves.
     */
    RBasic() :
        flags(RBIMPL_VALUE_NULL),
        klass(RBIMPL_VALUE_NULL)
    {
    }
#endif
};

これまたシンプルでRBasicはflagsklassの2つの値を持っている
flagsは、内部で多目的に使われているんですが、例えば構造体の型(後述のRStringやRArrayとか)を識別するのにも使っています。
klassは、オブジェクトが何のインスタンスであるかを示すクラスポインタ。

以上をふまえて、超ざっくりで言うと
オブジェクト(RObject構造体)は、クラスへのポインタとインスタンス変数の集合体
なので、Class内に定義したmethodsはオブジェクトには含まれていないです。

... 具体的によくわかんねーよって感じになると思うので
実際のRubyコードと対応させて説明してみる。

class Person
    attr_accessor :name, :age
end

上のシンプルなクラスからオブジェクトを生成する。

irb#1(main):001:0> hoge = Person.new
=> #<Person:0x007fe70187f7e0>

irb#1(main):002:0* hoge.name = 'john'
irb#1(main):003:0> hoge.age = 22

irb#1(main):004:0> hoge
=> #<Person:0x007fe70187f7e0 @name="john", @age=22>

この時の、#<Person:0x007fe70187f7e0>部分のPerson はクラスポインタの値で、16進数の0x007fe70187f7e0 はオブジェクトのポインタを示していて、これはオブジェクト毎にユニーク。
この生成されたオブジェクト毎にユニークなポインタがあるので、同じClassから生成されたオブジェクトが複数あってもオブジェクト毎に異なる値を追跡出来ているんですね

で、これらを対応させると...
RObject内のRBasicのklassはPersonを示す、RClass構造体へのポインタ。
RObject内のRBasicのflagsは色々なフラグがあるけど、例えばRObject構造体を示すフラグとか。
numivは2。
ivptrはjohnと22
iv_index_tblは@name, @ageとjohn,22を対応させるハッシュテーブルへのポインタ。
となる。

ここまでで、クラスから生成されたオブジェクトの実体についてざっくり説明したが、文字列や整数とかがRObject構造体と同じなのかと言うとそうではなくてまた別の構造体を使っています。
例えば文字列の値はRString構造体に保存していて、これまたオブジェクトなので文字列にインスタンス変数を持つことも出来ます。
※整数についてはまた話が長くなるので割愛

使う機会はないだろうけど、こんなことも出来ます。

irb#1(main):001:0> str = 'john'
=> "john"
irb#1(main):002:0> str.instance_variable_set('@age', 22)
=> 22
irb#1(main):003:0> str.instance_variables
=> [:@age]

あと厳密に言うと、RObject構造体と同じようにRBasicは共通なんだけど、RStringはivptrやらiv_index_tblは持っていないのでgeneric_iv_tblを使っています。

// ruby/include/ruby/internal/core/rstring.h#L73-L86

struct RString {
    struct RBasic basic;
    union {
        struct {
            long len;
            char *ptr;
            union {
                long capa;
                VALUE shared;
            } aux;
        } heap;
        char ary[RSTRING_EMBED_LEN_MAX + 1];
    } as;
};
// ruby/variable.c#L1217-L1231

static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
{
    struct ivar_update ivup;

    ivup.iv_extended = 0;
    ivup.u.iv_index_tbl = iv_index_tbl_make(obj);
    iv_index_tbl_extend(&ivup, id);
    st_update(generic_iv_tbl, (st_data_t)obj, generic_ivar_update,
          (st_data_t)&ivup);

    ivup.u.ivtbl->ivptr[ivup.index] = val;

    RB_OBJ_WRITTEN(obj, Qundef, val);
}

クラスから生成したオブジェクトの話からちょっと逸れたけど、おわり。





参考

https://github.com/ruby/ruby
http://i.loveruby.net/ja/rhg/book/object.html
Rubyのしくみ -Ruby Under a Microscope-