はじめに
ずんだもんに喋らせてみたくなったのだ。
VOICEVOX
『VOICEVOX』は無料で使えるテキスト読み上げ・歌声合成ソフトウェアなのだ。
Delphi から喋らせるのだ
VOICEVOX を起動しておくと、REST API を使って音声ファイルをゲットできるのだ。使える API は http://localhost:50021/docs で確認できるのだ。
ソースコードなのだ
ソースコードは Delphi 12 Athens で書かれているのだ。10.3 Rio 以降ならそのまま動作すると思うのだ。もちろん、無償の Community Edition でも大丈夫なのだ。
サポートユニット
まずは VOICEVOX 用のサポートユニットなのだ。
{ ---------------------------------------------------------------------------- }
{ VOICEVOX Support unit }
{ ---------------------------------------------------------------------------- }
unit uVoicevox;
interface
uses
System.Classes, System.SysUtils, System.IOUtils, System.JSON.Readers,
System.JSON.Types, System.JSON.Builders, System.Rtti, REST.Types, REST.Client;
type
TRESTRequestEx = class(TRESTRequest)
public
constructor Create(AOwner: TComponent); override;
end;
TVVSpeaker = record
ID: Integer;
Name: string;
Voice: string;
end;
TVVSpeakers = array of TVVSpeaker;
function GetSynthesisSpeakers: TVVSpeakers;
function GetSynthesisWavFile(FileName: string; Speaker: Integer; Text: string): Boolean;
implementation
const
BASE_URL = 'http://localhost:50021';
CONTENT_TYPE = 'application/json';
function GetSynthesisSpeakers: TVVSpeakers;
begin
var Cnt := 0;
with TRESTRequestEx.Create(nil) do
try
Client.BaseURL := BASE_URL;
Client.ContentType := CONTENT_TYPE;
Method := rmGET;
Resource := 'speakers';
Execute;
if (Response.StatusCode = 200) then
begin
var Iterator := TJSONIterator.Create(Response.JSONReader);
try
Iterator.Recurse;
while Iterator.Next do
begin
Iterator.Recurse;
Iterator.Next('name');
var CharName := Iterator.AsString;
Iterator.Next('styles');
Iterator.Recurse;
while Iterator.Next do
begin
if (Cnt mod 128) = 0 then
SetLength(Result, Length(Result) + 128);
Iterator.Recurse;
Result[Cnt].Name := CharName;
Iterator.Next('name');
Result[Cnt].Voice := Iterator.AsString;
Iterator.Next('id');
Result[Cnt].ID := Iterator.AsInteger;
Inc(Cnt);
Iterator.Return;
end;
Iterator.Return;
end;
finally
Iterator.Free;
end;
end;
finally
Free;
end;
SetLength(Result, Cnt);
end;
function GetSynthesisWavFile(FileName: string; Speaker: Integer; Text: string): Boolean;
begin
Result := False;
with TRESTRequestEx.Create(nil) do
try
Client.BaseURL := BASE_URL;
Client.ContentType := CONTENT_TYPE;
Method := rmPOST;
Resource := 'audio_query';
Params.AddItem('speaker', Speaker.ToString, TRESTRequestParameterKind.pkQUERY);
Params.AddItem('text' , Text , TRESTRequestParameterKind.pkQUERY);
Execute;
if (Response.StatusCode = 200) then
begin
Resource := 'synthesis';
Params.Delete('text');
Params.AddBody(Response.JSONText, CONTENT_TYPE);
Execute;
if (Response.StatusCode = 200) then
begin
TFile.WriteAllBytes(FileName, Response.RawBytes);
Result := True;
end;
end;
finally
Free;
end;
end;
{ TRESTRequestEx }
constructor TRESTRequestEx.Create(AOwner: TComponent);
begin
inherited;
Self.Client := TRESTClient.Create(Self);
Self.Response := TRESTResponse.Create(Self);
end;
end.
フォームファイル
次はフォームファイルなのだ。
object Form1: TForm1
Left = 0
Top = 0
Caption = 'VOICEVOX TEST'
ClientHeight = 92
ClientWidth = 710
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = #12513#12452#12522#12458
Font.Style = []
Position = poScreenCenter
OnCreate = FormCreate
TextHeight = 24
object btnPlay: TButton
Left = 614
Top = 32
Width = 75
Height = 32
Caption = 'Play'
Enabled = False
TabOrder = 2
OnClick = btnPlayClick
end
object edText: TEdit
Left = 279
Top = 32
Width = 329
Height = 32
TabOrder = 1
Text = #12371#12435#12395#12385#12399#12290
end
object cbSpeaker: TComboBox
Left = 16
Top = 32
Width = 257
Height = 32
Style = csDropDownList
TabOrder = 0
end
end
cbSpeaker の Style プロパティを csDropDownList
に、btnPlay の Enabled プロパティが False
に設定しておいてほしいのだ。
ユニットファイル
ユニットファイルなのだ。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, REST.Types, System.IOUtils,
Vcl.MPlayer, Winapi.MMSystem, Vcl.ComCtrls, uVoicevox;
type
TForm1 = class(TForm)
btnPlay: TButton;
edText: TEdit;
cbSpeaker: TComboBox;
procedure btnPlayClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
Speakers: TVVSpeakers;
procedure GetSpeakersThreadTerminate(Sender: TObject);
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// フォーム作成時
procedure TForm1.FormCreate(Sender: TObject);
begin
// スピーカーのリストを生成
var GetSpeakersThread := TThread.CreateAnonymousThread(
procedure
begin
Speakers := GetSynthesisSpeakers;
end
);
GetSpeakersThread.OnTerminate := GetSpeakersThreadTerminate;
GetSpeakersThread.Start;
end;
// スレッド終了時
procedure TForm1.GetSpeakersThreadTerminate(Sender: TObject);
begin
// スピーカーのリストをコンボボックスに入れる
var Speaker: TVVSpeaker;
for Speaker in Speakers do
begin
// "ID: 名前 (種類)" 形式のリストを cbSpeaker にセット
var s := Format('%d: %s (%s)', [Speaker.ID, Speaker.Name, Speaker.Voice]);
cbSpeaker.Items.Add(s);
end;
if cbSpeaker.Items.Count > 0 then
begin
cbSpeaker.ItemIndex := 0;
btnPlay.Enabled := True;
end;
end;
// ボタン押下時
procedure TForm1.btnPlayClick(Sender: TObject);
begin
// 音声合成
var FileName := 'WORK.WAV';
var SpeakerID := cbSpeaker.Items[cbSpeaker.ItemIndex].Split([':'])[0].ToInteger; // ID を取得
if GetSynthesisWavFile(FileName, SpeakerID , edText.Text) then
PlaySound(PChar(FileName), 0, SND_ASYNC);
end;
end.
実行してみるのだ
実行するとこんな感じになるのだ。
コンボボックスにはスピーカー (キャラクター) の一覧がセットされてるので、お好きなスピーカーを選択してくださいなのだ。ボクは 1, 3, 5, 7, 22, 38, 75, 76 なのだ。
そうしたらエディットボックスに喋らせたい文字列を入力して [Play] ボタンを押すだけなのだ。とってもお手軽なのだ。
説明なのだ
uVoicevox.pas
にはたった 2 つの関数があるだけなのだ。使い勝手の悪い実装になっているかもしれないけれど、解り易いとは思うのだ。
GetSynthesisSpeakers()
GetSynthesisSpeakers() はスピーカーの情報を動的配列 (TVVSpeakers) で返す関数なのだ。スピーカーの ID が決め打ちでいいのなら呼び出す必要はないのだ。
GetSynthesisWavFile()
GetSynthesisWavFile() は文字列から音声合成を行って WAV ファイルを出力する関数なのだ。
パラメータ | 型 | 説明 |
---|---|---|
FileName | string | 出力する WAV ファイルの名前 |
Speaker | Integer | スピーカー ID |
Text | string | 喋らせたい文字列 |
正常終了した場合には結果が True になるのだ。
サンプルプログラムは出力された WAV ファイルを、そのまま PlaySound()
API で鳴らしているのだ。別に TMediaPlayer で鳴らしても構わないのだ。
おわりに
喉の調子が悪い時にはずんだの妖精を REST API で呼ぶといいのだ。
「VOICEVOX をそのまま使えばいい」?そんな事言う奴ははんごろしにするのだ。
See also: